# panda_ai_code_generate **Repository Path**: pan_shengdong/ai_code_generate ## Basic Information - **Project Name**: panda_ai_code_generate - **Description**: 基于 Spring Boot 3 + Langchain4j+ LangGraph4j的 AI零代码应用生成平台。用户输入自然语言描述,由 Al Agent 自动执行并发素材搜集、代码生成、质量检査、项目构建的完整工作流,最终一键部署为可访问的 Web 应用。项目核心为一套AI工作流,并采用多级缓存、分布式限流、异步处理、护轨重试等多种优化策略,保证系统的高性能与稳定性。 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-27 - **Last Updated**: 2025-09-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 ​ 基于 Spring Boot 3 + Langchain4j+ LangGraph4j的 AI零代码应用生成平台。用户输入自然语言描述,由 Al Agent 自动执行并发素材搜集、代码生成、质量 检査、项目构建的完整工作流,最终一键部署为可访问的 Web 应用。项目核心为一套AI工作流,并采用多级缓存、分布式限流、异步处理、护轨重试等多种优化 策略,保证系统的高性能与稳定性。 # 项目初始化 https://start.aliyun.com/ https://start.spring.io/ # 后端增删改查代码生成 本次使用新的方式,一键式生成所有代码,包含controller代码,且能支持联表查询。 ```xml com.mybatis-flex mybatis-flex-spring-boot3-starter 1.11.0 com.mybatis-flex mybatis-flex-codegen 1.11.0 com.zaxxer HikariCP ``` ```java package generator; import cn.hutool.core.lang.Dict; import cn.hutool.setting.yaml.YamlUtil; import com.mybatisflex.codegen.Generator; import com.mybatisflex.codegen.config.GlobalConfig; import com.zaxxer.hikari.HikariDataSource; import java.util.Map; /** * @Author panda * @Date 2025-08-28 * @Des */ public class MyBatisCodeGenerator { // 需要生成的表名 private static final String[] TABLE_NAMES = {"user"}; public static void main(String[] args) { // 获取数据源信息 Dict dict = YamlUtil.loadByPath("application.yml"); Map dataSourceConfig = dict.getByPath("spring.datasource"); String url = String.valueOf(dataSourceConfig.get("url")); String username = String.valueOf(dataSourceConfig.get("username")); String password = String.valueOf(dataSourceConfig.get("password")); // 配置数据源 HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); // 创建配置内容 GlobalConfig globalConfig = createGlobalConfig(); // 通过 datasource 和 globalConfig 创建代码生成器 Generator generator = new Generator(dataSource, globalConfig); // 生成代码 generator.generate(); } // 详细配置见:https://mybatis-flex.com/zh/others/codegen.html public static GlobalConfig createGlobalConfig() { // 创建配置内容 GlobalConfig globalConfig = new GlobalConfig(); // 设置根包,建议先生成到一个临时目录下,生成代码后,再移动到项目目录下 globalConfig.getPackageConfig() .setBasePackage("com.panda.genresult"); // 设置表前缀和只生成哪些表,setGenerateTable 未配置时,生成所有表 globalConfig.getStrategyConfig() .setGenerateTable(TABLE_NAMES) // 设置逻辑删除的默认字段名称 .setLogicDeleteColumn("isDelete"); // 设置生成 entity 并启用 Lombok globalConfig.enableEntity() .setWithLombok(true) .setJdkVersion(21); // 设置生成 mapper globalConfig.enableMapper(); globalConfig.enableMapperXml(); // 设置生成 service globalConfig.enableService(); globalConfig.enableServiceImpl(); // 设置生成 controller globalConfig.enableController(); // 设置生成时间和字符串为空,避免多余的代码改动 globalConfig.getJavadocConfig() .setAuthor("程序员panda") .setSince(""); return globalConfig; } } ``` ![image-20250828152707078](README.assets/image-20250828152707078.png) ```java @Table("user") public class User implements Serializable { @Id(keyType = KeyType.Generator, value = KeyGenerators.snowFlakeId) private Long id; } ``` # AI生成代码应用 ## 需求分析 1、让AI生成原始网页 html css javascript 2、打包到一个文件中html ## 方案设计 1、用户输入提示词 2、AI大模型生成 3、提取生成内容 4、保存到本地文件 https://api-docs.deepseek.com/zh-cn/ https://platform.deepseek.com/api_keys https://docs.langchain4j.dev/integrations/language-models/open-ai/#spring-boot ## 测试AI调用 注意充值: ![image-20250902084407625](README.assets/image-20250902084407625.png) ![image-20250902084350724](README.assets/image-20250902084350724.png) ![image-20250902084714990](README.assets/image-20250902084714990.png) ## 参数调整 ![image-20250902085857855](README.assets/image-20250902085857855.png) ![image-20250902085922945](README.assets/image-20250902085922945.png) ### 参数调整的结果 ![image-20250902090058017](README.assets/image-20250902090058017.png) 页面显示:图片显示与主题不符合,可以优化下提示词或调用MCP工具调整。 ![image-20250902090224601](README.assets/image-20250902090224601.png) ## 保存到文件 ![image-20250902100156546](README.assets/image-20250902100156546.png) ![image-20250902104023866](README.assets/image-20250902104023866.png) ![image-20250902104031773](README.assets/image-20250902104031773.png) # SSE流式输出 ## reactor模式 ```xml dev.langchain4j langchain4j-reactor 1.1.0-beta7 ``` ## 流式输出 ```java @Override public Flux chatToGenCode(Long appId, String message, User loginUser) { //xxxxxxxxxxxxxxxxxx // 6. 调用 AI 生成代码(流式) Flux contentFlux = aiCodeGeneratorFacade.generateAndSaveCodeStream(message, codeGenTypeEnum, appId); // 7. 收集AI响应内容并在完成后记录到对话历史 StringBuilder aiResponseBuilder = new StringBuilder(); return contentFlux .map(chunk -> { // 收集AI响应内容 aiResponseBuilder.append(chunk); return chunk; }) .doOnComplete(() -> { // 流式响应完成后,添加AI消息到对话历史 String aiResponse = aiResponseBuilder.toString(); if (StrUtil.isNotBlank(aiResponse)) { chatHistoryService.addChatMessage(appId, aiResponse, ChatHistoryMessageTypeEnum.AI.getValue(), loginUser.getId()); } }) .doOnError(error -> { // 如果AI回复失败,也要记录错误消息 String errorMessage = "AI回复失败: " + error.getMessage(); chatHistoryService.addChatMessage(appId, errorMessage, ChatHistoryMessageTypeEnum.AI.getValue(), loginUser.getId()); }); } ``` ![image-20250903103437024](README.assets/image-20250903103437024.png) ```sh 1. 用户登录 curl -X POST "http://localhost:8855/api/user/login" \ -H "Content-Type: application/json" \ -d '{ "userAccount": "panda", "userPassword": "xxxxx" }' \ -c cookies.txt 2. 调用生成代码接口(流式) curl -G "http://localhost:8855/api/app/chat/gen/code" \ --data-urlencode "appId=xxxxxx" \ --data-urlencode "message=我需要一个简单的任务记录工具网站" \ -H "Accept: text/event-stream" \ -H "Cache-Control: no-cache" \ -b cookies.txt \ --no-buffer ``` # 部署AI应用 nginx部署: ![image-20250903141433310](README.assets/image-20250903141433310.png) # cursor前端生成 效果如下: ![image-20250903154437265](README.assets/image-20250903154437265.png) 打开部署地址: ![image-20250903154526064](README.assets/image-20250903154526064.png) # 多轮历史对话 ## 需求分析 1、对话持久化存储 2、对话历史的应用级别隔离 3、对话历史查询,展示10条历史消息 4、管理历史对话 ## 方案设计 1、分页查询,使用游标查询。【createTime】 ```java /** * 分页查询某个应用的对话历史(游标查询) * * @param appId 应用ID * @param pageSize 页面大小 * @param lastCreateTime 最后一条记录的创建时间 * @param request 请求 * @return 对话历史分页 */ @GetMapping("/app/{appId}") public BaseResponse> listAppChatHistory(@PathVariable Long appId, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) LocalDateTime lastCreateTime, HttpServletRequest request) { User loginUser = userService.getLoginUser(request); Page result = chatHistoryService.listAppChatHistoryByPage(appId, pageSize, lastCreateTime, loginUser); return ResultUtils.success(result); } @Override public Page listAppChatHistoryByPage(Long appId, int pageSize, LocalDateTime lastCreateTime, User loginUser) { ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用ID不能为空"); ThrowUtils.throwIf(pageSize <= 0 || pageSize > 50, ErrorCode.PARAMS_ERROR, "页面大小必须在1-50之间"); ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR); // 验证权限:只有应用创建者和管理员可以查看 App app = appService.getById(appId); ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在"); boolean isAdmin = UserConstant.ADMIN_ROLE.equals(loginUser.getUserRole()); boolean isCreator = app.getUserId().equals(loginUser.getId()); ThrowUtils.throwIf(!isAdmin && !isCreator, ErrorCode.NO_AUTH_ERROR, "无权查看该应用的对话历史"); // 构建查询条件 ChatHistoryQueryRequest queryRequest = new ChatHistoryQueryRequest(); queryRequest.setAppId(appId); queryRequest.setLastCreateTime(lastCreateTime); QueryWrapper queryWrapper = this.getQueryWrapper(queryRequest); // 查询数据 return this.page(Page.of(1, pageSize), queryWrapper); } ``` ## 代码实现 ```xml dev.langchain4j langchain4j-community-redis-spring-boot-starter 1.1.0-beta7 ``` yml ```yaml spring data: redis: host: localhost port: 6379 password: ttl: 3600 ``` config ```java @Configuration @ConfigurationProperties(prefix = "spring.data.redis") @Data public class RedisChatMemoryStoreConfig { private String host; private int port; private String password; private long ttl; @Bean public RedisChatMemoryStore redisChatMemoryStore() { return RedisChatMemoryStore.builder() .host(host) .port(port) .password(password) .ttl(ttl) .build(); } } ``` interface ```java /** * 保持上下文会话信息到历史记录 * @param memoryId * @param userMessage * @return */ HtmlCodeResult generateHtmlCode(@MemoryId int memoryId, @UserMessage String userMessage); ``` 工厂类构造 ```java @Configuration public class AiCodeGeneratorServiceFactory { @Resource private ChatModel chatModel; @Resource private StreamingChatModel streamingChatModel; // @Bean // public AiCodeGeneratorService aiCodeGeneratorService() { // return AiServices.builder(AiCodeGeneratorService.class) // .chatModel(chatModel) // .streamingChatModel(streamingChatModel) // .build(); // } private final RedisChatMemoryStore redisChatMemoryStore; public AiCodeGeneratorServiceFactory(RedisChatMemoryStore redisChatMemoryStore) { this.redisChatMemoryStore = redisChatMemoryStore; } @Bean public AiCodeGeneratorService aiCodeGeneratorService() { return AiServices.builder(AiCodeGeneratorService.class) .chatModel(chatModel) .streamingChatModel(streamingChatModel) // 根据 id 构建独立的对话记忆 .chatMemoryProvider(memoryId -> MessageWindowChatMemory .builder() .id(memoryId) .chatMemoryStore(redisChatMemoryStore) .maxMessages(20) .build()) .build(); } } ``` 测试类测试: ```java @Test public void testMemory() { HtmlCodeResult htmlCodeResult = aiCodeGeneratorService.generateHtmlCode(1, "做个helloword网页,代码总量不超过20行"); System.out.println("第一步:" + htmlCodeResult); HtmlCodeResult result = aiCodeGeneratorService.generateHtmlCode(1, "不要生成网站,高速我你刚刚做了什么?"); System.out.println("第二步:" + result); } ``` 结果: ![image-20250905144253886](README.assets/image-20250905144253886.png) ![image-20250905144321523](README.assets/image-20250905144321523.png) # AI工作流