青天明镜映红尘

青天明镜映红尘

青天明镜映红尘,度世舟上望苦海。 逍遥自在终超脱,彼岸之中看苍生。 CSDN同名,主更csdn,做项目更新编程导航
南京市
Java后端
2020
老鱼大学
Java后端

该用户为会员

会员专享项目教程/ 答疑等服务

AI零代码生成平台 扩展点:AI生成应用名称优化,手动停止AI生成。 # AI生成应用名称优化 这两个扩展点不难,前者可以利用AI实现,提示词: ``` 请你扮演精准提炼助手,根据用户输入的应用名称 / 功能描述文本 (例如 “创建一个现代化的个人博客网站,包含文章列表、详情页、分类标签、搜索功能、评论系统和个人简介页面。采用简洁的设计风格,支持响应式布局,文章支持 Markdown 格式,首页展示最新文章和热门推荐”), 需紧扣核心需求,去除冗余信息,凝练总结为不超过 12 个汉字的短句,确保语义完整、能准确体现应用核心属性。 注意,你必须仅仅返回总结后的不超过 12 个汉字的短句,不要返回多余的文字。 ``` 然后在ai service中直接编写方法 ```java /** * 根据用户输入的提示词,总结应用名称,不超过12个字 * @return */ @SystemMessage(fromResource = "prompt/appName.txt") String appNameGenerator(String userText); ``` 在生成应用的add方法中直接调用即可: ```java // 应用名称暂时为 initPrompt 前 12 位 //todo 改成让AI总结 生成应用名称 app.setAppName(aiCodeRouterService.appNameGenerator(initPrompt)); ``` # 手动停止AI生成 利用sse流式输出的`Flux.doOnCancel()` 注册一个回调,当 **Flux 的订阅被取消** 时触发。 关闭前端的 `EventSource` 实例,主动终止与后端的 SSE 长连接,触发后端的`Flux.doOnCancel()` 回调中的逻辑 后端则是在doOnCancel中进行资源释放的处理,例如删除在对话历史表中提示词的记录等。 前端同样可以通过编写提示词,让AI生成,前端提示词参考: ``` 你是一位专业的前端开发,帮我根据原型图、页面介绍、需求介绍、业务流程和后端接口信息,参考项目已有的代码风格,生成符合要求的完整代码。 ## 页面介绍 1)目前的功能是,当用户在页面上点击蓝色按钮后(如图一),后端持续以sse的方式返回内容,前端以打字机的效果在页面上展示(如图二),上述的逻辑是已有的逻辑,千万不要修改! 2)你要实现的是,当内容持续输出时,再次点击蓝色按钮(如图三),向后端发送事件,以中断sse的输出。 ## 后端接口 后端sse的流式输出代码如下: .... ```

AI零代码生成平台,针对vue工程化项目,存在有时提出修改需求,大模型不调用工具,直接返回结果,文件也没有修改。这是一个非常严重的bug,如果项目要上线,是必须要修复的。 # 方案设计 vue工程项目,修改原有应用时,AI有时不调用工具直接返回的原因,是因为目前chat_history表中存储的对话记忆,是精简过的,和memory中存储的不一致。 每次从aiCodeGeneratorFactory获取AiCodeGeneratorService,都是要先从caffine本地缓存的Cache中获取的。既然是缓存,就有过期和重建的问题,既然是本地缓存,那么重启后就会失效。 本地缓存缓存中没有,就会走重建的逻辑aiCodeGeneratorService。 重建的第一步,是构建langchain4j的MessageWindowChatMemory,将对话记忆存入redis中。但是在存入redis之前,会将MessageWindowChatMemory中的历史数据给清理掉,然后从数据库chat_history表中读取历史记录(这一步是重点,问题就出在这。保存在数据库中的vue项目对话记录,是经过精简美化阉割后的,保存的内容,让ai判断出不需要使用工具。) 在存入redis之前,为什么会将MessageWindowChatMemory中的历史数据给清理掉?观察RedisChatMemoryStore,它是作为属性被注入到AiCodeGeneratorFactory中的。因为是属性,所以被共享。 解决这个问题的方式有很多种:**(思路仅供参考,非教学,内容请自行甄别,有不合理之处欢迎指出)** 1、如果不将MessageWindowChatMemory中的历史数据给清理掉,可以解决这个问题,尝试在aiCodeGeneratorService方法中自己创建RedisChatMemoryStore实例,而不将RedisChatMemoryStore通过属性的方式注入。**然后设置永不过期**,不从数据库中加载历史对话到RedisChatMemoryStore中。 ```java RedisChatMemoryStore chatMemoryStore = RedisChatMemoryStore.builder().host("localhost").port(6379).prefix(String.valueOf(appId) + ":").build(); // chatHistoryService.ChatHisD2Memory(appId, messageWindowChatMemory, 20); ``` 2、但是在第一种方案中,设置RedisChatMemoryStore缓存永不过期,可能不太好,因为缓存是一种比较贵的资源。那么可以尝试使用其他的ChatMemoryStore,比如自己实现一个持久化到数据库中的。 3、还有一种方案,就是针对vue工程项目,创建一张原始对话记忆日志表,保存对话记忆的时候,将原始的带有工具调用信息的没有任何美化精简的对话记忆另外保存一份到该表中,重建缓存时从该表中读取,这一条在编程导航里好像看到过有代码实现。 由此可见vue工程化项目还是比较麻烦的,很多地方都需要特殊处理。

# 方案设计 在本篇方案设计中 html 和 三文件项目,都统一称为普通项目,vue工程化项目,称为vue项目,本篇提供一种可选思路,项目中的具体代码实现皆是根据此思路展开,并非专业教学资料,内容请自行甄别 首先需要观察普通项目和vue项目生成和修改的特点: - 生成:普通项目不需要调用工具,自己解析生成的内容,保存到文件中,vue项目需要调用工具处理 - 修改:普通项目是全量修改,每次修改都会生成新的文件。而vue项目因为体量大,不能每次修改就全量重新生成,而是采用调用工具进行单个或多个文件的批量修改。 统一新建一张`项目版本控制记录表`,记录每次修改时的版本文件信息: ![image-20251029223605625](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20251029223605625.png) 改造分为两部分,第一部分是原有生成逻辑的改造,另一部分是新开发的版本控制页面和后台逻辑。 ## 生成/修改项目后端改造 1. 在app表中增加version版本字段 2. 在调用`addApp`生成app时,初始化一个版本号为0。 3. 调用`chatToGenCod`e后,生成文件,并且保存对话记忆成功后,版本号 + 1。 对于vue项目,修改时调用`FileModifyTool`,能拿到appId,要替换的旧内容,替换后的新内容三个参数 在这里将该文件替换后的新内容保存到`项目版本控制记录表`,注意,如果当前应用版本是1,还要保存该文件要替换的旧内容。(如果不保存,那么修改完成后,项目目录中的vue项目,该文件已经被替换成了新的文件,这样就找不到最初的历史版本了。如果不保存,要找到最初的历史版本,就应该在生成第一版项目,调用`FileWriteTool`中将生成的第一版文件全部保存到数据库中, 没有必要。) 对于普通项目,在解析文件,写入本地目录后,追加写入到`项目版本控制记录表`的逻辑。 ## 版本控制页面 这一块的设计参考了git的版本管理展示,版本比对前端使用的是 **`v-code-diff`** 插件进行实现 1. 悬浮在应用选项卡上,增加一个历史版本按钮 2. 点击按钮后进入一个新页面,页面左侧展示该app**最新版**的所有文件,默认选中第一个,右侧展示该文件具体的代码 3. 如果用户双击左侧文件列表中的某一个文件,页面右下方弹出该文件的所有历史版本列表,展示创建人,创建时间,用户提示词 4. 单击某一条,右侧展示历史版本的代码,并且和当前不一致的高亮展示。 ## 版本控制后端逻辑 主要需要以下的接口: 1. 根据appId 查询最新版本的文件列表,用于左侧菜单展示,需要是树形结构。 2. 根据文件名和应用id 查询具体文件的内容返回,用于右侧部分的代码内容展示。 3. 根据文件名和应用id,找到某个文件的所有历史版本,返回创建人,创建时间,用户提示词,用于页面右下角的历史版本展示 4. 根据文件名和应用id,具体的版本,查询具体文件的内容返回。用于版本比对

AI零代码平台,版本控制功能,基本实现

AI零代码生成平台 第十一期项目优化小结 # 性能优化 目前不同用户的AI调用,是串行的,相当于同时只能给一个用户用。 因为我们使用的chatModel是Spring注入的属性,默认在同一个JVM中只存在一个实例 (A中注入C,B中注入C,A和B的C是同一个实例) 解决方案:多例模式 - 首先将推理模型和智能路由模型的配置,设置在配置文件中 - 为每一种模型创建配置类,并且注册成Bean(多例) - 修改各自类中原有的注入方式,要用Spring上下文的getBean # 缓存优化 缓存策略设计:缓存十页经典应用 - 缓存中有数据,就用缓存中的数据 - 缓存中没有数据,就查询数据库,写回到缓存中 缓存应该合理设置过期时间 项目中的实现:Spring-data-redis 缓存key的设计:将查询的对象,先转json,再转成md5,为什么要转成Md5,节省空间,避免bigkey 启动类需要加上@EnableCaching开启缓存,但是使用注解模式的弊端是不够灵活,在项目中要自定义CacheManager - 设置过期时间 - 序列化器 - 单独针对某个cache设置过期时间 # 实时性优化 vue工程化项目,采用的是异步打包的方式,可能项目生成完成后,打包还没有完成,导致页面无法正常展示 解决方式: - 同步打包 - 前端轮询打包状态,完成后刷新 - 异步打包完成后,通过SSE向前端推送进度 - 美团noCode的方案:后端连接vite服务器,后端直接把代码修改告诉vite,自动构建页面 # 限流 因为AI的对话是要消耗token的,必须要防刷,否则造成极大的成本负担,使用redisson限流 Redisson实现基于令牌桶的RRateLimiter算法 实现: - 接口级别限流 - 用户级别限流 - IP级别限流 使用自定义注解 + AOP实现 - 使用前置增强 - 创建一个唯一的限流key - 使用redisson的限流器 - 设置限流器参数 - 尝试获取令牌 生成key,也要根据限流类型进行区分 - 接口级别:方法名 - 用户级别:用户ID - IP级别:客户端IP # 安全审查 防止用户输入非法信息,将prompt发送给AI前进行检查 利用到langchain4j的advisor机制,类似于拦截器 - 拒绝过长的token - 拒绝敏感词 - 拒绝注入攻击 可以在AI服务工厂中集成护轨,也可以单独给某个方法使用护轨 # 稳定性优化 ## 重试策略 使用重试策略 设置maxRetries 设置重试护轨**(对应对话记忆治理扩展,如果没有调用工具,让AI重新调用工具)** **使用输出护轨,可能导致流式输出的响应不及时** ## 工具调用优化 防止AI重复调用工具,或者调用工具不稳定 - 设置AI最大调用次数 - 提供退出工具 # 成本优化 根据不同的业务类型,使用不同的模型*(改配置文件的base-url,模型名称和api-key),因为不同的模型,价格是不一样的 将默认提示词的结果保存到数据库中

AI零代码生成平台 第九期可视化修改 小结 # 需求分析 在此前,AI已经可以基于上下文历史对于生成的网站进行优化,但是是需要用户文字描述的,目前是需要实现让用户点击页面上的元素,然后输入修改需求,让AI精确知道是修改哪一块代码 目前主流的AI生成代码网站,是支持用户点选某一块的元素,手动输入信息实时修改(修改css样式,双向绑定),或者是给AI自动修改的 # 方案 不采用手动编辑的模式,只实现自动编辑 用户开启编辑模式,选择页面上的元素。 前端获取选中的元素,关联到提示词,发送给后端 后端调用AI进行修改,返回结果 # 父子网站通信 子网站需要通过嵌入一段代码,向主网站汇报,主网站接受并且处理,但是子网站是AI生成的,应该如何修改? 1. 通过给AI的提示词生成,但是增加代码的不稳定性,并且用户下载的代码中也会有 2. 动态注入代码,子父网站必须同源 # 前端开发,直接利用AI生成 # 后端修改 原生HTML或者三文件,是全量修改,vue项目,是增量修改。 原生HTML或者三文件是**通过修改提示词的方式** ## **vue项目是重点,需要增量修改** vue项目文件过多,不能每次有修改,都从头全量生成。通过给AI定义工具,实现增量修改:**本质上是在页面上选择元素,然后在输入框中输入修改的需求,和被修改的元素的信息打包发送给后端,后端AI框架利用工具,对单个文件或多个文件进行修改,然后重新构建打包。** - 读取单个文件 - 递归获取某个目录下的所有文件结构 - 删除单个文件 - 修改单个文件,替换掉文件中的部分内容 - 创建单个文件 首先要修改vue工程的提示词(减少大模型的幻觉,在提示词中指定应该使用哪种工具,以及如何使用) 然后定义工具 最后需要修改解析AI返回结果的ProjectFluxHandler类。因为不同的工具调用,返回的信息也是不同的。**在大模型返回的内容中,包含了调用的工具的名称,需要编写不同的策略。** **在各自的工具类中,定义输出的方法。然后用一个工厂去统一的管理工具** 所有的工具类,都继承BaseTool类: - 工具中文名 - 工具英文名 - 工具的信息,返回给页面 - 工具的信息,保存到数据库 写一个工具管理类, 创建工具和工具名称的映射关系。利用@PostConstruct注解,将自动注入的工具,在Spring Boot启动时,就存放到该类中的一个Map中

AI零代码生成平台,第八期小结 # 生成应用封面 需求:给每个应用生成一个封面,时机是在应用部署完成之后 使用自动化工具进行截图,并且需要压缩处理,并且将图片放入对象存储中 ## 方案 封面生成采用异步的方式,因为工具需要打开浏览器,如果是高并发的时间段,可能造成内存不足的情况,还应该引入排队的机制。 在选择工具时,要考虑到截图质量,以及是否完整支持JS执行 Selenium工具截图,本质上就是使用浏览器驱动,控制浏览器的行为 ,使程序可以像人为控制那样操作浏览器。利用webDriver进行统一管理,将Selenium的命令,翻译成各个浏览器可以理解的具体指令。相当于jvm的作用 webDriverManager 自动管理浏览器驱动的工具库。 本地生成截图 1. 创建截图工具类,静态代码块初始化驱动 2. 保存图片到文件,需要文件流和路径 3. 压缩文件,需要原本的图片路径,压缩后的图片路径 4. 等待页面加载的方法,需要webDriver 5. 生成网页截图,组合上述三个方法 ### 对象存储 上传压缩后的图片到对象存储,并且删除本地的压缩文件,返回对象存储的地址 ### 部署项目的方法修改 在得到部署可访问的地址后,还需要根据该路径进行截图,更新应用封面,保存到数据库(采用异步的方式) # 下载代码 只有自己生成的代码才能下载,并且下载的是原始代码,不是打包构建过后的代码,还需要排除一些不必要的文件 实现方式是将文件打包成ZIP压缩包,然后返回给前端,要设置特殊的响应头 这里的项目路径,是项目的保存路径,不是部署路径 ![image-20251018211746526](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20251018211746526.png) 1. 基础校验 2. 设置HTTP响应头(要下载的文件名,不要包含中文) 3. 定义文件过滤器 4. 压缩 # AI智能选择方案 让后端自动判断(AI决定),用户的需求应该生成原生HTML,简单页面,还是vue工程化项目 核心是让AI对用户的提示词进行分析,选择合适的方案。 智能路由,应该使用简单的模型,因为功能比较简单。编写提示词文件,编写接口和工厂,利用AI结构化输出。工厂的模型无需使用流式。 如果使用deep-seek的枚举输出,需要把配置中的结构化输出配置去除掉 后续优化成更轻量级的模型

时间紧,先做主线功能,最后统一完成扩展功能 AI零代码生成平台第七期小结 1、第七期是对于生成内容的扩展,从单一的html格式文件和css,js,html三件套的文件,进阶到企业开发中真实的vue工程化项目的生成。vue工程化项目,和前两者除了生成出文件的区别,还有需要运行安装和构建的命令。 2、生成此种复杂的项目,从方案选型上,首先排除掉上期直接输出markdown然后自己解析的做法,因为生成的文档较长,可能不能正确输出,并且解析的时候可能存在问题。如果采用智能体,相对复杂,所以本项目利用AI工具调用的特性去生成,由工具负责控制写入文件到项目指定的目录下。 3、因为生成的项目比较复杂,所以推荐使用deep-seek或其他大模型的深度推理模型。 4、为了将推理的过程在页面上以打字机的效果进行输出,所以使用tokenStream流式输出,并且在工具中,因为需要写入文件,而文件名是和appId相关的,那么工具中是如何能拿到该appId呢,要通过@ToolMemoryId注解实现对话上下文传参的能力。 5、工具调用流式输出,利用了类加载的机制,将源码中的实现,复制了一份到项目中,以相同的路径,故在JVM类加载时可以优先加载项目中的路径。但是如果需要打包部署,那么还是需要对jar包进行修改(这一点,在实际开发中,做过activiti工作流对达梦数据库的适配,部署项目上线时,需要修改对应activiti jar包中的源码) 6、处理流式输出的结果,项目中是自己封装了一套消息类去处理AI 响应消息,工具执行结果消息和工具调用消息。 7、因为vue工程项目的特性,还需要提供一套利用代码运行打包部署的方法。主要是npm install 和 npm run build,如果是windows系统,是npm.exe。这里考虑到性能,还开发了异步接口,给流式输出完成时调用。在jdk21中,支持虚拟线程的方式,相比于传统的线程更加轻量级,传统的线程需要和操作系统关联,是一个比较重的操作。 8、可以编写策略类,对于生成普通文件,和vue项目,进行分派处理,因为两者无论是生成流式输出,还是写入文件,安装部署的流程都是不一致的。

AI零代码生成平台第六期总结 1、第六期实现了对话历史的功能,此对话历史包括两部分,第一部分是将用户和AI的对话历史保存在数据库中,便于页面上回显。第二部分则是将对话历史和langchain4j进行整合,存储到redis中,便于AI理解上下文。 2、在展示对话历史时,分页采用的是游标分页的形式,传统的分页,是相对偏移,如果在查询的过程中,数据库中频繁发生新增或修改,那么可能会发生重复查询的问题。同时,在查询条数相同的场景下,页数越靠后,查询的效率就越低,因为要跳过前n页再去查m条。游标分页,是绝对偏移,如果主键ID是自增的(企业开发中很少用自增),那么自然推荐是用ID作为游标,因为ID不会重复并且有索引。在实际开发中,可以使用创建时间,或者专门的不会重复的排序字段作为游标。在查询时,也应该建立联合索引,伪代码:where 条件 = xxx and 游标 > xxx limit 每页展示多少条。 3、与AI的对话记忆,本项目中采用的是langchain4j整合redis,利用RedisChatMemoryStore类。在工厂类中,是给每一个应用ID,都创建了一个代理类的实例。同时引入了caffine本地缓存(因为项目目前是单体项目),key是appId,value是对应的AiCodeGeneratorService。整体流程,在获取AiCodeGeneratorService实例前,首先去caffine中查询,如果查询不到,就去新建。 4、在创建AiCodeGeneratorService对象时,需要从数据库中读取历史消息,保存到RedisChatMemoryStore中。在保存之前,需要清理掉RedisChatMemoryStore中的历史信息。能走到新建AiCodeGeneratorService对象这一步,说明有两种情况,第一是第一次创建了一个appId为1的caffine缓存,还有可能是之前appId为1的caffine缓存到期了,如果是后者,就体现出清理RedisChatMemoryStore中的历史信息的必要性。 这里有一个疑问:我第一次用appId为1的创建了一个MessageWindowChatMemory,第二次依旧用appId为1的创建了一个MessageWindowChatMemory,这两个MessageWindowChatMemory都不是同一个实例,为什么第二个MessageWindowChatMemory会有第一个的记忆? 答案是虽然是不同的实例,但是在创建时共享了同一个redisChatMemoryStore。redisChatMemoryStore是作为成员变量定义的。导致底层存储的数据被复用。 接下来完成第五期和第六期的扩展点,然后再继续向后学习

目前AI零代码生成平台做到第五期。前几期总结: 1、该项目可以看作是AI智能体项目的扩展,将AI智能体项目中所涉及的AI的特性,在完整的项目中运用。 2、用户模块和云图库基本一致,所以这一块直接复制代码,不再重新开发,区别在于持久层从mp换成了mf,但是差别不大。本项目的前端部分,大部分都是利用AI生成的,这里也体现出了提示词的重要性,会向AI提问和不会提问,差别很大。如果不使用AI生成,完全手写前端页面,对于我来说比较有难度,花费的时间相对也会比较长,尤其是sse对接的部分。 3、本项目中运用的AI框架是langchain4j,和spring ai在语法上有区别,但是特性上相差不大。项目中使用代理(AiServices.builder) + 注解(@SystemMessage) 的方式,调用AI大模型(deepseek),同时将提示词专门保存到了配置文件中统一管理 4、AI生成前端代码,本质上是利用给AI预设提示词,然后用户输入需求,即可生成对应的页面代码,支持单文件(html格式)和多文件(html,js,css)两种模式,生成的效果,一方面取决于大模型的质量,另一方面和提示词也有关系,所以如何编写专业的提示词非常重要。 5、生成的代码文件,暂时是保存在了本地目录下,针对不同文件的解析和保存方式,利用了模板 + 执行器 + 策略 + 门面模式进行抽象优化 6、为了在前端页面模仿出打字机的效果,后端和上一个项目相同,同样是返回SSE格式的响应。这里在返回时,考虑到AI生成内容中的空格,做了特殊处理,转成了json格式再进行返回。 7、在业务设计上,采用了appId进行区分,相当于聊天室的messageId,后续也会通过该字段进行对话记忆的隔离。 8、生成的应用支持用户在页面上预览和部署,预览采用的是spring boot 访问静态资源服务的接口,部署则是利用nginx进行代理,将用户带有deployKey的请求,代理到项目部署根目录下。项目部署的目录,和保存代码的目录也是分离的。在部署时,是将保存代码目录中的内容,复制到部署目录下,并且要对应一个deployKey

下载 APP