From aeb124afcef69c8e43230bc0b31cee0616a5d9c9 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期四, 12 三月 2026 13:53:47 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java |  262 ++++++++++++++++++++++++++
 src/main/resources/sql/20260312_create_sys_ai_prompt_template.sql     |   18 +
 src/main/java/com/zy/ai/controller/AiPromptTemplateController.java    |   97 +++++++++
 src/main/java/com/zy/ai/entity/AiPromptTemplate.java                  |   56 +++++
 src/main/java/com/zy/ai/enums/AiPromptScene.java                      |   35 +++
 src/main/java/com/zy/ai/mapper/AiPromptTemplateMapper.java            |   11 +
 src/main/java/com/zy/ai/utils/AiPromptUtils.java                      |   19 +
 src/main/java/com/zy/ai/service/WcsDiagnosisService.java              |   25 +
 src/main/java/com/zy/ai/service/AiPromptTemplateService.java          |   22 ++
 9 files changed, 538 insertions(+), 7 deletions(-)

diff --git a/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java b/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java
new file mode 100644
index 0000000..7e919c5
--- /dev/null
+++ b/src/main/java/com/zy/ai/controller/AiPromptTemplateController.java
@@ -0,0 +1,97 @@
+package com.zy.ai.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.zy.ai.entity.AiPromptTemplate;
+import com.zy.ai.service.AiPromptTemplateService;
+import com.zy.common.web.BaseController;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/prompt/template")
+@RequiredArgsConstructor
+public class AiPromptTemplateController extends BaseController {
+
+    private final AiPromptTemplateService aiPromptTemplateService;
+
+    @GetMapping("/sceneList/auth")
+    @ManagerAuth
+    public R sceneList() {
+        return R.ok(aiPromptTemplateService.listSupportedScenes());
+    }
+
+    @GetMapping("/list/auth")
+    @ManagerAuth
+    public R list(@RequestParam(value = "sceneCode", required = false) String sceneCode,
+                  @RequestParam(value = "published", required = false) Short published,
+                  @RequestParam(value = "status", required = false) Short status) {
+        QueryWrapper<AiPromptTemplate> wrapper = new QueryWrapper<>();
+        if (sceneCode != null && !sceneCode.trim().isEmpty()) {
+            wrapper.eq("scene_code", sceneCode.trim());
+        }
+        if (published != null) {
+            wrapper.eq("published", published);
+        }
+        if (status != null) {
+            wrapper.eq("status", status);
+        }
+        wrapper.orderByAsc("scene_code").orderByDesc("version").orderByDesc("id");
+        List<AiPromptTemplate> list = aiPromptTemplateService.list(wrapper);
+        return R.ok(list);
+    }
+
+    @GetMapping("/active/auth")
+    @ManagerAuth
+    public R active(@RequestParam("sceneCode") String sceneCode) {
+        try {
+            return R.ok(aiPromptTemplateService.resolvePublished(sceneCode));
+        } catch (IllegalArgumentException | IllegalStateException e) {
+            return R.error(e.getMessage());
+        }
+    }
+
+    @PostMapping("/save/auth")
+    @ManagerAuth
+    public R save(@RequestBody AiPromptTemplate template) {
+        try {
+            return R.ok(aiPromptTemplateService.savePrompt(template, getUserId()));
+        } catch (IllegalArgumentException e) {
+            return R.error(e.getMessage());
+        }
+    }
+
+    @PostMapping("/publish/auth")
+    @ManagerAuth
+    public R publish(@RequestParam("id") Long id) {
+        try {
+            return R.ok(aiPromptTemplateService.publishPrompt(id, getUserId()));
+        } catch (IllegalArgumentException e) {
+            return R.error(e.getMessage());
+        }
+    }
+
+    @PostMapping("/delete/auth")
+    @ManagerAuth
+    public R delete(@RequestParam("id") Long id) {
+        try {
+            return R.ok(aiPromptTemplateService.deletePrompt(id));
+        } catch (IllegalArgumentException e) {
+            return R.error(e.getMessage());
+        }
+    }
+
+    @PostMapping("/initDefaults/auth")
+    @ManagerAuth
+    public R initDefaults() {
+        return R.ok(aiPromptTemplateService.initDefaultsIfMissing());
+    }
+}
diff --git a/src/main/java/com/zy/ai/entity/AiPromptTemplate.java b/src/main/java/com/zy/ai/entity/AiPromptTemplate.java
new file mode 100644
index 0000000..a915108
--- /dev/null
+++ b/src/main/java/com/zy/ai/entity/AiPromptTemplate.java
@@ -0,0 +1,56 @@
+package com.zy.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("sys_ai_prompt_template")
+public class AiPromptTemplate implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String name;
+
+    @TableField("scene_code")
+    private String sceneCode;
+
+    private Integer version;
+
+    private String content;
+
+    /**
+     * 1 鍚敤 0 绂佺敤
+     */
+    private Short status;
+
+    /**
+     * 1 宸插彂甯� 0 鏈彂甯�
+     */
+    private Short published;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("published_by")
+    private Long publishedBy;
+
+    @TableField("published_time")
+    private Date publishedTime;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+    private String memo;
+}
diff --git a/src/main/java/com/zy/ai/enums/AiPromptScene.java b/src/main/java/com/zy/ai/enums/AiPromptScene.java
new file mode 100644
index 0000000..42b65ee
--- /dev/null
+++ b/src/main/java/com/zy/ai/enums/AiPromptScene.java
@@ -0,0 +1,35 @@
+package com.zy.ai.enums;
+
+public enum AiPromptScene {
+
+    DIAGNOSE_STREAM("wcs_diagnose_stream", "WCS宸℃璇婃柇"),
+    SENSOR_CHAT("wcs_sensor_chat", "WCS涓撳闂瓟");
+
+    private final String code;
+    private final String label;
+
+    AiPromptScene(String code, String label) {
+        this.code = code;
+        this.label = label;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public static AiPromptScene ofCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (AiPromptScene item : values()) {
+            if (item.code.equals(code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/zy/ai/mapper/AiPromptTemplateMapper.java b/src/main/java/com/zy/ai/mapper/AiPromptTemplateMapper.java
new file mode 100644
index 0000000..c4d251a
--- /dev/null
+++ b/src/main/java/com/zy/ai/mapper/AiPromptTemplateMapper.java
@@ -0,0 +1,11 @@
+package com.zy.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zy.ai.entity.AiPromptTemplate;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiPromptTemplateMapper extends BaseMapper<AiPromptTemplate> {
+}
diff --git a/src/main/java/com/zy/ai/service/AiPromptTemplateService.java b/src/main/java/com/zy/ai/service/AiPromptTemplateService.java
new file mode 100644
index 0000000..e269966
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/AiPromptTemplateService.java
@@ -0,0 +1,22 @@
+package com.zy.ai.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zy.ai.entity.AiPromptTemplate;
+
+import java.util.List;
+import java.util.Map;
+
+public interface AiPromptTemplateService extends IService<AiPromptTemplate> {
+
+    AiPromptTemplate resolvePublished(String sceneCode);
+
+    AiPromptTemplate savePrompt(AiPromptTemplate template, Long operatorUserId);
+
+    AiPromptTemplate publishPrompt(Long id, Long operatorUserId);
+
+    boolean deletePrompt(Long id);
+
+    int initDefaultsIfMissing();
+
+    List<Map<String, Object>> listSupportedScenes();
+}
diff --git a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
index 7c10893..757e877 100644
--- a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
+++ b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -2,11 +2,13 @@
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.zy.ai.entity.AiPromptTemplate;
 import com.zy.ai.entity.ChatCompletionRequest;
 import com.zy.ai.entity.ChatCompletionResponse;
 import com.zy.ai.entity.WcsDiagnosisRequest;
+import com.zy.ai.enums.AiPromptScene;
 import com.zy.ai.mcp.service.SpringAiMcpToolManager;
-import com.zy.ai.utils.AiPromptUtils;
+import com.zy.ai.service.AiPromptTemplateService;
 import com.zy.ai.utils.AiUtils;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.enums.RedisKeyType;
@@ -32,24 +34,25 @@
     @Autowired
     private RedisUtil redisUtil;
     @Autowired
-    private AiPromptUtils aiPromptUtils;
-    @Autowired
     private AiUtils aiUtils;
     @Autowired
     private SpringAiMcpToolManager mcpToolManager;
+    @Autowired
+    private AiPromptTemplateService aiPromptTemplateService;
 
     public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
         List<ChatCompletionRequest.Message> messages = new ArrayList<>();
+        AiPromptTemplate promptTemplate = aiPromptTemplateService.resolvePublished(AiPromptScene.DIAGNOSE_STREAM.getCode());
 
         ChatCompletionRequest.Message mcpSystem = new ChatCompletionRequest.Message();
         mcpSystem.setRole("system");
-        mcpSystem.setContent(aiPromptUtils.getAiDiagnosePromptMcp());
+        mcpSystem.setContent(promptTemplate.getContent());
 
         ChatCompletionRequest.Message mcpUser = new ChatCompletionRequest.Message();
         mcpUser.setRole("user");
         mcpUser.setContent(aiUtils.buildDiagnosisUserContentMcp(request));
 
-        runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, null);
+        runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, promptTemplate, 0.3, 2048, emitter, null);
     }
 
     public void askStream(String prompt,
@@ -81,16 +84,17 @@
         }
 
         final String finalChatId = chatId;
+        AiPromptTemplate promptTemplate = aiPromptTemplateService.resolvePublished(AiPromptScene.SENSOR_CHAT.getCode());
 
         ChatCompletionRequest.Message mcpSystem = new ChatCompletionRequest.Message();
         mcpSystem.setRole("system");
-        mcpSystem.setContent(aiPromptUtils.getWcsSensorPromptMcp());
+        mcpSystem.setContent(promptTemplate.getContent());
 
         ChatCompletionRequest.Message mcpUser = new ChatCompletionRequest.Message();
         mcpUser.setRole("user");
         mcpUser.setContent("銆愮敤鎴锋彁闂�慭n" + (prompt == null ? "" : prompt));
 
-        runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, 0.3, 2048, emitter, finalChatId);
+        runMcpStreamingDiagnosis(messages, mcpSystem, mcpUser, promptTemplate, 0.3, 2048, emitter, finalChatId);
     }
 
     public List<Map<String, Object>> listChats() {
@@ -161,6 +165,7 @@
     private void runMcpStreamingDiagnosis(List<ChatCompletionRequest.Message> baseMessages,
                                           ChatCompletionRequest.Message systemPrompt,
                                           ChatCompletionRequest.Message userQuestion,
+                                          AiPromptTemplate promptTemplate,
                                           Double temperature,
                                           Integer maxTokens,
                                           SseEmitter emitter,
@@ -272,6 +277,12 @@
                         Map<String, Object> meta = new java.util.HashMap<>();
                         meta.put("chatId", chatId);
                         meta.put("title", buildTitleFromPrompt(userQuestion.getContent()));
+                        if (promptTemplate != null) {
+                            meta.put("promptTemplateId", promptTemplate.getId());
+                            meta.put("promptSceneCode", promptTemplate.getSceneCode());
+                            meta.put("promptVersion", promptTemplate.getVersion());
+                            meta.put("promptName", promptTemplate.getName());
+                        }
                         meta.put("createdAt", createdAt);
                         meta.put("updatedAt", System.currentTimeMillis());
                         redisUtil.hmset(metaKey, meta, CHAT_TTL_SECONDS);
diff --git a/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java b/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java
new file mode 100644
index 0000000..fedfd7f
--- /dev/null
+++ b/src/main/java/com/zy/ai/service/impl/AiPromptTemplateServiceImpl.java
@@ -0,0 +1,262 @@
+package com.zy.ai.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zy.ai.entity.AiPromptTemplate;
+import com.zy.ai.enums.AiPromptScene;
+import com.zy.ai.mapper.AiPromptTemplateMapper;
+import com.zy.ai.service.AiPromptTemplateService;
+import com.zy.ai.utils.AiPromptUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service("aiPromptTemplateService")
+@RequiredArgsConstructor
+public class AiPromptTemplateServiceImpl extends ServiceImpl<AiPromptTemplateMapper, AiPromptTemplate> implements AiPromptTemplateService {
+
+    private final AiPromptUtils aiPromptUtils;
+
+    @Override
+    public AiPromptTemplate resolvePublished(String sceneCode) {
+        AiPromptScene scene = requireScene(sceneCode);
+        AiPromptTemplate prompt = findPublished(scene.getCode());
+        if (prompt == null) {
+            synchronized (("ai_prompt_scene_init_" + scene.getCode()).intern()) {
+                prompt = findPublished(scene.getCode());
+                if (prompt == null) {
+                    prompt = ensurePublishedScene(scene);
+                }
+            }
+        }
+
+        if (prompt == null) {
+            throw new IllegalStateException("鏈壘鍒板凡鍙戝竷鐨� Prompt锛宻ceneCode=" + scene.getCode());
+        }
+        return prompt;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AiPromptTemplate savePrompt(AiPromptTemplate template, Long operatorUserId) {
+        if (template == null) {
+            throw new IllegalArgumentException("Prompt 涓嶈兘涓虹┖");
+        }
+        AiPromptScene scene = requireScene(template.getSceneCode());
+        String content = template.getContent();
+        if (content == null || content.trim().isEmpty()) {
+            throw new IllegalArgumentException("Prompt 鍐呭涓嶈兘涓虹┖");
+        }
+
+        if (template.getId() == null) {
+            AiPromptTemplate entity = new AiPromptTemplate();
+            int version = nextVersion(scene.getCode());
+            entity.setName(defaultName(scene, version, template.getName()));
+            entity.setSceneCode(scene.getCode());
+            entity.setVersion(version);
+            entity.setContent(content);
+            entity.setStatus(normalizeStatus(template.getStatus()));
+            entity.setPublished((short) 0);
+            entity.setCreatedBy(operatorUserId);
+            entity.setMemo(trim(template.getMemo()));
+            this.save(entity);
+            return entity;
+        }
+
+        AiPromptTemplate db = this.getById(template.getId());
+        if (db == null) {
+            throw new IllegalArgumentException("Prompt 涓嶅瓨鍦�");
+        }
+        if (!scene.getCode().equals(db.getSceneCode())) {
+            throw new IllegalArgumentException("涓嶅厑璁镐慨鏀� Prompt 鎵�灞炲満鏅�");
+        }
+        if (Short.valueOf((short) 1).equals(db.getPublished())) {
+            throw new IllegalArgumentException("宸插彂甯� Prompt 涓嶅厑璁哥洿鎺ヤ慨鏀癸紝璇锋柊寤虹増鏈悗鍐嶅彂甯�");
+        }
+
+        db.setName(defaultName(scene, db.getVersion() == null ? 1 : db.getVersion(), template.getName()));
+        db.setContent(content);
+        db.setStatus(normalizeStatus(template.getStatus()));
+        db.setMemo(trim(template.getMemo()));
+        this.updateById(db);
+        return db;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AiPromptTemplate publishPrompt(Long id, Long operatorUserId) {
+        if (id == null) {
+            throw new IllegalArgumentException("id 涓嶈兘涓虹┖");
+        }
+        AiPromptTemplate db = this.getById(id);
+        if (db == null) {
+            throw new IllegalArgumentException("Prompt 涓嶅瓨鍦�");
+        }
+        if (db.getContent() == null || db.getContent().trim().isEmpty()) {
+            throw new IllegalArgumentException("Prompt 鍐呭涓嶈兘涓虹┖");
+        }
+
+        UpdateWrapper<AiPromptTemplate> clearWrapper = new UpdateWrapper<>();
+        clearWrapper.eq("scene_code", db.getSceneCode()).set("published", 0);
+        this.update(clearWrapper);
+
+        db.setPublished((short) 1);
+        db.setStatus((short) 1);
+        db.setPublishedBy(operatorUserId);
+        db.setPublishedTime(new Date());
+        if (db.getVersion() == null || db.getVersion() <= 0) {
+            db.setVersion(nextVersion(db.getSceneCode()));
+        }
+        if (db.getName() == null || db.getName().trim().isEmpty()) {
+            AiPromptScene scene = requireScene(db.getSceneCode());
+            db.setName(defaultName(scene, db.getVersion(), null));
+        }
+        this.updateById(db);
+        return db;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deletePrompt(Long id) {
+        if (id == null) {
+            return false;
+        }
+        AiPromptTemplate db = this.getById(id);
+        if (db == null) {
+            return false;
+        }
+        if (Short.valueOf((short) 1).equals(db.getPublished())) {
+            throw new IllegalArgumentException("宸插彂甯� Prompt 涓嶅厑璁稿垹闄わ紝璇峰厛鍙戝竷鍏朵粬鐗堟湰");
+        }
+        return this.removeById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int initDefaultsIfMissing() {
+        int changed = 0;
+        for (AiPromptScene scene : AiPromptScene.values()) {
+            AiPromptTemplate prompt = findPublished(scene.getCode());
+            if (prompt == null) {
+                ensurePublishedScene(scene);
+                changed++;
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    public List<Map<String, Object>> listSupportedScenes() {
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (AiPromptScene scene : AiPromptScene.values()) {
+            HashMap<String, Object> item = new HashMap<>();
+            item.put("code", scene.getCode());
+            item.put("label", scene.getLabel());
+            result.add(item);
+        }
+        return result;
+    }
+
+    private AiPromptTemplate ensurePublishedScene(AiPromptScene scene) {
+        AiPromptTemplate latest = findLatest(scene.getCode());
+        if (latest == null) {
+            AiPromptTemplate seed = new AiPromptTemplate();
+            seed.setName(defaultName(scene, 1, null));
+            seed.setSceneCode(scene.getCode());
+            seed.setVersion(1);
+            seed.setContent(aiPromptUtils.getDefaultPrompt(scene.getCode()));
+            seed.setStatus((short) 1);
+            seed.setPublished((short) 1);
+            seed.setPublishedTime(new Date());
+            seed.setMemo("绯荤粺鍒濆鍖栭粯璁� Prompt");
+            this.save(seed);
+            log.info("Initialized default AI prompt, sceneCode={}, version={}", scene.getCode(), seed.getVersion());
+            return seed;
+        }
+
+        UpdateWrapper<AiPromptTemplate> clearWrapper = new UpdateWrapper<>();
+        clearWrapper.eq("scene_code", scene.getCode()).set("published", 0);
+        this.update(clearWrapper);
+
+        latest.setStatus((short) 1);
+        latest.setPublished((short) 1);
+        if (latest.getPublishedTime() == null) {
+            latest.setPublishedTime(new Date());
+        }
+        this.updateById(latest);
+        return latest;
+    }
+
+    private AiPromptTemplate findPublished(String sceneCode) {
+        QueryWrapper<AiPromptTemplate> wrapper = new QueryWrapper<>();
+        wrapper.eq("scene_code", sceneCode)
+                .eq("status", 1)
+                .eq("published", 1)
+                .orderByDesc("version")
+                .orderByDesc("id")
+                .last("limit 1");
+        return this.getOne(wrapper, false);
+    }
+
+    private AiPromptTemplate findLatest(String sceneCode) {
+        QueryWrapper<AiPromptTemplate> wrapper = new QueryWrapper<>();
+        wrapper.eq("scene_code", sceneCode)
+                .orderByDesc("version")
+                .orderByDesc("id")
+                .last("limit 1");
+        return this.getOne(wrapper, false);
+    }
+
+    private int nextVersion(String sceneCode) {
+        QueryWrapper<AiPromptTemplate> wrapper = new QueryWrapper<>();
+        wrapper.eq("scene_code", sceneCode)
+                .select("max(version) as version");
+        Map<String, Object> row = this.getMap(wrapper);
+        if (row == null || row.get("version") == null) {
+            return 1;
+        }
+        Object value = row.get("version");
+        if (value instanceof Number) {
+            return ((Number) value).intValue() + 1;
+        }
+        return Integer.parseInt(String.valueOf(value)) + 1;
+    }
+
+    private Short normalizeStatus(Short status) {
+        return status != null && status == 0 ? (short) 0 : (short) 1;
+    }
+
+    private String defaultName(AiPromptScene scene, Integer version, String name) {
+        String value = trim(name);
+        if (value != null && !value.isEmpty()) {
+            return value;
+        }
+        return scene.getLabel() + " v" + version;
+    }
+
+    private AiPromptScene requireScene(String sceneCode) {
+        String code = trim(sceneCode);
+        AiPromptScene scene = AiPromptScene.ofCode(code);
+        if (scene == null) {
+            throw new IllegalArgumentException("涓嶆敮鎸佺殑 Prompt 鍦烘櫙: " + sceneCode);
+        }
+        return scene;
+    }
+
+    private String trim(String value) {
+        if (value == null) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return trimmed.isEmpty() ? null : trimmed;
+    }
+}
diff --git a/src/main/java/com/zy/ai/utils/AiPromptUtils.java b/src/main/java/com/zy/ai/utils/AiPromptUtils.java
index 71b4a3c..3fed8c0 100644
--- a/src/main/java/com/zy/ai/utils/AiPromptUtils.java
+++ b/src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -1,10 +1,29 @@
 package com.zy.ai.utils;
 
+import com.zy.ai.enums.AiPromptScene;
 import org.springframework.stereotype.Component;
 
 @Component
 public class AiPromptUtils {
 
+    public String getDefaultPrompt(String sceneCode) {
+        AiPromptScene scene = AiPromptScene.ofCode(sceneCode);
+        if (scene == null) {
+            throw new IllegalArgumentException("涓嶆敮鎸佺殑 Prompt 鍦烘櫙: " + sceneCode);
+        }
+        return getDefaultPrompt(scene);
+    }
+
+    public String getDefaultPrompt(AiPromptScene scene) {
+        if (scene == AiPromptScene.DIAGNOSE_STREAM) {
+            return getAiDiagnosePromptMcp();
+        }
+        if (scene == AiPromptScene.SENSOR_CHAT) {
+            return getWcsSensorPromptMcp();
+        }
+        throw new IllegalArgumentException("涓嶆敮鎸佺殑 Prompt 鍦烘櫙: " + scene.getCode());
+    }
+
     //AI璇婃柇绯荤粺Prompt
     public String getAiDiagnosePromptMcp() {
         String prompt = "浣犳槸涓�鍚嶈祫娣� WCS锛堜粨鍌ㄦ帶鍒剁郴缁燂級涓庤嚜鍔ㄥ寲绔嬪簱涓撳锛岀啛鎮夛細鍫嗗灈鏈恒�佽緭閫佺嚎銆佹彁鍗囨満銆佺┛姊溅绛夎澶囩殑浠诲姟鍒嗛厤鍜岃繍琛岄�昏緫锛屼篃鐔熸倝甯歌鐨勭郴缁熷崱姝汇�佷换鍔′笉鎵ц銆佽澶囩┖闂蹭絾鏃犱换鍔$瓑闂妯″紡銆俓n\n" +
diff --git a/src/main/resources/sql/20260312_create_sys_ai_prompt_template.sql b/src/main/resources/sql/20260312_create_sys_ai_prompt_template.sql
new file mode 100644
index 0000000..a64c476
--- /dev/null
+++ b/src/main/resources/sql/20260312_create_sys_ai_prompt_template.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS `sys_ai_prompt_template` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+  `name` VARCHAR(128) NOT NULL COMMENT 'Prompt 鍚嶇О',
+  `scene_code` VARCHAR(64) NOT NULL COMMENT '鍦烘櫙缂栫爜',
+  `version` INT NOT NULL COMMENT '鐗堟湰鍙�',
+  `content` LONGTEXT NOT NULL COMMENT 'Prompt 鍐呭',
+  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '鐘舵��:1鍚敤0绂佺敤',
+  `published` TINYINT NOT NULL DEFAULT 0 COMMENT '鏄惁宸插彂甯�:1鏄�0鍚�',
+  `created_by` BIGINT DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `published_by` BIGINT DEFAULT NULL COMMENT '鍙戝竷浜�',
+  `published_time` DATETIME DEFAULT NULL COMMENT '鍙戝竷鏃堕棿',
+  `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+  `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+  `memo` VARCHAR(255) DEFAULT NULL COMMENT '澶囨敞',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_sys_ai_prompt_scene_version` (`scene_code`, `version`),
+  KEY `idx_sys_ai_prompt_scene_publish` (`scene_code`, `published`, `status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI Prompt 妯℃澘鐗堟湰琛�';

--
Gitblit v1.9.1