#2026山东大学软件学院项目实训(四)——AI应用生成模块完整实现
文章标签:#Java #SpringBoot #SSE #AI编程 #LangChain4j
一、模块整体设计思路
AI应用生成模块是AI零代码应用生成平台的核心功能,负责将单机版AI代码生成能力与应用管理系统深度集成,实现用户-应用-代码的绑定关联。
本模块遵循核心设计原则:
- 应用绑定:代码生成与应用ID强关联,文件按应用ID规范存储
- 流式响应:采用SSE服务器推送技术,实现代码实时流式输出
- 权限校验:仅应用创建者可触发代码生成,保障数据安全
- 文件规范:统一文件目录命名规则,避免存储混乱
- 异常统一:全局捕获异常,返回友好错误提示
二、AI应用生成服务开发
1. 业务流程设计
平台化代码生成核心流程:
- 用户在主页输入提示词创建应用,生成应用记录入库
- 获取应用ID后跳转至AI对话页面
- 前端调用SSE流式生成接口,后端校验权限并触发AI生成
- 代码按
codeGenType_appId规则保存至文件系统 - 流式返回生成内容至前端,实时展示生成效果
2. 核心代码改造
(1)代码保存模板改造(CodeFileSaverTemplate)
重构代码保存逻辑,新增应用ID参数,实现文件与应用绑定:
/** * 模板方法:保存代码的标准流程(使用appId) * @param result 代码结果对象 * @param appId 应用ID * @return 保存的目录 */publicfinalFilesaveCode(Tresult,LongappId){// 1.验证输入validateInput(result);// 2.构建基于appId的目录StringbaseDirPath=buildUniqueDir(appId);// 3.保存文件(具体实现由子类提供)saveFiles(result,baseDirPath);// 4.返回目录文件对象returnnewFile(baseDirPath);}/** * 构建基于appId的目录路径 * @param appId 应用ID * @return 目录路径 */protectedfinalStringbuildUniqueDir(LongappId){if(appId==null){thrownewBusinessException(ErrorCode.PARAMS_ERROR,"应用ID不能为空");}StringcodeType=getCodeType().getValue();StringuniqueDirName=StrUtil.format("{}_{}",codeType,appId);StringdirPath=FILE_SAVE_ROOT_DIR+File.separator+uniqueDirName;FileUtil.mkdir(dirPath);returndirPath;}(2)代码保存执行器改造(CodeFileSaverExecutor)
修改执行方法,补充appId参数,适配新的保存逻辑:
/** * 执行代码保存(使用appId) * @param codeResult 代码结果对象 * @param codeGenType 代码生成类型 * @param appId 应用ID * @return 保存的目录 */publicstaticFileexecuteSaver(ObjectcodeResult,CodeGenTypeEnumcodeGenType,LongappId){returnswitch(codeGenType){caseHTML->htmlCodeFileSaver.saveCode((HtmlCodeResult)codeResult,appId);caseMULTI_FILE->multiFileCodeFileSaver.saveCode((MultiFileCodeResult)codeResult,appId);default->thrownewBusinessException(ErrorCode.SYSTEM_ERROR,"不支持的代码生成类型:"+codeGenType);};}(3)AI代码生成门面改造(AiCodeGeneratorFacade)
所有生成方法新增appId参数,同步适配流式生成:
/** * 统一入口:根据类型生成并保存代码(流式, 使用appId) * @param userMessage 用户提示词 * @param codeGenTypeEnum 生成类型 * @param appId 应用ID * @return 流式响应 */publicFlux<String>generateAndSaveCodeStream(StringuserMessage,CodeGenTypeEnumcodeGenTypeEnum,LongappId){if(codeGenTypeEnum==null){thrownewBusinessException(ErrorCode.SYSTEM_ERROR,"生成类型为空");}returnswitch(codeGenTypeEnum){caseHTML->{Flux<String>codeStream=aiCodeGeneratorService.generateHtmlCodeStream(userMessage);yieldprocessCodeStream(codeStream,CodeGenTypeEnum.HTML,appId);}caseMULTI_FILE->{Flux<String>codeStream=aiCodeGeneratorService.generateMultiFileCodeStream(userMessage);yieldprocessCodeStream(codeStream,CodeGenTypeEnum.MULTI_FILE,appId);}default->{StringerrorMessage="不支持的生成类型:"+codeGenTypeEnum.getValue();thrownewBusinessException(ErrorCode.SYSTEM_ERROR,errorMessage);}};}(4)应用服务生成方法(AppService#chatToGenCode)
实现权限校验、应用查询、AI生成调用的核心逻辑:
@OverridepublicFlux<String>chatToGenCode(LongappId,Stringmessage,UserloginUser){// 1.参数校验ThrowUtils.throwIf(appId==null||appId<=0,ErrorCode.PARAMS_ERROR,"应用ID不能为空");ThrowUtils.throwIf(StrUtil.isBlank(message),ErrorCode.PARAMS_ERROR,"用户消息不能为空");// 2.查询应用信息Appapp=this.getById(appId);ThrowUtils.throwIf(app==null,ErrorCode.NOT_FOUND_ERROR,"应用不存在");// 3.验证权限:仅本人可生成代码if(!app.getUserId().equals(loginUser.getId())){thrownewBusinessException(ErrorCode.NO_AUTH_ERROR,"无权限访问该应用");}// 4.获取代码生成类型StringcodeGenTypeStr=app.getCodeGenType();CodeGenTypeEnumcodeGenTypeEnum=CodeGenTypeEnum.getEnumByValue(codeGenTypeStr);if(codeGenTypeEnum==null){thrownewBusinessException(ErrorCode.SYSTEM_ERROR,"不支持的代码生成类型");}// 5.调用AI生成代码returnaiCodeGeneratorFacade.generateAndSaveCodeStream(message,codeGenTypeEnum,appId);}三、SSE流式接口开发
1. 接口设计
采用SSE(Server-Sent Events)实现流式响应,使用GET请求便于前端EventSource对接,声明响应类型为text/event-stream。
2. 核心接口代码(AppController)
/** * 应用聊天生成代码(流式SSE) * @param appId 应用ID * @param message 用户消息 * @param request 请求对象 * @return 生成结果流 */@GetMapping(value="/chat/gen/code",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<String>chatToGenCode(@RequestParamLongappId,@RequestParamStringmessage,HttpServletRequestrequest){// 参数校验ThrowUtils.throwIf(appId==null||appId<=0,ErrorCode.PARAMS_ERROR,"应用ID无效");ThrowUtils.throwIf(StrUtil.isBlank(message),ErrorCode.PARAMS_ERROR,"用户消息不能为空");// 获取当前登录用户UserloginUser=userService.getLoginUser(request);// 调用服务生成代码(流式)returnappService.chatToGenCode(appId,message,loginUser);}四、SSE流式接口优化
原生SSE接口存在空格丢失、无法区分正常结束/异常中断两大问题,需针对性优化。
1. 优化1:解决前端空格丢失问题
问题原因
前端EventSource解析纯文本流式数据时,空格/换行符会被异常过滤,导致代码格式错乱。
解决方案
将流式数据封装为ServerSentEvent,数据转为JSON格式传输,保留原始格式。
2. 优化2:添加生成完成标识
问题原因
SSE默认通过关闭连接标识传输结束,无法区分正常完成与网络异常中断。
解决方案
追加done事件,前端通过监听事件类型判断生成状态。
3. 优化后完整接口代码
/** * 应用聊天生成代码(优化版SSE) * @param appId 应用ID * @param message 用户消息 * @param request 请求对象 * @return 优化后流式响应 */@GetMapping(value="/chat/gen/code",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<ServerSentEvent<String>>chatToGenCode(@RequestParamLongappId,@RequestParamStringmessage,HttpServletRequestrequest){// 参数校验ThrowUtils.throwIf(appId==null||appId<=0,ErrorCode.PARAMS_ERROR,"应用ID无效");ThrowUtils.throwIf(StrUtil.isBlank(message),ErrorCode.PARAMS_ERROR,"用户消息不能为空");// 获取当前登录用户UserloginUser=userService.getLoginUser(request);// 调用服务生成代码Flux<String>contentFlux=appService.chatToGenCode(appId,message,loginUser);// 封装数据+添加结束事件returncontentFlux.map(chunk->{// 封装为JSON,保留空格格式Map<String,String>wrapper=Map.of("d",chunk);StringjsonData=JSONUtil.toJsonStr(wrapper);returnServerSentEvent.<String>builder().data(jsonData).build();}).concatWith(Mono.just(// 发送done事件标识生成完成ServerSentEvent.<String>builder().event("done").data("").build()));}五、接口测试
1. 测试步骤
- 用户登录获取会话
- 调用SSE流式生成接口
- 查看实时输出结果
2. 测试命令(CURL)
# 1.用户登录,保存cookiecurl-XPOST"http://localhost:8123/api/user/login"\-H"Content-Type: application/json"\-d'{"userAccount":"yupi","userPassword":"12345678"}'\-ccookies.txt# 2.调用SSE流式生成接口curl-G"http://localhost:8123/api/app/chat/gen/code"\--data-urlencode"appId=303320512563961856"\--data-urlencode"message=我需要一个简单的任务记录工具网站"\-H"Accept: text/event-stream"\-H"Cache-Control: no-cache"\-bcookies.txt\--no-buffer3. 测试效果
优化后接口可正常保留空格/换行,生成完成后触发done事件,前端可精准判断生成状态。
六、模块核心要点总结
- 应用绑定:通过appId关联代码文件,解决平台化存储混乱问题
- 流式响应:SSE技术实现代码实时输出,提升用户交互体验
- 双层优化:JSON封装解决空格丢失,done事件明确生成结束状态
- 权限管控:仅应用创建者可触发生成,保障平台数据安全
- 规范统一:文件目录、接口返回、异常处理全链路标准化
