作者 :大洪聊AI
本章目标 :基于策略模式+工厂模式重构对话架构,实现LLM意图识别、任务型对话槽位填充,解决循环依赖,统一Prompt与流式工具,打造符合开闭原则的生产级对话系统
前置条件:第五章多轮对话100%完成,上下文持久化、RAG知识库功能全部正常
前言
上一章我们实现了多轮对话与上下文持久化,系统已经具备完整的问答能力。但随着业务功能增加,代码会出现两个致命问题:
- if-else 爆炸:每加一个业务场景,就要在主服务里加一堆判断分支,代码越来越臃肿,改一处容易崩全局
- 职责混乱:查订单、转人工、产品咨询逻辑混在一起,不符合单一职责原则,维护成本指数级上升
这一章我们用策略模式 + 工厂模式重构整个对话架构,配合大模型做意图识别,把不同业务场景拆成独立的处理器。新增业务只需要加一个类,完全不用修改原有代码,真正符合开闭原则。同时我们会解决Spring循环依赖问题,统一Prompt管理,封装流式工具类,让代码质量提升一个台阶。
一、整体架构设计
1.1 为什么用策略模式+工厂模式
| 痛点 | 解决方案 | 收益 |
|---|---|---|
| 大量if-else,代码臃肿 | 每个意图对应一个Handler策略类 | 代码结构清晰,职责分离 |
| 新增业务要改原有代码 | 工厂类自动扫描所有Handler | 符合开闭原则,扩展无侵入 |
| 流式、存库逻辑重复 | 抽取ChatStreamHelper通用工具 | 减少70%重复代码 |
| Prompt散落在各处 | 统一CustomerServicePrompt管理 | 修改规则只改一个地方 |
1.2 完整调用链路
用户发消息
↓
ChatController → DoubaoAiService
↓
1. IntentRecognizer 识别用户意图(LLM零样本分类)
2. 保存用户消息到MySQL
3. IntentHandlerFactory 根据意图路由到对应处理器
↓
┌─────────┬──────────┬──────────┬──────────┐
│咨询产品 │ 查询订单 │ 转人工 │ 其他问题 │
│Handler │ Handler │ Handler │ Handler │
└─────────┴──────────┴──────────┴──────────┘
↓
统一调用 ChatStreamHelper(上下文组装+流式输出+消息存库)
↓
SSE流式返回给前端
1.3 已实现的意图与处理器
我们先实现4个核心处理器,剩余意图自动兜底,后续可无缝扩展:
| 处理器 | 对应意图 | 是否调用LLM | 业务说明 |
|---|---|---|---|
| ProductConsultHandler | 咨询产品 | 是 | RAG检索知识库,流式生成回答 |
| QueryOrderHandler | 查询订单 | 否 | 槽位填充,多轮追问订单号 |
| TransferHumanHandler | 转人工 | 否 | 返回固定话术与联系方式 |
| OtherIntentHandler | 其他问题 | 是 | 通用大模型回答,兜底所有未实现意图 |
⚠️ 注意:「申请退款」「投诉建议」在意图列表里,但暂不做独立处理器,会自动兜底到
OtherIntentHandler,后续扩展直接加对应Handler即可,完全不用改原有代码。
二、核心架构代码实现
2.1 第一步:定义意图处理器接口
所有意图处理器都实现同一个接口,保证统一的调用规范。
新建文件:src/main/java/org/example/service/intent/IntentHandler.java
java
package org.example.service.intent;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 意图处理器统一接口
*/
public interface IntentHandler {
/**
* 获取当前处理器支持的意图名称
*/
String getSupportedIntent();
/**
* 处理用户请求
* @param sessionId 会话ID
* @param message 用户原始消息
* @param emitter SSE流式发射器
*/
void handle(String sessionId, String message, SseEmitter emitter);
}
2.2 第二步:实现意图处理器工厂
工厂类负责自动收集所有处理器,根据意图名称路由,彻底消灭if-else。
新建文件:src/main/java/org/example/service/intent/IntentHandlerFactory.java
java
package org.example.service.intent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 意图处理器工厂:自动收集所有Handler,按意图路由
*/
@Component
public class IntentHandlerFactory {
private final Map<String, IntentHandler> handlerMap = new HashMap<>();
/**
* Spring自动注入所有实现了IntentHandler接口的Bean
*/
@Autowired
public IntentHandlerFactory(List<IntentHandler> handlers) {
for (IntentHandler handler : handlers) {
handlerMap.put(handler.getSupportedIntent(), handler);
}
}
/**
* 根据意图获取处理器,未知意图自动兜底到「其他问题」
*/
public IntentHandler getHandler(String intent) {
return handlerMap.getOrDefault(intent, handlerMap.get("其他问题"));
}
}
2.3 第三步:实现LLM意图识别器
基于豆包大模型的零样本能力,不需要任何训练数据,就能精准分类用户意图。
新建文件:src/main/java/org/example/util/IntentRecognizer.java
java
package org.example.util;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 大模型意图识别器
*/
@Component
public class IntentRecognizer {
@Autowired
@Qualifier("doubaoChatModel")
private ChatLanguageModel chatModel;
/**
* 预定义6类客服常见意图
*/
private static final List<String> INTENTS = Arrays.asList(
"咨询产品",
"查询订单",
"申请退款",
"投诉建议",
"转人工",
"其他问题"
);
/**
* 识别用户消息对应的意图
*/
public String recognize(String message) {
StringBuilder prompt = new StringBuilder();
prompt.append("你是专业的客服意图识别助手,请严格从下面的意图列表中选择最匹配的一项。\n");
prompt.append("只返回意图名称本身,不要加任何解释、标点、序号。\n\n");
prompt.append("可选意图列表:\n");
prompt.append(String.join("\n", INTENTS)).append("\n\n");
prompt.append("用户消息:").append(message);
String result = chatModel.generate(prompt.toString()).trim();
// 兜底:不在预定义列表里就归为其他问题
if (!INTENTS.contains(result)) {
return "其他问题";
}
return result;
}
}
三、实现4个核心意图处理器
3.1 转人工处理器(固定话术,最简实现)
新建文件:src/main/java/org/example/service/intent/TransferHumanHandler.java
java
package org.example.service.intent;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import org.springframework.http.MediaType;
/**
* 转人工意图处理器
*/
@Component
public class TransferHumanHandler implements IntentHandler {
@Override
public String getSupportedIntent() {
return "转人工";
}
@Override
public void handle(String sessionId, String message, SseEmitter emitter) {
try {
StringBuilder reply = new StringBuilder();
reply.append("好的,正在为您转接人工客服,请稍候。\n\n");
reply.append("人工客服工作时间:周一至周五 9:00-18:00\n");
reply.append("客服热线:400-123-4567\n");
reply.append("您也可以留下联系方式,我们会尽快与您取得联系。");
emitter.send(reply.toString(), MediaType.TEXT_PLAIN);
emitter.send("[DONE]", MediaType.TEXT_PLAIN);
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
}
}
3.2 查询订单处理器(槽位填充+多轮追问)
⚠️ 踩坑预警:这里会用到IntentHandlerFactory做意图切换,直接注入会产生Spring循环依赖,我们先用构造器注入,后面再解决。
新建文件:src/main/java/org/example/service/intent/QueryOrderHandler.java
java
package org.example.service.intent;
import org.example.util.IntentRecognizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 查询订单意图处理器:槽位填充 + 多轮追问
*/
@Component
public class QueryOrderHandler implements IntentHandler {
private final IntentRecognizer intentRecognizer;
private final IntentHandlerFactory handlerFactory;
// 关键:@Lazy 延迟加载,解决循环依赖
@Autowired
public QueryOrderHandler(
IntentRecognizer intentRecognizer,
@Lazy IntentHandlerFactory handlerFactory
) {
this.intentRecognizer = intentRecognizer;
this.handlerFactory = handlerFactory;
}
// 订单号正则:10-12位字母数字组合
private static final Pattern ORDER_NO_PATTERN = Pattern.compile("\\b[A-Za-z0-9]{10,12}\\b");
// 每个会话的槽位状态(生产环境建议存Redis,这里用本地Map演示)
private final Map<String, Map<String, String>> sessionSlots = new HashMap<>();
@Override
public String getSupportedIntent() {
return "查询订单";
}
@Override
public void handle(String sessionId, String message, SseEmitter emitter) {
// 1. 检测用户是否切换了意图(比如查订单中途说要转人工)
String currentIntent = intentRecognizer.recognize(message);
if (!"查询订单".equals(currentIntent)) {
sessionSlots.remove(sessionId);
IntentHandler newHandler = handlerFactory.getHandler(currentIntent);
newHandler.handle(sessionId, message, emitter);
return;
}
// 2. 获取当前会话的槽位
Map<String, String> slots = sessionSlots.getOrDefault(sessionId, new HashMap<>());
// 3. 从用户消息中提取订单号
String orderNo = extractOrderNo(message);
if (orderNo != null) {
slots.put("orderNo", orderNo.toUpperCase());
sessionSlots.put(sessionId, slots);
}
// 4. 没有订单号就追问
if (!slots.containsKey("orderNo")) {
try {
String reply = "请问您的订单号是多少呢?订单号一般是10-12位的字母数字组合,在订单详情页可以查到。";
emitter.send(reply, MediaType.TEXT_PLAIN);
emitter.send("[DONE]", MediaType.TEXT_PLAIN);
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
return;
}
// 5. 有订单号,查询订单状态(实际项目对接订单系统接口)
String orderNoToQuery = slots.get("orderNo");
String orderStatus = mockQueryOrder(orderNoToQuery);
try {
emitter.send(orderStatus, MediaType.TEXT_PLAIN);
emitter.send("[DONE]", MediaType.TEXT_PLAIN);
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
// 6. 查询完成,清空当前槽位
sessionSlots.remove(sessionId);
}
/**
* 从文本中提取订单号
*/
private String extractOrderNo(String message) {
Matcher matcher = ORDER_NO_PATTERN.matcher(message);
if (matcher.find()) {
return matcher.group();
}
return null;
}
/**
* 模拟查询订单状态
*/
private String mockQueryOrder(String orderNo) {
StringBuilder sb = new StringBuilder();
sb.append("查询到您的订单信息:\n");
sb.append("订单号:").append(orderNo).append("\n");
if (orderNo.startsWith("A")) {
sb.append("状态:已发货,运输中\n");
sb.append("预计送达:明天18:00前\n");
sb.append("物流单号:SF1234567890");
} else if (orderNo.startsWith("B")) {
sb.append("状态:已签收\n");
sb.append("签收时间:2026-06-01 14:32");
} else {
sb.append("状态:未查询到相关订单\n");
sb.append("请您核对订单号是否正确,或联系人工客服协助查询。");
}
return sb.toString();
}
}
循环依赖问题说明
产生原因:
IntentHandlerFactory构造器注入所有IntentHandlerQueryOrderHandler又需要注入IntentHandlerFactory做意图切换- 形成闭环:Factory → QueryOrderHandler → Factory,Spring无法实例化
解决方案 :
给 QueryOrderHandler 里的 IntentHandlerFactory 加上 @Lazy 注解,Spring会在真正用到的时候才创建实例,打破循环。这是Spring官方推荐的循环依赖解决方案之一。
四、生产级代码优化
4.1 统一Prompt管理类
之前Prompt散落在各个Handler里,修改规则要改很多地方。现在统一收敛到一个工具类。
新建文件:src/main/java/org/example/utils/CustomerServicePrompt.java
java
package org.example.utils;
/**
* 客服系统统一Prompt管理
*/
public class CustomerServicePrompt {
/**
* 全局统一系统提示词,所有LLM调用共用
*/
public static String systemPrompt() {
StringBuilder sb = new StringBuilder();
sb.append("你是【XX公司】的专业AI客服,必须严格遵守以下规则:\n");
sb.append("1. 语气友好、耐心、礼貌,全程使用「您」称呼用户\n");
sb.append("2. 回答简洁清晰、专业有条理,复杂内容用数字分点说明\n");
sb.append("3. 只输出纯文本,不要使用markdown、代码块、表情符号\n");
sb.append("4. 结合上下文理解用户需求,不要答非所问\n");
sb.append("5. 绝对禁止编造信息,不知道的内容明确告知用户\n");
return sb.toString();
}
/**
* 通用问答用户Prompt(OtherIntentHandler使用)
*/
public static String generalUserPrompt(String question) {
return "请根据上面的客服规则,回答用户的问题:" + question;
}
/**
* RAG问答用户Prompt(ProductConsultHandler使用)
*/
public static String ragUserPrompt(String question, String relevantContent) {
StringBuilder sb = new StringBuilder();
sb.append("请你严格使用下面的【知识库内容】回答用户问题。\n");
sb.append("如果知识库中没有相关信息,直接回复:「抱歉,我没有找到相关信息,请您咨询人工客服。」\n");
sb.append("禁止编造任何知识库中没有的内容。\n\n");
sb.append("【知识库内容】\n");
sb.append(relevantContent).append("\n\n");
sb.append("用户问题:").append(question);
return sb.toString();
}
}
4.2 封装流式输出通用工具类
每个Handler里都写一遍流式输出、上下文组装、消息存库的逻辑,重复代码太多。我们抽取成通用工具类。
新建文件:src/main/java/org/example/service/intent/ChatStreamHelper.java
java
package org.example.service.intent;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.StreamingResponseHandler;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.output.Response;
import org.example.service.MessageService;
import org.example.utils.CustomerServicePrompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.List;
/**
* 流式对话通用工具:统一处理上下文组装、流式输出、消息存库
*/
@Component
public class ChatStreamHelper {
@Autowired
private MessageService messageService;
// 滑动窗口大小
private static final int WINDOW_SIZE = 10;
/**
* 带上下文的流式生成回答
* @param sessionId 会话ID
* @param userPrompt 增强后的用户Prompt
* @param streamingModel 流式大模型
* @param emitter SSE发射器
*/
public void streamWithContext(
String sessionId,
String userPrompt,
StreamingChatLanguageModel streamingModel,
SseEmitter emitter
) {
// 1. 获取滑动窗口历史消息
List<ChatMessage> contextMessages = messageService.buildSlidingWindowContext(sessionId, WINDOW_SIZE);
// 2. 移除刚入库的最后一条用户原话(避免和增强Prompt重复)
if (!contextMessages.isEmpty() && contextMessages.get(contextMessages.size() - 1) instanceof UserMessage) {
contextMessages.remove(contextMessages.size() - 1);
}
// 3. 组装完整消息:系统规则 + 历史上下文 + 当前增强问题
contextMessages.add(0, SystemMessage.from(CustomerServicePrompt.systemPrompt()));
contextMessages.add(UserMessage.from(userPrompt));
// 4. 拼接完整AI回复,用于最后存库
StringBuilder fullResponse = new StringBuilder();
// 5. 流式调用大模型
streamingModel.generate(contextMessages, new StreamingResponseHandler<AiMessage>() {
@Override
public void onNext(String token) {
try {
emitter.send(token, MediaType.TEXT_PLAIN);
fullResponse.append(token);
} catch (IOException e) {
emitter.completeWithError(e);
}
}
@Override
public void onComplete(Response<AiMessage> response) {
try {
emitter.send("[DONE]", MediaType.TEXT_PLAIN);
emitter.complete();
// 6. 流式结束,保存AI回复到数据库
messageService.addMessage(sessionId, "assistant", fullResponse.toString());
} catch (IOException e) {
emitter.completeWithError(e);
}
}
@Override
public void onError(Throwable error) {
emitter.completeWithError(error);
}
});
}
/**
* 发送固定话术回答(自动保存到数据库)
*/
public void sendFixedResponse(
String sessionId,
String response,
SseEmitter emitter
) {
try {
emitter.send(response, MediaType.TEXT_PLAIN);
emitter.send("[DONE]", MediaType.TEXT_PLAIN);
emitter.complete();
messageService.addMessage(sessionId, "assistant", response);
} catch (IOException e) {
emitter.completeWithError(e);
}
}
}
4.3 改造产品咨询处理器
用 ChatStreamHelper 简化代码,接入统一Prompt。
修改文件:src/main/java/org/example/service/intent/ProductConsultHandler.java
java
package org.example.service.intent;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import org.example.service.RagService;
import org.example.utils.CustomerServicePrompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 产品咨询意图处理器:RAG知识库检索 + 流式生成
*/
@Component
public class ProductConsultHandler implements IntentHandler {
@Autowired
private RagService ragService;
@Autowired
@Qualifier("doubaoStreamingChatModel")
private StreamingChatLanguageModel streamingModel;
@Autowired
private ChatStreamHelper chatStreamHelper;
@Override
public String getSupportedIntent() {
return "咨询产品";
}
@Override
public void handle(String sessionId, String message, SseEmitter emitter) {
// 1. 检索知识库
String relevantContent = ragService.retrieveRelevantContent(message);
// 2. 无相关内容,直接拒答
if (relevantContent.isEmpty()) {
chatStreamHelper.sendFixedResponse(
sessionId,
"抱歉,我没有找到相关产品信息,请您咨询人工客服。",
emitter
);
return;
}
// 3. 有相关内容,构建RAG增强Prompt并流式生成
String userPrompt = CustomerServicePrompt.ragUserPrompt(message, relevantContent);
chatStreamHelper.streamWithContext(sessionId, userPrompt, streamingModel, emitter);
}
}
4.4 改造其他问题处理器
修改文件:src/main/java/org/example/service/intent/OtherIntentHandler.java
java
package org.example.service.intent;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import org.example.utils.CustomerServicePrompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 其他问题意图处理器:通用大模型回答,兜底所有未实现意图
*/
@Component
public class OtherIntentHandler implements IntentHandler {
@Autowired
@Qualifier("doubaoStreamingChatModel")
private StreamingChatLanguageModel streamingModel;
@Autowired
private ChatStreamHelper chatStreamHelper;
@Override
public String getSupportedIntent() {
return "其他问题";
}
@Override
public void handle(String sessionId, String message, SseEmitter emitter) {
String userPrompt = CustomerServicePrompt.generalUserPrompt(message);
chatStreamHelper.streamWithContext(sessionId, userPrompt, streamingModel, emitter);
}
}
💡 可以看到,接入
ChatStreamHelper之后,每个Handler只需要关注自己的业务逻辑,代码量减少了70%以上,结构非常清晰。
五、重构主服务DoubaoAiService
现在主服务只做流程编排,不包含任何业务逻辑,职责非常单一。
修改文件:src/main/java/org/example/service/impl/DoubaoAiService.java
java
package org.example.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.example.service.AiService;
import org.example.service.MessageService;
import org.example.service.intent.IntentHandler;
import org.example.service.intent.IntentHandlerFactory;
import org.example.util.IntentRecognizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@Slf4j
@Service("doubaoAiService")
public class DoubaoAiService implements AiService {
@Autowired
private IntentRecognizer intentRecognizer;
@Autowired
private IntentHandlerFactory handlerFactory;
@Autowired
private MessageService messageService;
@Override
public void streamGenerate(String sessionId, String message, SseEmitter emitter) {
// 1. 大模型识别意图
String intent = intentRecognizer.recognize(message);
log.info("识别到用户意图:{}", intent);
// 2. 保存用户消息到数据库
messageService.addMessage(sessionId, "user", message);
// 3. 路由到对应处理器处理
IntentHandler handler = handlerFactory.getHandler(intent);
handler.handle(sessionId, message, emitter);
}
}
六、前端说明
前端代码完全不需要修改。因为我们的接口地址、请求参数、返回格式都和第五章完全一致,只是后端内部做了架构重构,前端无感知。
这就是好的架构的好处:后端内部优化升级,完全不影响前端调用。
七、完整功能验证
按照下面的顺序逐一测试,确保所有功能正常:
测试1:基础聊天
- 发送「你好」,AI正常回复,走「其他问题」意图
测试2:产品咨询 + RAG
- 上传产品知识库文档
- 发送「你们产品多少钱」,AI准确返回价格,走「咨询产品」意图
- 发送「你们支持视频会议吗」,AI回复找不到相关信息,防编造生效
测试3:查询订单 + 槽位追问
- 发送「我想查订单」,AI回复「请问您的订单号是多少?」
- 发送「A1234567890」,AI返回订单详情
- 验证多轮追问正常
测试4:中途切换意图
- 发送「我要查订单」→ AI追问订单号
- 不等回复订单号,直接发送「算了,帮我转人工」
- AI立即回复转人工话术,说明意图切换正常
测试5:转人工
- 发送「我要找人工」,AI返回人工客服信息,走「转人工」意图
测试6:持久化验证
- 刷新页面,历史消息全部保留
- 继续对话,AI能记住上下文
✅ 全部测试通过,说明意图识别架构重构完成。
八、常见问题排查
问题1:启动报错循环依赖
- 检查
QueryOrderHandler的构造器里,IntentHandlerFactory是否加了@Lazy注解 - 检查包扫描路径是否正确,确保所有Bean都被Spring扫描到
问题2:意图识别不准确
- 优化
IntentRecognizer里的Prompt,加重语气要求严格从列表选 - 把模型
temperature调到0.1-0.3,降低创造性 - 增加更明确的示例,做少样本分类
问题3:固定话术没有保存到数据库
- 检查对应Handler是不是调用了
chatStreamHelper.sendFixedResponse(),而不是自己直接emitter.send - 确认
ChatStreamHelper里的存库逻辑正常
问题4:上下文不生效
- 检查
ChatStreamHelper里的滑动窗口大小是否合适 - 确认请求时传了正确的
sessionId - 检查消息是否成功写入了数据库
九、本章总结
本章我们完成了对话架构的生产级重构:
- ✅ 用策略模式+工厂模式重构了对话架构,彻底消灭if-else
- ✅ 实现了基于LLM的零样本意图识别,支持6类客服意图
- ✅ 实现了4个核心意图处理器,支持槽位填充与多轮追问
- ✅ 解决了Spring循环依赖的经典问题
- ✅ 统一了Prompt管理,所有规则集中维护
- ✅ 封装了ChatStreamHelper通用工具,大幅减少重复代码
- ✅ 主服务职责单一,只做编排,符合单一职责原则
- ✅ 完全符合开闭原则:新增意图只加类,不改原有代码
现在系统的可扩展性和可维护性已经达到了生产级标准,后续不管加多少业务场景,都不会让代码变得混乱。
下章预告
下一章我们将进入基础优化与本地部署阶段,实现双层缓存节省API费用、全局异常统一处理、前端性能优化,最后完成前后端一体化打包,实现一键部署开箱即用。
关注我,第一时间收到更新通知!