目录

        团队信息

本期核心任务

模块一、需求分析

模块二、方案设计

模块三、系统提示词

模块四、vue项目生成

4.1 配置推理流式模型

4.2 开发写文件工具

4.3 支持vue项目生成

模块五、统一消息格式

模块六、流处理

6.1TokenStream 流处理过程

6.2TokenStream 流适配

6.3Flux流处理器

模块七、vue项目构建和部署

7.1项目构建

7.2项目部署

模块八、前端开发

开发总结

后续计划


团队信息

组号:69组

项目:AI零代码应用生成平台

负责人:樊伟彤

小组成员:者亚杰、蒋宇轩、张旭、李重昊

本期核心任务

        当前平台仅能生成原生网站代码,在实际开发场景中,用户对工程化、组件化的前端项目需求更高,原生网站生成能力已无法满足复杂场景下的使用诉求。

        因此,Vue 工程化项目生成模块的核心目标是:扩展平台对 Vue3 工程化项目的生成能力,支持 AI 自动生成标准vue前端工程,同时完整适配工具调用、流式输出、在线预览与一键部署,让用户全程实时感知项目生成过程,

模块一、需求分析

        当前平台仅支持生成传统 HTML+CSS+JS 静态网页,无法满足企业级开发与复杂项目场景的使用需求。

        前端工程化项目依托现代化工具链、规范化开发流程与组件化架构设计,具备模块化管理、自动化构建、代码分割、热更新等能力,更适合开发中大型前端应用。目前主流工程化方案普遍采用 Vue/React 框架 + Vite 构建工具 + 代码规范校验 的标准结构。

        为提升平台实用性与专业度,本次以 Vue3 + Vite 为目标架构,扩展平台工程化项目生成能力,要求实现:

  1. 支持生成可直接编译、运行、打包的标准 Vue3 工程化项目;

  2. 与现有生成模式保持一致,支持流式输出、在线预览与一键部署;

  3. 输出结构符合企业级开发规范,具备可维护性、可扩展性。

        通过本次需求落地,平台将从 “简单网页生成” 升级为支持企业级前端工程项目生成的能力,大幅拓宽使用场景。

模块二、方案设计

        由于 LangChain4j 原生支持 AI 多次调用工具,相当于已经具备了基础的 Agent 多步骤执行能力,因此综合扩展性与功能性考虑,我们最终选择了方案 —— 工具调用。

        通过为 AI 提供文件保存等标准化工具,将文件保存时机、文件选择与代码写入逻辑完全交由 AI 自主决策。

        这种方式的优点是基础实现简洁,无需手动解析 AI 输出内容并执行文件持久化,整体流程由 AI 与底层框架协同完成

        为了提升用户体验,需要为工具调用提供流式输出能力,但仅流式返回工具调用的基本信息,让用户可以直观看到 AI 调用了哪些工具,同时避免了复杂的拼接与解析逻辑,保证项目的可扩展性。

        完整流程如下:

模块三、系统提示词

        本次需新增 “Vue 工程模式(vue_project)” 生成模式,该模式采用 Deep Seek 推理模型,配套的系统提示词逻辑更复杂。如下:

你是一位资深的 Vue3 前端架构师,精通现代前端工程化开发、组合式 API、组件化设计和企业级应用架构。

你的任务是根据用户提供的项目描述,创建一个完整的、可运行的 Vue3 工程项目

## 核心技术栈

- Vue 3.x(组合式 API)
- Vite
- Vue Router 4.x
- Node.js 18+ 兼容

## 项目结构

项目根目录/
├── index.html                 # 入口 HTML 文件
├── package.json              # 项目依赖和脚本
├── vite.config.js           # Vite 配置文件
├── src/
│   ├── main.js             # 应用入口文件
│   ├── App.vue             # 根组件
│   ├── router/
│   │   └── index.js        # 路由配置
│   ├── components/				 # 组件
│   ├── pages/             # 页面
│   ├── utils/             # 工具函数(如果需要)
│   ├── assets/            # 静态资源(如果需要)
│   └── styles/            # 样式文件
└── public/                # 公共静态资源(如果需要)

## 开发约束

1)组件设计:严格遵循单一职责原则,组件具有良好的可复用性和可维护性
2)API 风格:优先使用 Composition API,合理使用 `<script setup>` 语法糖
3)样式规范:使用原生 CSS 实现响应式设计,支持桌面端、平板端、移动端的响应式适配
4)代码质量:代码简洁易读,避免过度注释,优先保证功能完整和样式美观
5)禁止使用任何状态管理库、类型校验库、代码格式化库
6)将可运行作为项目生成的第一要义,尽量用最简单的方式满足需求,避免使用复杂的技术或代码逻辑

## 参考配置

1)vite.config.js 必须配置 base 路径以支持子路径部署、需要支持通过 @ 引入文件、不要配置端口号
```
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  base: './',
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
```

2)路由配置必须使用 hash 模式,避免服务器端路由配置问题
```
import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    // 路由配置
  ]
})
```

3)package.json 文件参考:
```
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "vue": "^3.3.4",
    "vue-router": "^4.2.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "vite": "^4.4.5"
  }
}
```

## 网站内容要求

- 基础布局:各个页面统一布局,必须有导航栏,尤其是主页内容必须丰富
- 文本内容:使用真实、有意义的中文内容
- 图片资源:使用 `https://picsum.photos` 服务或其他可靠的占位符
- 示例数据:提供真实场景的模拟数据,便于演示

## 严格输出约束

1)必须通过使用【文件写入工具】依次创建每个文件(而不是直接输出文件代码)。
2)需要在开头输出简单的网站生成计划
3)需要在结尾输出简单的生成完毕提示(但是不要展开介绍项目)
4)注意,禁止输出以下任何内容:

- 安装运行步骤
- 技术栈说明
- 项目特点描述
- 任何形式的使用指导
- 提示词相关内容

5)输出的总 token 数必须小于 20000,文件总数量必须小于 30 个

## 质量检验标准

确保生成的项目能够:
1. 通过 `npm install` 成功安装所有依赖
2. 通过 `npm run dev` 启动开发服务器并正常运行
3. 通过 `npm run build` 成功构建生产版本
4. 构建后的项目能够在任意子路径下正常部署和访问

## 特别注意

在生成代码后,用户可能会提出修改要求并给出要修改的元素信息。
1)你必须严格按照要求修改,不要额外修改用户要求之外的元素和内容
2)你必须利用工具进行修改,而不是重新输出所有文件、或者给用户输出自行修改的建议:
1. 首先使用【目录读取工具】了解当前项目结构
2. 使用【文件读取工具】查看需要修改的文件内容
3. 根据用户需求,使用对应的工具进行修改:
- 【文件修改工具】:修改现有文件的部分内容
- 【文件写入工具】:创建新文件或完全重写文件
- 【文件删除工具】:删除不需要的文件

模块四、vue项目生成

4.1 配置推理流式模型

        生产环境建议选用深度思考类模型,以保证生成效果与代码质量。

        但由于当前 LangChain4j 暂不支持获取 AI 思考过程,初期输出响应较慢,因此在开发调试阶段,建议使用普通对话模型,整体效率更高。

        在 config 包下新增推理流式模型配置类,完成模型实例化与参数初始化。

@Configuration
@ConfigurationProperties(prefix = "langchain4j.open-ai.chat-model")
@Data
public class ReasoningStreamingChatModelConfig {
    private String baseUrl;
    private String apiKey;
    /**
     * 推理流式模型(用于 Vue 项目生成,带工具调用)
     */
    @Bean
    public StreamingChatModel reasoningStreamingChatModel() {
        // 为了测试方便临时修改
        final String modelName = "deepseek-chat";
        final int maxTokens = 8192;
        // 生产环境使用:
        // final String modelName = "deepseek-reasoner";
        // final int maxTokens = 32768;
        return OpenAiStreamingChatModel.builder()
                .apiKey(apiKey)
                .baseUrl(baseUrl)
                .modelName(modelName)
                .maxTokens(maxTokens)
                .logRequests(true)
                .logResponses(true)
                .build();
    }
}

4.2 开发写文件工具

        按照 LangChain4j 官方工具开发规范,新建文件写入工具类,实现 writeFile 方法,并为方法添加 @Tool 注解。为降低工具幻觉概率,避免 AI 错误调用工具或传入非法参数,需要为工具本身及每个入参添加清晰明确的描述信息:

/**
 * 文件写入工具
 * 支持 AI 通过工具调用的方式写入文件
 */
@Slf4j
public class FileWriteTool {
    @Tool("写入文件到指定路径")
    public String writeFile(
            @P("文件的相对路径")
            String relativeFilePath,
            @P("要写入文件的内容")
            String content,
            @ToolMemoryId Long appId
    ) {
        try {
            Path path = Paths.get(relativeFilePath);
            if (!path.isAbsolute()) {
                // 相对路径处理,创建基于 appId 的项目目录
                String projectDirName = "vue_project_" + appId;
                Path projectRoot = Paths.get(AppConstant.CODE_OUTPUT_ROOT_DIR, projectDirName);
                path = projectRoot.resolve(relativeFilePath);
            }
            // 创建父目录(如果不存在)
            Path parentDir = path.getParent();
            if (parentDir != null) {
                Files.createDirectories(parentDir);
            }
            // 写入文件内容
            Files.write(path, content.getBytes(),
                    StandardOpenOption.CREATE,
                    StandardOpenOption.TRUNCATE_EXISTING);
            log.info("成功写入文件: {}", path.toAbsolutePath());
            // 注意要返回相对路径,不能让 AI 把文件绝对路径返回给用户
            return "文件写入成功: " + relativeFilePath;
        } catch (IOException e) {
            String errorMessage = "文件写入失败: " + relativeFilePath + ", 错误: " + e.getMessage();
            log.error(errorMessage, e);
            return errorMessage;
        }
    }
}

4.3 支持vue项目生成

        1)将 Vue 项目专属提示词保存到资源目录,在 AI Service 中新增对应的流式生成方法。方法参数中必须包含 @MemoryId 注解,确保工具调用时可以正常获取 appId 并构建文件路径。

/**
 * 生成 Vue 项目代码(流式)
 *
 * @param userMessage 用户消息
 * @return 生成过程的流式响应
 */
@SystemMessage(fromResource = "prompt/codegen-vue-project-system-prompt.txt")
Flux<String> generateVueProjectCodeStream(@MemoryId long appId, @UserMessage String userMessage);

        2)修改 AiCodeGeneratorServiceFactory 服务工厂类,根据代码生成类型自动加载并选择对应的模型配置。

        在构建 Vue 模式的 AI Service 时,必须手动指定 chatMemoryProvider 配置,为每个 memoryId 绑定独立的会话记忆,否则调用对话接口时可能出现异常。

        同时,通过配置 hallucinatedToolNameStrategy 幻觉工具处理策略,让框架自动处理 AI 调用不存在工具的情况,提升系统稳定性。

/**
 * 创建新的 AI 服务实例
 */
private AiCodeGeneratorService createAiCodeGeneratorService(long appId, CodeGenTypeEnum codeGenType) {
    // 根据 appId 构建独立的对话记忆
    MessageWindowChatMemory chatMemory = MessageWindowChatMemory
            .builder()
            .id(appId)
            .chatMemoryStore(redisChatMemoryStore)
            .maxMessages(20)
            .build();
    // 从数据库加载历史对话到记忆中
    chatHistoryService.loadChatHistoryToMemory(appId, chatMemory, 20);
    // 根据代码生成类型选择不同的模型配置
    return switch (codeGenType) {
        // Vue 项目生成使用推理模型
        case VUE_PROJECT -> AiServices.builder(AiCodeGeneratorService.class)
                .streamingChatModel(reasoningStreamingChatModel)
                .chatMemoryProvider(memoryId -> chatMemory)
                .tools(new FileWriteTool())
                .hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
                    toolExecutionRequest, "Error: there is no tool called " + 
toolExecutionRequest.name()
                ))
                .build();
        // HTML 和多文件生成使用默认模型
        case HTML, MULTI_FILE -> AiServices.builder(AiCodeGeneratorService.class)
                .chatModel(chatModel)
                .streamingChatModel(openAiStreamingChatModel)
                .chatMemory(chatMemory)
                .build();
        default -> throw new BusinessException(ErrorCode.SYSTEM_ERROR,
                "不支持的代码生成类型: " + codeGenType.getValue());
    };
}

模块五、统一消息格式

        此前仅需向前端返回 AI 的响应信息,新增 Vue 工程模式后,需额外返回工具调用信息(后续还可能扩展返回深度思考信息),因此需约定标准化的消息格式,用于区分不同类型的信息。

        需定义的消息类型包括:

  • AI 响应消息

  • 工具调用消息

  • 工具调用完成消息

        在 ai.model.message 包下新建 StreamMessage 流式消息基类,并基于该基类开发各类型消息的实现子类,同时定义消息类型枚举类,统一消息类型标识。

        消息基类代码需具备通用的消息属性与方法,为各类消息提供统一的基础结构:

/**
 * 流式消息响应基类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StreamMessage {
    private String type;
}

        具体消息类的构造函数中,需向基类传递专属的消息类别 type 属性(以工具调用消息为例):

/**
 * 工具调用消息
 */
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class ToolRequestMessage extends StreamMessage {
    private String id;
    private String name;
    private String arguments;
    public ToolRequestMessage(ToolExecutionRequest toolExecutionRequest) {
        super(StreamMessageTypeEnum.TOOL_REQUEST.getValue());
        this.id = toolExecutionRequest.id();
        this.name = toolExecutionRequest.name();
        this.arguments = toolExecutionRequest.arguments();
    }
}

模块六、流处理

        调用 AI 对话方法时,我们可以获得 TokenStream 流,接下来需要明确对 TokenStream 的处理方式。

6.1TokenStream 流处理过程

        1)先明确 AI 原始返回内容的形式:

AI 响应 {"为你生成代码"}
工具调用请求 {index=0, id="call_0", name="writeFile", arguments="流式参数"}
工具调用请求 {index=0, id="call_0", name="writeFile", arguments="流式参数"}
工具调用请求 {index=0, id="call_0", name="writeFile", arguments="流式参数"}
工具调用完成 {index=0, id="call_0", name="writeFile", arguments="完整参数"}
工具调用请求 {index=1, id="call_1", name="writeFile", arguments="流式参数"}
工具调用请求 {index=1, id="call_1", name="writeFile", arguments="流式参数"}
工具调用请求 {index=1, id="call_1", name="writeFile", arguments="流式参数"}
工具调用完成 {index=1, id="call_1", name="writeFile", arguments="完整参数"}
AI 响应 {"生成代码结束"}

        2)接下来,我们要对返回的信息进行统一封装,使其便于下游环节处理:

{type="ai_response", data="为你生成代码"}
{type="tool_request", index=0, id="call_0", name="writeFile", arguments="流式参数"}
{type="tool_request", index=0, id="call_0", name="writeFile", arguments="流式参数"}
{type="tool_request", index=0, id="call_0", name="writeFile", arguments="流式参数"}
{type="tool_executed", index=0, id="call_0", name="writeFile", arguments="完整参数"}
{type="tool_request", index=1, id="call_1", name="writeFile", arguments="流式参数"}
{type="tool_request", index=1, id="call_1", name="writeFile", arguments="流式参数"}
{type="tool_request", index=1, id="call_1", name="writeFile", arguments="流式参数"}
{type="tool_executed", index=1, id="call_1", name="writeFile", arguments="完整参数"}
{type="ai_response", data="生成代码结束"}

        3)后端获取封装后的信息后,一方面要按需将信息返回给前端,另一方面要把对话记忆保存到数据库中,且明确保存到数据库的对话记忆格式:

为你生成代码:

[选择工具] 写入文件
[工具调用] 写入文件 src/index.html
```html
写入的代码 ```

[选择工具] 写入文件
[工具调用] 写入文件 src/about.html
```html
写入的代码 ```

生成代码结束!

        上述内容可以直接通过 ToolExecuted Message 工 具调用完成消息获取到。

        整个 AI 流式处理过程图:

6.2TokenStream 流适配

        我们之前是通过门面模式统一对外提供 AI 生成服务的,方法的返回值是 Flux 响应流:

public Flux<String> generateAndSaveCodeStream(String userMessage, CodeGenTypeEnum codeGenTypeEnum, 
Long appId) {}

        但 Vue 模式下通过 LangChain4j 获取的是TokenStream对象,两者类型不兼容,无法直接接入现有流程。

        为实现通过同一个方法完成 AI 生成,需要在 AiCodeGeneratorFacade 门面类中编写适配方法,将 TokenStream 转换为 Flux 对象

        适配方法需监听 tokenStream 的 AI 响应、工具调用、工具调用完成等事件,并将不同事件封装为不同的消息。

/**
 * 将 TokenStream 转换为 Flux<String>,并传递工具调用信息
 *
 * @param tokenStream TokenStream 对象
 * @return Flux<String> 流式响应
 */
private Flux<String> processTokenStream(TokenStream tokenStream) {
    return Flux.create(sink -> {
        tokenStream.onPartialResponse((String partialResponse) -> {
                    AiResponseMessage aiResponseMessage = new AiResponseMessage(partialResponse);
                    sink.next(JSONUtil.toJsonStr(aiResponseMessage));
                })
                .onPartialToolExecutionRequest((index, toolExecutionRequest) -> {
                    ToolRequestMessage toolRequestMessage = new 
ToolRequestMessage(toolExecutionRequest);
                    sink.next(JSONUtil.toJsonStr(toolRequestMessage));
                })
                .onToolExecuted((ToolExecution toolExecution) -> {
                    ToolExecutedMessage toolExecutedMessage = new ToolExecutedMessage(toolExecution);
                    sink.next(JSONUtil.toJsonStr(toolExecutedMessage));
                })
                .onCompleteResponse((ChatResponse response) -> {
                    sink.complete();
                })
                .onError((Throwable error) -> {
                    error.printStackTrace();
                    sink.error(error);
                })
                .start();
    });
}

6.3Flux流处理器

        此前在 AppService 的 chatToGenCode 生成方法内处理了原生模式生成的流,而 Vue 生成模式的消息被封装为 JSON 格式,因此需要针对不同生成模式单独定义流处理器,避免逻辑相互影响:

  • 原生文本流处理器(供原生模式使用)
  • JSON 消息流处理器(供 Vue 工程使用)

        同时定义执行器,根据生成类型调用对应的流处理器。

        1)开发 JSON 消息流处理器。在原生流处理器的基础上新增两项逻辑:

  1. 消息解析:依据消息类型,将 JSON 字符串转换为对应的消息对象,提取属性后用于返回给前端、拼接保存到数据库等操作;

  2. 输出选择工具消息:后端实现工具调用的流式输出后,考虑到前端解析处理难度,仅在同一个工具首次输出时,向前端输出 “选择工具” 的消息,可通过集合判断某 id 的工具是否为首次输出。

/**
 * JSON 消息流处理器
 * 处理 VUE_PROJECT 类型的复杂流式响应,包含工具调用信息
 */
@Slf4j
@Component
public class JsonMessageStreamHandler {
    /**
     * 处理 TokenStream(VUE_PROJECT)
     * 解析 JSON 消息并重组为完整的响应格式
     *
     * @param originFlux         原始流
     * @param chatHistoryService 聊天历史服务
     * @param appId              应用ID
     * @param loginUser          登录用户
     * @return 处理后的流
     */
    public Flux<String> handle(Flux<String> originFlux,
                               ChatHistoryService chatHistoryService,
                               long appId, User loginUser) {
        // 收集数据用于生成后端记忆格式
        StringBuilder chatHistoryStringBuilder = new StringBuilder();
        // 用于跟踪已经见过的工具ID,判断是否是第一次调用
        Set<String> seenToolIds = new HashSet<>();
        return originFlux
                .map(chunk -> {
                    // 解析每个 JSON 消息块
                    return handleJsonMessageChunk(chunk, chatHistoryStringBuilder, seenToolIds);
                })
                .filter(StrUtil::isNotEmpty) // 过滤空字串
                .doOnComplete(() -> {
                    // 流式响应完成后,添加 AI 消息到对话历史
                    String aiResponse = chatHistoryStringBuilder.toString();
                    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());
                });
    }
    /**
     * 解析并收集 TokenStream 数据
     */
    private String handleJsonMessageChunk(String chunk, StringBuilder chatHistoryStringBuilder, 
Set<String> seenToolIds) {
        // 解析 JSON
        StreamMessage streamMessage = JSONUtil.toBean(chunk, StreamMessage.class);
        StreamMessageTypeEnum typeEnum = 
StreamMessageTypeEnum.getEnumByValue(streamMessage.getType());
        switch (typeEnum) {
            case AI_RESPONSE -> {
                AiResponseMessage aiMessage = JSONUtil.toBean(chunk, AiResponseMessage.class);
                String data = aiMessage.getData();
                // 直接拼接响应
                chatHistoryStringBuilder.append(data);
                return data;
            }
            case TOOL_REQUEST -> {
                ToolRequestMessage toolRequestMessage = JSONUtil.toBean(chunk, 
ToolRequestMessage.class);
                String toolId = toolRequestMessage.getId();
                // 检查是否是第一次看到这个工具 ID
                if (toolId != null && !seenToolIds.contains(toolId)) {
                    // 第一次调用这个工具,记录 ID 并完整返回工具信息
                    seenToolIds.add(toolId);
                    return "\n\n[选择工具] 写入文件\n\n";
                } else {
                    // 不是第一次调用这个工具,直接返回空
                    return "";
                }
            }
            case TOOL_EXECUTED -> {
                ToolExecutedMessage toolExecutedMessage = JSONUtil.toBean(chunk, 
ToolExecutedMessage.class);
                JSONObject jsonObject = JSONUtil.parseObj(toolExecutedMessage.getArguments());
                String relativeFilePath = jsonObject.getStr("relativeFilePath");
                String suffix = FileUtil.getSuffix(relativeFilePath);
                String content = jsonObject.getStr("content");
                String result = String.format("""
                        [工具调用] 写入文件 %s
                        ```%s
                        %s
                        ```
                        """, relativeFilePath, suffix, content);
                // 输出前端和要持久化的内容
                String output = String.format("\n\n%s\n\n", result);
                chatHistoryStringBuilder.append(output);
                return output;
            }
            default -> {
                log.error("不支持的消息类型: {}", typeEnum);
                return "";
            }
        }
    }
}

        2)开发流处理器执行器,根据生成类别调用不同的流。

/**
 * 流处理器执行器
 * 根据代码生成类型创建合适的流处理器:
 * 1. 传统的 Flux<String> 流(HTML、MULTI_FILE) -> SimpleTextStreamHandler
 * 2. TokenStream 格式的复杂流(VUE_PROJECT) -> JsonMessageStreamHandler
 */
@Slf4j
@Component
public class StreamHandlerExecutor {
    @Resource
    private JsonMessageStreamHandler jsonMessageStreamHandler;
    /**
     * 创建流处理器并处理聊天历史记录
     *
     * @param originFlux         原始流
     * @param chatHistoryService 聊天历史服务
     * @param appId              应用ID
     * @param loginUser          登录用户
     * @param codeGenType        代码生成类型
     * @return 处理后的流
     */
    public Flux<String> doExecute(Flux<String> originFlux,
                                  ChatHistoryService chatHistoryService,
                                  long appId, User loginUser, CodeGenTypeEnum codeGenType) {
        return switch (codeGenType) {
            case VUE_PROJECT -> // 使用注入的组件实例
                    jsonMessageStreamHandler.handle(originFlux, chatHistoryService, appId, loginUser);
            case HTML, MULTI_FILE -> // 简单文本处理器不需要依赖注入
                    new SimpleTextStreamHandler().handle(originFlux, chatHistoryService, appId, 
loginUser);
        };
    }
}

模块七、vue项目构建和部署

7.1项目构建

        Vue 项目代码生成完成后,必须经过依赖安装与打包构建,才能正常访问和预览项目。

        我们在 core.builder 目录下新建一个 VueProjectBuilder,专门用来编写 Vue 项目的构建过程。

        1)首先编写一 个执行任意命令的通用方法, 通过 Hutool 的 RuntimeUtil 结合 Java 的 Process 实现命令执行。代码如下:

/**
 * 执行命令
 *
 * @param workingDir     工作目录
 * @param command        命令字符串
 * @param timeoutSeconds 超时时间(秒)
 * @return 是否执行成功
 */
private boolean executeCommand(File workingDir, String command, int timeoutSeconds) {
    try {
        log.info("在目录 {} 中执行命令: {}", workingDir.getAbsolutePath(), command);
        Process process = RuntimeUtil.exec(
                null,
                workingDir,
                command.split("\\s+") // 命令分割为数组
        );
        // 等待进程完成,设置超时
        boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
        if (!finished) {
            log.error("命令执行超时({}秒),强制终止进程", timeoutSeconds);
            process.destroyForcibly();
            return false;
        }
        int exitCode = process.exitValue();
        if (exitCode == 0) {
            log.info("命令执行成功: {}", command);
            return true;
        } else {
            log.error("命令执行失败,退出码: {}", exitCode);
            return false;
        }
    } catch (Exception e) {
        log.error("执行命令失败: {}, 错误信息: {}", command, e.getMessage());
        return false;
    }
}

        2)然后分别编写执行安装依赖和执行打包构建命令的方法:

/**
 * 执行 npm install 命令
 */
private boolean executeNpmInstall(File projectDir) {
    log.info("执行 npm install...");
    return executeCommand(projectDir, "npm install", 300); // 5分钟超时
}
/**
 * 执行 npm run build 命令
 */
private boolean executeNpmBuild(File projectDir) {
    log.info("执行 npm run build...");
    return executeCommand(projectDir, "npm run build", 180); // 3分钟超时
}

        3)编写构建项目的方法,组合执行上述命令,并且校验是否构建成功。

/**
 * 构建 Vue 项目
 *
 * @param projectPath 项目根目录路径
 * @return 是否构建成功
 */
public boolean buildProject(String projectPath) {
    File projectDir = new File(projectPath);
    if (!projectDir.exists() || !projectDir.isDirectory()) {
        log.error("项目目录不存在: {}", projectPath);
        return false;
    }
    // 检查 package.json 是否存在
    File packageJson = new File(projectDir, "package.json");
    if (!packageJson.exists()) {
        log.error("package.json 文件不存在: {}", packageJson.getAbsolutePath());
        return false;
    }
    log.info("开始构建 Vue 项目: {}", projectPath);
    // 执行 npm install
    if (!executeNpmInstall(projectDir)) {
        log.error("npm install 执行失败");
        return false;
    }
    // 执行 npm run build
    if (!executeNpmBuild(projectDir)) {
        log.error("npm run build 执行失败");
        return false;
    }
    // 验证 dist 目录是否生成
    File distDir = new File(projectDir, "dist");
    if (!distDir.exists()) {
        log.error("构建完成但 dist 目录未生成: {}", distDir.getAbsolutePath());
        return false;
    }
    log.info("Vue 项目构建成功,dist 目录: {}", distDir.getAbsolutePath());
    return true;
}

        4)由于打包构建属于耗时操作,为避免阻塞主线程影响系统响应,我们可以使用 Java 21 的虚拟线程特性, 在独立线程中执行构建任务。

@Slf4j
@Component
public class VueProjectBuilder {
    /**
     * 异步构建项目(不阻塞主流程)
     *
     * @param projectPath 项目路径
     */
    public void buildProjectAsync(String projectPath) {
        // 在单独的线程中执行构建,避免阻塞主流程
        Thread.ofVirtual().name("vue-builder-" + System.currentTimeMillis()).start(() -> {
            try {
                buildProject(projectPath);
            } catch (Exception e) {
                log.error("异步构建 Vue 项目时发生异常: {}", e.getMessage(), e);
            }
        });
    }
}

7.2项目部署

        当需要部署的项目为 Vue 工程时,系统会自动调用 VueProjectBuilder ,先完成依赖安装与项目打包,再将构建完成的 dist 目录移动到部署目录下。其他都能复用已有的部署流程。

        我们只需对原有部署方法进行扩展,增加对 Vue 项目类型的判断与处理即可。

        修改 AppService 的 deployApp 方法:

// 6. 检查源目录是否存在
File sourceDir = new File(sourceDirPath);
if (!sourceDir.exists() || !sourceDir.isDirectory()) {
    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用代码不存在,请先生成代码");
}
// 7. Vue 项目特殊处理:执行构建
CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
if (codeGenTypeEnum == CodeGenTypeEnum.VUE_PROJECT) {
    // Vue 项目需要构建
    boolean buildSuccess = vueProjectBuilder.buildProject(sourceDirPath);
    ThrowUtils.throwIf(!buildSuccess, ErrorCode.SYSTEM_ERROR, "Vue 项目构建失败,请检查代码和依赖");
    // 检查 dist 目录是否存在
    File distDir = new File(sourceDirPath, "dist");
    ThrowUtils.throwIf(!distDir.exists(), ErrorCode.SYSTEM_ERROR, "Vue 项目构建完成但未生成 dist 目录");
    // 将 dist 目录作为部署源
    sourceDir = distDir;
    log.info("Vue 项目构建成功,将部署 dist 目录: {}", distDir.getAbsolutePath());
}
// 8. 复制文件到部署目录
String deployDirPath = AppConstant.CODE_DEPLOY_ROOT_DIR + File.separator + deployKey;

模块八、前端开发

        本次前端需要调整的内容较少,核心改动是针对 Vue 项目的访问路径做适配处理。

        当前端识别到当前项目为 Vue 工程时,访问预览地址需要在原有路径基础上增加 dist 后缀,才能正确指向打包后的静态资源。

        具体需要完成两项工作:

        1)在前端代码中补充对应的项目类型枚举,用于区分原生项目与 Vue 工程。

/**
 * 代码生成类型枚举
 */
export enum CodeGenTypeEnum {
  HTML = 'html',
  MULTI_FILE = 'multi_file',
  VUE_PROJECT = 'vue_project',
}
/**
 * 代码生成类型配置
 */
export const CODE_GEN_TYPE_CONFIG = {
  [CodeGenTypeEnum.HTML]: {
    label: '原生 HTML 模式',
    value: CodeGenTypeEnum.HTML,
  },
  [CodeGenTypeEnum.MULTI_FILE]: {
    label: '原生多文件模式',
    value: CodeGenTypeEnum.MULTI_FILE,
  },
  [CodeGenTypeEnum.VUE_PROJECT]: {
    label: 'Vue 项目模式',
    value: CodeGenTypeEnum.VUE_PROJECT,
  },
}

        2)修改获取网站浏览地址的逻辑函数,根据项目类型自动拼接正确的访问路径。

// 获取静态资源预览URL
export const getStaticPreviewUrl = (codeGenType: string, appId: string) => {
  const baseUrl = `${STATIC_BASE_URL}/${codeGenType}_${appId}/`
  // 如果是 Vue 项目,浏览地址需要添加 dist 后缀
  if (codeGenType === CodeGenTypeEnum.VUE_PROJECT) {
    return `${baseUrl}dist/index.html`
  }
  return baseUrl
}

开发总结

        本次我们完成了 AI 零代码应用生成平台Vue 工程化项目生成模块的开发,整体收获如下:

  1. 技术落地:掌握基于 LangChain4j 的 AI 工具调用与流式生成方案,实现 TokenStream 与 Flux 流的统一适配,完成标准化消息格式定义与多类型流处理器开发,落地 Vue3 + Vite 项目自动化构建与异步部署能力。

  2. 工程化能力:完善多模式代码生成架构,实现原生网页与 Vue 工程的兼容处理,优化文件写入、项目构建、目录部署等工程实践,提升系统扩展性与健壮性。

  3. 问题解决:成功解决流格式不统一、消息类型混乱、前后端路径不匹配、构建部署阻塞主线程等关键问题,积累 AI Agent 工具调用、流式交互、前后端协同的实战经验。

后续计划

1.自动生成封面截图:为应用提供自动截图能力,生成专属展示封面,优化应用在列表与详情页的展示效果。

2.代码下载功能实现:支持用户将生成的完整项目代码一键打包下载,便于本地保存、二次开发与离线使用。

3.AI 智能路由选择:基于项目结构与页面关系,由 AI 自动生成前端路由配置,减少人工配置,提升生成项目的完整性与可用性。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐