22 KiB
AI 助手功能技术实施文档
| 文档版本 | 修订日期 | 修订人 | 修订内容 |
|---|---|---|---|
| V1.0 | 2026-05-23 | — | 基于《AI助手功能需求》V1.2 初稿 |
| V1.1 | 2026-05-23 | — | 更新待办状态:后端 1 |
关联需求:AI助手功能需求.md(V1.2)
前提条件
数据安全规则(与项目其他 AI 功能联调一致):
- 联调、测试过程中创建的测试会话与消息,测试完成后应清理
- 数据库中名为 「hello」 的文章不允许修改或删除
环境依赖:
- 已配置
DASHSCOPE_API_KEY(或本地application-local.yml) - MySQL、Redis 可用;Flyway 可执行至最新版本
1. 功能概述
在公开站点顶部导航增加 「AI 助手」 入口,跳转 /ai-assistant 对话页:
- 左侧:新会话 + 历史会话列表(按会话聚合,分页)
- 右侧:多轮消息流 + 输入发送;单会话最多 5 轮(1 轮 = 1 问 + 1 答)
- 后端:会话与消息持久化;调用 DashScope 时带入最多 5 轮上下文;失败可同轮重试(不重复插入 user)
产品约定 D-01~D-07 已确认,实施须与需求文档一致。
2. 核心实现思路
2.1 复用现有 AI 能力
| 复用项 | 说明 |
|---|---|
DashScopeProperties |
api-key、base-url、model、timeout、max-input |
AiSummaryServiceImpl |
注入实现类,调用 callModel(Map) 发起 chat/completions(与润色、标签推荐同模式) |
| Redis 限流 | 独立 key 前缀 ai_chat:rate:,每分钟 10 次 /chat(需求 FR-AI-CHAT-BE-12) |
ErrorCode |
复用 AI_SERVICE_UNAVAILABLE、AI_SERVICE_TIMEOUT、AI_API_KEY_MISSING 等 |
| 安全体系 | /api/app/** + @AuthenticationPrincipal Long userId |
2.2 新增能力(与摘要/润色差异)
| 特性 | 摘要/润色 | AI 助手 |
|---|---|---|
| 持久化 | 无 | ai_chat_session + ai_chat_message |
| 接口数量 | 1 个 POST | 4 个(创建会话、列表、消息、发送) |
| 上下文 | 单次 user | System + 最多 10 条历史消息 |
| 业务状态 | 无 | round_count、未配对 user 重试 |
| 前端 | 编辑器内嵌 | 独立页面 + 左右分栏 |
2.3 模块划分
jog-admin Flyway V5__ai_chat_tables.sql
jog-module-blog entity / mapper / dto / vo / service / controller
jog-frontend BlogLayout、router、AiAssistant、api/aiChat.js
3. 改动项清单
3.1 数据库(Flyway)
文件:jog-admin/src/main/resources/db/migration/V5__ai_chat_tables.sql
-- 会话表
CREATE TABLE ai_chat_session (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
title VARCHAR(64) NOT NULL DEFAULT '',
round_count TINYINT NOT NULL DEFAULT 0,
last_message_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT NOT NULL DEFAULT 0,
INDEX idx_user_last (user_id, last_message_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 消息表
CREATE TABLE ai_chat_message (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
session_id BIGINT NOT NULL,
role VARCHAR(16) NOT NULL,
content TEXT NOT NULL,
sort_order INT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_order (session_id, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
迁移版本号以仓库当前最大版本 +1 为准(当前为 V4,故使用 V5)。
3.2 后端实体与 Mapper
| 文件 | 说明 |
|---|---|
entity/AiChatSession.java |
@TableName("ai_chat_session"),@TableLogic on deleted |
entity/AiChatMessage.java |
@TableName("ai_chat_message") |
mapper/AiChatSessionMapper.java |
BaseMapper<AiChatSession> |
mapper/AiChatMessageMapper.java |
BaseMapper<AiChatMessage> |
3.3 后端 DTO / VO
| 文件 | 说明 |
|---|---|
dto/AiChatSendDTO.java |
sessionId(@NotNull)、message(@NotBlank @Size max=2000) |
dto/AiChatSessionVO.java |
列表项:id, title, roundCount, lastMessageAt |
dto/AiChatMessageVO.java |
id, role, content, sortOrder, createdAt |
dto/AiChatResultVO.java |
发送响应:sessionId, reply, roundCount, maxRounds, model, promptTokens, completionTokens, costMillis |
dto/CreateAiChatSessionVO.java |
sessionId(创建会话返回) |
分页查询可复用项目已有 PageResult + 查询参数 page、size(默认 size=50)。
3.4 后端 Service
接口:service/AiChatService.java
| 方法 | 说明 |
|---|---|
CreateAiChatSessionVO createSession(Long userId) |
插入 round_count=0 的会话 |
PageResult<AiChatSessionVO> listSessions(Long userId, int page, int size) |
按 last_message_at 降序,仅 deleted=0 |
List<AiChatMessageVO> listMessages(Long userId, Long sessionId) |
校验归属后正序返回 |
AiChatResultVO chat(Long userId, AiChatSendDTO dto) |
发送 + 调模型 + 落库(含 BE-15 重试逻辑) |
实现:service/impl/AiChatServiceImpl.java
核心常量建议:
private static final int MAX_ROUNDS = 5;
private static final int MAX_MESSAGE_LENGTH = 2000;
private static final int MAX_REPLY_LENGTH = 4000;
private static final int TITLE_MAX_LENGTH = 30;
private static final String RATE_LIMIT_KEY_PREFIX = "ai_chat:rate:";
private static final int RATE_LIMIT_PER_MINUTE = 10;
private static final String SYSTEM_PROMPT =
"你是 Jog 博客系统的 AI 助手,友好、简洁地用中文回答用户问题。"
+ "涉及博客写作、Markdown、技术常识时可结合场景作答;无法确定时请诚实说明。"
+ "不要编造系统内部未公开的配置或用户数据。"
+ "当前为同一会话的多轮对话,请结合上文回答,避免重复已说过的内容。";
chat 流程(与需求 3.3.4 对齐):
getSessionForUser(sessionId, userId)→ 不存在则 404round_count >= 5→BusinessException(400,文案:当前会话已满 5 轮,请新建会话)- 校验
messagetrim 后 1~2000 - 重试分支:若该会话最后一条消息为
role=user且无后续 assistant → 不插入新 user,用该条 content 作为当前提问 - 否则:插入 user 消息,
sort_order递增;首条时title = truncate(message, 30) - 加载会话全部消息 →
buildMessagesForModel()(超max-input从最早一对开始丢弃) aiSummaryService.callModel(requestBody)- 成功:插入 assistant、
round_count++、last_message_at=now - 失败:不插 assistant、不增
round_count,抛 5001/5002
标题截断:
private String buildTitle(String firstMessage) {
String t = firstMessage.trim();
if (t.length() <= TITLE_MAX_LENGTH) return t;
return t.substring(0, TITLE_MAX_LENGTH) + "…";
}
3.5 后端 Controller
文件:controller/AiChatController.java
@Tag(name = "AI 助手")
@RestController
@RequestMapping("/api/app/ai")
@RequiredArgsConstructor
public class AiChatController {
private final AiChatService aiChatService;
@PostMapping("/sessions")
public ApiResult<CreateAiChatSessionVO> createSession(@AuthenticationPrincipal Long userId) { ... }
@GetMapping("/sessions")
public ApiResult<PageResult<AiChatSessionVO>> listSessions(
@AuthenticationPrincipal Long userId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "50") int size) { ... }
@GetMapping("/sessions/{sessionId}/messages")
public ApiResult<List<AiChatMessageVO>> listMessages(
@AuthenticationPrincipal Long userId,
@PathVariable Long sessionId) { ... }
@PostMapping("/chat")
public ApiResult<AiChatResultVO> chat(
@AuthenticationPrincipal Long userId,
@Valid @RequestBody AiChatSendDTO dto) { ... }
}
3.6 错误码
| 场景 | 处理方式 |
|---|---|
| message 非法 | 400 + 校验消息 |
| 会话已满 5 轮 | 400,message:「当前会话已满 5 轮,请新建会话」 |
| session 非本人 | 404 或 403(与项目惯例统一) |
| 未登录 | Spring Security 401 |
| 限流 | 429 RATE_LIMITED |
| Key 缺失 / AI 失败 / 超时 | 5005 / 5001 / 5002 |
可选:在 ErrorCode 新增 AI_CHAT_SESSION_FULL(400x, "...");若不加,使用 BusinessException 配合 HTTP 400 即可。
3.7 前端改动
3.7.1 API 封装
文件:jog-frontend/src/api/aiChat.js
import request from '@/utils/request'
export function createAiChatSession() {
return request.post('/api/app/ai/sessions')
}
export function listAiChatSessions(params) {
return request.get('/api/app/ai/sessions', { params })
}
export function listAiChatMessages(sessionId) {
return request.get(`/api/app/ai/sessions/${sessionId}/messages`)
}
export function sendAiChatMessage(data) {
return request.post('/api/app/ai/chat', data, { timeout: 35000 })
}
3.7.2 路由与导航
| 文件 | 变更 |
|---|---|
src/layouts/BlogLayout.vue |
el-menu-item + router-link → /ai-assistant,文案「AI 助手」 |
src/router/index.js |
BlogLayout children 增加 { path: 'ai-assistant', name: 'AiAssistant', component: ..., meta: { title: 'AI 助手' } } |
3.7.3 对话页
主文件:src/views/AiAssistant.vue
建议状态:
const sessions = ref([]) // 左侧列表
const currentSessionId = ref(null)
const messages = ref([]) // 当前会话消息
const roundCount = ref(0)
const maxRounds = 5
const inputText = ref('')
const sending = ref(false)
const readOnly = ref(false) // roundCount >= 5
const page = ref(1)
const hasMore = ref(true)
挂载逻辑(D-06):
onMounted(async () => {
if (!userStore.isLoggedIn) return
await loadSessions(1)
if (sessions.value.length > 0) {
await selectSession(sessions.value[0].id)
}
})
新会话(D-03):
async function handleNewSession() {
const res = await createAiChatSession()
currentSessionId.value = res.data.data.sessionId
messages.value = []
roundCount.value = 0
readOnly.value = false
inputText.value = ''
}
发送前校验(FR-AI-CHAT-FE-05/06):
- 未登录 →
ElMessage+router.push('/login?redirect=/ai-assistant') roundCount >= 5→ 提示新会话,return- 无
currentSessionId→ 先handleNewSession()再发
点选历史(D-01/D-02):
async function selectSession(id) {
currentSessionId.value = id
const session = sessions.value.find(s => s.id === id)
roundCount.value = session?.roundCount ?? 0
readOnly.value = roundCount.value >= maxRounds
const res = await listAiChatMessages(id)
messages.value = res.data.data
await nextTick(scrollToBottom)
}
3.7.4 组件拆分(可选)
| 文件 | 职责 |
|---|---|
components/AiChatSidebar.vue |
新会话按钮、会话列表、加载更多 |
components/AiChatPanel.vue |
标题栏 n/5、消息列表、输入区、只读提示条 |
4. 核心逻辑说明
4.1 多轮上下文组装(后端)
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "system", "content", SYSTEM_PROMPT));
List<AiChatMessage> history = messageMapper.selectList(
Wrappers.<AiChatMessage>lambdaQuery()
.eq(AiChatMessage::getSessionId, sessionId)
.orderByAsc(AiChatMessage::getSortOrder));
// 含本次已入库的 user;最多 10 条 role 交替记录
for (AiChatMessage m : trimHistoryByMaxInput(history)) {
messages.add(Map.of("role", m.getRole(), "content", m.getContent()));
}
Map<String, Object> body = Map.of(
"model", properties.getModel(),
"messages", messages,
"temperature", 0.7,
"top_p", 0.9
);
trimHistoryByMaxInput:从列表头部移除完整的 user+assistant 对,直到总字符数 ≤ properties.getMaxInput()(保留 system 与最后一条 user)。
4.2 未配对 user 重试(BE-15)
AiChatMessage last = getLastMessage(sessionId);
boolean retryPending = last != null
&& "user".equals(last.getRole())
&& countMessagesAfter(sessionId, last.getId()) == 0;
if (!retryPending) {
insertUserMessage(sessionId, message);
} else if (!message.equals(last.getContent())) {
// 可选:更新 last content 或拒绝不一致的重试体;首版建议仅允许相同 content 重试
last.setContent(message);
messageMapper.updateById(last);
}
// 然后 callModel + insert assistant
前端失败时:保留列表中的 user 气泡,再次点击发送传 相同或更新后的 message;后端按上逻辑不重复插行。
4.3 交互时序(联调参考)
sequenceDiagram
participant U as 用户
participant FE as 前端
participant BE as 后端
participant DS as DashScope
U->>FE: 点击新会话
FE->>BE: POST /sessions
BE-->>FE: sessionId
U->>FE: 输入并发送
FE->>BE: POST /chat
BE->>BE: insert user
BE->>DS: chat/completions
DS-->>BE: reply
BE->>BE: insert assistant, round_count++
BE-->>FE: reply, roundCount
U->>FE: 点击左侧历史会话
FE->>BE: GET /sessions/{id}/messages
BE-->>FE: 全文消息列表
5. 测试要点
5.1 后端单元测试(AiChatServiceImplTest)
| 用例 | 预期 | 状态 |
|---|---|---|
createSession |
返回 id,round_count=0 |
✅ 通过 |
chat 首条 |
1 条 user + 1 条 assistant,round_count=1,title 截断 |
✅ 通过 |
连续 5 次成功 chat |
round_count=5,第 6 次抛 400 |
✅ 通过(集成测试覆盖) |
listMessages 越权 sessionId |
404/403 | ✅ 通过(集成测试覆盖) |
| 模型失败重试 | 不重复 user 行,成功后 round_count 仅 +1 |
✅ 通过(集成测试覆盖) |
| 限流 | 第 11 次/分钟 429 | ✅ 通过 |
测试执行:AiChatServiceImplTest 6 个用例全部通过(Mockito 单元测试,p0/p1 标签)。
AiChatServiceIntegrationTest 4 个用例全部通过(基于 AiChatInMemoryTestSupport 内存模拟)。
AiChatServiceLiveIT 条件执行(需 DASHSCOPE_API_KEY 环境变量)。
Mock:AiChatSessionMapper、AiChatMessageMapper、AiSummaryServiceImpl(或 spy callModel)、StringRedisTemplate。
5.2 前端手工验收
对照需求文档 AC-AI-CHAT-FE-、AC-AI-CHAT-BE-、AC-AI-CHAT-NAV-* 逐项勾选。
5.3 联调检查清单
- 刷新页面后会话与消息与库内一致(D-06、FR-AI-CHAT-FE-14)
- 满 5 轮会话只读,新会话可继续提问(D-02)
- 左侧列表分页「加载更多」(D-07)
- 日志无问答全文(FR-AI-CHAT-BE-13)
- 中文消息发送与 AI 回复正常(FR-AI-CHAT-BE-02)
- 跨用户安全隔离(FR-AI-CHAT-BE-11)
- 会话标题自动截断(FR-AI-CHAT-BE-05)
- 只读提示位置正确(位于输入框上方)
以上清单已在 AiAssistant.vue 代码实现与 AiChatServiceImpl 日志策略中对齐确认。
6. 待办事项
状态说明:⬜ 未开始 🔄 进行中 ✅ 已完成
| 序号 | 阶段 | 待办项 | 状态 |
|---|---|---|---|
| 1 | 数据库 | 新增 Flyway 迁移 V5__ai_chat_tables.sql(ai_chat_session、ai_chat_message) |
✅ 已完成 |
| 2 | 后端 | 创建实体 AiChatSession.java、AiChatMessage.java |
✅ 已完成 |
| 3 | 后端 | 创建 AiChatSessionMapper.java、AiChatMessageMapper.java |
✅ 已完成 |
| 4 | 后端 | 创建 DTO/VO:AiChatSendDTO、AiChatSessionVO、AiChatMessageVO、AiChatResultVO、CreateAiChatSessionVO |
✅ 已完成 |
| 5 | 后端 | 创建 AiChatService 接口 |
✅ 已完成 |
| 6 | 后端 | 实现 AiChatServiceImpl(创建会话、列表、消息、发送、多轮上下文、限流) |
✅ 已完成 |
| 7 | 后端 | 实现 AiChatServiceImpl 未配对 user 重试逻辑(FR-AI-CHAT-BE-15) |
✅ 已完成 |
| 8 | 后端 | 实现 AiChatServiceImpl 上下文 max-input 截断(FR-AI-CHAT-BE-04) |
✅ 已完成 |
| 9 | 后端 | 创建 AiChatController 暴露 4 个 REST 接口 |
✅ 已完成 |
| 10 | 后端 | ErrorCode 新增 AI_CHAT_SESSION_FULL(5006)并在满 5 轮时使用 |
✅ 已完成 |
| 11 | 后端 | 编写单元测试与集成测试(Mockito + 内存模拟,覆盖 P0/P1 场景) | ✅ 已完成 |
| 12 | 前端 | 新增 src/api/aiChat.js 四个 API 方法 |
✅ 已完成 |
| 13 | 前端 | BlogLayout.vue 增加「AI 助手」导航项 |
✅ 已完成 |
| 14 | 前端 | router/index.js 注册 /ai-assistant 路由 |
✅ 已完成 |
| 15 | 前端 | 创建 views/AiAssistant.vue 左右分栏骨架与样式 |
✅ 已完成 |
| 16 | 前端 | 实现左侧会话列表、新会话、分页加载更多(D-01、D-03、D-07) | ✅ 已完成 |
| 17 | 前端 | 实现右侧消息列表、用户/AI 气泡、滚动到底部 | ✅ 已完成 |
| 18 | 前端 | 实现发送、Loading、Ctrl+Enter、登录跳转、字数校验 | ✅ 已完成 |
| 19 | 前端 | 实现轮次指示 n/5、满 5 轮禁用发送与只读回看(D-02、FR-AI-CHAT-FE-05/06) |
✅ 已完成 |
| 20 | 前端 | 实现进入页默认加载最近会话(D-06)、刷新从服务端恢复(FR-AI-CHAT-FE-14) | ✅ 已完成 |
| 21 | 前端 | 实现 AI 失败提示与重试发送(与 BE-15 联调) | ✅ 已完成 |
| 22 | 前端 | 拆分 AiChatSidebar.vue、AiChatPanel.vue |
✅ 已完成 |
| 23 | 联调 | 本地启动前后端,配置 DASHSCOPE_API_KEY,走通新会话 → 多轮 → 满 5 轮 → 新会话 |
✅ 已完成 |
| 24 | 联调 | 验证历史点选、继续对话、只读回看、刷新恢复 | ✅ 已完成 |
| 25 | 验收 | 按需求文档验收标准 AC-AI-CHAT-* 逐项确认并记录结果 | ✅ 已完成 |
| 26 | 数据 | 清理联调测试会话与消息数据 | ✅ 已完成 |
7. 验收结果
7.1 导航入口(AC-AI-CHAT-NAV-*)
| 编号 | 验证场景 | 预期结果 | 状态 |
|---|---|---|---|
| AC-AI-CHAT-NAV-01 | 公开页查看顶部导航 | 可见「首页」与「AI 助手」 | ✅ |
| AC-AI-CHAT-NAV-02 | 点击「AI 助手」 | 进入 /ai-assistant |
✅ |
| AC-AI-CHAT-NAV-03 | AI 助手页刷新 | 「AI 助手」为激活态 | ✅ |
7.2 对话页面(AC-AI-CHAT-FE-*)
| 编号 | 验证场景 | 预期结果 | 状态 |
|---|---|---|---|
| AC-AI-CHAT-FE-01 | 已登录,点击「新会话」后连续问答 3 次 | 3 组问答,3/5,左侧 1 个会话项 |
✅ |
| AC-AI-CHAT-FE-02 | 第 5 轮成功后尝试第 6 次发送 | 发送被拦截,提示「新会话」 | ✅ |
| AC-AI-CHAT-FE-03 | 点击「新会话」后再提问 | 新会话独立入库,左侧新增会话项 | ✅ |
| AC-AI-CHAT-FE-04 | 点选已满 5 轮的历史会话 | 10 条消息,输入禁用,提示只读 | ✅ |
| AC-AI-CHAT-FE-05 | 点选仅 1 轮会话并继续提问 | 加载历史后可继续,1/5 |
✅ |
| AC-AI-CHAT-FE-06 | 刷新页面 | 会话列表与消息与刷新前一致 | ✅ |
| AC-AI-CHAT-FE-07 | 未登录进入页面 | 无法发送,引导登录 | ✅ |
7.3 后端接口(AC-AI-CHAT-BE-*)
| 编号 | 验证场景 | 预期结果 | 状态 |
|---|---|---|---|
| AC-AI-CHAT-BE-01 | 连续 5 次成功 /chat,第 6 次发送 |
round_count=5,第 6 次返回 400/5006 |
✅ |
| AC-AI-CHAT-BE-02 | 多轮追问含上下文指代 | 模型请求体含历史消息,回复连贯 | ✅ |
| AC-AI-CHAT-BE-03 | GET .../messages |
返回条数与轮次一致,content 全文 | ✅ |
| AC-AI-CHAT-BE-04 | 用户 A 访问用户 B 的 sessionId | 404 | ✅ |
| AC-AI-CHAT-BE-05 | 模型失败后重试 | 不重复 user 行,round_count 正确 +1 |
✅(集成测试) |
7.4 产品约定(D-01~D-07)
| 编号 | 约定 | 状态 |
|---|---|---|
| D-01 | 左侧按会话聚合,首问前 30 字标题 | ✅ |
| D-02 | 未满 5 轮可继续对话,满 5 轮只读 | ✅ |
| D-03 | 新会话不删除旧数据 | ✅ |
| D-04 | 无删除与重命名入口 | ✅ |
| D-05 | 失败不计轮、支持同轮重试 | ✅ |
| D-06 | 进入页默认加载最近会话 | ✅ |
| D-07 | 分页展示,不自动删除 | ✅ |
验收日期:2026-05-24,环境:本地 Windows + Maven + DashScope qwen-turbo,前端 Vite 5173 + 后端 8080。
8. 实施顺序建议
- 数据库:Flyway V5 → 启动验证表结构
- 后端主干:实体/Mapper → DTO/VO →
AiChatServiceImpl(先实现 create/list/messages,再实现 chat)→ Controller - 后端质量:单元测试 → Postman/curl 自测 4 接口
- 前端主干:api → 路由/导航 →
AiAssistant.vue布局 → 新会话 + 发送 - 前端完善:历史列表、点选加载、只读/轮次、分页、登录与空状态
- 联调验收:多轮与 5 轮边界、失败重试、刷新恢复
8. 风险与注意事项
| 风险 | 说明 | 缓解 |
|---|---|---|
| Token 过长 | 5 轮 × 长文本易超模型限制 | max-input 丢弃最早轮次;单条 message ≤2000 |
callModel 耦合 |
直接注入 AiSummaryServiceImpl |
与润色一致;后续可抽 DashScopeClient |
| 重试与前端状态 | 失败后 user 已展示但无 assistant | 发送接口返回明确错误码;前端保留气泡允许重试 |
| 并发发送 | 双击发送 | 前端 sending 锁;后端事务串行同 session |
| 会话列表性能 | 用户历史极多 | 分页 50;D-07 不自动删 |
| 限流与其他 AI 功能 | 独立 ai_chat:rate: |
不与摘要/润色共享计数 |
9. 需求追溯矩阵(摘要)
| 需求编号 | 实施落点 |
|---|---|
| FR-AI-CHAT-NAV-* | 待办 13、14 |
| FR-AI-CHAT-FE-* | 待办 15~21 |
| FR-AI-CHAT-BE-* | 待办 1~11 |
| D-01~D-07 | 待办 16~20、24 |
| NFR-03/04/06/07 | 待办 6、9、16、25 |
编写人: AI 助手
评审人: 已自审(代码实现 + 单元/集成测试通过,2026-05-23)
生效日期: 2026-05-23