From 11bc1959f0586309ab0f7ac876246741ec65270e Mon Sep 17 00:00:00 2001 From: wangzhiwei Date: Tue, 3 Mar 2026 14:55:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(content):=20=E6=B7=BB=E5=8A=A0skill?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E6=94=AF=E4=BB=98=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加GLM大模型配置支持 - 配置生产环境Redis连接信息 - 更新支付回调URL路径配置 - 添加微信和支付宝支付配置到生产环境 - 修改异常处理器捕获BizException - 添加内容详情、需求说明和介绍字段 - 将内容管理重命名为skill管理 - 添加取消收藏功能接口 - 添加用户历史查看、收藏、购买和创建内容列表接口 - 实现用户行为统计和个性化内容推荐功能 - 更新数据库映射文件以支持新字段和查询功能 --- db/alter_payment_order_pay_type.sql | 5 + .../skills/common/LoginUserCacheUtil.java | 170 +++++++++++++ .../com/kexue/skills/config/GlmConfig.java | 73 ++++++ .../controller/CmsContentController.java | 91 ++++++- .../skills/controller/PayController.java | 10 +- .../skills/controller/SkillGenController.java | 34 ++- .../com/kexue/skills/entity/CmsContent.java | 12 +- .../java/com/kexue/skills/entity/CmsTag.java | 3 + .../kexue/skills/entity/dto/CmsTagDto.java | 2 + .../entity/request/GenIntroduceRequest.java | 23 ++ .../entity/request/SkillGenRequest.java | 4 +- .../entity/request/SkillPreGenRequest.java | 9 +- .../skills/entity/request/SkillRequest.java | 65 ++++- .../skills/entity/response/SkillResponse.java | 1 + .../interceptor/BizExceptionAdvice.java | 2 +- .../kexue/skills/mapper/CmsContentMapper.java | 74 ++++++ .../skills/service/CmsContentService.java | 32 +++ .../kexue/skills/service/SkillGenService.java | 14 +- .../kexue/skills/service/SysUserService.java | 8 + .../service/impl/CmsContentServiceImpl.java | 223 ++++++++++++++++-- .../impl/ContentPurchaseServiceImpl.java | 17 ++ .../skills/service/impl/PayServiceImpl.java | 58 ++++- .../service/impl/PaymentOrderServiceImpl.java | 15 +- .../service/impl/SkillGenServiceImpl.java | 166 ++++++++++++- .../service/impl/SysUserServiceImpl.java | 134 ++++++++--- src/main/resources/application-common.yml | 8 +- src/main/resources/application-dev.yml | 9 +- src/main/resources/application-prod.yml | 22 ++ src/main/resources/application.yml | 9 +- src/main/resources/logback-spring.xml | 6 +- .../resources/mapper/CmsContentMapper.xml | 112 ++++++++- src/main/resources/mapper/CmsTagMapper.xml | 16 +- .../resources/mapper/PaymentOrderMapper.xml | 8 +- 33 files changed, 1305 insertions(+), 130 deletions(-) create mode 100644 db/alter_payment_order_pay_type.sql create mode 100644 src/main/java/com/kexue/skills/common/LoginUserCacheUtil.java create mode 100644 src/main/java/com/kexue/skills/config/GlmConfig.java create mode 100644 src/main/java/com/kexue/skills/entity/request/GenIntroduceRequest.java diff --git a/db/alter_payment_order_pay_type.sql b/db/alter_payment_order_pay_type.sql new file mode 100644 index 0000000..1008035 --- /dev/null +++ b/db/alter_payment_order_pay_type.sql @@ -0,0 +1,5 @@ +-- 修改payment_order表,为pay_type字段添加默认值 +ALTER TABLE `payment_order` MODIFY COLUMN `pay_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '支付方式:1.微信 2.支付宝'; + +-- 输出修改结果 +SELECT '修改payment_order表pay_type字段默认值完成' AS result; \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/common/LoginUserCacheUtil.java b/src/main/java/com/kexue/skills/common/LoginUserCacheUtil.java new file mode 100644 index 0000000..68e33eb --- /dev/null +++ b/src/main/java/com/kexue/skills/common/LoginUserCacheUtil.java @@ -0,0 +1,170 @@ +package com.kexue.skills.common; + +import com.kexue.skills.entity.request.LoginUser; +import com.kexue.skills.mapper.*; +import jakarta.servlet.http.HttpServletRequest; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * LoginUser缓存工具类 + * 用于更新Redis中的LoginUser对象 + */ +@Component +public class LoginUserCacheUtil { + + @Resource + private RedissonClient redissonClient; + + @Resource + private CmsContentLikeMapper cmsContentLikeMapper; + + @Resource + private CmsContentViewMapper cmsContentViewMapper; + + @Resource + private CmsContentMapper cmsContentMapper; + + @Resource + private ContentPurchaseMapper contentPurchaseMapper; + + /** + * 更新用户的收藏列表 + * + * @param token 用户token + * @param userId 用户ID + */ + public void updateFavorites(String token, Long userId) { + updateLoginUserList(token, userId, "favorites", () -> cmsContentLikeMapper.queryRecentLikesByUserId(userId, 20)); + } + + /** + * 更新用户的查看历史列表 + * + * @param token 用户token + * @param userId 用户ID + */ + public void updateHistory(String token, Long userId) { + updateLoginUserList(token, userId, "history", () -> cmsContentViewMapper.queryRecentViewsByUserId(userId, 20)); + } + + /** + * 更新用户的创建记录列表 + * + * @param token 用户token + * @param userId 用户ID + */ + public void updateCreate(String token, Long userId) { + updateLoginUserList(token, userId, "create", () -> cmsContentMapper.queryRecentCreatedByUserId(userId, 20)); + } + + /** + * 更新用户的购买记录列表 + * + * @param token 用户token + * @param userId 用户ID + */ + public void updateHas(String token, Long userId) { + updateLoginUserList(token, userId, "has", () -> { + List has = new ArrayList<>(); + try { + com.kexue.skills.entity.dto.ContentPurchaseDto purchaseDto = new com.kexue.skills.entity.dto.ContentPurchaseDto(); + purchaseDto.setUserId(userId); + List purchases = contentPurchaseMapper.getList(purchaseDto); + if (purchases != null && !purchases.isEmpty()) { + java.util.LinkedHashSet contentIdSet = new java.util.LinkedHashSet<>(); + for (com.kexue.skills.entity.ContentPurchase purchase : purchases) { + if (contentIdSet.size() < 20) { + contentIdSet.add(purchase.getContentId()); + } else { + break; + } + } + has.addAll(contentIdSet); + } + } catch (Exception e) { + e.printStackTrace(); + has = java.util.Collections.emptyList(); + } + return has; + }); + } + + /** + * 更新LoginUser中的列表属性 + * + * @param token 用户token + * @param userId 用户ID + * @param fieldName 字段名 + * @param supplier 列表数据提供者 + */ + private void updateLoginUserList(String token, Long userId, String fieldName, java.util.function.Supplier> supplier) { + try { + String key = "loginUser:" + token; + Object loginUserObj = redissonClient.getBucket(key).get(); + if (loginUserObj != null) { + String loginUserJson = loginUserObj.toString(); + com.kexue.skills.entity.request.LoginUser loginUser = com.alibaba.fastjson.JSON.parseObject(loginUserJson, com.kexue.skills.entity.request.LoginUser.class); + + if (loginUser != null && loginUser.getUserInfo() != null && loginUser.getUserInfo().getUserId().equals(userId)) { + List list = supplier.get(); + + switch (fieldName) { + case "favorites": + loginUser.setFavorites(list); + break; + case "history": + loginUser.setHistory(list); + break; + case "create": + loginUser.setCreate(list); + break; + case "has": + loginUser.setHas(list); + break; + } + + // 写回Redis + redissonClient.getBucket(key).set(com.alibaba.fastjson.JSON.toJSONString(loginUser)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 从请求中获取token + * + * @return token + */ + public String getTokenFromRequest() { + org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null && requestAttributes instanceof org.springframework.web.context.request.ServletRequestAttributes) { + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + if (request != null) { + return request.getHeader("Authorization"); + } + } + return null; + } + + /** + * 获取当前登录用户ID + * + * @return 用户ID + */ + public Long getCurrentUserId() { + try { + Object loginId = cn.dev33.satoken.stp.StpUtil.getLoginId(); + return Long.parseLong(loginId.toString()); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/com/kexue/skills/config/GlmConfig.java b/src/main/java/com/kexue/skills/config/GlmConfig.java new file mode 100644 index 0000000..0d04f8b --- /dev/null +++ b/src/main/java/com/kexue/skills/config/GlmConfig.java @@ -0,0 +1,73 @@ +package com.kexue.skills.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * GLM API配置类 + * + * @author 维哥 + * @since 2026-02-27 + */ +@Component +@ConfigurationProperties(prefix = "spring.ai.glm") +public class GlmConfig { + + private String baseUrl; + private String apiKey; + private ChatOptions chat; + + public static class ChatOptions { + private String model; + private Double temperature; + private Integer maxTokens; + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Integer getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; + } + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public ChatOptions getChat() { + return chat; + } + + public void setChat(ChatOptions chat) { + this.chat = chat; + } +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/controller/CmsContentController.java b/src/main/java/com/kexue/skills/controller/CmsContentController.java index 3332795..0e7f3d2 100644 --- a/src/main/java/com/kexue/skills/controller/CmsContentController.java +++ b/src/main/java/com/kexue/skills/controller/CmsContentController.java @@ -21,7 +21,7 @@ import javax.annotation.Resource; */ @RestController @RequestMapping("api/cmsContent") -@Tag(name = "内容(skills)管理 Api") +@Tag(name = "skill(skills)管理 Api") @CrossOrigin(origins = "*") public class CmsContentController { /** @@ -60,7 +60,7 @@ public class CmsContentController { * @return 单条数据 */ @PostMapping("queryById/{contentId}") - @Operation(summary = "通过ID查询内容", description = "通过ID查询内容") + @Operation(summary = "通过ID查询skill", description = "通过ID查询skill") public CommonResult queryById(@PathVariable("contentId") Long contentId) { // 增加阅读量 cmsContentService.increaseViewCount(contentId); @@ -74,7 +74,7 @@ public class CmsContentController { * @return 新增结果 */ @PostMapping("/insert") - @Operation(summary = "新增内容", description = "新增内容") + @Operation(summary = "新增skill", description = "新增skill") @RequireAuth public CommonResult insert(@RequestBody CmsContent cmsContent) { return CommonResult.success(cmsContentService.insert(cmsContent)); @@ -87,7 +87,7 @@ public class CmsContentController { * @return 编辑结果 */ @PostMapping("/update") - @Operation(summary = "更新内容", description = "更新内容") + @Operation(summary = "更新skill", description = "更新skill") @RequireAuth public CommonResult update(@RequestBody CmsContent cmsContent) { return CommonResult.success(cmsContentService.update(cmsContent)); @@ -96,7 +96,7 @@ public class CmsContentController { /** * 更新审核状态 * - * @param contentId 内容ID + * @param contentId skillID * @param auditStatus 审核状态 * @param reviewerId 审核人ID * @param reviewerName 审核人名称 @@ -117,7 +117,7 @@ public class CmsContentController { /** * 更新发布状态 * - * @param contentId 内容ID + * @param contentId skillID * @param publishStatus 发布状态 * @param publishTime 发布时间 * @param updateBy 更新人 @@ -136,7 +136,7 @@ public class CmsContentController { /** * 增加阅读量 * - * @param contentId 内容ID + * @param contentId skillID * @return 增加结果 */ @PostMapping("/increaseViewCount/{contentId}") @@ -152,7 +152,7 @@ public class CmsContentController { * @return 删除数据 */ @PostMapping("/logicDeleteById") - @Operation(summary = "逻辑删除内容", description = "逻辑删除内容") + @Operation(summary = "逻辑删除skill", description = "逻辑删除skill") @RequireAuth public CommonResult logicDeleteById(@RequestBody IdDto idDto) { return CommonResult.success(cmsContentService.logicDeleteById(idDto.getId(), "admin") > 0); @@ -165,7 +165,7 @@ public class CmsContentController { * @return 删除数据 */ @PostMapping("deleteById/{contentId}") - @Operation(summary = "物理删除内容", description = "物理删除内容") + @Operation(summary = "物理删除skill", description = "物理删除skill") @RequireAuth public CommonResult deleteById(@PathVariable("contentId") Long contentId) { return CommonResult.success(cmsContentService.deleteById(contentId) > 0); @@ -174,26 +174,91 @@ public class CmsContentController { /** * 添加收藏 * - * @param contentId 内容ID + * @param contentId skillID * @return 操作结果 */ @PostMapping("/addFavorite") - @Operation(summary = "添加收藏", description = "添加内容收藏") + @Operation(summary = "添加收藏", description = "添加skill收藏") @RequireAuth public CommonResult addFavorite(@RequestParam("contentId") Long contentId) { return CommonResult.success(cmsContentService.addFavorite(contentId) > 0); } + /** + * 取消收藏 + * + * @param contentId skillID + * @return 操作结果 + */ + @PostMapping("/removeFavorite") + @Operation(summary = "取消收藏", description = "取消skill收藏") + @RequireAuth + public CommonResult removeFavorite(@RequestParam("contentId") Long contentId) { + return CommonResult.success(cmsContentService.removeFavorite(contentId) > 0); + } + /** * 添加查看记录 * - * @param contentId 内容ID + * @param contentId skillID * @return 操作结果 */ @PostMapping("/addView") - @Operation(summary = "添加查看记录", description = "添加内容查看记录") + @Operation(summary = "添加查看记录", description = "添加skill查看记录") @RequireAuth public CommonResult addView(@RequestParam("contentId") Long contentId) { return CommonResult.success(cmsContentService.addView(contentId) > 0); } + + /** + * 获取用户历史查看的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + @PostMapping("/getUserHistory") + @Operation(summary = "获取用户历史查看", description = "获取当前用户历史查看的内容列表,带分页") + @RequireAuth + public CommonResult> getUserHistory(@RequestBody CmsContentDto queryDto) { + return CommonResult.success(cmsContentService.getPageListByUserHistory(queryDto)); + } + + /** + * 获取用户收藏的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + @PostMapping("/getUserFavorites") + @Operation(summary = "获取用户收藏", description = "获取当前用户收藏的内容列表,带分页") + @RequireAuth + public CommonResult> getUserFavorites(@RequestBody CmsContentDto queryDto) { + return CommonResult.success(cmsContentService.getPageListByUserFavorites(queryDto)); + } + + /** + * 获取用户购买的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + @PostMapping("/getUserPurchases") + @Operation(summary = "获取用户拥有", description = "获取当前用户购买的内容列表,带分页") + @RequireAuth + public CommonResult> getUserPurchases(@RequestBody CmsContentDto queryDto) { + return CommonResult.success(cmsContentService.getPageListByUserPurchases(queryDto)); + } + + /** + * 获取用户创建的内容列表 + * + * @param queryDto 筛选条件,包含分页信息和发布状态 + * @return 查询结果 + */ + @PostMapping("/getUserCreated") + @Operation(summary = "获取用户创建", description = "获取当前用户创建的内容列表,带分页,可查询已发布、未发布") + @RequireAuth + public CommonResult> getUserCreated(@RequestBody CmsContentDto queryDto) { + return CommonResult.success(cmsContentService.getPageListByUserCreated(queryDto)); + } } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/controller/PayController.java b/src/main/java/com/kexue/skills/controller/PayController.java index c64ef28..9ab4f2d 100644 --- a/src/main/java/com/kexue/skills/controller/PayController.java +++ b/src/main/java/com/kexue/skills/controller/PayController.java @@ -39,6 +39,8 @@ public class PayController { @PostMapping("/wx/create") public CommonResult> createWechatPay(@RequestBody PaymentOrder order) { try { + // 设置支付类型为微信支付(1) + order.setPayType(1); // 创建支付订单 PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order); // 生成微信支付参数 @@ -56,7 +58,7 @@ public class PayController { * @return 回调响应 */ @Operation(summary = "处理微信支付回调", description = "处理微信支付回调") - @PostMapping("/pay/wx/notify") + @PostMapping("/wx/notify") public String handleWechatPayNotify(HttpServletRequest request) { return payService.handleWechatPayNotify(request); } @@ -70,6 +72,8 @@ public class PayController { @PostMapping("/alipay/create") public CommonResult createAlipay(@RequestBody PaymentOrder order) { try { + // 设置支付类型为支付宝(2) + order.setPayType(2); // 创建支付订单 PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order); // 生成支付宝支付表单 @@ -87,7 +91,7 @@ public class PayController { * @return 回调响应 */ @Operation(summary = "处理支付宝支付回调", description = "处理支付宝支付回调") - @PostMapping("/ali-pay/trade/notify") + @PostMapping("/alipay/trade/notify") public String handleAlipayNotify(HttpServletRequest request) { return payService.handleAlipayNotify(request); } @@ -98,7 +102,7 @@ public class PayController { * @return 同步回调响应 */ @Operation(summary = "处理支付宝支付同步回调", description = "处理支付宝支付同步回调") - @GetMapping("/ali-pay/trade/return") + @GetMapping("/alipay/trade/return") public CommonResult> handleAlipayReturn(HttpServletRequest request) { Map result = payService.handleAlipayReturn(request); if (result.get("success").equals(true)) { diff --git a/src/main/java/com/kexue/skills/controller/SkillGenController.java b/src/main/java/com/kexue/skills/controller/SkillGenController.java index 33d69b6..b6acdc8 100644 --- a/src/main/java/com/kexue/skills/controller/SkillGenController.java +++ b/src/main/java/com/kexue/skills/controller/SkillGenController.java @@ -2,6 +2,8 @@ package com.kexue.skills.controller; import com.alibaba.fastjson.JSONObject; import com.kexue.skills.common.CommonResult; +import com.kexue.skills.entity.CmsContent; +import com.kexue.skills.entity.request.GenIntroduceRequest; import com.kexue.skills.entity.request.SkillGenRequest; import com.kexue.skills.entity.request.SkillPreGenRequest; import com.kexue.skills.entity.response.SkillResponse; @@ -32,14 +34,26 @@ public class SkillGenController { * @return 生成结果 */ @PostMapping("/preGenerate") - @Operation(summary = "生成技能", description = "生成技能") + @Operation(summary = "预生成技能", description = "生成技能") public CommonResult preGenerate(@RequestBody SkillPreGenRequest request) { - return CommonResult.success(skillGenService.preGenerate(request)); + return CommonResult.success(skillGenService.preGenerateV2(request)); + } + + /** + * 生成技能V2 + * + * @param request 生成请求 + * @return 生成结果 + */ + @PostMapping("/preGenerateV2") + @Operation(summary = "预生成技能V2", description = "使用新模型生成技能") + public CommonResult preGenerateV2(@RequestBody SkillPreGenRequest request) { + return CommonResult.success(skillGenService.preGenerateV2(request)); } @PostMapping("/generate") - @Operation(summary = "生成Skill对应的md文件", description = "生成Skill对应的md文件") - public CommonResult generate(@RequestBody SkillGenRequest request) { + @Operation(summary = "生成技能", description = "生成技能") + public CommonResult generate(@RequestBody SkillGenRequest request) { return CommonResult.success(skillGenService.generate(request)); } @@ -54,4 +68,16 @@ public class SkillGenController { public CommonResult analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest request) { return CommonResult.success(skillGenService.analyzeSkill(request)); } + + /** + * 生成技能介绍 + * + * @param request 技能内容 + * @return 技能介绍 + */ + @PostMapping("/genIntroduce") + @Operation(summary = "生成技能介绍", description = "生成技能介绍") + public CommonResult genIntroduce(@RequestBody GenIntroduceRequest request) { + return CommonResult.success(skillGenService.genIntroduce(request.getContent())); + } } diff --git a/src/main/java/com/kexue/skills/entity/CmsContent.java b/src/main/java/com/kexue/skills/entity/CmsContent.java index f95aef9..90dac17 100644 --- a/src/main/java/com/kexue/skills/entity/CmsContent.java +++ b/src/main/java/com/kexue/skills/entity/CmsContent.java @@ -30,9 +30,6 @@ public class CmsContent extends BaseEntity implements Serializable { @Schema(description ="是否是官方:0否,1是") private Boolean isOfficial; - @Schema(description ="分类ID列表,逗号分隔") - private String categoryIds; - @Schema(description ="图标") private String icon; @@ -45,6 +42,15 @@ public class CmsContent extends BaseEntity implements Serializable { @Schema(description ="内容摘要") private String summary; + @Schema(description ="详细描述") + private String description; + + @Schema(description ="需求说明") + private String requirement; + + @Schema(description ="介绍信息") + private String introduce; + @Schema(description ="分享数量") private Integer shareCount; diff --git a/src/main/java/com/kexue/skills/entity/CmsTag.java b/src/main/java/com/kexue/skills/entity/CmsTag.java index d84aa9b..8bfd096 100644 --- a/src/main/java/com/kexue/skills/entity/CmsTag.java +++ b/src/main/java/com/kexue/skills/entity/CmsTag.java @@ -33,6 +33,9 @@ public class CmsTag extends BaseEntity implements Serializable { @Schema(description ="状态(1启用,2禁用)") private Integer status; + @Schema(description ="标签图标") + private String icon; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Schema(description ="创建时间") private Date createTime; diff --git a/src/main/java/com/kexue/skills/entity/dto/CmsTagDto.java b/src/main/java/com/kexue/skills/entity/dto/CmsTagDto.java index d48f123..0f672cf 100644 --- a/src/main/java/com/kexue/skills/entity/dto/CmsTagDto.java +++ b/src/main/java/com/kexue/skills/entity/dto/CmsTagDto.java @@ -20,6 +20,8 @@ public class CmsTagDto extends BaseQueryDto { private Integer status; + private String icon; + private Integer deleteFlag; } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/request/GenIntroduceRequest.java b/src/main/java/com/kexue/skills/entity/request/GenIntroduceRequest.java new file mode 100644 index 0000000..b9829de --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/GenIntroduceRequest.java @@ -0,0 +1,23 @@ +package com.kexue.skills.entity.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 技能生成请求参数 + * + * @author 维哥 + * @since 2026-01-28 + */ +@Data +@ApiModel(value = "功能介绍生成请求参数") +public class GenIntroduceRequest implements Serializable { + + @Schema(description = "yaml或者skill.md内容", required = true) + private String content; + +} diff --git a/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java b/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java index 5c90734..8025d40 100644 --- a/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java +++ b/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java @@ -16,6 +16,6 @@ public class SkillGenRequest implements Serializable { private String description; @Schema(description = "技能标签") private List tags; - @Schema(description = "技能内容ID(参照模板ID)") - private String contentId; + @Schema(description = "需求说明") + private String requirement; } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/request/SkillPreGenRequest.java b/src/main/java/com/kexue/skills/entity/request/SkillPreGenRequest.java index 2fbe772..f8bd1d9 100644 --- a/src/main/java/com/kexue/skills/entity/request/SkillPreGenRequest.java +++ b/src/main/java/com/kexue/skills/entity/request/SkillPreGenRequest.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; +import java.util.List; /** * 技能生成请求参数 @@ -19,7 +20,11 @@ public class SkillPreGenRequest implements Serializable { @Schema(description = "用户提示词") private String prompt; - @Schema(description = "模板id") - private Long contentId; + @Schema(description = "文件地址") + private String fileUrl; + + @Schema(description = "文件地址列表") + private List fileUrls; + } diff --git a/src/main/java/com/kexue/skills/entity/request/SkillRequest.java b/src/main/java/com/kexue/skills/entity/request/SkillRequest.java index 4394997..5e04a7c 100644 --- a/src/main/java/com/kexue/skills/entity/request/SkillRequest.java +++ b/src/main/java/com/kexue/skills/entity/request/SkillRequest.java @@ -89,6 +89,49 @@ public class SkillRequest implements Serializable { } } + // 新的构造方法,支持文件URL列表 + public SkillRequest(String model, String systemContent, String prompt, List fileUrls, double temperature, int maxTokens) { + this.model = model; + this.messages = new ArrayList<>(); + + Message systemMessage = new Message(); + systemMessage.setRole("system"); + systemMessage.setContent(systemContent); + this.messages.add(systemMessage); + + // 构建包含文件URL的用户消息 + List messageContents = new ArrayList<>(); + + // 添加文件URL + if (fileUrls != null && !fileUrls.isEmpty()) { + for (String fileUrl : fileUrls) { + MessageContent fileContent = new MessageContent(); + fileContent.setType("file_url"); + FileUrl fileUrlObj = new FileUrl(); + fileUrlObj.setUrl(fileUrl); + fileContent.setFile_url(fileUrlObj); + messageContents.add(fileContent); + } + } + + // 添加文本内容 + MessageContent textContent = new MessageContent(); + textContent.setType("text"); + textContent.setText(prompt); + messageContents.add(textContent); + + Message userMessage = new Message(); + userMessage.setRole("user"); + userMessage.setContent(messageContents); + this.messages.add(userMessage); + + this.temperature = temperature; + this.max_tokens = maxTokens; + + this.response_format = new ResponseFormat(); + this.response_format.setType("json_object"); + } + public static SkillRequest createDefault() { return new SkillRequest(true); } @@ -100,7 +143,27 @@ public class SkillRequest implements Serializable { private static final long serialVersionUID = 1L; private String role; - private String content; + private Object content; // 可以是String或List + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class MessageContent implements Serializable { + private static final long serialVersionUID = 1L; + + private String type; + private String text; + private FileUrl file_url; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class FileUrl implements Serializable { + private static final long serialVersionUID = 1L; + + private String url; } @Data diff --git a/src/main/java/com/kexue/skills/entity/response/SkillResponse.java b/src/main/java/com/kexue/skills/entity/response/SkillResponse.java index d9f55dc..7b348e7 100644 --- a/src/main/java/com/kexue/skills/entity/response/SkillResponse.java +++ b/src/main/java/com/kexue/skills/entity/response/SkillResponse.java @@ -12,4 +12,5 @@ public class SkillResponse implements Serializable { private String name; private String description; private List tags; + private String summary; } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/interceptor/BizExceptionAdvice.java b/src/main/java/com/kexue/skills/interceptor/BizExceptionAdvice.java index 7cc8698..0a051d7 100644 --- a/src/main/java/com/kexue/skills/interceptor/BizExceptionAdvice.java +++ b/src/main/java/com/kexue/skills/interceptor/BizExceptionAdvice.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.ResponseBody; @Slf4j @ControllerAdvice public class BizExceptionAdvice { - @ExceptionHandler(value = RuntimeException.class) + @ExceptionHandler(value = BizException.class) @ResponseBody public CommonResult handleException(BizException e){ e.printStackTrace(); diff --git a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java index b5b73d6..1e73a3e 100644 --- a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java +++ b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java @@ -135,4 +135,78 @@ public interface CmsContentMapper { * @return 内容ID列表 */ List queryRecentCreatedByUserId(@Param("userId") Long userId, @Param("limit") int limit); + + /** + * 获取用户历史查看的内容列表 + * + * @param userId 用户ID + * @param offset 偏移量 + * @param limit 限制数量 + * @return 查询结果 + */ + List getPageListByUserHistory(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit); + + /** + * 获取用户历史查看的内容总数 + * + * @param userId 用户ID + * @return 总记录数 + */ + int getPageListByUserHistoryCount(@Param("userId") Long userId); + + /** + * 获取用户收藏的内容列表 + * + * @param userId 用户ID + * @param offset 偏移量 + * @param limit 限制数量 + * @return 查询结果 + */ + List getPageListByUserFavorites(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit); + + /** + * 获取用户收藏的内容总数 + * + * @param userId 用户ID + * @return 总记录数 + */ + int getPageListByUserFavoritesCount(@Param("userId") Long userId); + + /** + * 获取用户购买的内容列表 + * + * @param userId 用户ID + * @param offset 偏移量 + * @param limit 限制数量 + * @return 查询结果 + */ + List getPageListByUserPurchases(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit); + + /** + * 获取用户购买的内容总数 + * + * @param userId 用户ID + * @return 总记录数 + */ + int getPageListByUserPurchasesCount(@Param("userId") Long userId); + + /** + * 获取用户创建的内容列表 + * + * @param userId 用户ID + * @param publishStatus 发布状态 + * @param offset 偏移量 + * @param limit 限制数量 + * @return 查询结果 + */ + List getPageListByUserCreated(@Param("userId") Long userId, @Param("publishStatus") Integer publishStatus, @Param("offset") int offset, @Param("limit") int limit); + + /** + * 获取用户创建的内容总数 + * + * @param userId 用户ID + * @param publishStatus 发布状态 + * @return 总记录数 + */ + int getPageListByUserCreatedCount(@Param("userId") Long userId, @Param("publishStatus") Integer publishStatus); } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/CmsContentService.java b/src/main/java/com/kexue/skills/service/CmsContentService.java index 20f81d0..cbd1c8a 100644 --- a/src/main/java/com/kexue/skills/service/CmsContentService.java +++ b/src/main/java/com/kexue/skills/service/CmsContentService.java @@ -133,4 +133,36 @@ public interface CmsContentService extends BaseService { * @return 影响行数 */ int addView(Long contentId); + + /** + * 获取用户历史查看的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + PageInfo getPageListByUserHistory(CmsContentDto queryDto); + + /** + * 获取用户收藏的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + PageInfo getPageListByUserFavorites(CmsContentDto queryDto); + + /** + * 获取用户购买的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + PageInfo getPageListByUserPurchases(CmsContentDto queryDto); + + /** + * 获取用户创建的内容列表 + * + * @param queryDto 筛选条件,包含分页信息 + * @return 查询结果 + */ + PageInfo getPageListByUserCreated(CmsContentDto queryDto); } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/SkillGenService.java b/src/main/java/com/kexue/skills/service/SkillGenService.java index d1e9262..95769c5 100644 --- a/src/main/java/com/kexue/skills/service/SkillGenService.java +++ b/src/main/java/com/kexue/skills/service/SkillGenService.java @@ -1,6 +1,7 @@ package com.kexue.skills.service; import com.alibaba.fastjson.JSONObject; +import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.request.SkillGenRequest; import com.kexue.skills.entity.request.SkillPreGenRequest; import com.kexue.skills.entity.request.SkillAnalyzeRequest; @@ -22,7 +23,10 @@ public interface SkillGenService { */ SkillResponse preGenerate(SkillPreGenRequest request); - JSONObject generate(SkillGenRequest request); + SkillResponse preGenerateV2(SkillPreGenRequest request); + + CmsContent generate(SkillGenRequest request); + /** * 分析技能 @@ -31,4 +35,12 @@ public interface SkillGenService { * @return 分析结果 */ String analyzeSkill(SkillAnalyzeRequest request); + + /** + * 生成技能介绍 + * + * @param content 技能内容 + * @return 技能介绍 + */ + String genIntroduce(String content); } diff --git a/src/main/java/com/kexue/skills/service/SysUserService.java b/src/main/java/com/kexue/skills/service/SysUserService.java index 090c2d4..39ba528 100644 --- a/src/main/java/com/kexue/skills/service/SysUserService.java +++ b/src/main/java/com/kexue/skills/service/SysUserService.java @@ -110,4 +110,12 @@ public interface SysUserService extends BaseService { * @return 登录结果 */ LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto); + + /** + * 根据用户名或手机号查询用户 + * + * @param usernameOrPhone 用户名或手机号 + * @return 用户对象 + */ + SysUser getUserByUsernameOrPhone(String usernameOrPhone); } diff --git a/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java index af0f85d..286d957 100644 --- a/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java @@ -2,6 +2,7 @@ package com.kexue.skills.service.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; +import com.kexue.skills.common.LoginUserCacheUtil; import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.CmsContentView; import com.kexue.skills.entity.CmsContentLike; @@ -37,6 +38,9 @@ public class CmsContentServiceImpl implements CmsContentService { @Resource private CmsContentLikeMapper cmsContentLikeMapper; + + @Resource + private LoginUserCacheUtil loginUserCacheUtil; /** * 分页查询 @@ -54,17 +58,10 @@ public class CmsContentServiceImpl implements CmsContentService { queryDto.setPageSize(BaseQueryDto.DEFAULT_PAGE_SIZE); } - // 打印分页参数 - System.out.println("PageNum: " + queryDto.getPageNum() + ", PageSize: " + queryDto.getPageSize()); - // 使用PageHelper进行分页 PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize()); List list = this.cmsContentMapper.getPageList(queryDto); PageInfo pageInfo = new PageInfo<>(list); - - // 打印分页结果 - System.out.println("Total: " + pageInfo.getTotal() + ", PageSize: " + pageInfo.getPageSize() + ", List Size: " + list.size()); - return pageInfo; } @@ -238,7 +235,19 @@ public class CmsContentServiceImpl implements CmsContentService { */ @Override public int increaseViewCount(Long contentId) { - return this.cmsContentMapper.increaseViewCount(contentId); + // 增加阅读量 + int result = this.cmsContentMapper.increaseViewCount(contentId); + + try { + // 尝试获取当前登录用户ID + Long userId = Long.parseLong(StpUtil.getLoginId().toString()); + // 调用addView方法添加查看记录 + this.addView(contentId); + } catch (Exception e) { + // 未登录用户,不记录查看历史 + } + + return result; } /** @@ -265,7 +274,7 @@ public class CmsContentServiceImpl implements CmsContentService { } /** - * 添加收藏 + * 添加/取消收藏 * * @param contentId 内容ID * @return 影响行数 @@ -285,24 +294,43 @@ public class CmsContentServiceImpl implements CmsContentService { // 检查用户是否已经收藏过该内容 CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId); + int result = 0; + if (existingLike != null) { - return 0; // 已经收藏过,不允许再次收藏 + // 已经收藏过,执行取消收藏操作 + result = cmsContentLikeMapper.deleteById(existingLike.getLikeId()); + + // 减少内容的点赞数 + if (content.getLikeCount() > 0) { + content.setLikeCount(content.getLikeCount() - 1); + this.cmsContentMapper.update(content); + } + } else { + // 未收藏过,执行添加收藏操作 + CmsContentLike likeRecord = new CmsContentLike(); + likeRecord.setUserId(userId); + likeRecord.setUserName(userName); + likeRecord.setContentId(contentId); + likeRecord.setContentTitle(content.getTitle()); + likeRecord.setLikeTime(new Date()); + likeRecord.setDeleteFlag(0); + + // 增加内容的点赞数 + content.setLikeCount(content.getLikeCount() + 1); + this.cmsContentMapper.update(content); + + result = cmsContentLikeMapper.insert(likeRecord); } - // 新增收藏记录 - CmsContentLike likeRecord = new CmsContentLike(); - likeRecord.setUserId(userId); - likeRecord.setUserName(userName); - likeRecord.setContentId(contentId); - likeRecord.setContentTitle(content.getTitle()); - likeRecord.setLikeTime(new Date()); - likeRecord.setDeleteFlag(0); + // 更新Redis中的LoginUser对象 + if (result > 0) { + String token = loginUserCacheUtil.getTokenFromRequest(); + if (token != null) { + loginUserCacheUtil.updateFavorites(token, userId); + } + } - // 增加内容的点赞数 - content.setLikeCount(content.getLikeCount() + 1); - this.cmsContentMapper.update(content); - - return cmsContentLikeMapper.insert(likeRecord); + return result; } /** @@ -332,6 +360,14 @@ public class CmsContentServiceImpl implements CmsContentService { this.cmsContentMapper.update(content); } + // 更新Redis中的LoginUser对象 + if (result > 0) { + String token = loginUserCacheUtil.getTokenFromRequest(); + if (token != null) { + loginUserCacheUtil.updateFavorites(token, userId); + } + } + return result; } @@ -376,6 +412,7 @@ public class CmsContentServiceImpl implements CmsContentService { // 检查用户是否已经查看过该内容(5分钟内) CmsContentView existingView = cmsContentViewMapper.queryByUserIdAndContentId(userId, contentId); + int result = 0; if (existingView == null) { // 新增查看记录 CmsContentView viewRecord = new CmsContentView(); @@ -387,7 +424,7 @@ public class CmsContentServiceImpl implements CmsContentService { viewRecord.setDeleteFlag(0); viewRecord.setCreateBy(userName); viewRecord.setUpdateBy(userName); - return cmsContentViewMapper.insert(viewRecord); + result = cmsContentViewMapper.insert(viewRecord); } else { // 检查是否超过5分钟 Date fiveMinutesAgo = new Date(System.currentTimeMillis() - 5 * 60 * 1000); @@ -395,13 +432,147 @@ public class CmsContentServiceImpl implements CmsContentService { // 更新查看时间 existingView.setViewTime(new Date()); existingView.setUpdateBy(userName); - return cmsContentViewMapper.update(existingView); + result = cmsContentViewMapper.update(existingView); } - return 0; // 5分钟内已查看过,不重复记录 + // 5分钟内已查看过,不重复记录 } + + // 更新Redis中的LoginUser对象 + if (result > 0) { + String token = loginUserCacheUtil.getTokenFromRequest(); + if (token != null) { + loginUserCacheUtil.updateHistory(token, userId); + } + } + + return result; } catch (Exception e) { // 未登录用户,不记录查看历史 return 0; } } + + @Override + public PageInfo getPageListByUserHistory(CmsContentDto queryDto) { + // 获取当前登录用户ID + Long userId = Long.parseLong(StpUtil.getLoginId().toString()); + + // 添加参数校验,确保分页参数有效 + if (queryDto.getPageNum() == null || queryDto.getPageNum() < 1) { + queryDto.setPageNum(BaseQueryDto.DEFAULT_CURRENT_PAGE); + } + if (queryDto.getPageSize() == null || queryDto.getPageSize() < 1) { + queryDto.setPageSize(BaseQueryDto.DEFAULT_PAGE_SIZE); + } + + // 计算偏移量 + int offset = (queryDto.getPageNum() - 1) * queryDto.getPageSize(); + int limit = queryDto.getPageSize(); + + // 查询数据 + List list = this.cmsContentMapper.getPageListByUserHistory(userId, offset, limit); + int total = this.cmsContentMapper.getPageListByUserHistoryCount(userId); + + // 构建分页结果 + PageInfo pageInfo = new PageInfo<>(list); + pageInfo.setTotal(total); + pageInfo.setPageNum(queryDto.getPageNum()); + pageInfo.setPageSize(queryDto.getPageSize()); + pageInfo.setPages((total + limit - 1) / limit); + + return pageInfo; + } + + @Override + public PageInfo getPageListByUserFavorites(CmsContentDto queryDto) { + // 获取当前登录用户ID + Long userId = Long.parseLong(StpUtil.getLoginId().toString()); + + // 添加参数校验,确保分页参数有效 + if (queryDto.getPageNum() == null || queryDto.getPageNum() < 1) { + queryDto.setPageNum(BaseQueryDto.DEFAULT_CURRENT_PAGE); + } + if (queryDto.getPageSize() == null || queryDto.getPageSize() < 1) { + queryDto.setPageSize(BaseQueryDto.DEFAULT_PAGE_SIZE); + } + + // 计算偏移量 + int offset = (queryDto.getPageNum() - 1) * queryDto.getPageSize(); + int limit = queryDto.getPageSize(); + + // 查询数据 + List list = this.cmsContentMapper.getPageListByUserFavorites(userId, offset, limit); + int total = this.cmsContentMapper.getPageListByUserFavoritesCount(userId); + + // 构建分页结果 + PageInfo pageInfo = new PageInfo<>(list); + pageInfo.setTotal(total); + pageInfo.setPageNum(queryDto.getPageNum()); + pageInfo.setPageSize(queryDto.getPageSize()); + pageInfo.setPages((total + limit - 1) / limit); + + return pageInfo; + } + + @Override + public PageInfo getPageListByUserPurchases(CmsContentDto queryDto) { + // 获取当前登录用户ID + Long userId = Long.parseLong(StpUtil.getLoginId().toString()); + + // 添加参数校验,确保分页参数有效 + if (queryDto.getPageNum() == null || queryDto.getPageNum() < 1) { + queryDto.setPageNum(BaseQueryDto.DEFAULT_CURRENT_PAGE); + } + if (queryDto.getPageSize() == null || queryDto.getPageSize() < 1) { + queryDto.setPageSize(BaseQueryDto.DEFAULT_PAGE_SIZE); + } + + // 计算偏移量 + int offset = (queryDto.getPageNum() - 1) * queryDto.getPageSize(); + int limit = queryDto.getPageSize(); + + // 查询数据 + List list = this.cmsContentMapper.getPageListByUserPurchases(userId, offset, limit); + int total = this.cmsContentMapper.getPageListByUserPurchasesCount(userId); + + // 构建分页结果 + PageInfo pageInfo = new PageInfo<>(list); + pageInfo.setTotal(total); + pageInfo.setPageNum(queryDto.getPageNum()); + pageInfo.setPageSize(queryDto.getPageSize()); + pageInfo.setPages((total + limit - 1) / limit); + + return pageInfo; + } + + @Override + public PageInfo getPageListByUserCreated(CmsContentDto queryDto) { + // 获取当前登录用户ID + Long userId = Long.parseLong(StpUtil.getLoginId().toString()); + + // 添加参数校验,确保分页参数有效 + if (queryDto.getPageNum() == null || queryDto.getPageNum() < 1) { + queryDto.setPageNum(BaseQueryDto.DEFAULT_CURRENT_PAGE); + } + if (queryDto.getPageSize() == null || queryDto.getPageSize() < 1) { + queryDto.setPageSize(BaseQueryDto.DEFAULT_PAGE_SIZE); + } + + // 计算偏移量 + int offset = (queryDto.getPageNum() - 1) * queryDto.getPageSize(); + int limit = queryDto.getPageSize(); + + // 查询数据 + List list = this.cmsContentMapper.getPageListByUserCreated(userId, queryDto.getPublishStatus(), offset, limit); + int total = this.cmsContentMapper.getPageListByUserCreatedCount(userId, queryDto.getPublishStatus()); + + // 构建分页结果 + PageInfo pageInfo = new PageInfo<>(list); + pageInfo.setTotal(total); + pageInfo.setPageNum(queryDto.getPageNum()); + pageInfo.setPageSize(queryDto.getPageSize()); + pageInfo.setPages((total + limit - 1) / limit); + + return pageInfo; + } } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java index 64ccf77..64196ec 100644 --- a/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java @@ -2,6 +2,7 @@ package com.kexue.skills.service.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; +import com.kexue.skills.common.LoginUserCacheUtil; import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.ContentPurchase; import com.kexue.skills.entity.dto.ContentPurchaseDto; @@ -21,6 +22,8 @@ import java.util.Date; import java.util.List; import java.util.UUID; +import cn.dev33.satoken.stp.StpUtil; + /** * (ContentPurchase)表服务实现类 * @@ -41,6 +44,9 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { @Resource private PointsAccountService pointsAccountService; + + @Resource + private LoginUserCacheUtil loginUserCacheUtil; /** * 分页查询 @@ -195,6 +201,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { // 5. 根据支付方式处理支付 String transactionNo = UUID.randomUUID().toString().replaceAll("-", ""); + boolean isPaid = false; if (payType == 1) { // 余额支付 BigDecimal price = content.getPrice(); @@ -208,6 +215,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { purchase.setStatus(2); // 已支付 purchase.setPurchaseTime(new Date()); this.insert(purchase); + isPaid = true; } else if (payType == 2) { // 积分支付 Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付"); @@ -223,6 +231,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { purchase.setStatus(2); // 已支付 purchase.setPurchaseTime(new Date()); this.insert(purchase); + isPaid = true; } else if (payType == 3 || payType == 4) { // 微信支付或支付宝支付 // 这里只创建购买记录,实际支付由前端调用支付接口完成 @@ -237,6 +246,14 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { Assert.isTrue(false, "不支持的支付方式"); } + // 更新Redis中的LoginUser对象 + if (isPaid) { + String token = loginUserCacheUtil.getTokenFromRequest(); + if (token != null) { + loginUserCacheUtil.updateHas(token, userId); + } + } + return purchase; } diff --git a/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java index 810927a..d56c554 100644 --- a/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java @@ -153,10 +153,26 @@ public class PayServiceImpl implements PayService { // 构建请求XML String xml = mapToXml(params); + logger.info("微信支付请求XML: {}", xml); // 发送请求到微信支付接口 - String xmlResult = HttpUtil.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml); + String xmlResult = null; + try { + logger.info("开始发送微信支付请求到: https://api.mch.weixin.qq.com/pay/unifiedorder"); + xmlResult = HttpUtil.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml); + logger.info("微信支付接口返回XML: {}", xmlResult); + } catch (Exception e) { + logger.error("发送微信支付请求失败", e); + throw new RuntimeException("发送微信支付请求失败: " + e.getMessage()); + } + + if (xmlResult == null || xmlResult.isEmpty()) { + logger.error("微信支付接口返回空响应"); + throw new RuntimeException("微信支付接口返回空响应"); + } + Map result = xmlToMap(xmlResult); + logger.info("解析后的微信支付响应: {}", result); // 处理响应 if ("SUCCESS".equals(result.get("return_code")) && "SUCCESS".equals(result.get("result_code"))) { @@ -165,8 +181,9 @@ public class PayServiceImpl implements PayService { payParams.put("order_no", order.getOrderNo()); return payParams; } else { - logger.error("微信支付下单失败: {}", result.get("return_msg")); - throw new RuntimeException("微信支付下单失败: " + result.get("return_msg")); + String returnMsg = result.get("return_msg") != null ? result.get("return_msg") : "未知错误"; + logger.error("微信支付下单失败: {}", returnMsg); + throw new RuntimeException("微信支付下单失败: " + returnMsg); } } catch (Exception e) { logger.error("创建微信支付订单失败", e); @@ -181,10 +198,11 @@ public class PayServiceImpl implements PayService { */ @Override public String handleWechatPayNotify(HttpServletRequest request) { + BufferedReader reader = null; try { // 读取回调数据 InputStream inputStream = request.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { @@ -197,6 +215,10 @@ public class PayServiceImpl implements PayService { // 验证签名 String sign = params.get("sign"); + if (sign == null) { + logger.error("微信支付回调签名为空"); + return ""; + } params.remove("sign"); String expectedSign = generateSignature(params, paymentConfig.getWechat().getMchKey()); @@ -211,9 +233,11 @@ public class PayServiceImpl implements PayService { String transactionId = params.get("transaction_id"); // 更新支付订单状态 - PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo); - if (order != null) { - paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId); + if (orderNo != null) { + PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo); + if (order != null) { + paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId); + } } return ""; @@ -224,6 +248,14 @@ public class PayServiceImpl implements PayService { } catch (Exception e) { logger.error("处理微信支付回调失败", e); return ""; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception e) { + logger.error("关闭流失败", e); + } + } } } @@ -286,7 +318,9 @@ public class PayServiceImpl implements PayService { // 获取回调参数 Map params = new HashMap<>(); request.getParameterMap().forEach((key, values) -> { - params.put(key, values[0]); + if (values != null && values.length > 0) { + params.put(key, values[0]); + } }); // 初始化支付宝客户端 @@ -317,9 +351,11 @@ public class PayServiceImpl implements PayService { String transactionId = params.get("trade_no"); // 更新支付订单状态 - PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo); - if (order != null) { - paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId); + if (orderNo != null) { + PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo); + if (order != null) { + paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId); + } } return "success"; diff --git a/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java index 2a95bef..c4191ed 100644 --- a/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java @@ -7,6 +7,8 @@ import com.kexue.skills.entity.dto.PaymentOrderDto; import com.kexue.skills.mapper.PaymentOrderMapper; import com.kexue.skills.service.AccountService; import com.kexue.skills.service.PaymentOrderService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +26,8 @@ import java.util.UUID; @Service("paymentOrderService") @Transactional(rollbackFor = Exception.class) public class PaymentOrderServiceImpl implements PaymentOrderService { + + private static final Logger logger = LoggerFactory.getLogger(PaymentOrderServiceImpl.class); @Resource private PaymentOrderMapper paymentOrderMapper; @@ -162,6 +166,13 @@ public class PaymentOrderServiceImpl implements PaymentOrderService { paymentOrder.setOrderNo(orderNo); // 设置初始状态 paymentOrder.setStatus(1); // 1.待支付 + // 设置支付类型默认值(防止null) + if (paymentOrder.getPayType() == null) { + paymentOrder.setPayType(1); // 默认微信支付 + logger.info("设置默认支付类型为微信支付(1)"); + } else { + logger.info("支付类型已设置为: {}", paymentOrder.getPayType()); + } // 设置创建时间和更新时间 Date now = new Date(); paymentOrder.setCreateTime(now); @@ -169,7 +180,9 @@ public class PaymentOrderServiceImpl implements PaymentOrderService { // 设置默认值 paymentOrder.setDeleteFlag(0); // 保存订单 + logger.info("准备保存支付订单,payType: {}", paymentOrder.getPayType()); this.paymentOrderMapper.insert(paymentOrder); + logger.info("支付订单保存成功"); return paymentOrder; } @@ -209,7 +222,7 @@ public class PaymentOrderServiceImpl implements PaymentOrderService { } } catch (Exception e) { // 记录错误但不影响支付回调的处理 - java.util.logging.Logger.getLogger(getClass().getName()).severe("更新购买记录状态失败: " + e.getMessage()); + logger.error("更新购买记录状态失败: {}", e.getMessage(), e); } } } diff --git a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java index 621e081..1895e33 100644 --- a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java @@ -1,9 +1,12 @@ package com.kexue.skills.service.impl; +import cn.dev33.satoken.stp.StpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.kexue.skills.common.util.HttpUtil; import com.kexue.skills.config.DeepSeekConfig; +import com.kexue.skills.config.GlmConfig; +import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.CmsTag; import com.kexue.skills.entity.dto.CmsTagDto; import com.kexue.skills.entity.request.SkillAnalyzeRequest; @@ -11,13 +14,20 @@ import com.kexue.skills.entity.request.SkillGenRequest; import com.kexue.skills.entity.request.SkillPreGenRequest; import com.kexue.skills.entity.request.SkillRequest; import com.kexue.skills.entity.response.SkillResponse; +import com.kexue.skills.mapper.CmsContentMapper; import com.kexue.skills.service.CmsTagService; import com.kexue.skills.service.SkillGenService; +import jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.stream.Collectors; /** * 技能生成服务实现 @@ -32,9 +42,15 @@ public class SkillGenServiceImpl implements SkillGenService { @Autowired private DeepSeekConfig deepSeekConfig; + @Autowired + private GlmConfig glmConfig; + @Autowired private CmsTagService cmsTagService; + @Resource + private CmsContentMapper cmsContentMapper; + /** * 生成技能 * @@ -99,7 +115,80 @@ public class SkillGenServiceImpl implements SkillGenService { } @Override - public JSONObject generate(SkillGenRequest request) { + public SkillResponse preGenerateV2(SkillPreGenRequest request) { + log.info("生成技能请求V2: {}", request); + String url = glmConfig.getBaseUrl() + "/chat/completions"; + String apiKey = glmConfig.getApiKey(); + String model = glmConfig.getChat().getModel(); + double temperature = glmConfig.getChat().getTemperature(); + int maxTokens = glmConfig.getChat().getMaxTokens(); + + // 从数据库中读取cms_tag表的标签信息 + CmsTagDto tagDto = new CmsTagDto(); + tagDto.setDeleteFlag(0); + tagDto.setStatus(1); + List tags = cmsTagService.getList(tagDto); + + // 将标签名称拼接成逗号分隔的字符串 + StringBuilder tagsList = new StringBuilder(); + for (int i = 0; i < tags.size(); i++) { + CmsTag tag = tags.get(i); + tagsList.append(tag.getTagName()); + if (i < tags.size() - 1) { + tagsList.append(","); + } + } + + // 构建系统消息内容 + String systemContent = "你是一个专业的AI技能设计助手。请根据agent skills撰写规范,按照用户提出的主题描述及参考文件,生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"" + tagsList.toString() + "\",并简述这个skill的具体价值点。输出json格式,仅输出以上所提到的名称、描述、标签、价值点,节点名称分别为name、description、tags、value_point,节点内容以中文形式返回。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。"; + + // 准备文件URL列表 + List fileUrls = new ArrayList<>(); + if (request.getFileUrl() != null && !request.getFileUrl().isEmpty()) { + fileUrls.add(request.getFileUrl()); + } + if (request.getFileUrls() != null && !request.getFileUrls().isEmpty()) { + fileUrls.addAll(request.getFileUrls()); + } + + // 创建技能请求 + SkillRequest skillRequest = new SkillRequest(model, systemContent, request.getPrompt(), fileUrls, temperature, maxTokens); + + try { + // 发送HTTP请求到API + String response = HttpUtil.sendPostRequest(url, skillRequest, apiKey, null); + log.info("API响应: {}", response); + + // 解析返回结果 + JSONObject responseJson = JSON.parseObject(response); + List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); + + if (choices != null && !choices.isEmpty()) { + // 获取最新的choice(这里是第一个,因为只有一个) + JSONObject latestChoice = choices.get(0); + JSONObject message = latestChoice.getJSONObject("message"); + String content = message.getString("content"); + + // 解析content中的JSON + JSONObject skillJson = JSON.parseObject(content); + SkillResponse skillResponse = new SkillResponse(); + skillResponse.setName(skillJson.getString("name")); + skillResponse.setDescription(skillJson.getString("description")); + skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class)); + skillResponse.setSummary(skillJson.getString("value_point")); + + log.info("解析技能响应: {}", skillResponse); + return skillResponse; + } + } catch (Exception e) { + log.error("调用API失败: {}", e.getMessage(), e); + } + + return null; + } + + @Override + public CmsContent generate(SkillGenRequest request) { log.info("生成技能请求: {}", request); String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; @@ -113,31 +202,69 @@ public class SkillGenServiceImpl implements SkillGenService { // 将标签名称拼接成逗号分隔的字符串 StringBuilder tagsList = new StringBuilder(); + String defaultIcon = ""; for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); if (tags1.contains(tag.getTagId()+"")) { + if (StringUtil.isEmpty(defaultIcon)){ + defaultIcon = tag.getIcon(); + } tagsList.append(tag.getTagName()); if (i < tags.size() - 1) { tagsList.append(","); } } } - String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档。无需其他额外说明。"; - String userContent = "请根据以下Skill信息生成skills.md文档内容:Skill名称:SKILL_NAME,Skill描述:DESCRIPTION,Skill标签:TAGS 。"; - userContent = userContent.replace("SKILL_NAME", request.getName()).replace("DESCRIPTION", request.getDescription()).replace("TAGS", tagsList.toString()); + String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,对于每个节点的内容要输出type,文件对应file,目录对应directory,每个节点都必须有name和content属性,无需其他额外说明。"; + String userContent = "请根据以下Skill信息生成skills.md文档内容:Skill名称:SKILL_NAME,Skill描述:DESCRIPTION,Skill标签:TAGS ,摘要:SUMMARY。"; + userContent = userContent.replace("SKILL_NAME", request.getName()).replace("DESCRIPTION", request.getDescription()).replace("TAGS", tagsList.toString()).replace("SUMMARY", request.getRequirement()); SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),systemContent,userContent,deepSeekConfig.getChat().getTemperature(), 8192,"text"); try { // 发送HTTP请求到deepseek API String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); log.info("Deepseek API响应: {}", deepseekResponse); JSONObject responseJson = JSON.parseObject(deepseekResponse); - return responseJson; + String content = responseJson.getJSONArray("choices").toJavaList(JSONObject.class).get(0).getJSONObject("message").getString("content"); + CmsContent cmsContent = getCmsContent(request, content, StpUtil.getLoginIdAsLong(),defaultIcon); + // 保存到数据库 + cmsContentMapper.insert(cmsContent); + return cmsContent; } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); } return null; } + private CmsContent getCmsContent(SkillGenRequest request, String CmsContent,Long userId,String defaultIcon){ + CmsContent cmsContent = new CmsContent(); + cmsContent.setTitle(request.getName()); + cmsContent.setDescription(request.getDescription()); + cmsContent.setContent(CmsContent); + cmsContent.setTags(request.getTags().stream().map(String::valueOf).collect(Collectors.joining(","))); + cmsContent.setIsPaid(0);//免费 + cmsContent.setIsOfficial(false); + cmsContent.setPublishStatus(1); + cmsContent.setAuthorId(userId); + cmsContent.setAuthorName(null); + cmsContent.setAuditStatus(1); + cmsContent.setViewCount(0); + cmsContent.setCommentCount(0); + cmsContent.setRequiredPoints(0); + cmsContent.setSupportPointsPay(0); + cmsContent.setPrice(new BigDecimal(0)); + cmsContent.setLikeCount(0); + cmsContent.setShareCount(0); + cmsContent.setCreateTime(new Date()); + cmsContent.setUpdateTime(new Date()); + cmsContent.setContentType(1); + cmsContent.setCreateBy(userId+""); + cmsContent.setUpdateBy(userId+""); + cmsContent.setDeleteFlag(0); + cmsContent.setIcon(defaultIcon); + cmsContent.setRequirement(request.getRequirement()); + return cmsContent; + } + /** * 分析技能 @@ -152,4 +279,33 @@ public class SkillGenServiceImpl implements SkillGenService { // 不需要数据库操作,直接返回结果 return "技能分析成功: " + request.getSkillId(); } + + @Override + public String genIntroduce(String content) { + log.info("生成技能介绍请求: {}", content); + String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; + + String systemContent = "你是一个专业的AI技能设计助手。我会给你提供一个完整的skill的内容,请你帮我总结出skill的作用,能够解决的问题,输出一段描述文本"; + SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, content, 0.3, 500, "text"); + + try { + // 发送HTTP请求到deepseek API + String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); + log.info("Deepseek API响应: {}", deepseekResponse); + + // 解析返回结果 + JSONObject responseJson = JSON.parseObject(deepseekResponse); + List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); + + if (choices != null && !choices.isEmpty()) { + JSONObject latestChoice = choices.get(0); + JSONObject message = latestChoice.getJSONObject("message"); + return message.getString("content"); + } + } catch (Exception e) { + log.error("调用Deepseek API失败: {}", e.getMessage(), e); + } + + return null; + } } diff --git a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java index 610874f..f1d9557 100644 --- a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java @@ -9,6 +9,7 @@ import com.github.pagehelper.PageInfo; import com.kexue.skills.common.Assert; import com.kexue.skills.common.CacheManager; import com.kexue.skills.common.Const; +import com.kexue.skills.common.LoginUserCacheUtil; import com.kexue.skills.config.CaptchaConfig; import com.kexue.skills.entity.*; import com.kexue.skills.entity.dto.ContentPurchaseDto; @@ -136,8 +137,9 @@ public class SysUserServiceImpl implements SysUserService { //写一个salt生成方法 sysUser.setSalt(System.currentTimeMillis()+""); try { - String md5Pwd = MD5Util.encryptToHex(sysUser.getPwd()+sysUser.getSalt()); - sysUser.setPwd(md5Pwd); + // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 + String encryptedPwd = MD5Util.doubleEncrypt(sysUser.getPwd(), sysUser.getSalt()); + sysUser.setPwd(encryptedPwd); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } @@ -219,12 +221,24 @@ public class SysUserServiceImpl implements SysUserService { // 验证码验证 validateCaptcha(loginDto); - // 查询用户 - SysUser sysUser = getUserByUsername(loginDto.getUsername()); + // 查询用户(支持用户名或手机号) + SysUser sysUser = getUserByUsernameOrPhone(loginDto.getUsername()); // 密码验证 validatePassword(loginDto.getPassword(), sysUser); + // 检查用户是否已有token,如果有则使旧token失效 + String oldToken = CacheManager.getTokenFromCache(sysUser.getUserName()); + if (oldToken != null && !oldToken.isEmpty()) { + // 从Redis中删除旧token对应的登录信息 + redissonClient.getBucket("loginUser:" + oldToken).delete(); + // 使旧token失效 + cn.dev33.satoken.stp.StpUtil.logoutByTokenValue(oldToken); + // 从缓存中移除旧token + CacheManager.removeTokenFromCache(sysUser.getUserName()); + log.info("用户:{}的旧token已失效", sysUser.getUserName()); + } + // 生成token String token = generateToken(sysUser.getUserId()); @@ -234,6 +248,9 @@ public class SysUserServiceImpl implements SysUserService { // 存储登录信息到Redis saveLoginUserToRedis(token, loginUser); + // 存储token与用户名的映射关系到CacheManager + CacheManager.putTokenToCache(sysUser.getUserName(), token); + // 构建返回对象 LoginUserDto sysUserDto = buildLoginUserDto(loginUser); @@ -248,7 +265,7 @@ public class SysUserServiceImpl implements SysUserService { * @param loginDto 登录请求参数 */ private void validateLoginParams(LoginDto loginDto) { - Assert.notNull(loginDto.getUsername(), "用户名不能位空"); + Assert.notNull(loginDto.getUsername(), "用户名或手机号不能位空"); Assert.notNull(loginDto.getPassword(), "密码不能位空"); } @@ -283,6 +300,23 @@ public class SysUserServiceImpl implements SysUserService { return sysUser; } + /** + * 根据用户名或手机号查询用户 + * + * @param usernameOrPhone 用户名或手机号 + * @return 用户对象 + */ + public SysUser getUserByUsernameOrPhone(String usernameOrPhone) { + // 先尝试通过用户名查询 + SysUser sysUser = sysUserMapper.getByUsername(usernameOrPhone); + if (sysUser == null) { + // 如果用户名查询不到,尝试通过手机号查询 + sysUser = sysUserMapper.getByTel(usernameOrPhone); + Assert.notNull(sysUser, "用户不存在"); + } + return sysUser; + } + /** * 密码验证 * @@ -523,25 +557,39 @@ public class SysUserServiceImpl implements SysUserService { return sysUserDto; } + @Resource + private LoginUserCacheUtil loginUserCacheUtil; + @Override public boolean resetPassword(ResetPasswordDto resetPasswordDto) { - Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空"); + Assert.notNull(resetPasswordDto.getUserName(), "用户名或手机号不能位空"); Assert.notNull(resetPasswordDto.getOldPassword(), "旧密码不能位空"); Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空"); - SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); - Assert.notNull(sysUser, "用户名不存在"); + // 获取当前登录用户ID + Long currentUserId = loginUserCacheUtil.getCurrentUserId(); + Assert.notNull(currentUserId, "请先登录"); + + // 查询用户(支持用户名或手机号) + SysUser sysUser = getUserByUsernameOrPhone(resetPasswordDto.getUserName()); + + // 检查是否是用户自己修改密码,或者是管理员 + boolean isAdmin = Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase()); + boolean isSelf = currentUserId.equals(sysUser.getUserId()); + Assert.isTrue(isAdmin || isSelf, "只能修改自己的密码"); try { - String oldMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getOldPassword() + sysUser.getSalt()); - Assert.equals(oldMd5Pwd, sysUser.getPwd(), "旧密码不正确"); + // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 + String oldEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getOldPassword(), sysUser.getSalt()); + Assert.equals(oldEncryptedPwd, sysUser.getPwd(), "旧密码不正确"); - String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt()); - sysUser.setPwd(newMd5Pwd); + // 对新密码进行双重加密 + String newEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getNewPassword(), sysUser.getSalt()); + sysUser.setPwd(newEncryptedPwd); sysUserMapper.update(sysUser); // 清除旧的token - CacheManager.removeTokenFromCache(resetPasswordDto.getUserName()); + CacheManager.removeTokenFromCache(sysUser.getUserName()); return true; } catch (NoSuchAlgorithmException e) { @@ -551,19 +599,20 @@ public class SysUserServiceImpl implements SysUserService { @Override public boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto) { - Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空"); + Assert.notNull(resetPasswordDto.getUserName(), "用户名或手机号不能位空"); Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空"); - SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); - Assert.notNull(sysUser, "用户名不存在"); + // 查询用户(支持用户名或手机号) + SysUser sysUser = getUserByUsernameOrPhone(resetPasswordDto.getUserName()); try { - String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt()); - sysUser.setPwd(newMd5Pwd); + // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 + String newEncryptedPwd = MD5Util.doubleEncrypt(resetPasswordDto.getNewPassword(), sysUser.getSalt()); + sysUser.setPwd(newEncryptedPwd); sysUserMapper.update(sysUser); // 清除旧的token - CacheManager.removeTokenFromCache(resetPasswordDto.getUserName()); + CacheManager.removeTokenFromCache(sysUser.getUserName()); return true; } catch (NoSuchAlgorithmException e) { @@ -587,17 +636,23 @@ public class SysUserServiceImpl implements SysUserService { @Override public boolean resetPwd(Long userId, String newPassword, String operator) { - // 检查操作人是否为管理员 - Assert.isTrue(Const.ADMIN_USER_LIST.contains(operator.toLowerCase()), "只有管理员才能执行此操作"); + // 获取当前登录用户ID + Long currentUserId = loginUserCacheUtil.getCurrentUserId(); + Assert.notNull(currentUserId, "请先登录"); // 查询用户是否存在 SysUser sysUser = sysUserMapper.queryById(userId); Assert.notNull(sysUser, "用户不存在"); + + // 检查是否是用户自己修改密码,或者是管理员 + boolean isAdmin = Const.ADMIN_USER_LIST.contains(operator.toLowerCase()); + boolean isSelf = currentUserId.equals(sysUser.getUserId()); + Assert.isTrue(isAdmin || isSelf, "只能修改自己的密码"); try { - // 生成新的加密密码 - String newMd5Pwd = MD5Util.encryptToHex(newPassword + sysUser.getSalt()); - sysUser.setPwd(newMd5Pwd); + // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 + String newEncryptedPwd = MD5Util.doubleEncrypt(newPassword, sysUser.getSalt()); + sysUser.setPwd(newEncryptedPwd); // 更新用户密码 sysUserMapper.update(sysUser); @@ -613,8 +668,9 @@ public class SysUserServiceImpl implements SysUserService { @Override public boolean resetPasswordByUsernameOrPhone(String usernameOrPhone, String newPassword, String operator) { - // 检查操作人是否为管理员 - //Assert.isTrue(Const.ADMIN_USER_LIST.contains(operator.toLowerCase()), "只有管理员才能执行此操作"); + // 获取当前登录用户ID + Long currentUserId = loginUserCacheUtil.getCurrentUserId(); + Assert.notNull(currentUserId, "请先登录"); // 查询用户是否存在(先尝试通过用户名查询,再尝试通过手机号查询) SysUser sysUser = sysUserMapper.getByUsername(usernameOrPhone); @@ -622,11 +678,16 @@ public class SysUserServiceImpl implements SysUserService { sysUser = sysUserMapper.getByTel(usernameOrPhone); } Assert.notNull(sysUser, "用户不存在"); + + // 检查是否是用户自己修改密码,或者是管理员 + boolean isAdmin = Const.ADMIN_USER_LIST.contains(operator.toLowerCase()); + boolean isSelf = currentUserId.equals(sysUser.getUserId()); + Assert.isTrue(isAdmin || isSelf, "只能修改自己的密码"); try { - // 生成新的加密密码 - String newMd5Pwd = MD5Util.encryptToHex(newPassword + sysUser.getSalt()); - sysUser.setPwd(newMd5Pwd); + // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 + String newEncryptedPwd = MD5Util.doubleEncrypt(newPassword, sysUser.getSalt()); + sysUser.setPwd(newEncryptedPwd); // 更新用户密码 sysUserMapper.update(sysUser); @@ -732,6 +793,18 @@ public class SysUserServiceImpl implements SysUserService { sysUser = createUserByPhone(phone); } + // 检查用户是否已有token,如果有则使旧token失效 + String oldToken = CacheManager.getTokenFromCache(sysUser.getUserName()); + if (oldToken != null && !oldToken.isEmpty()) { + // 从Redis中删除旧token对应的登录信息 + redissonClient.getBucket("loginUser:" + oldToken).delete(); + // 使旧token失效 + cn.dev33.satoken.stp.StpUtil.logoutByTokenValue(oldToken); + // 从缓存中移除旧token + CacheManager.removeTokenFromCache(sysUser.getUserName()); + log.info("用户:{}的旧token已失效", sysUser.getUserName()); + } + // 使用Sa-Token登录,生成token cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId()); // 获取生成的token @@ -806,6 +879,9 @@ public class SysUserServiceImpl implements SysUserService { // 将LoginUser对象存储到Redis缓存中,使用token作为key redissonClient.getBucket("loginUser:" + token).set(cn.hutool.json.JSONUtil.toJsonStr(loginUser), 86400, java.util.concurrent.TimeUnit.SECONDS); + // 存储token与用户名的映射关系到CacheManager + CacheManager.putTokenToCache(sysUser.getUserName(), token); + // 创建LoginUserDto返回对象 LoginUserDto sysUserDto = buildLoginUserDto(loginUser); diff --git a/src/main/resources/application-common.yml b/src/main/resources/application-common.yml index 72c1596..40c38ee 100644 --- a/src/main/resources/application-common.yml +++ b/src/main/resources/application-common.yml @@ -2,9 +2,9 @@ common: # Redis配置 redis: - host: 127.0.0.1 - port: 6379 -# host: 43.248.97.19 -# port: 16379 +# host: 127.0.0.1 +# port: 6379 + host: 43.248.97.19 + port: 16379 password: 654321 database: 1 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ed14db7..521ba92 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -106,6 +106,7 @@ web: path: /kexue/agent-skills/upload/ # 支付配置 + payment: # 微信支付配置 wechat: @@ -115,15 +116,15 @@ payment: mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D privateKeyPath: apiclient_key.pem domain: https://api.mch.weixin.qq.com - notifyUrl: http://127.0.0.1:19001/pay/wx/notify - returnUrl: http://127.0.0.1:19001/pay/success + notifyUrl: http://127.0.0.1:19001/api/pay/wx/notify + returnUrl: http://127.0.0.1:19001/api/pay/success # 支付宝支付配置 alipay: appId: 2021004138642603 privateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBzqh/sBfx8UsiKIzSaGm3QwMape+PEmHaFYsP0XxRLyRe5d3H1r4JBvE/GBCDarXkZMvJ3PKeUpO61i7LZtNBN7M6XbMvs/eiaipUmWCNF4IK+ilhOqr8GNryjb6tBnO0uzd1c7UmAvxF+MKkxti0qfscP+Tr6pkuF14DofOyITY56x0y36DmEc85rr213llQ/bSX5nVPHWfddtrqB+TNFvrnAN10NxnUurDK+wd7fKq3TgMjYNDINPGxFYbYezeIGvYj6E34mFS3b3wyxTcB5DeL4GV1FAWqJ0Yk+3hHT3OdmK/UnNEr55w+r3liQBCDRBoE4fldbG5CtW2/ER0lAgMBAAECggEAC2kgNMFFCZaddS49Ws2k5WA1qKUHjvsdsO8N32EZ3YUYXGM2gLem0uJSWKqD4RmDTcVyiJcsmLBHnjfvux+Z2HTOA4ZzFvFqBlPwzqkA7MYxP0fIVWyz1R9WN9Yv+cPEbhG7CU8XkHTYuknoylVUfWUn1s7jD736oyuYrxcQdgsHOHpLHngvsELLa1pv2EURohvr4p+zirMjFGuz3BVaGgVpWegn0nJ/8n1y0ZTM04Mvm/zXpGQQxfuUxrK2owQMFViY7BrRQXlPVeUM/IPAx7cvxLkR5hl9UgAl+nH6FCsm5osEvUln6VLhGTmNFBLCN9piX6sZaPEKZKRBCz0GQQKBgQDD4Dq+dGQAoCmnXdkzI6DHGXOePa+sGnj1Y55dRcrPWPKtCPeEnIPxhVCJ2+cqYK49K6youhLYzs3h8y/M4lh0JQLKP4zTSyZnARmdYHW/SyTu9BcHCdiPwZBrmn7bCWotGf3r5QRIJT6ilZEj8cLnF+9+gd0YyLyRE7Rmihq2UQKBgQCpps7qghRv3JIB0Hb4nBCbAeyPMjHj+7BSUsui2Dhdg3MeTk48a7RLl+r139pMzgTm5Pj0VG6qslUeqP2HlQ46o0Z2bPeohfXH4zMJi2amh4MvAFp4t8eNCc6faeqpJPTTQj3hS4drFnHEHBeFfgFCXZKhjYeP7SP2WVLOQvUAlQKBgFIq6fmjEaBBj7ep4sdVFsjuoFWtQthLcppd47z03hMFGSgFLu/uSFs0tYhfOyXH0M/QVmmhRO62Mh+qyE6GVNzD+dultQmd6Mok5/3gzQQmHaQvuMk3FCWZ6V96O+Temi+5S499TsKE/TVu0Kfnbv9KRykmiP0wmAmz3mV1YadBAoGABc5quIX5MxbmfF9pIvscamG3efMq1/WuRDMHOyyRSUoNb5UYgmLhSdEKPp4Jt6U5b7mYd6xIGVl/Jkx8WN6WHRWnfLggBcmH7u5sub/mpH5w0/P8JLONhds3Eieq210jb/ONcJ+II/chr6eSeoQkgOP498SDRj7Eg1LtTZfnEL0CgYBnMeXQWKUae+xES5NsEX7D0lwSCotb7attTHeE6vZOI77TzsURb4jOEAhYVsdJ8lm+J1Sv1Wjnr2yMxiRH2G+I1tUxGcI8OkRT26FxFdMl1RdbTf+gDM8IjMiu+Li+plIzb28VtF8q/Umgd+5LTlSBmM2yoiL8RKtmStjr5iIuhA==" publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB" - notifyUrl: http://127.0.0.1:19001/pay/ali-pay/trade/notify + notifyUrl: http://127.0.0.1:19001/api/pay/alipay/trade/notify returnUrl: https://shuziren.xueai.art/alipay-success signType: RSA2 charset: UTF-8 - gatewayUrl: https://openapi.alipay.com/gateway.do + gatewayUrl: https://openapi.alipay.com/gateway.do \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 8e73bd8..e9df013 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -101,3 +101,25 @@ jetcache: web: upload: path: /kexue/agent-skills/upload/ + +payment: + # 微信支付配置 + wechat: + appId: wx7d13d99de5be3bfa + mchId: 1673321732 + mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B + mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D + privateKeyPath: apiclient_key.pem + domain: https://api.mch.weixin.qq.com + notifyUrl: http://43.248.97.19:19000/pay/wx/notify + returnUrl: http://43.248.97.19:19000/pay/success + # 支付宝支付配置 + alipay: + appId: 2021004138642603 + privateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBzqh/sBfx8UsiKIzSaGm3QwMape+PEmHaFYsP0XxRLyRe5d3H1r4JBvE/GBCDarXkZMvJ3PKeUpO61i7LZtNBN7M6XbMvs/eiaipUmWCNF4IK+ilhOqr8GNryjb6tBnO0uzd1c7UmAvxF+MKkxti0qfscP+Tr6pkuF14DofOyITY56x0y36DmEc85rr213llQ/bSX5nVPHWfddtrqB+TNFvrnAN10NxnUurDK+wd7fKq3TgMjYNDINPGxFYbYezeIGvYj6E34mFS3b3wyxTcB5DeL4GV1FAWqJ0Yk+3hHT3OdmK/UnNEr55w+r3liQBCDRBoE4fldbG5CtW2/ER0lAgMBAAECggEAC2kgNMFFCZaddS49Ws2k5WA1qKUHjvsdsO8N32EZ3YUYXGM2gLem0uJSWKqD4RmDTcVyiJcsmLBHnjfvux+Z2HTOA4ZzFvFqBlPwzqkA7MYxP0fIVWyz1R9WN9Yv+cPEbhG7CU8XkHTYuknoylVUfWUn1s7jD736oyuYrxcQdgsHOHpLHngvsELLa1pv2EURohvr4p+zirMjFGuz3BVaGgVpWegn0nJ/8n1y0ZTM04Mvm/zXpGQQxfuUxrK2owQMFViY7BrRQXlPVeUM/IPAx7cvxLkR5hl9UgAl+nH6FCsm5osEvUln6VLhGTmNFBLCN9piX6sZaPEKZKRBCz0GQQKBgQDD4Dq+dGQAoCmnXdkzI6DHGXOePa+sGnj1Y55dRcrPWPKtCPeEnIPxhVCJ2+cqYK49K6youhLYzs3h8y/M4lh0JQLKP4zTSyZnARmdYHW/SyTu9BcHCdiPwZBrmn7bCWotGf3r5QRIJT6ilZEj8cLnF+9+gd0YyLyRE7Rmihq2UQKBgQCpps7qghRv3JIB0Hb4nBCbAeyPMjHj+7BSUsui2Dhdg3MeTk48a7RLl+r139pMzgTm5Pj0VG6qslUeqP2HlQ46o0Z2bPeohfXH4zMJi2amh4MvAFp4t8eNCc6faeqpJPTTQj3hS4drFnHEHBeFfgFCXZKhjYeP7SP2WVLOQvUAlQKBgFIq6fmjEaBBj7ep4sdVFsjuoFWtQthLcppd47z03hMFGSgFLu/uSFs0tYhfOyXH0M/QVmmhRO62Mh+qyE6GVNzD+dultQmd6Mok5/3gzQQmHaQvuMk3FCWZ6V96O+Temi+5S499TsKE/TVu0Kfnbv9KRykmiP0wmAmz3mV1YadBAoGABc5quIX5MxbmfF9pIvscamG3efMq1/WuRDMHOyyRSUoNb5UYgmLhSdEKPp4Jt6U5b7mYd6xIGVl/Jkx8WN6WHRWnfLggBcmH7u5sub/mpH5w0/P8JLONhds3Eieq210jb/ONcJ+II/chr6eSeoQkgOP498SDRj7Eg1LtTZfnEL0CgYBnMeXQWKUae+xES5NsEX7D0lwSCotb7attTHeE6vZOI77TzsURb4jOEAhYVsdJ8lm+J1Sv1Wjnr2yMxiRH2G+I1tUxGcI8OkRT26FxFdMl1RdbTf+gDM8IjMiu+Li+plIzb28VtF8q/Umgd+5LTlSBmM2yoiL8RKtmStjr5iIuhA==" + publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB" + notifyUrl: http://43.248.97.19:19000/pay/ali-pay/trade/notify + returnUrl: https://shuziren.xueai.art/alipay-success + signType: RSA2 + charset: UTF-8 + gatewayUrl: https://openapi.alipay.com/gateway.do \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 111f815..63ab60f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: profiles: - active: dev + active: prod application: name: agentSkills version: 1.0.0 @@ -35,6 +35,13 @@ spring: model: deepseek-chat temperature: 0.3 max-tokens: 500 + glm: + base-url: https://open.bigmodel.cn/api/paas/v4 + api-key: ${GLM_API_KEY:57a566a125bb4492900db0578969f27d.JLpuGSzr9hhLurM7} + chat: + model: glm-4.6v + temperature: 0.7 + max-tokens: 8192 # 包含公共配置文件 spring.config.import: diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 36e5486..f8edb37 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -2,7 +2,7 @@ - + @@ -13,10 +13,10 @@ - ${LOG_HOME}/hyxp-portal.log + ${LOG_HOME}/skill.log - ${LOG_HOME}/hyxp-portal.%d{yyyy-MM-dd}.log + ${LOG_HOME}/skill.%d{yyyy-MM-dd}.log 30 diff --git a/src/main/resources/mapper/CmsContentMapper.xml b/src/main/resources/mapper/CmsContentMapper.xml index 23549cb..37edfc4 100644 --- a/src/main/resources/mapper/CmsContentMapper.xml +++ b/src/main/resources/mapper/CmsContentMapper.xml @@ -8,6 +8,9 @@ + + + @@ -43,7 +46,7 @@ select - content_id, title, subtitle, content_type, summary, content, cover_image, author_id, author_name, + content_id, title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name, reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time, view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, origin, tags, create_time, update_time, create_by, update_by, delete_flag from cms_content @@ -103,7 +106,7 @@ select - content_id, title, subtitle, content_type, summary, content, cover_image, author_id, author_name, + content_id, title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name, reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time, view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, origin, tags, create_time, update_time, create_by, update_by, delete_flag from cms_content @@ -233,10 +236,10 @@ - insert into cms_content(title, subtitle, content_type, summary, content, cover_image, author_id, author_name, + insert into cms_content(title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name, reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time, view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, origin, tags, create_time, update_time, create_by, update_by, delete_flag) - values (#{title}, #{subtitle}, #{contentType}, #{summary}, #{content}, #{coverImage}, #{authorId}, #{authorName}, + values (#{title}, #{subtitle}, #{contentType}, #{summary}, #{description}, #{requirement}, #{introduce}, #{content}, #{coverImage}, #{authorId}, #{authorName}, #{reviewerId}, #{reviewerName}, #{auditStatus}, #{auditComment}, #{publishStatus}, #{publishTime}, #{viewCount}, #{likeCount}, #{commentCount}, #{sort}, #{isPaid}, #{price}, #{requiredPoints}, #{supportPointsPay}, #{isOfficial}, #{shareCount}, #{fileUrl}, #{icon}, #{background}, #{origin}, #{tags}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag}) @@ -257,6 +260,15 @@ summary = #{summary}, + + description = #{description}, + + + requirement = #{requirement}, + + + introduce = #{introduce}, + content = #{content}, @@ -391,4 +403,92 @@ limit #{limit} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/CmsTagMapper.xml b/src/main/resources/mapper/CmsTagMapper.xml index 4d632ff..3102a0e 100644 --- a/src/main/resources/mapper/CmsTagMapper.xml +++ b/src/main/resources/mapper/CmsTagMapper.xml @@ -8,6 +8,7 @@ + @@ -18,7 +19,7 @@ @@ -26,7 +27,7 @@ select - tag_id, tag_name, description, use_count, status, create_time, update_time, create_by, update_by, delete_flag + tag_id, tag_name, description, use_count, status, icon, create_time, update_time, create_by, update_by, delete_flag from cms_tag @@ -69,8 +70,8 @@ - insert into cms_tag(tag_name, description, use_count, status, create_time, update_time, create_by, update_by, delete_flag) - values (#{tagName}, #{description}, #{useCount}, #{status}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag}) + insert into cms_tag(tag_name, description, use_count, status, icon, create_time, update_time, create_by, update_by, delete_flag) + values (#{tagName}, #{description}, #{useCount}, #{status}, #{icon}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag}) @@ -89,6 +90,9 @@ status = #{status}, + + icon = #{icon}, + update_time = #{updateTime}, @@ -114,7 +118,7 @@