【无标题】
·
2026山东大学软件学院项目实训(二)|程序处理写入与门面模式深度落地
模块概述
在我们的LangChain4j+工作流+微服务AI零代码应用生成平台中,程序处理写入模块承接AI结构化输出的代码结果,负责将HTML/CSS/JS代码写入本地文件系统;而门面模式是本模块的核心设计思路,用于统一AI代码生成与文件保存的复杂流程,降低系统耦合、简化外部调用。
本文将带你从零到一实现该模块,详解代码逻辑与设计模式落地。
一、模块背景:为什么需要门面模式?
经过前面的开发,我们已经实现:
- AI通过LangChain4j生成结构化代码结果(单HTML文件/多文件模式)
- 代码封装为
HtmlCodeResult/MultiFileCodeResult对象
但此时面临两个核心问题:
- 流程分散:代码生成、文件解析、目录创建、文件写入是多个独立步骤,客户端需手动编排,极易出错
- 耦合过高:外部调用需直接依赖AI服务、文件工具类,子系统变更会影响调用方
而门面模式(Facade Pattern) 恰好解决这类问题:
为子系统提供统一高层接口,隐藏内部复杂逻辑,让客户端只与门面类交互,实现解耦与简化调用。
在本模块中:
- 子系统:AI代码生成服务、文件保存工具类
- 门面类:
AiCodeGeneratorFacade - 客户端:测试类/后续Controller层
二、第一步:定义代码生成类型枚举
首先定义代码生成类型枚举,统一管理两种生成模式,避免硬编码,提升可维护性。
2.1 枚举代码实现
import lombok.Getter;
/**
* 代码生成类型枚举
* 用于区分原生HTML单文件、原生多文件两种生成模式
*/
@Getter
public enum CodeGenTypeEnum {
/**
* 原生HTML模式:所有代码整合在一个HTML文件中
*/
HTML("原生HTML模式", "html"),
/**
* 原生多文件模式:分离HTML、CSS、JS三个文件
*/
MULTI_FILE("原生多文件模式", "multi_file");
// 文字描述
private final String text;
// 枚举值
private final String value;
CodeGenTypeEnum(String text, String value) {
this.text = text;
this.value = value;
}
/**
* 根据value获取枚举
* @param value 枚举值
* @return 对应的枚举对象
*/
public static CodeGenTypeEnum getEnumByValue(String value) {
// 空值判断
if (ObjectUtil.isEmpty(value)) {
return null;
}
// 遍历匹配
for (CodeGenTypeEnum anEnum : CodeGenTypeEnum.values()) {
if (anEnum.value.equals(value)) {
return anEnum;
}
}
return null;
}
}
2.2 枚举作用
- 统一两种生成模式的标识,避免魔法值
- 提供根据value匹配枚举的工具方法,方便后续流程判断
三、第二步:编写文件保存工具类CodeFileSaver
该工具类负责目录创建、文件写入、两种模式的代码保存,是核心工具层。
3.1 核心设计要点
- 保存根目录:项目根目录下的
/tmp/code_output,避免污染业务目录 - 唯一目录:采用业务类型+雪花ID命名,确保目录不重复
- 两种保存逻辑:适配单HTML、多文件模式
- 编码统一:使用
UTF-8写入,避免乱码 - 忽略提交:将
tmp目录加入.gitignore,防止生成文件提交到仓库
3.2 工具类代码实现
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.IdUtil;
import cn.hutool.core.util.StrUtil;
import java.io.File;
import java.nio.charset.StandardCharsets;
/**
* 代码文件保存工具类
* 负责将AI生成的结构化代码写入本地文件系统
*/
public class CodeFileSaver {
/**
* 文件保存根目录:项目根目录/tmp/code_output
*/
private static final String FILE_SAVE_ROOT_DIR = System.getProperty("user.dir") + "/tmp/code_output";
/**
* 保存单HTML文件结果
* @param result HTML代码结果对象
* @return 保存后的目录文件
*/
public static File saveHtmlCodeResult(HtmlCodeResult result) {
// 构建唯一目录
String baseDirPath = buildUniqueDir(CodeGenTypeEnum.HTML.getValue());
// 写入HTML文件
writeToFile(baseDirPath, "index.html", result.getHtmlCode());
return new File(baseDirPath);
}
/**
* 保存多文件(HTML+CSS+JS)结果
* @param result 多文件代码结果对象
* @return 保存后的目录文件
*/
public static File saveMultiFileCodeResult(MultiFileCodeResult result) {
// 构建唯一目录
String baseDirPath = buildUniqueDir(CodeGenTypeEnum.MULTI_FILE.getValue());
// 分别写入三个文件
writeToFile(baseDirPath, "index.html", result.getHtmlCode());
writeToFile(baseDirPath, "style.css", result.getCssCode());
writeToFile(baseDirPath, "script.js", result.getJsCode());
return new File(baseDirPath);
}
/**
* 构建唯一目录路径:规则 业务类型_雪花ID
* @param bizType 业务类型(html/multi_file)
* @return 唯一目录路径
*/
private static String buildUniqueDir(String bizType) {
// 生成唯一目录名
String uniqueDirName = StrUtil.format("{}_{}", bizType, IdUtil.getSnowflakeNextIdStr());
// 拼接完整路径
String dirPath = FILE_SAVE_ROOT_DIR + File.separator + uniqueDirName;
// 创建目录(不存在则创建)
FileUtil.mkdir(dirPath);
return dirPath;
}
/**
* 写入单个文件
* @param dirPath 目录路径
* @param filename 文件名
* @param content 文件内容
*/
private static void writeToFile(String dirPath, String filename, String content) {
String filePath = dirPath + File.separator + filename;
// 按UTF-8编码写入字符串
FileUtil.writeString(content, filePath, StandardCharsets.UTF_8);
}
}
3.3 关键细节说明
- 雪花ID:使用Hutool工具类生成全局唯一ID,避免目录冲突
- 目录自动创建:
FileUtil.mkdir会自动创建不存在的目录 - 编码规范:强制使用
StandardCharsets.UTF-8,解决跨系统乱码问题 - 职责单一:仅负责文件写入,不耦合业务逻辑
四、第三步:门面模式落地——AiCodeGeneratorFacade
这是本模块的核心,通过门面类统一生成+保存流程,对外提供极简调用入口。
4.1 门面类设计思路
- 注入AI代码生成服务
AiCodeGeneratorService - 提供统一入口方法:根据生成类型自动选择对应逻辑
- 封装异常处理,抛出统一业务异常
- 隐藏内部调用细节,客户端只需传入用户描述+生成类型
4.2 门面类代码实现
import com.yupi.yuaicodemother.ai.service.AiCodeGeneratorService;
import com.yupi.yuaicodemother.core.exception.BusinessException;
import com.yupi.yuaicodemother.core.model.enums.CodeGenTypeEnum;
import com.yupi.yuaicodemother.ai.model.result.HtmlCodeResult;
import com.yupi.yuaicodemother.ai.model.result.MultiFileCodeResult;
import lombok.Resource;
import org.springframework.stereotype.Service;
import java.io.File;
/**
* AI代码生成门面类
* 统一封装代码生成、文件保存逻辑,简化外部调用
*/
@Service
public class AiCodeGeneratorFacade {
/**
* 注入AI代码生成服务
*/
@Resource
private AiCodeGeneratorService aiCodeGeneratorService;
/**
* 统一入口:根据类型生成并保存代码
* @param userMessage 用户输入的网站描述
* @param codeGenTypeEnum 代码生成类型枚举
* @return 保存后的文件目录
*/
public File generateAndSaveCode(String userMessage, CodeGenTypeEnum codeGenTypeEnum) {
// 空值校验
if (codeGenTypeEnum == null) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成类型为空");
}
// 根据枚举类型选择对应逻辑
return switch (codeGenTypeEnum) {
// 单HTML模式
case HTML -> generateAndSaveHtmlCode(userMessage);
// 多文件模式
case MULTI_FILE -> generateAndSaveMultiFileCode(userMessage);
// 不支持的类型
default -> {
String errorMessage = "不支持的生成类型:" + codeGenTypeEnum.getValue();
throw new BusinessException(ErrorCode.SYSTEM_ERROR, errorMessage);
}
};
}
/**
* 生成单HTML模式代码并保存
* @param userMessage 用户描述
* @return 保存目录
*/
private File generateAndSaveHtmlCode(String userMessage) {
// 1. 调用AI生成结构化代码
HtmlCodeResult result = aiCodeGeneratorService.generateHtmlCode(userMessage);
// 2. 调用工具类保存文件
return CodeFileSaver.saveHtmlCodeResult(result);
}
/**
* 生成多文件模式代码并保存
* @param userMessage 用户描述
* @return 保存目录
*/
private File generateAndSaveMultiFileCode(String userMessage) {
// 1. 调用AI生成结构化代码
MultiFileCodeResult result = aiCodeGeneratorService.generateMultiFileCode(userMessage);
// 2. 调用工具类保存文件
return CodeFileSaver.saveMultiFileCodeResult(result);
}
}
4.3 门面类核心价值
- 统一入口:外部只需调用
generateAndSaveCode一个方法,无需关心内部流程 - 解耦依赖:客户端不直接依赖
AiCodeGeneratorService和CodeFileSaver - 易于维护:新增生成类型只需修改枚举和门面类,不影响调用方
- 异常统一:集中处理参数校验、异常抛出,规范错误提示
五、第四步:单元测试验证全流程
编写测试类,验证AI生成+文件保存全流程是否正常。
import com.yupi.yuaicodemother.core.model.enums.CodeGenTypeEnum;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.io.File;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* AI代码生成门面类测试
* 验证生成+保存全流程
*/
@SpringBootTest
class AiCodeGeneratorFacadeTest {
@Resource
private AiCodeGeneratorFacade aiCodeGeneratorFacade;
/**
* 测试多文件模式生成并保存
*/
@Test
void generateAndSaveCode() {
// 调用门面类统一方法
File file = aiCodeGeneratorFacade.generateAndSaveCode("任务记录网站", CodeGenTypeEnum.MULTI_FILE);
// 断言文件不为空
assertNotNull(file);
// 控制台打印保存路径
System.out.println("文件保存路径:" + file.getAbsolutePath());
}
}
测试效果
- 控制台输出唯一保存路径
- 打开路径可看到生成的
index.html/style.css/script.js - 双击
index.html可直接预览AI生成的网站
六、门面模式在本模块的核心价值总结
| 问题 | 未用门面模式 | 使用门面模式后 |
|---|---|---|
| 调用复杂度 | 需手动调用AI服务+文件工具,步骤繁琐 | 仅需调用一个方法,极简入口 |
| 系统耦合 | 客户端依赖多个子系统,变更影响大 | 客户端只依赖门面类,解耦彻底 |
| 可维护性 | 代码分散,新增模式需修改多处 | 集中管理流程,符合开闭原则 |
| 代码可读性 | 逻辑散乱,难以快速理解流程 | 门面类清晰编排流程,一目了然 |
本质上,本模块的门面模式遵循迪米特法则(最少知识原则):
客户端只需要知道做什么(生成并保存代码),不需要知道怎么做(AI调用、目录创建、文件写入)。
七、模块总结
本模块通过枚举+工具类+门面模式,完美实现了AI代码生成后的本地文件落地:
- 用枚举统一生成类型,规范代码
- 用工具类封装文件写入,职责单一
- 用门面模式统一流程,简化调用、降低耦合
更多推荐




所有评论(0)