feat(content): 添加skill管理和支付配置功能

- 添加GLM大模型配置支持
- 配置生产环境Redis连接信息
- 更新支付回调URL路径配置
- 添加微信和支付宝支付配置到生产环境
- 修改异常处理器捕获BizException
- 添加内容详情、需求说明和介绍字段
- 将内容管理重命名为skill管理
- 添加取消收藏功能接口
- 添加用户历史查看、收藏、购买和创建内容列表接口
- 实现用户行为统计和个性化内容推荐功能
- 更新数据库映射文件以支持新字段和查询功能
This commit is contained in:
wangzhiwei 2026-03-03 14:55:06 +08:00
parent e16fbdf2d6
commit 11bc1959f0
33 changed files with 1305 additions and 130 deletions

View File

@ -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;

View File

@ -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<Long> has = new ArrayList<>();
try {
com.kexue.skills.entity.dto.ContentPurchaseDto purchaseDto = new com.kexue.skills.entity.dto.ContentPurchaseDto();
purchaseDto.setUserId(userId);
List<com.kexue.skills.entity.ContentPurchase> purchases = contentPurchaseMapper.getList(purchaseDto);
if (purchases != null && !purchases.isEmpty()) {
java.util.LinkedHashSet<Long> 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<List<Long>> 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<Long> 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;
}
}
}

View File

@ -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;
}
}

View File

@ -21,7 +21,7 @@ import javax.annotation.Resource;
*/
@RestController
@RequestMapping("api/cmsContent")
@Tag(name = "内容skills管理 Api")
@Tag(name = "skillskills管理 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<CmsContent> 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<CmsContent> 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<CmsContent> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> addView(@RequestParam("contentId") Long contentId) {
return CommonResult.success(cmsContentService.addView(contentId) > 0);
}
/**
* 获取用户历史查看的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
@PostMapping("/getUserHistory")
@Operation(summary = "获取用户历史查看", description = "获取当前用户历史查看的内容列表,带分页")
@RequireAuth
public CommonResult<PageInfo<CmsContent>> getUserHistory(@RequestBody CmsContentDto queryDto) {
return CommonResult.success(cmsContentService.getPageListByUserHistory(queryDto));
}
/**
* 获取用户收藏的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
@PostMapping("/getUserFavorites")
@Operation(summary = "获取用户收藏", description = "获取当前用户收藏的内容列表,带分页")
@RequireAuth
public CommonResult<PageInfo<CmsContent>> getUserFavorites(@RequestBody CmsContentDto queryDto) {
return CommonResult.success(cmsContentService.getPageListByUserFavorites(queryDto));
}
/**
* 获取用户购买的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
@PostMapping("/getUserPurchases")
@Operation(summary = "获取用户拥有", description = "获取当前用户购买的内容列表,带分页")
@RequireAuth
public CommonResult<PageInfo<CmsContent>> getUserPurchases(@RequestBody CmsContentDto queryDto) {
return CommonResult.success(cmsContentService.getPageListByUserPurchases(queryDto));
}
/**
* 获取用户创建的内容列表
*
* @param queryDto 筛选条件包含分页信息和发布状态
* @return 查询结果
*/
@PostMapping("/getUserCreated")
@Operation(summary = "获取用户创建", description = "获取当前用户创建的内容列表,带分页,可查询已发布、未发布")
@RequireAuth
public CommonResult<PageInfo<CmsContent>> getUserCreated(@RequestBody CmsContentDto queryDto) {
return CommonResult.success(cmsContentService.getPageListByUserCreated(queryDto));
}
}

View File

@ -39,6 +39,8 @@ public class PayController {
@PostMapping("/wx/create")
public CommonResult<Map<String, String>> 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<String> 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<Map<String, Object>> handleAlipayReturn(HttpServletRequest request) {
Map<String, Object> result = payService.handleAlipayReturn(request);
if (result.get("success").equals(true)) {

View File

@ -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<SkillResponse> 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<SkillResponse> preGenerateV2(@RequestBody SkillPreGenRequest request) {
return CommonResult.success(skillGenService.preGenerateV2(request));
}
@PostMapping("/generate")
@Operation(summary = "生成Skill对应的md文件", description = "生成Skill对应的md文件")
public CommonResult<JSONObject> generate(@RequestBody SkillGenRequest request) {
@Operation(summary = "生成技能", description = "生成技能")
public CommonResult<CmsContent> generate(@RequestBody SkillGenRequest request) {
return CommonResult.success(skillGenService.generate(request));
}
@ -54,4 +68,16 @@ public class SkillGenController {
public CommonResult<String> 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<String> genIntroduce(@RequestBody GenIntroduceRequest request) {
return CommonResult.success(skillGenService.genIntroduce(request.getContent()));
}
}

View File

@ -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;

View File

@ -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;

View File

@ -20,6 +20,8 @@ public class CmsTagDto extends BaseQueryDto {
private Integer status;
private String icon;
private Integer deleteFlag;
}

View File

@ -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;
}

View File

@ -16,6 +16,6 @@ public class SkillGenRequest implements Serializable {
private String description;
@Schema(description = "技能标签")
private List<String> tags;
@Schema(description = "技能内容ID参照模板ID")
private String contentId;
@Schema(description = "需求说明")
private String requirement;
}

View File

@ -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<String> fileUrls;
}

View File

@ -89,6 +89,49 @@ public class SkillRequest implements Serializable {
}
}
// 新的构造方法支持文件URL列表
public SkillRequest(String model, String systemContent, String prompt, List<String> 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<MessageContent> 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<MessageContent>
}
@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

View File

@ -12,4 +12,5 @@ public class SkillResponse implements Serializable {
private String name;
private String description;
private List<String> tags;
private String summary;
}

View File

@ -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();

View File

@ -135,4 +135,78 @@ public interface CmsContentMapper {
* @return 内容ID列表
*/
List<Long> queryRecentCreatedByUserId(@Param("userId") Long userId, @Param("limit") int limit);
/**
* 获取用户历史查看的内容列表
*
* @param userId 用户ID
* @param offset 偏移量
* @param limit 限制数量
* @return 查询结果
*/
List<CmsContent> 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<CmsContent> 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<CmsContent> 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<CmsContent> 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);
}

View File

@ -133,4 +133,36 @@ public interface CmsContentService extends BaseService {
* @return 影响行数
*/
int addView(Long contentId);
/**
* 获取用户历史查看的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
PageInfo<CmsContent> getPageListByUserHistory(CmsContentDto queryDto);
/**
* 获取用户收藏的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
PageInfo<CmsContent> getPageListByUserFavorites(CmsContentDto queryDto);
/**
* 获取用户购买的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
PageInfo<CmsContent> getPageListByUserPurchases(CmsContentDto queryDto);
/**
* 获取用户创建的内容列表
*
* @param queryDto 筛选条件包含分页信息
* @return 查询结果
*/
PageInfo<CmsContent> getPageListByUserCreated(CmsContentDto queryDto);
}

View File

@ -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);
}

View File

@ -110,4 +110,12 @@ public interface SysUserService extends BaseService {
* @return 登录结果
*/
LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto);
/**
* 根据用户名或手机号查询用户
*
* @param usernameOrPhone 用户名或手机号
* @return 用户对象
*/
SysUser getUserByUsernameOrPhone(String usernameOrPhone);
}

View File

@ -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<CmsContent> list = this.cmsContentMapper.getPageList(queryDto);
PageInfo<CmsContent> 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<CmsContent> 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<CmsContent> list = this.cmsContentMapper.getPageListByUserHistory(userId, offset, limit);
int total = this.cmsContentMapper.getPageListByUserHistoryCount(userId);
// 构建分页结果
PageInfo<CmsContent> 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<CmsContent> 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<CmsContent> list = this.cmsContentMapper.getPageListByUserFavorites(userId, offset, limit);
int total = this.cmsContentMapper.getPageListByUserFavoritesCount(userId);
// 构建分页结果
PageInfo<CmsContent> 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<CmsContent> 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<CmsContent> list = this.cmsContentMapper.getPageListByUserPurchases(userId, offset, limit);
int total = this.cmsContentMapper.getPageListByUserPurchasesCount(userId);
// 构建分页结果
PageInfo<CmsContent> 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<CmsContent> 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<CmsContent> list = this.cmsContentMapper.getPageListByUserCreated(userId, queryDto.getPublishStatus(), offset, limit);
int total = this.cmsContentMapper.getPageListByUserCreatedCount(userId, queryDto.getPublishStatus());
// 构建分页结果
PageInfo<CmsContent> pageInfo = new PageInfo<>(list);
pageInfo.setTotal(total);
pageInfo.setPageNum(queryDto.getPageNum());
pageInfo.setPageSize(queryDto.getPageSize());
pageInfo.setPages((total + limit - 1) / limit);
return pageInfo;
}
}

View File

@ -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;
}

View File

@ -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<String, String> 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 "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名为空]]></return_msg></xml>";
}
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 "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
@ -224,6 +248,14 @@ public class PayServiceImpl implements PayService {
} catch (Exception e) {
logger.error("处理微信支付回调失败", e);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>";
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
logger.error("关闭流失败", e);
}
}
}
}
@ -286,7 +318,9 @@ public class PayServiceImpl implements PayService {
// 获取回调参数
Map<String, String> 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";

View File

@ -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);
}
}
}

View File

@ -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<CmsTag> 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<String> 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<JSONObject> 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<JSONObject> 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;
}
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -2,7 +2,7 @@
<configuration scan="true" scanPeriod="60 seconds">
<!-- 定义日志文件的存储路径 -->
<property name="LOG_HOME" value="/data/service/logs/yuelong-portal" />
<property name="LOG_HOME" value="/kexue/agentSkills/backend/logs" />
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@ -13,10 +13,10 @@
<!-- 每日滚动文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/hyxp-portal.log</file>
<file>${LOG_HOME}/skill.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天生成一个新的日志文件 -->
<fileNamePattern>${LOG_HOME}/hyxp-portal.%d{yyyy-MM-dd}.log</fileNamePattern>
<fileNamePattern>${LOG_HOME}/skill.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留 30 天的历史日志文件 -->
<maxHistory>30</maxHistory>
</rollingPolicy>

View File

@ -8,6 +8,9 @@
<result property="subtitle" column="subtitle" jdbcType="VARCHAR"/>
<result property="contentType" column="content_type" jdbcType="INTEGER"/>
<result property="summary" column="summary" jdbcType="VARCHAR"/>
<result property="description" column="description" jdbcType="LONGVARCHAR"/>
<result property="requirement" column="requirement" jdbcType="LONGVARCHAR"/>
<result property="introduce" column="introduce" jdbcType="LONGVARCHAR"/>
<result property="content" column="content" jdbcType="LONGVARCHAR"/>
<result property="coverImage" column="cover_image" jdbcType="VARCHAR"/>
<result property="authorId" column="author_id" jdbcType="BIGINT"/>
@ -43,7 +46,7 @@
<!--查询单个-->
<select id="queryById" resultMap="CmsContentMap">
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
@ -53,7 +56,7 @@
<!--查询分页列表-->
<select id="getPageList" resultMap="CmsContentMap">
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 id="getPageListWithPagination" resultMap="CmsContentMap">
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
@ -195,7 +198,7 @@
<!--查询列表-->
<select id="getList" resultMap="CmsContentMap">
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 id="insert" keyProperty="contentId" useGeneratedKeys="true">
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})
</insert>
@ -257,6 +260,15 @@
<if test="summary != null">
summary = #{summary},
</if>
<if test="description != null">
description = #{description},
</if>
<if test="requirement != null">
requirement = #{requirement},
</if>
<if test="introduce != null">
introduce = #{introduce},
</if>
<if test="content != null">
content = #{content},
</if>
@ -391,4 +403,92 @@
limit #{limit}
</select>
<!--获取用户历史查看的内容列表-->
<select id="getPageListByUserHistory" resultMap="CmsContentMap">
select
c.content_id, c.title, c.subtitle, c.content_type, c.summary, c.description, c.requirement, c.introduce, c.content, c.cover_image, c.author_id, c.author_name,
c.reviewer_id, c.reviewer_name, c.audit_status, c.audit_comment, c.publish_status, c.publish_time,
c.view_count, c.like_count, c.comment_count, c.sort, c.is_paid, c.price, c.required_points, c.support_points_pay, c.is_official, c.share_count, c.file_url, c.icon, c.background, c.origin, c.tags, c.create_time, c.update_time, c.create_by, c.update_by, c.delete_flag
from cms_content c
inner join cms_content_view cv on c.content_id = cv.content_id
where cv.user_id = #{userId} and c.delete_flag = 0
order by cv.view_time desc
limit #{offset}, #{limit}
</select>
<!--获取用户历史查看的内容总数-->
<select id="getPageListByUserHistoryCount" resultType="int">
select count(distinct c.content_id)
from cms_content c
inner join cms_content_view cv on c.content_id = cv.content_id
where cv.user_id = #{userId} and c.delete_flag = 0
</select>
<!--获取用户收藏的内容列表-->
<select id="getPageListByUserFavorites" resultMap="CmsContentMap">
select
c.content_id, c.title, c.subtitle, c.content_type, c.summary, c.description, c.requirement, c.introduce, c.content, c.cover_image, c.author_id, c.author_name,
c.reviewer_id, c.reviewer_name, c.audit_status, c.audit_comment, c.publish_status, c.publish_time,
c.view_count, c.like_count, c.comment_count, c.sort, c.is_paid, c.price, c.required_points, c.support_points_pay, c.is_official, c.share_count, c.file_url, c.icon, c.background, c.origin, c.tags, c.create_time, c.update_time, c.create_by, c.update_by, c.delete_flag
from cms_content c
inner join cms_content_like cl on c.content_id = cl.content_id
where cl.user_id = #{userId} and c.delete_flag = 0
order by cl.like_time desc
limit #{offset}, #{limit}
</select>
<!--获取用户收藏的内容总数-->
<select id="getPageListByUserFavoritesCount" resultType="int">
select count(distinct c.content_id)
from cms_content c
inner join cms_content_like cl on c.content_id = cl.content_id
where cl.user_id = #{userId} and c.delete_flag = 0
</select>
<!--获取用户购买的内容列表-->
<select id="getPageListByUserPurchases" resultMap="CmsContentMap">
select
c.content_id, c.title, c.subtitle, c.content_type, c.summary, c.description, c.requirement, c.introduce, c.content, c.cover_image, c.author_id, c.author_name,
c.reviewer_id, c.reviewer_name, c.audit_status, c.audit_comment, c.publish_status, c.publish_time,
c.view_count, c.like_count, c.comment_count, c.sort, c.is_paid, c.price, c.required_points, c.support_points_pay, c.is_official, c.share_count, c.file_url, c.icon, c.background, c.origin, c.tags, c.create_time, c.update_time, c.create_by, c.update_by, c.delete_flag
from cms_content c
inner join content_purchase cp on c.content_id = cp.content_id
where cp.user_id = #{userId} and cp.status = 1 and c.delete_flag = 0
order by cp.purchase_time desc
limit #{offset}, #{limit}
</select>
<!--获取用户购买的内容总数-->
<select id="getPageListByUserPurchasesCount" resultType="int">
select count(distinct c.content_id)
from cms_content c
inner join content_purchase cp on c.content_id = cp.content_id
where cp.user_id = #{userId} and cp.status = 1 and c.delete_flag = 0
</select>
<!--获取用户创建的内容列表-->
<select id="getPageListByUserCreated" resultMap="CmsContentMap">
select
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
where author_id = #{userId} and delete_flag = 0
<if test="publishStatus != null">
and publish_status = #{publishStatus}
</if>
order by create_time desc
limit #{offset}, #{limit}
</select>
<!--获取用户创建的内容总数-->
<select id="getPageListByUserCreatedCount" resultType="int">
select count(*)
from cms_content
where author_id = #{userId} and delete_flag = 0
<if test="publishStatus != null">
and publish_status = #{publishStatus}
</if>
</select>
</mapper>

View File

@ -8,6 +8,7 @@
<result property="description" column="description" jdbcType="VARCHAR"/>
<result property="useCount" column="use_count" jdbcType="INTEGER"/>
<result property="status" column="status" jdbcType="INTEGER"/>
<result property="icon" column="icon" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="createBy" column="create_by" jdbcType="VARCHAR"/>
@ -18,7 +19,7 @@
<!--查询单个-->
<select id="queryById" resultMap="CmsTagMap">
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
where tag_id = #{tagId}
</select>
@ -26,7 +27,7 @@
<!--查询分页列表-->
<select id="getPageList" resultMap="CmsTagMap">
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
<where>
<if test="tagId != null">
@ -48,7 +49,7 @@
<!--查询列表-->
<select id="getList" resultMap="CmsTagMap">
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
<where>
<if test="tagId != null">
@ -69,8 +70,8 @@
<!--新增所有列-->
<insert id="insert" keyProperty="tagId" useGeneratedKeys="true">
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})
</insert>
<!--通过主键修改数据-->
@ -89,6 +90,9 @@
<if test="status != null">
status = #{status},
</if>
<if test="icon != null">
icon = #{icon},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
@ -114,7 +118,7 @@
<!--根据分类ID查询标签列表-->
<select id="getTagsByCategoryId" resultMap="CmsTagMap">
select
t.tag_id, t.tag_name, t.description, t.use_count, t.status, t.create_time, t.update_time, t.create_by, t.update_by, t.delete_flag
t.tag_id, t.tag_name, t.description, t.use_count, t.status, t.icon, t.create_time, t.update_time, t.create_by, t.update_by, t.delete_flag
from cms_tag t
inner join cms_category_tag ct on t.tag_id = ct.tag_id
where ct.category_id = #{categoryId} and t.delete_flag = 0 and t.status = 1

View File

@ -11,8 +11,8 @@
<result property="payType" column="pay_type" jdbcType="INTEGER"/>
<result property="status" column="status" jdbcType="INTEGER"/>
<result property="channelOrderNo" column="channel_order_no" jdbcType="VARCHAR"/>
<result property="BusinessId" column="business_id" jdbcType="BIGINT"/>
<result property="BusinessType" column="business_type" jdbcType="VARCHAR"/>
<result property="businessId" column="business_id" jdbcType="BIGINT"/>
<result property="businessType" column="business_type" jdbcType="VARCHAR"/>
<result property="remark" column="remark" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
@ -118,7 +118,7 @@
<if test="userId != null">user_id,</if>
<if test="userName != null">user_name,</if>
<if test="amount != null">amount,</if>
<if test="payType != null">pay_type,</if>
pay_type,
<if test="status != null">status,</if>
<if test="channelOrderNo != null">channel_order_no,</if>
<if test="businessId != null">business_id,</if>
@ -135,7 +135,7 @@
<if test="userId != null">#{userId},</if>
<if test="userName != null">#{userName},</if>
<if test="amount != null">#{amount},</if>
<if test="payType != null">#{payType},</if>
#{payType},
<if test="status != null">#{status},</if>
<if test="channelOrderNo != null">#{channelOrderNo},</if>
<if test="businessId != null">#{businessId},</if>