ai项目(一)
新建会话功能
需要有sessionid:因为sessionid不能重复,所以我们选择唯一的uuid,并且因为我们要记录会话历史,所以数据库中也要存储sessionid
方法再页面上查看如下接口以及路径:
/ais是网关路由
- 请求路径:/ais/session
n=3是限制后面我热点数据要3个 - 请求参数(Query):/ais/session?n=3
这个是创建会话的响应数据:
{
"code": 200,
"msg": "OK",
"data": {
"sessionId": "718f331076e442059a10da26be5f292d",
"title": "Hello,我是玄枢AI助理",
"describe": "我是由玄枢学堂倾力打造的智能助理,我不仅能推荐课程、答疑解惑,还能为您激发创意、畅聊心事。",
"examples": [
{
"title": "知识讲解",
"describe": "面向对象的三大特征是什么?"
},
{
"title": "课程介绍",
"describe": "《Java高级架构与微服务》能学到什么内容?"
},
{
"title": "课程购买",
"describe": "《Java进阶编程实战》的课程价格是多少?"
}
]
},
"requestId": "0e3d0acb9c884a539424f857be3e0cb1"
}
功能实现:
- 创建实体类,mapper
- 创建返回给前端的vo,可以看我们上面的响应数据,都要什么了
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SessionVO {
// 会话ID,用于唯一标识当前的AI助手会话。
private String sessionId;
// AI助手的标题,用于显示助手的名称或身份。
private String title;
// AI助手的描述,简要介绍助手的功能或特点。
private String describe;
//示例列表,包含一些使用助手的示例。
private List<Example> examples;
// Example类表示每个示例的标题和描述。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Example {
//示例的标题,描述了示例的类型或内容。
private String title;
// 示例的描述,具体说明了示例的内容或问题。
private String describe;
}
}
- 映射nacos的配置,把卸载nacos里的热点问题都读下来
![image]()
@Data
@Configuration
@ConfigurationProperties(prefix = "xs.ai.session")
public class SessionProperties {
// AI助手的标题,用于显示助手的名称或身份。
private String title;
//AI助手的描述,简要介绍助手的功能或特点。
private String describe;
//示例列表,包含一些使用助手的示例。
private List<SessionVO.Example> examples;
}
- 写controller
@RestController
@RequestMapping("/session")
@RequiredArgsConstructor
public class SessionController {
private final IChatSessionService chatSessionService;
//新建会话
@PostMapping
public SessionVO createSession(@RequestParam(value = "n", defaultValue = "3") Integer num) {
return this.chatSessionService.createSession(num);
}
}
- 写service
@Service
//要注入数据,就要写这个注解
@RequiredArgsConstructor
public class ChatSessionServiceImpl extends ServiceImpl<ChatSessionMapper, ChatSession> implements IChatSessionService {
//从ioc中把SessionProperties里的数据注入下来
private final SessionProperties sessionProperties;
@Override
public SessionVO createSession(Integer num) {
SessionVO ret=new SessionVO();
//1 生产sessionId
String uuid = IdUtil.simpleUUID();
ret.setSessionId(uuid);
//2 读取 nacos 数据 num个随机问题
ret.setTitle(sessionProperties.getTitle());
ret.setDescribe(sessionProperties.getDescribe());
// 从list中随机num个元素,重新组织成集合
//使用hutool工具,随机从list里取num个数据组成一个新的数组
List<SessionVO.Example> examples = RandomUtil.randomEleList(sessionProperties.getExamples(), num);
//ret数据现在全了
ret.setExamples(examples);
//3 数据库保存会话信息
Long userId = 2l;
//chatsession建造者构建了一个ChatSession对象,把数据库需要的数据赋值进来,title我们前端是写死的,不需要存进来
ChatSession chatSession = ChatSession.builder()
.sessionId(uuid)
//.title(sessionProperties.getTitle())
.creater(userId)
.createTime(java.time.LocalDateTime.now())
.updater(userId)
.updateTime(java.time.LocalDateTime.now())
.userId(userId)
.build();
//mp存进去
this.save(chatSession);
//4 返回vo对象
return ret;
}
}
换一换热点功能
热门问题我们把他放到nacos里了
- controller
@GetMapping("/hot")
public List<SessionVO.Example> hotExamples(@RequestParam(value = "n", defaultValue = "3") Integer num) {
return this.chatSessionService.hotExamples(num);
}
- service
@Override
public List<SessionVO.Example> hotExamples(Integer num) {
// 从list中随机num个元素,重新组织成集合
List<SessionVO.Example> examples = RandomUtil.randomEleList(sessionProperties.getExamples(), num);
return examples;
}
新建会话&换一换流程图:

流式对话
SSE(Server-Sent Events),是基于 HTTP 的单向服务器推送技术,通过 text/event-stream 响应类型维持长连接,后端持续分段推送文本数据;多用于 AI 大模型流式输出,只能服务端向客户端发消息,双向实时通信用 WebSocket。
响应结构说明:(SSE)
1、每行数据,都是一个json数据
2、json中有eventData和eventType,两个属性
3、eventData:文本内容
4、eventType:事件类型,1001-数据事件,1002-停止事件,1003-参数事件
响应数据如下:
data:{"eventData":"课程","eventType":1001}
data:{"eventData":"id\n","eventType":1001}
data:{"eventData":":","eventType":1001}
data:{"eventData":"1880","eventType":1001}
data:{"eventData":"5332","eventType":1001}
data:{"eventData":"5357","eventType":1001}
data:{"eventData":"5225","eventType":1001}
data:{"eventData":"346\n\n","eventType":1001}
data:{"eventData":"发展。","eventType":1001}
data:{"eventData":"","eventType":1001}
data:{"eventType":1002}
- 定义了一下前端返回数据的vo(响应对象)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatEventVO {
//文本内容
private Object eventData;
//事件类型,1001-数据事件,1002-停止事件,1003-参数事件
private int eventType;
}
- 定义一下枚举事件类型eventType
@Getter
public enum ChatEventTypeEnum {
DATA(1001, "数据事件"),
STOP(1002, "停止事件"),
PARAM(1003, "参数事件");
private final int value;
private final String desc;
ChatEventTypeEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}
@Override
public String toString() {
return this.name();
}
}
- 定义dto(前端到controller数据)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatDTO {
//用户的问题
private String question;
/**
* 会话id,一个用户可以有多个
*/
private String sessionId;
}
- 创建SpringAi配置
@Configuration
@RequiredArgsConstructor
public class SpringAIConfig {
//private final SystemPromptConfig systemPromptConfig;
@Bean
//SpringAI 对接阿里通义千问(DashScope)的大模型客户端,底层封装了调用通义千问接口的能力。
public ChatClient chatClient(DashScopeChatModel dashScopeChatModel) {
//建造者模式创建 ChatClient。`ChatClient` 是 SpringAI 提供的核心 API,用来**发送 prompt、和大模型对话,支持 SSE 流式返回**。
return ChatClient.builder(dashScopeChatModel)
//.defaultSystem(systemPromptConfig.getChatSystemMessage().get())
//打印日志
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
}
- controller
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatController {
private final IChatService chatService;
/**
* 流式对话
* @param chatDTO
* @return
*/
//自定义注解,因为controller里有@RestController这个,所以默认都是返回json类型数据,家里这个注解之后,就是数据本身是什么类型就返回什么类型,像这里就是流式数据,流式数据不能用json类型返回
@NoWrapper
```
// 定义一个注解 `NoWrapper`,用于标记Controller中的方法,表示该方法不需要包装返回结果。
@Retention(RetentionPolicy.RUNTIME) // 保留策略为运行时
@Target(ElementType.METHOD) // 该注解仅适用于方法
public @interface NoWrapper {
}
```
//这里是指定用的是流式输出,SSE
@PostMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatEventVO> chat(@RequestBody ChatDTO chatDTO) {
return this.chatService.chat(chatDTO.getQuestion(), chatDTO.getSessionId());
}
}
- service
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatServiceImpl implements IChatService {
private final ChatClient chatClient;
private final SystemPromptConfig systemPromptConfig;
@Override
public Flux<ChatEventVO> chat(String question, String sessionId) {
//开始构建一次对话请求。
return chatClient.prompt()
//设置**用户消息**
.user(question)
//设置**系统提示词 System Prompt**
.system(p -> p.text(
systemPromptConfig.getChatSystemMessage().get().param("now", DateUtil.now()))
//流式返回
.stream()
.chatResponse()
//`map` 是响应式流的转换操作:每收到大模型一段 token,就把文本取出,构建一个`ChatEventVO`,事件类型标记为`DATA`(正常内容片段)。这里就是把大模型原生流式数据,包装成项目自定义 VO。
.map(response -> ChatEventVO.builder()
.eventData(response.getResult().getOutput().getText())
.eventType(ChatEventTypeEnum.DATA.getValue()).build())
//`concatWith`:追加消息,在前面所有流元素全部发送完成之后,追加一条消息事件类型是`STOP`,告诉前端:大模型输出结束,流关闭。
.concatWith(Flux.just(ChatEventVO.builder()
.eventData(null)
.eventType(ChatEventTypeEnum.STOP.getValue()).build()));
}
}
nacos中存储系统提示词
(通过nacos listener实现提示词动态更新)
-
yml里加配置,nacos
![image]()
-
定义配置类,读取到了配置文件的名称、分组、超时时间,根据yml写的
@Data
@Configuration
@ConfigurationProperties(prefix = "xs.ai.prompt")
public class AIProperties {
private System system;
@Data
public static class System {
private Chat chat;
@Data
public static class Chat {
private String dataId;
private String group = "DEFAULT_GROUP";
private long timeoutMs = 20000L;
}
}
}
- 定制提示词类,读取nacos配置文件内容
@Slf4j
@Getter
@Configuration
@RequiredArgsConstructor
public class SystemPromptConfig {
//注入nacos刚才的配置
private final AIProperties aiProperties;
//这个是专门读nacos的
private final NacosConfigManager nacosConfigManager;
private final AtomicReference<String> chatSystemMessage=new AtomicReference<>();
@PostConstruct // 初始化时加载配置
public void init() {
// 读取配置文件
loadConfig(aiProperties.getSystem().getChat(), chatSystemMessage);
}
private void loadConfig(AIProperties.System.Chat chatConfig, AtomicReference<String> target) {
try {
var dataId = chatConfig.getDataId();
var group = chatConfig.getGroup();
var timeoutMs = chatConfig.getTimeoutMs();
// 1. 主动读取配置
String config = nacosConfigManager.getConfigService().getConfig(dataId, group, timeoutMs);
target.set(config);//放入到原子对象中
log.info("读取{}成功,内容为:{}", target, config);
// 2. 添加监听器,实现动态更新
nacosConfigManager.getConfigService().addListener(dataId, group, new Listener() {
@Override
public Executor getExecutor() {
return null; // 使用 Nacos 客户端的默认线程池
}
@Override
public void receiveConfigInfo(String info) {
target.set(info);
log.info("更新{}成功,内容为:{}", target, info);
}
});
} catch (Exception e) {
log.error("加载配置失败", e);
}
}
}


- 加一个系统提示词,在service里
private final SystemPromptConfig systemPromptConfig;
@Override
public Flux<ChatEventVO> chat(String question, String sessionId) {
return chatClient.prompt()
//这里加进来系统提示词了
.system(p -> p.text(systemPromptConfig.getChatSystemMessage().get())
.param("now", DateUtil.now()))
}
新建会话流程概括:
前端发送请求,传了一个num=3,新建一个sessionid,从nacos读取数据(包括热点问题)
创建聊天会话对象存到mysql中,返回一个vo对象(包括三个热点问题)
热点流程:
点击换一换功能,从nacos读取数据(包括所有热点数据),其中num个生成新数组,返回给前端num个热点问题
- Nacos 系统提示词
通过NacosConfigManager读取提示词,注册 Listener,Nacos 页面修改提示词不用重启服务,自动生效;用AtomicReference保证多线程安全读取。 - 流式 SSE 对话
前端传 ChatDTO(question+sessionId),Controller 加@NoWrapper+produces = MediaType.TEXT_EVENT_STREAM_VALUE,返回 Flux 流;Service 调用 SpringAI,每拿到一段 token 封装成 DATA 事件,模型输出结束追加 STOP 事件;SSE 逐段推送给前端打字机效果。



浙公网安备 33010602011771号