提示词保存

This commit is contained in:
2025-11-13 01:06:28 +08:00
parent fc7d2ccea5
commit c652d0ddf3
49 changed files with 4072 additions and 2452 deletions

View File

@@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.ai.chat.controller.app;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatConversationCreateMyReqVO;
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatConversationRespVO;
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatConversationUpdateMyReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
import com.fhs.core.trans.anno.TransMethodResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - AI 聊天对话")
@RestController
@RequestMapping("/ai/chat/conversation")
@Validated
public class AppAiChatConversationController {
@Resource
private AiChatConversationService chatConversationService;
@PostMapping("/create-my")
@Operation(summary = "创建【我的】聊天对话")
public CommonResult<Long> createChatConversationMy(@RequestBody @Valid AppAiChatConversationCreateMyReqVO createReqVO) {
// 将 App VO 转换为 Admin VO
AiChatConversationCreateMyReqVO adminReqVO = BeanUtils.toBean(createReqVO, AiChatConversationCreateMyReqVO.class);
return success(chatConversationService.createChatConversationMy(adminReqVO, getLoginUserId()));
}
@PutMapping("/update-my")
@Operation(summary = "更新【我的】聊天对话")
public CommonResult<Boolean> updateChatConversationMy(@RequestBody @Valid AppAiChatConversationUpdateMyReqVO updateReqVO) {
// 将 App VO 转换为 Admin VO
AiChatConversationUpdateMyReqVO adminReqVO = BeanUtils.toBean(updateReqVO, AiChatConversationUpdateMyReqVO.class);
chatConversationService.updateChatConversationMy(adminReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/my-list")
@Operation(summary = "获得【我的】聊天对话列表")
@TransMethodResult
public CommonResult<List<AppAiChatConversationRespVO>> getChatConversationMyList() {
List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
return success(BeanUtils.toBean(list, AppAiChatConversationRespVO.class));
}
@GetMapping("/get-my")
@Operation(summary = "获得【我的】聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
@TransMethodResult
public CommonResult<AppAiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
conversation = null;
}
return success(BeanUtils.toBean(conversation, AppAiChatConversationRespVO.class));
}
@DeleteMapping("/delete-my")
@Operation(summary = "删除聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
public CommonResult<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
chatConversationService.deleteChatConversationMy(id, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete-by-unpinned")
@Operation(summary = "删除未置顶的聊天对话")
public CommonResult<Boolean> deleteChatConversationMyByUnpinned() {
chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId());
return success(true);
}
}

View File

@@ -0,0 +1,194 @@
package cn.iocoder.yudao.module.ai.chat.controller.app;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatMessageRespVO;
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatMessageSendReqVO;
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatMessageSendRespVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - 聊天消息")
@RestController
@RequestMapping("/ai/chat/message")
@Validated
@Slf4j
public class AppAiChatMessageController {
@Resource
private AiChatMessageService chatMessageService;
@Resource
private AiChatConversationService chatConversationService;
@Resource
private AiKnowledgeSegmentService knowledgeSegmentService;
@Resource
private AiKnowledgeDocumentService knowledgeDocumentService;
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
@PostMapping("/send")
public CommonResult<AppAiChatMessageSendRespVO> sendMessage(@Valid @RequestBody AppAiChatMessageSendReqVO sendReqVO) {
// 将 App VO 转换为 Admin VO
AiChatMessageSendReqVO adminReqVO = BeanUtils.toBean(sendReqVO, AiChatMessageSendReqVO.class);
// 调用 Service然后转换响应
var adminResp = chatMessageService.sendMessage(adminReqVO, getLoginUserId());
// 手动转换 segments因为内部类类型不同
AppAiChatMessageSendRespVO appResp = convertSendRespVO(adminResp);
return success(appResp);
}
/**
* 将 Admin 的 SendRespVO 转换为 App 的 SendRespVO
* 主要处理 segments 字段的类型转换
*/
private AppAiChatMessageSendRespVO convertSendRespVO(cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO adminResp) {
if (adminResp == null) {
return null;
}
AppAiChatMessageSendRespVO appResp = new AppAiChatMessageSendRespVO();
// 转换 send
if (adminResp.getSend() != null) {
AppAiChatMessageSendRespVO.Message appSend = BeanUtils.toBean(adminResp.getSend(), AppAiChatMessageSendRespVO.Message.class);
if (adminResp.getSend().getSegments() != null) {
appSend.setSegments(convertKnowledgeSegments(adminResp.getSend().getSegments()));
}
appResp.setSend(appSend);
}
// 转换 receive
if (adminResp.getReceive() != null) {
AppAiChatMessageSendRespVO.Message appReceive = BeanUtils.toBean(adminResp.getReceive(), AppAiChatMessageSendRespVO.Message.class);
if (adminResp.getReceive().getSegments() != null) {
appReceive.setSegments(convertKnowledgeSegments(adminResp.getReceive().getSegments()));
}
appResp.setReceive(appReceive);
}
return appResp;
}
/**
* 转换 KnowledgeSegment 列表
*/
private List<AppAiChatMessageSendRespVO.KnowledgeSegment> convertKnowledgeSegments(
List<cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO.KnowledgeSegment> adminSegments) {
if (adminSegments == null) {
return null;
}
return convertList(adminSegments, segment -> {
if (segment == null) {
return null;
}
return new AppAiChatMessageSendRespVO.KnowledgeSegment()
.setId(segment.getId())
.setContent(segment.getContent())
.setDocumentId(segment.getDocumentId())
.setDocumentName(segment.getDocumentName());
});
}
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<CommonResult<AppAiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AppAiChatMessageSendReqVO sendReqVO) {
// 将 App VO 转换为 Admin VO
AiChatMessageSendReqVO adminReqVO = BeanUtils.toBean(sendReqVO, AiChatMessageSendReqVO.class);
// 调用 Service然后转换响应流
return chatMessageService.sendChatMessageStream(adminReqVO, getLoginUserId())
.map(result -> {
if (result.getData() != null) {
// 手动转换 segments因为内部类类型不同
AppAiChatMessageSendRespVO appResp = convertSendRespVO(result.getData());
return success(appResp);
}
return success((AppAiChatMessageSendRespVO) null);
});
}
@Operation(summary = "获得指定对话的消息列表")
@GetMapping("/list-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
public CommonResult<List<AppAiChatMessageRespVO>> getChatMessageListByConversationId(
@RequestParam("conversationId") Long conversationId) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
return success(Collections.emptyList());
}
// 1. 获取消息列表
List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
if (CollUtil.isEmpty(messageList)) {
return success(Collections.emptyList());
}
// 2. 拼接数据,主要是知识库段落信息
Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
List<AppAiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AppAiChatMessageRespVO.class);
for (int i = 0; i < messageList.size(); i++) {
AiChatMessageDO message = messageList.get(i);
if (CollUtil.isEmpty(message.getSegmentIds())) {
continue;
}
// 设置知识库段落信息
messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
if (segment == null) {
return null;
}
AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
if (document == null) {
return null;
}
return new AppAiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
.setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
}));
}
return success(messageVOList);
}
@Operation(summary = "删除消息")
@DeleteMapping("/delete")
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
public CommonResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
chatMessageService.deleteChatMessage(id, getLoginUserId());
return success(true);
}
@Operation(summary = "删除指定对话的消息")
@DeleteMapping("/delete-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
return success(true);
}
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.ai.chat.vo.app;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - AI 聊天对话创建【我的】 Request VO")
@Data
public class AppAiChatConversationCreateMyReqVO {
@Schema(description = "聊天角色编号", example = "666")
private Long roleId;
@Schema(description = "知识库编号", example = "1204")
private Long knowledgeId;
}

View File

@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.ai.chat.vo.app;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
import com.fhs.core.trans.anno.Trans;
import com.fhs.core.trans.constant.TransType;
import com.fhs.core.trans.vo.VO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 App - AI 聊天对话 Response VO")
@Data
public class AppAiChatConversationRespVO implements VO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long userId;
@Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
private String title;
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean pinned;
@Schema(description = "角色编号", example = "1")
@Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"})
private Long roleId;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
private Long modelId;
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
private String model;
@Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
private String modelName;
@Schema(description = "角色设定", example = "一个快乐的程序员")
private String systemMessage;
@Schema(description = "温度参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
private Double temperature;
@Schema(description = "单条回复的最大 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
private Integer maxTokens;
@Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer maxContexts;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
// ========== 关联 role 信息 ==========
@Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png")
private String roleAvatar;
@Schema(description = "角色名字", example = "小黄")
private String roleName;
}

View File

@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.ai.chat.vo.app;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "用户 App - AI 聊天对话更新【我的】 Request VO")
@Data
public class AppAiChatConversationUpdateMyReqVO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "对话编号不能为空")
private Long id;
@Schema(description = "对话标题", example = "我是一个标题")
private String title;
@Schema(description = "是否置顶", example = "true")
private Boolean pinned;
@Schema(description = "模型编号", example = "1")
private Long modelId;
@Schema(description = "知识库编号", example = "1")
private Long knowledgeId;
@Schema(description = "角色设定", example = "一个快乐的程序员")
private String systemMessage;
@Schema(description = "温度参数", example = "0.8")
private Double temperature;
@Schema(description = "单条回复的最大 Token 数量", example = "4096")
private Integer maxTokens;
@Schema(description = "上下文的最大 Message 数量", example = "10")
private Integer maxContexts;
}

View File

@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.ai.chat.vo.app;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "用户 App - AI 聊天消息 Response VO")
@Data
public class AppAiChatMessageRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long conversationId;
@Schema(description = "回复消息编号", example = "1024")
private Long replyId;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
private String type; // 参见 MessageType 枚举类
@Schema(description = "用户编号", example = "4096")
private Long userId;
@Schema(description = "角色编号", example = "888")
private Long roleId;
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
private String model;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
private Long modelId;
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
private String content;
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
private String reasoningContent;
@Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean useContext;
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
private List<Long> segmentIds;
@Schema(description = "知识库段落数组")
private List<KnowledgeSegment> segments;
@Schema(description = "联网搜索的网页内容数组")
private List<AiWebSearchResponse.WebPage> webSearchPages;
@Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png")
private List<String> attachmentUrls;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
private LocalDateTime createTime;
@Schema(description = "知识库段落", example = "Java 开发手册")
@Data
public static class KnowledgeSegment {
@Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
private String content;
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long documentId;
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
private String documentName;
}
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.ai.chat.vo.app;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "用户 App - AI 聊天消息发送 Request VO")
@Data
public class AppAiChatMessageSendReqVO {
@Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "聊天对话编号不能为空")
private Long conversationId;
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法")
@NotEmpty(message = "聊天内容不能为空")
private String content;
@Schema(description = "是否携带上下文", example = "true")
private Boolean useContext;
@Schema(description = "是否联网搜索", example = "true")
private Boolean useSearch;
@Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png")
private List<String> attachmentUrls;
}

View File

@@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.ai.chat.vo.app;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "用户 App - AI 聊天消息发送 Response VO")
@Data
public class AppAiChatMessageSendRespVO {
@Schema(description = "发送消息", requiredMode = Schema.RequiredMode.REQUIRED)
private Message send;
@Schema(description = "接收消息", requiredMode = Schema.RequiredMode.REQUIRED)
private Message receive;
@Schema(description = "消息")
@Data
public static class Message {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
private String type; // 参见 MessageType 枚举类
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
private String content;
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
private String reasoningContent;
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
private List<Long> segmentIds;
@Schema(description = "知识库段落数组")
private List<KnowledgeSegment> segments;
@Schema(description = "联网搜索的网页内容数组")
private List<AiWebSearchResponse.WebPage> webSearchPages;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@Schema(description = "知识库段落", example = "Java 开发手册")
@Data
public static class KnowledgeSegment {
@Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
private String content;
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long documentId;
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
private String documentName;
}
}

View File

@@ -1,54 +1,50 @@
package cn.iocoder.yudao.module.ai.userprompt.controller;
package cn.iocoder.yudao.module.ai.userprompt.controller.app;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.ai.userprompt.vo.*;
import cn.iocoder.yudao.module.ai.dal.dataobject.userprompt.UserPromptDO;
import cn.iocoder.yudao.module.ai.userprompt.service.UserPromptService;
import cn.iocoder.yudao.module.ai.userprompt.vo.UserPromptPageReqVO;
import cn.iocoder.yudao.module.ai.userprompt.vo.UserPromptRespVO;
import cn.iocoder.yudao.module.ai.userprompt.vo.UserPromptSaveReqVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Tag(name = "AI - 用户提示词")
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.USER_PROMPT_NOT_EXISTS;
@Tag(name = "用户 App - 用户提示词")
@RestController
@RequestMapping("/ai/user-prompt")
@Validated
public class UserPromptController {
public class AppUserPromptController {
@Resource
private UserPromptService userPromptService;
@PostMapping("/create")
@Operation(summary = "创建用户提示词")
@PreAuthorize("@ss.hasPermission('ai:user-prompt:create')")
public CommonResult<Long> createUserPrompt(@Valid @RequestBody UserPromptSaveReqVO createReqVO) {
// 设置当前登录用户ID
createReqVO.setUserId(getLoginUserId());
return success(userPromptService.createUserPrompt(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新用户提示词")
@PreAuthorize("@ss.hasPermission('ai:user-prompt:update')")
public CommonResult<Boolean> updateUserPrompt(@Valid @RequestBody UserPromptSaveReqVO updateReqVO) {
public CommonResult<Boolean> updateUserPrompt(@RequestBody UserPromptSaveReqVO updateReqVO) {
// 设置当前登录用户ID确保只能更新自己的提示词
updateReqVO.setUserId(getLoginUserId());
// 更新时如果前端没有传递 sortuseCountisPublic 等字段后端会自动从数据库获取
// 注意移除了 @Valid 注解因为这些字段在更新时可以为空后端会自动填充
userPromptService.updateUserPrompt(updateReqVO);
return success(true);
}
@@ -56,49 +52,36 @@ public class UserPromptController {
@DeleteMapping("/delete")
@Operation(summary = "删除用户提示词")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('ai:user-prompt:delete')")
public CommonResult<Boolean> deleteUserPrompt(@RequestParam("id") Long id) {
// 验证是否为当前用户的提示词
UserPromptDO userPrompt = userPromptService.getUserPrompt(id);
if (userPrompt == null || !userPrompt.getUserId().equals(getLoginUserId())) {
// 提示词不存在或不属于当前用户返回错误
throw exception(USER_PROMPT_NOT_EXISTS);
}
userPromptService.deleteUserPrompt(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除用户提示词")
@PreAuthorize("@ss.hasPermission('ai:user-prompt:delete')")
public CommonResult<Boolean> deleteUserPromptList(@RequestParam("ids") List<Long> ids) {
userPromptService.deleteUserPromptListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得用户提示词")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('ai:user-prompt:query')")
public CommonResult<UserPromptRespVO> getUserPrompt(@RequestParam("id") Long id) {
UserPromptDO userPrompt = userPromptService.getUserPrompt(id);
// 验证是否为当前用户的提示词
if (userPrompt != null && !userPrompt.getUserId().equals(getLoginUserId())) {
userPrompt = null;
}
return success(BeanUtils.toBean(userPrompt, UserPromptRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得用户提示词分页")
@PreAuthorize("@ss.hasPermission('ai:user-prompt:query')")
public CommonResult<PageResult<UserPromptRespVO>> getUserPromptPage(@Valid UserPromptPageReqVO pageReqVO) {
// 设置当前登录用户ID只查询当前用户的提示词
pageReqVO.setUserId(getLoginUserId());
PageResult<UserPromptDO> pageResult = userPromptService.getUserPromptPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, UserPromptRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出用户提示词 Excel")
@PreAuthorize("@ss.hasPermission('ai:user-prompt:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportUserPromptExcel(@Valid UserPromptPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<UserPromptDO> list = userPromptService.getUserPromptPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "用户提示词.xls", "数据", UserPromptRespVO.class,
BeanUtils.toBean(list, UserPromptRespVO.class));
}
}

View File

@@ -44,10 +44,35 @@ public class UserPromptServiceImpl implements UserPromptService {
@Override
public void updateUserPrompt(UserPromptSaveReqVO updateReqVO) {
// 校验存在
validateUserPromptExists(updateReqVO.getId());
// 更新
UserPromptDO updateObj = BeanUtils.toBean(updateReqVO, UserPromptDO.class);
// 1. 手动验证前端表单字段(与前端表单对应)
if (updateReqVO.getName() == null || updateReqVO.getName().trim().isEmpty()) {
throw new IllegalArgumentException("提示词名称不能为空");
}
if (updateReqVO.getContent() == null || updateReqVO.getContent().trim().isEmpty()) {
throw new IllegalArgumentException("提示词内容不能为空");
}
if (updateReqVO.getStatus() == null) {
throw new IllegalArgumentException("状态不能为空");
}
// 2. 校验存在并获取现有记录
UserPromptDO existing = validateUserPromptExists(updateReqVO.getId());
// 3. 手动设置要更新的字段(只更新前端表单中的字段)
UserPromptDO updateObj = new UserPromptDO();
updateObj.setId(updateReqVO.getId());
updateObj.setName(updateReqVO.getName().trim());
updateObj.setContent(updateReqVO.getContent().trim());
updateObj.setCategory(updateReqVO.getCategory() != null ? updateReqVO.getCategory().trim() : null);
updateObj.setStatus(updateReqVO.getStatus());
// 4. 自动填充前端表单中没有的字段(从数据库获取)
updateObj.setSort(existing.getSort());
updateObj.setUseCount(existing.getUseCount());
updateObj.setIsPublic(existing.getIsPublic());
updateObj.setUserId(existing.getUserId()); // 保持用户ID不变
// 5. 执行更新
userPromptMapper.updateById(updateObj);
}
@@ -66,10 +91,12 @@ public class UserPromptServiceImpl implements UserPromptService {
}
private void validateUserPromptExists(Long id) {
if (userPromptMapper.selectById(id) == null) {
private UserPromptDO validateUserPromptExists(Long id) {
UserPromptDO userPrompt = userPromptMapper.selectById(id);
if (userPrompt == null) {
throw exception(USER_PROMPT_NOT_EXISTS);
}
return userPrompt;
}
@Override