834 lines
19 KiB
Markdown
834 lines
19 KiB
Markdown
|
||
|
||
# 后端初始化
|
||
|
||
## 初始化
|
||
|
||
springweb
|
||
|
||
lombok
|
||
|
||
mysqldriver
|
||
|
||
|
||
|
||
把apprication.properited 改成yml格式
|
||
|
||
然后加入
|
||
|
||
```yml
|
||
spring:
|
||
application:
|
||
name: zds-ai 项目名字
|
||
|
||
server:
|
||
port: 2778
|
||
servlet:
|
||
context-path: /api 后端访问的路径吧
|
||
```
|
||
|
||
|
||
|
||
### 依赖整合
|
||
|
||
hutool knife4j
|
||
|
||
```xml
|
||
|
||
<!-- hutool工具类-->
|
||
<dependency>
|
||
<groupId>cn.hutool</groupId>
|
||
<artifactId>hutool-all</artifactId>
|
||
<version>5.8.38</version>
|
||
</dependency>
|
||
<!-- knife4j接口文档--> -->
|
||
<dependency>
|
||
<groupId>com.github.xiaoymin</groupId>
|
||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||
<version>4.4.0</version>
|
||
</dependency>
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
改xml配置
|
||
|
||
```yml
|
||
# springdoc-openapi
|
||
springdoc:
|
||
group-configs:
|
||
- group: 'default'
|
||
packages-to-scan: com.zds.zdsai.controller
|
||
# knife4j
|
||
knife4j:
|
||
enable: true
|
||
setting:
|
||
language: zh_cn
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
### 写模板代码 自定义异常
|
||
|
||
已经在包中 用就可以了
|
||
|
||
|
||
|
||
|
||
|
||
### 全局跨域配置(样板代码)
|
||
|
||
直接在config包中 样板代码
|
||
|
||
```java
|
||
@Configuration
|
||
public class CorsConfig implements WebMvcConfigurer {
|
||
|
||
@Override
|
||
public void addCorsMappings(CorsRegistry registry) {
|
||
// 覆盖所有请求
|
||
registry.addMapping("/**")
|
||
// 允许发送 Cookie
|
||
.allowCredentials(true)
|
||
// 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
|
||
.allowedOriginPatterns("*")
|
||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||
.allowedHeaders("*")
|
||
.exposedHeaders("*");
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
# 前端初始化
|
||
|
||
emm
|
||
|
||
使用nvm 来管理node.js
|
||
|
||
|
||
|
||
安装ant design
|
||
|
||
```shell
|
||
npm i --save ant-design-vue@4.x
|
||
```
|
||
|
||
全局装配
|
||
|
||
```typescript
|
||
import './assets/main.css'
|
||
|
||
import { createApp } from 'vue'
|
||
import { createPinia } from 'pinia'
|
||
|
||
import App from './App.vue'
|
||
import router from './router'
|
||
|
||
import Antd from 'ant-design-vue'
|
||
import 'ant-design-vue/dist/reset.css'
|
||
|
||
const app = createApp(App)
|
||
|
||
app.use(createPinia())
|
||
app.use(router)
|
||
app.use(Antd)
|
||
|
||
app.mount('#app')
|
||
|
||
```
|
||
|
||
|
||
|
||
安装
|
||
|
||
axios
|
||
|
||
```
|
||
npm install axios
|
||
```
|
||
|
||
|
||
|
||
```ts
|
||
import axios from 'axios'
|
||
import { message } from 'ant-design-vue'
|
||
|
||
// 创建 Axios 实例
|
||
const myAxios = axios.create({
|
||
baseURL: 'http://localhost:8123/api',
|
||
timeout: 60000,
|
||
withCredentials: true,
|
||
})
|
||
|
||
// 全局请求拦截器
|
||
myAxios.interceptors.request.use(
|
||
function (config) {
|
||
// Do something before request is sent
|
||
return config
|
||
},
|
||
function (error) {
|
||
// Do something with request error
|
||
return Promise.reject(error)
|
||
},
|
||
)
|
||
|
||
// 全局响应拦截器
|
||
myAxios.interceptors.response.use(
|
||
function (response) {
|
||
const { data } = response
|
||
// 未登录
|
||
if (data.code === 40100) {
|
||
// 不是获取用户信息的请求,并且用户目前不是已经在用户登录页面,则跳转到登录页面
|
||
if (
|
||
!response.request.responseURL.includes('user/get/login') &&
|
||
!window.location.pathname.includes('/user/login')
|
||
) {
|
||
message.warning('请先登录')
|
||
window.location.href = `/user/login?redirect=${window.location.href}`
|
||
}
|
||
}
|
||
return response
|
||
},
|
||
function (error) {
|
||
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
||
// Do something with response error
|
||
return Promise.reject(error)
|
||
},
|
||
)
|
||
|
||
export default myAxios
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
## openapi 前端代码生成器
|
||
|
||
[@umijs/openapi - npm](https://www.npmjs.com/package/@umijs/openapi) 官网自己去学把
|
||
|
||
```
|
||
npm i --save-dev @umijs/openapi
|
||
|
||
npm i --save-dev tslib
|
||
```
|
||
|
||
```
|
||
export default {
|
||
requestLibPath: "import request from '@/request'",
|
||
schemaPath: 'http://localhost:2778/api/v3/api-docs',
|
||
serversPath: './src',
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
# 用户登录
|
||
|
||
<img src="https://pic.code-nav.cn/course_picture/1608440217629360130/ahLctL73Pp9LatVp.svg" alt="img" style="zoom: 80%;" />
|
||
|
||
|
||
|
||
|
||
|
||
接口权限
|
||
|
||
1.未登录也可以使用
|
||
2,登录用户才能使用
|
||
3,未登录也可以使用,但是登录用户能进行更多操作(比如登录后查看全文)
|
||
4,仅管理员才能使用
|
||
|
||
|
||
|
||
|
||
|
||
### mybatis flex
|
||
|
||
生成器 不用学看官方就行
|
||
|
||
然后就是说 这个
|
||
|
||
entity是数据库与实体类的连接
|
||
|
||
|
||
|
||
|
||
|
||
### @MapperScan
|
||
|
||
可能扫描到mapper包
|
||
|
||
|
||
|
||
### Dto就是业务代码互相传递的类
|
||
|
||
|
||
|
||
Vo是脱敏的类
|
||
|
||
|
||
|
||
### 用户登录接口
|
||
|
||
```java
|
||
@Override
|
||
public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
|
||
//校验参数
|
||
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
|
||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||
}
|
||
if (userAccount.length() < 4) {
|
||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过知短");
|
||
}
|
||
if (userPassword.length() < 8) {
|
||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过知短");
|
||
}
|
||
|
||
//加密密码
|
||
String encryptPassword = getEncryptPassword(userPassword);
|
||
|
||
//查询用户
|
||
|
||
QueryWrapper queryWrapper = new QueryWrapper();
|
||
queryWrapper.eq("userAccount", userAccount);
|
||
queryWrapper.eq("userPassword", encryptPassword);
|
||
User user = this.mapper.selectOneByQuery(queryWrapper);
|
||
ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
|
||
//记录用户的登录态
|
||
|
||
request.getSession().setAttribute(USER_LOGIN_STATE, user);
|
||
return this.getLoginUserVO(user);
|
||
}
|
||
```
|
||
|
||
**request.getSession().setAttribute(USER_LOGIN_STATE, user);** 很重要的 种session
|
||
|
||
|
||
|
||
|
||
|
||
## 注解(重要)
|
||
|
||
### 0先引入依赖
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-aop</artifactId>
|
||
</dependency>
|
||
|
||
```
|
||
|
||
|
||
|
||
### 1在annotation包下 创建注解类
|
||
|
||
```java
|
||
@Target(ElementType.METHOD) //在方法上的注解
|
||
@Retention(RetentionPolicy.RUNTIME) //注解的策略
|
||
public @interface AuthCheck {
|
||
|
||
/**
|
||
* 必须有某个角色
|
||
*/
|
||
String mustRole() default "";
|
||
}
|
||
|
||
```
|
||
|
||
### 2 创建一个切面 在aop包里面
|
||
|
||
|
||
|
||
```java
|
||
package com.zds.zdsai.aop;
|
||
|
||
import com.zds.zdsai.annotation.AuthCheck;
|
||
import com.zds.zdsai.exception.BusinessException;
|
||
import com.zds.zdsai.exception.ErrorCode;
|
||
import com.zds.zdsai.model.entity.User;
|
||
import com.zds.zdsai.model.enums.UserRoleEnum;
|
||
import com.zds.zdsai.service.UserService;
|
||
import jakarta.annotation.Resource;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import org.aspectj.lang.ProceedingJoinPoint;
|
||
import org.aspectj.lang.annotation.Around;
|
||
import org.aspectj.lang.annotation.Aspect;
|
||
import org.springframework.stereotype.Component;
|
||
import org.springframework.web.context.request.RequestAttributes;
|
||
import org.springframework.web.context.request.RequestContextHolder;
|
||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||
|
||
@Aspect
|
||
@Component
|
||
public class AuthInterceptor {
|
||
|
||
@Resource
|
||
private UserService userService;
|
||
|
||
/**
|
||
* 执行拦截
|
||
*
|
||
* @param joinPoint 切入点
|
||
* @param authCheck 权限校验注解
|
||
*/
|
||
//切点 -->你在什么时候拦截这个方法
|
||
@Around("@annotation(authCheck)")
|
||
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
|
||
String mustRole = authCheck.mustRole();
|
||
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||
// 当前登录用户
|
||
User loginUser = userService.getLoginUser(request);
|
||
UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
|
||
// 不需要权限,放行
|
||
if (mustRoleEnum == null) {
|
||
return joinPoint.proceed();
|
||
}
|
||
// 以下为:必须有该权限才通过
|
||
// 获取当前用户具有的权限
|
||
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
|
||
// 没有权限,拒绝
|
||
if (userRoleEnum == null) {
|
||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
||
}
|
||
// 要求必须有管理员权限,但用户没有管理员权限,拒绝
|
||
if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
|
||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
||
}
|
||
// 通过权限校验,放行
|
||
return joinPoint.proceed();
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
**@Aspect**来定义这是一个切面
|
||
|
||
**@annotation(authcheck)** --》就是通知 只有在有authcheck注解的 才能进行权限校验
|
||
|
||
**joinPoint.proceed()**; 放行
|
||
|
||
|
||
|
||
|
||
|
||
## 前端id与后端id传值不一致
|
||
|
||
因为后端的int值太tmd大 而前端的js的不支持那么大的 所以会少那么一些
|
||
|
||
加个样板代码
|
||
|
||
```java
|
||
/**
|
||
* Spring MVC Json 配置
|
||
*/
|
||
@JsonComponent
|
||
public class JsonConfig {
|
||
|
||
/**
|
||
* 添加 Long 转 json 精度丢失的配置
|
||
*/
|
||
@Bean
|
||
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
|
||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||
SimpleModule module = new SimpleModule();
|
||
module.addSerializer(Long.class, ToStringSerializer.instance);
|
||
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
|
||
objectMapper.registerModule(module);
|
||
return objectMapper;
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
## 前端
|
||
|
||
前端想要获取用户的登陆数据 是不是每次刷新页面 就要写一个fetch函数来获取呢?
|
||
|
||
不要 我们直接写一个引入pinna
|
||
|
||
> pinna
|
||
|
||
```ts
|
||
import { useLoginUserStore } from '@/stores/loginUser.ts'
|
||
|
||
const loginUserStore = useLoginUserStore()
|
||
loginUserStore.fetchLoginUser()
|
||
|
||
```
|
||
|
||
获取用户
|
||
|
||
|
||
|
||
|
||
|
||
# Ai大模型接入
|
||
|
||
## langchain4j
|
||
|
||
> 引入依赖
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>dev.langchain4j</groupId>
|
||
<artifactId>langchain4j</artifactId>
|
||
<version>1.1.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>dev.langchain4j</groupId>
|
||
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
|
||
<version>1.1.0-beta7</version>
|
||
</dependency>
|
||
|
||
```
|
||
|
||
|
||
|
||
> application.xml
|
||
|
||
```yml
|
||
# AI
|
||
langchain4j:
|
||
open-ai:
|
||
chat-model:
|
||
base-url: https://api.deepseek.com
|
||
api-key: <Your API Key>
|
||
model-name: deepseek-chat
|
||
log-requests: true
|
||
log-responses: true
|
||
|
||
```
|
||
|
||
|
||
|
||
### 创建接口AiCodeGeneratorService
|
||
|
||
```java
|
||
package com.zds.zdsai.ai;
|
||
|
||
import dev.langchain4j.service.SystemMessage;
|
||
|
||
/**
|
||
* AI代码生成服务
|
||
*/
|
||
public interface AiCodeGeneratorService {
|
||
|
||
/**
|
||
* 生成HTML代码
|
||
*
|
||
* @param userMessage 用户输入
|
||
* @return 代码
|
||
*/
|
||
@SystemMessage(fromResource = "prompt/codegen-html-system-prompt.txt")
|
||
String generateCode(String userMessage);
|
||
|
||
/**
|
||
* 生成多文件代码
|
||
*
|
||
* @param userMessage 用户输入
|
||
* @return 代码
|
||
*/
|
||
@SystemMessage(fromResource = "prompt/codegen-multi-file-system-prompt.txt")
|
||
String generateMultiFileCode(String userMessage);
|
||
}
|
||
|
||
```
|
||
|
||
写提示词 当然 如果词的长度少 你可以不用建立txt
|
||
|
||
|
||
|
||
然后就是 写一个代理工厂 来创建实例
|
||
|
||
```java
|
||
package com.zds.zdsai.ai;/*
|
||
*@auther 郑笃实
|
||
*@version 1.0
|
||
*
|
||
*/
|
||
|
||
import dev.langchain4j.model.chat.ChatModel;
|
||
import dev.langchain4j.service.AiServices;
|
||
import jakarta.annotation.Resource;
|
||
import org.springframework.context.annotation.Bean;
|
||
|
||
//Ai服务创建工厂
|
||
public class AiCodeGeneratorServiceFactory {
|
||
|
||
//指定使用的大模型
|
||
@Resource
|
||
private ChatModel chatModel;
|
||
|
||
/**
|
||
* 创建AI 代码生成器代码服务
|
||
* @return
|
||
*/
|
||
@Bean
|
||
public AiCodeGeneratorService createAiCodeGeneratorService() {
|
||
return AiServices.create(AiCodeGeneratorService.class, chatModel);
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
是这样的 所以及就获得了一个String的 返回结果
|
||
|
||
但是问题来了你该如何将String给用户看??直接看??也行 或许
|
||
|
||
我们可以将String转化为JSON 让ai按照格式生成相应key 这样我们就可以 更好的展示相关的内容了
|
||
|
||
|
||
|
||
```java
|
||
package com.zds.zdsai.ai.model;
|
||
|
||
import lombok.Data;
|
||
//Html代码结果
|
||
@Data
|
||
public class HtmlCodeResult {
|
||
|
||
/**
|
||
* Html代码
|
||
*/
|
||
private String htmlCode;
|
||
|
||
//描述
|
||
private String description;
|
||
}
|
||
|
||
```
|
||
|
||

|
||
|
||
然后改一下接口的返回值 这样就好了
|
||
|
||
- method: POST
|
||
- url: https://api.deepseek.com/chat/completions
|
||
- headers: [Authorization: Beare...c6], [User-Agent: langchain4j-openai], [Content-Type: application/json]
|
||
- body: {
|
||
"model" : "deepseek-chat",
|
||
"messages" : [ {
|
||
"role" : "system",
|
||
"content" : "你是一位资深的 Web 前端开发专家,精通 HTML、CSS 和原生 JavaScript。你擅长构建响应式、美观且代码整洁的单页面网站。\r\n\r\n你的任务是根据用户提供的网站描述,生成一个完整、独立的单页面网站。你需要一步步思考,并最终将所有代码整合到一个 HTML 文件中。\r\n\r\n约束:\r\n1. 技术栈: 只能使用 HTML、CSS 和原生 JavaScript。\r\n2. 禁止外部依赖: 绝对不允许使用任何外部 CSS 框架、JS 库或字体库。所有功能必须用原生代码实现。\r\n3. 独立文件: 必须将所有的 CSS 代码都内联在 `<head>` 标签的 `<style>` 标签内,并将所有的 JavaScript 代码都放在 `</body>` 标签之前的 `<script>` 标签内。最终只输出一个 `.html` 文件,不包含任何外部文件引用。\r\n4. 响应式设计: 网站必须是响应式的,能够在桌面和移动设备上良好显示。请优先使用 Flexbox 或 Grid 进行布局。\r\n5. 内容填充: 如果用户描述中缺少具体文本或图片,请使用有意义的占位符。例如,文本可以使用 Lorem Ipsum,图片可以使用 https://picsum.photos 的服务 (例如 `<img src=\"https://picsum.photos/800/600\" alt=\"Placeholder Image\">`)。\r\n6. 代码质量: 代码必须结构清晰、有适当的注释,易于阅读和维护。\r\n7. 交互性: 如果用户描述了交互功能 (如 Tab 切换、图片轮播、表单提交提示等),请使用原生 JavaScript 来实现。\r\n8. 安全性: 不要包含任何服务器端代码或逻辑。所有功能都是纯客户端的。\r\n9. 输出格式: 你的最终输出必须包含 HTML 代码块,可以在代码块之外添加解释、标题或总**结性文字。格式如下:\r\n\r\n```html\r\n... HTML 代码 ...\r\n\r\n"
|
||
}, {
|
||
"role" : "user",
|
||
"content" : "做个程序员鱼皮的工作记录小工具\nYou must answer strictly in the following JSON format: {\n\"htmlCode\": (type: string),\n\"description\": (type: string)\n}"
|
||
} ],**
|
||
"stream" : false
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
> 但是 啊 但是 如果ai生成的文字太大 以至于 Stirng类型装不下 或者ai不听你的 json不按照格式来 怎么办呢??、
|
||
|
||
1. 设置maxtoken
|
||
|
||
2.严格限制
|
||
|
||
```
|
||
langchain4j:
|
||
open-ai:
|
||
chat-model:
|
||
strict-json-schema: true
|
||
response-format: json_object
|
||
|
||
```
|
||
|
||
3. 加上注释 Description("HTML代码")
|
||
|
||
这样 我们发送这个model的时候 就可以带上描述 这样ai可以更好的理解
|
||
|
||
|
||
|
||
## 设计模式 门面模式
|
||
|
||
先建立枚举类 **CodeGenTypeEnum**
|
||
|
||
然后再core包下面写一个文件存储类FileSaver 就是将代码存到文件中
|
||
|
||

|
||
|
||
|
||
|
||
先写出来了 门面 门面调用aiCodeGeneratorService 和 Filesave
|
||
|
||
**AiCodeGeneratorFacade**
|
||
|
||
```java
|
||
package com.zds.zdsai.core;
|
||
|
||
import com.zds.zdsai.ai.AiCodeGeneratorService;
|
||
import com.zds.zdsai.ai.model.HtmlCodeResult;
|
||
import com.zds.zdsai.ai.model.MultiFileCodeResult;
|
||
import com.zds.zdsai.exception.BusinessException;
|
||
import com.zds.zdsai.exception.ErrorCode;
|
||
import com.zds.zdsai.model.enums.CodeGenTypeEnum;
|
||
import jakarta.annotation.Resource;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import java.io.File;
|
||
|
||
/**
|
||
* AI 代码生成外观类,组合生成和保存功能
|
||
*/
|
||
@Service
|
||
public class AiCodeGeneratorFacade {
|
||
|
||
@Resource
|
||
private AiCodeGeneratorService aiCodeGeneratorService;
|
||
|
||
/**
|
||
* 统一入口:根据类型生成并保存代码
|
||
*
|
||
* @param userMessage 用户提示词
|
||
* @param codeGenTypeEnum 生成类型
|
||
* @return 保存的目录
|
||
*/
|
||
public File generateAndSaveCode(String userMessage, CodeGenTypeEnum codeGenTypeEnum) {
|
||
if (codeGenTypeEnum == null) {
|
||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成类型为空");
|
||
}
|
||
return switch (codeGenTypeEnum) {
|
||
case HTML -> generateAndSaveHtmlCode(userMessage);
|
||
case MULTI_FILE -> generateAndSaveMultiFileCode(userMessage);
|
||
default -> {
|
||
String errorMessage = "不支持的生成类型:" + codeGenTypeEnum.getValue();
|
||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, errorMessage);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 生成 HTML 模式的代码并保存
|
||
*
|
||
* @param userMessage 用户提示词
|
||
* @return 保存的目录
|
||
*/
|
||
private File generateAndSaveHtmlCode(String userMessage) {
|
||
HtmlCodeResult result = aiCodeGeneratorService.generateHtmlCode(userMessage);
|
||
return CodeFileSaver.saveHtmlCodeResult(result);
|
||
}
|
||
|
||
/**
|
||
* 生成多文件模式的代码并保存
|
||
*
|
||
* @param userMessage 用户提示词
|
||
* @return 保存的目录
|
||
*/
|
||
private File generateAndSaveMultiFileCode(String userMessage) {
|
||
MultiFileCodeResult result = aiCodeGeneratorService.generateMultiFileCode(userMessage);
|
||
return CodeFileSaver.saveMultiFileCodeResult(result);
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
## SSE结构化流式输出
|
||
|
||
等待全部返回太麻烦了 于是我们选择 SSE结构化流式输出
|
||
|
||
> langchain4j +Reactor
|
||
|
||

|
||
|
||
|
||
|
||
首先我们应该 修改工厂 符合流式
|
||
|
||
```java
|
||
package com.zds.zdsai.ai;/*
|
||
*@auther 郑笃实
|
||
*@version 1.0
|
||
*
|
||
*/
|
||
|
||
import dev.langchain4j.model.chat.ChatModel;
|
||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||
import dev.langchain4j.service.AiServices;
|
||
import jakarta.annotation.Resource;
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
|
||
//Ai服务创建工厂
|
||
@Configuration
|
||
public class AiCodeGeneratorServiceFactory {
|
||
|
||
//指定使用的大模型
|
||
@Resource
|
||
private ChatModel chatModel;
|
||
|
||
@Resource
|
||
private StreamingChatModel streamingChatModel;
|
||
|
||
/**
|
||
* 创建AI 代码生成器代码服务
|
||
* @return
|
||
*/
|
||
@Bean
|
||
public AiCodeGeneratorService createAiCodeGeneratorService() {
|
||
return AiServices.builder(AiCodeGeneratorService.class)
|
||
.chatModel(chatModel)//哪个chatmodel
|
||
.streamingChatModel(streamingChatModel)//哪个streamingChatModel
|
||
.build();
|
||
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
然后再在**AiCodeGeneratorService** 中 添加两个适合流式的代码
|
||
|
||
```java
|
||
|
||
/**
|
||
* 生成HTML代码
|
||
*
|
||
* @param userMessage 用户输入
|
||
* @return 代码
|
||
*/
|
||
@SystemMessage(fromResource = "prompt/codegen-html-system-prompt.txt")
|
||
Flux<String> generateHtmlCodeStream(String userMessage);
|
||
|
||
/**
|
||
* 生成多文件代码
|
||
*
|
||
* @param userMessage 用户输入
|
||
* @return 代码
|
||
*/
|
||
@SystemMessage(fromResource = "prompt/codegen-multi-file-system-prompt.txt")
|
||
Flux<String> generateMultiFileCodeStream(String userMessage);
|
||
```
|
||
|