From 3df611f809478836b2409ea5b92938b1242f16d3 Mon Sep 17 00:00:00 2001 From: wangzhiwei Date: Mon, 23 Mar 2026 11:38:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(content):=20=E6=B7=BB=E5=8A=A0=E4=BB=8E?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E5=AF=BC=E5=85=A5Excel=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E5=86=85=E5=AE=B9=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增从指定目录批量导入Excel数据到CmsContent的功能 - 添加ImportPathDto请求参数实体类 - 实现importFromPath方法支持目录遍历和文件批量导入 - 添加truncateTable方法用于清空表数据 - 优化Excel导入逻辑增加异常处理和空值检查 - 调整批量处理大小从100改为10 - 更新审核状态和发布状态的描述文案 - 修复分享次数和官方标识字段的默认值设置 - 将Servlet API从javax迁移到jakarta - 更新README.md完善项目文档 - 优化技能解析逻辑支持多层级目录结构 - 修复AI模型生成中的标签选择和参数验证问题 --- README.md | 193 ++++++++++++++- pom.xml | 8 +- .../skills/common/util/SkillZipParser.java | 193 +++++++-------- .../controller/CmsContentController.java | 21 ++ .../skills/controller/PayController.java | 4 +- .../skills/controller/SkillGenController.java | 12 - .../skills/controller/SysUserController.java | 7 +- .../com/kexue/skills/entity/CmsContent.java | 6 +- .../skills/entity/request/ImportPathDto.java | 26 ++ .../entity/request/SysUserUpdateDto.java | 38 +++ .../kexue/skills/mapper/CmsContentMapper.java | 7 + .../skills/service/CmsContentService.java | 10 + .../com/kexue/skills/service/PayService.java | 2 +- .../kexue/skills/service/SysUserService.java | 9 + .../service/impl/CmsContentServiceImpl.java | 227 ++++++++++++------ .../skills/service/impl/PayServiceImpl.java | 4 +- .../service/impl/SkillGenServiceImpl.java | 34 ++- .../service/impl/SysUserServiceImpl.java | 66 ++++- .../resources/mapper/CmsContentMapper.xml | 5 + 19 files changed, 667 insertions(+), 205 deletions(-) create mode 100644 src/main/java/com/kexue/skills/entity/request/ImportPathDto.java create mode 100644 src/main/java/com/kexue/skills/entity/request/SysUserUpdateDto.java diff --git a/README.md b/README.md index c6f074f..dd82440 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,192 @@ -# agent-skill-backend +# 可学AI-skills平台后端 -agent-skill-backend +## 项目简介 + +可学AI-skills平台是一个基于Spring Boot的智能技能管理系统,提供技能生成、内容管理、用户认证、支付等功能。 + +## 技术栈 + +- **基础框架**:Spring Boot 3.2.2 +- **持久层**:MyBatis 3.0.3 +- **数据库**:MySQL +- **缓存**:Redis +- **认证**:Sa-Token 1.38.0 +- **模板引擎**:Thymeleaf +- **API文档**:Swagger 3.0.0 +- **文件处理**:sevenzipjbinding 16.02-2.01 +- **短信服务**:SMS4J 3.3.5 +- **分布式锁**:Redisson 3.23.5 +- **AI集成**:DeepSeek、GLM-4.6v + +## 项目结构 + +``` +backend/ +├── .mvn/ # Maven包装器 +├── db/ # 数据库脚本 +├── src/ +│ ├── main/ +│ │ ├── java/com/kexue/skills/ # 主源码 +│ │ │ ├── annotation/ # 自定义注解 +│ │ │ ├── aspect/ # AOP切面 +│ │ │ ├── common/ # 通用工具和常量 +│ │ │ ├── config/ # 配置类 +│ │ │ ├── controller/ # 控制器 +│ │ │ ├── entity/ # 实体类 +│ │ │ ├── exception/ # 异常处理 +│ │ │ ├── interceptor/ # 拦截器 +│ │ │ ├── mapper/ # 数据访问层 +│ │ │ ├── service/ # 服务层 +│ │ │ ├── task/ # 定时任务 +│ │ │ ├── utils/ # 工具类 +│ │ │ └── SkillsApp.java # 应用入口 +│ │ └── resources/ # 资源文件 +│ │ ├── mapper/ # MyBatis映射文件 +│ │ ├── sql/ # SQL脚本 +│ │ ├── static/ # 静态资源 +│ │ ├── templates/ # Thymeleaf模板 +│ │ ├── application-*.yml # 配置文件 +│ │ └── logback-spring.xml # 日志配置 +│ └── test/ # 测试代码 +├── .gitignore # Git忽略文件 +├── Dockerfile # Docker构建文件 +├── README.md # 项目说明 +├── mvnw.cmd # Maven包装器脚本 +└── pom.xml # Maven依赖配置 +``` + +## 核心功能 + +### 1. 用户认证与授权 +- 基于Sa-Token的认证系统 +- 支持账号密码登录 +- 支持手机验证码登录 +- 角色权限管理 +- 防重复提交 + +### 2. 内容管理系统 +- 内容分类管理 +- 内容标签管理 +- 内容发布与管理 +- 内容点赞与浏览统计 + +### 3. 技能生成系统 +- 技能上传与解析(支持RAR等压缩格式) +- 技能结构分析 +- 技能介绍生成 + +### 4. 支付系统 +- 微信支付集成 +- 支付宝集成 +- 支付订单管理 + +### 5. 账户管理 +- 账户余额管理 +- 积分管理 +- 交易记录 + +### 6. 系统管理 +- 菜单管理 +- 角色管理 +- 字典管理 +- 系统日志 + +### 7. AI集成 +- DeepSeek模型集成 +- GLM-4.6v模型集成 +- 智能内容生成 + +## 快速开始 + +### 环境要求 +- JDK 17+ +- Maven 3.6+ +- MySQL 5.7+ +- Redis 5.0+ + +### 配置说明 +1. 修改 `application-dev.yml` 文件中的数据库连接信息 +2. 修改 `application.yml` 文件中的Redis连接信息 +3. 修改 `application.yml` 文件中的AI模型API密钥 +4. 修改 `application.yml` 文件中的短信服务配置 + +### 数据库初始化 +1. 执行 `db/create_tables.sql` 创建数据库表 +2. 执行 `db/init_data.sql` 初始化基础数据 + +### 启动项目 +```bash +# 编译项目 +mvn clean compile + +# 运行项目 +mvn spring-boot:run +``` + +### 访问地址 +- 项目首页:http://localhost:8080 +- Swagger文档:http://localhost:8080/doc.html + +## 主要API + +### 用户认证 +- `POST /api/login` - 用户登录 +- `POST /api/logout` - 用户登出 +- `GET /api/currentUser` - 获取当前用户信息 + +### 内容管理 +- `GET /api/cms/content/list` - 获取内容列表 +- `POST /api/cms/content/save` - 保存内容 +- `DELETE /api/cms/content/delete` - 删除内容 + +### 技能管理 +- `POST /api/skill/upload` - 上传技能 +- `POST /api/skill/analyze` - 分析技能结构 +- `POST /api/skill/genIntroduce` - 生成技能介绍 + +### 支付管理 +- `POST /api/pay/wx` - 微信支付 +- `POST /api/pay/alipay` - 支付宝支付 +- `GET /api/payment/order/list` - 获取支付订单列表 + +## 部署说明 + +### Docker部署 +1. 构建Docker镜像 +```bash +docker build -t agent-skills . +``` + +2. 运行Docker容器 +```bash +docker run -p 8080:8080 --name agent-skills agent-skills +``` + +### 生产环境部署 +1. 打包项目 +```bash +mvn clean package -DskipTests + +配置文件直接打在jar包内 +``` + +2. 部署jar包 +```bash +java -jar agentSkills.jar --spring.profiles.active=prod +或者执行脚本启动 +./start.sh +``` + +## 注意事项 + +1. 项目使用Redis作为缓存,需要确保Redis服务正常运行 +2. 项目使用阿里云短信服务,需要配置相关参数 +3. 项目使用AI模型API,需要配置相关API密钥 + +## 许可证 + +本项目仅供内部使用,未经授权不得用于商业用途。 + +## 联系方式 + +如有问题,请联系项目维护人员。 diff --git a/pom.xml b/pom.xml index 5f44968..a5089f3 100644 --- a/pom.xml +++ b/pom.xml @@ -146,11 +146,11 @@ 1.3.2 - + - javax.servlet - javax.servlet-api - 4.0.1 + jakarta.servlet + jakarta.servlet-api + 6.0.0 provided diff --git a/src/main/java/com/kexue/skills/common/util/SkillZipParser.java b/src/main/java/com/kexue/skills/common/util/SkillZipParser.java index 36ab985..2ebdb5c 100644 --- a/src/main/java/com/kexue/skills/common/util/SkillZipParser.java +++ b/src/main/java/com/kexue/skills/common/util/SkillZipParser.java @@ -84,93 +84,7 @@ public class SkillZipParser { return generateYaml(skillStructure); } - /** - * 解析skill zip包并生成yaml描述 - * @param zipFilePath zip文件路径 - * @param author 作者 - * @param defaultSkillName 默认技能名称 - * @return 包含技能信息和yaml描述的Map - * @throws IOException 解析过程中的IO异常 - * @throws SevenZipException 解析RAR文件时的异常 - */ - public static Map parseSkillZip(String zipFilePath, String author, String defaultSkillName) throws IOException, SevenZipException { - // 检查文件扩展名 - String fileExtension = ""; - if (zipFilePath.contains(".")) { - int dotIndex = zipFilePath.lastIndexOf("."); - fileExtension = zipFilePath.substring(dotIndex).toLowerCase(); - } - - // 根据文件类型选择不同的解析方法 - if (".rar".equals(fileExtension)) { - return parseRarFile(zipFilePath, author, defaultSkillName); - } else { - // 默认为zip文件 - try (ZipFile zipFile = new ZipFile(zipFilePath, StandardCharsets.UTF_8)) { - // 从压缩文件中提取技能信息 - Map skillInfo = extractSkillInfo(zipFile, defaultSkillName); - String skillName = (String) skillInfo.get("name"); - String skillDescription = (String) skillInfo.get("description"); - List skillTags = (List) skillInfo.get("tags"); - - // 生成技能包结构 - Map skillStructure = generateSkillStructure(zipFile, skillName, skillDescription, skillTags, author); - - // 生成yaml - String yamlContent = generateYaml(skillStructure); - - // 构建返回结果 - Map result = new LinkedHashMap<>(); - result.put("name", skillName); - result.put("description", skillDescription); - result.put("tags", skillTags); - result.put("yamlContent", yamlContent); - // 包含md文件的完整内容 - if (skillInfo.containsKey("skillMdText")) { - result.put("skillMdText", skillInfo.get("skillMdText")); - } - - return result; - } - } - } - - /** - * 解析rar文件 - * @param rarFilePath rar文件路径 - * @param author 作者 - * @param defaultSkillName 默认技能名称 - * @return 技能信息 - * @throws IOException 解析过程中的IO异常 - * @throws SevenZipException 解析RAR文件时的异常 - */ - private static Map parseRarFile(String rarFilePath, String author, String defaultSkillName) throws IOException, SevenZipException { - // 从rar文件中提取技能信息 - Map skillInfo = extractSkillInfoFromRar(rarFilePath, defaultSkillName); - String skillName = (String) skillInfo.get("name"); - String skillDescription = (String) skillInfo.get("description"); - List skillTags = (List) skillInfo.get("tags"); - // 生成技能包结构 - Map skillStructure = generateSkillStructureFromRar(rarFilePath, skillName, skillDescription, skillTags, author); - - // 生成yaml - String yamlContent = generateYaml(skillStructure); - - // 构建返回结果 - Map result = new LinkedHashMap<>(); - result.put("name", skillName); - result.put("description", skillDescription); - result.put("tags", skillTags); - result.put("yamlContent", yamlContent); - // 包含md文件的完整内容 - if (skillInfo.containsKey("skillMdText")) { - result.put("skillMdText", skillInfo.get("skillMdText")); - } - - return result; - } - /** * 从rar文件中提取技能信息 * @param rarFilePath rar文件路径 @@ -197,7 +111,7 @@ public class SkillZipParser { // 使用简单接口 ISimpleInArchive simpleInArchive = archive.getSimpleInterface(); - // 遍历所有文件条目 + // 首先尝试在根目录查找md文件 for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { // 检查是否是根目录下的md文件 String path = item.getPath(); @@ -224,6 +138,55 @@ public class SkillZipParser { break; // 找到一个md文件后就停止 } } + + // 如果根目录没有找到md文件,检查根目录下的文件夹 + if (!foundMdFile) { + // 收集根目录下的文件夹 + List rootFolders = new ArrayList<>(); + for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { + if (item.isFolder()) { + String path = item.getPath(); + // 确保是根目录下的文件夹(路径中不包含/) + if (!path.contains("/") && !path.contains("\\")) { + rootFolders.add(path); + } + } + } + + // 检查每个根目录文件夹下的md文件 + for (String folder : rootFolders) { + for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { + if (!item.isFolder()) { + String path = item.getPath(); + // 检查是否是该文件夹下的md文件 + if (path.endsWith(".md") && (path.equals(folder + "/skill.md") || path.equals(folder + "/SKILL.md") || + path.equals(folder + "/readme.md") || path.equals(folder + "/README.md"))) { + // 读取文件内容 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + item.extractSlow(data -> { + try { + outputStream.write(data); + } catch (IOException e) { + throw new RuntimeException(e); + } + return data.length; + }); + + String content = outputStream.toString(StandardCharsets.UTF_8); + + // 存储md文件的完整内容 + skillInfo.put("skillMdText", content); + + // 解析md内容 + parseSkillsMd(content, skillInfo); + foundMdFile = true; + break; // 找到一个md文件后就停止 + } + } + } + if (foundMdFile) break; // 找到一个md文件后就停止 + } + } } // 如果没有找到 md 文件,使用默认值 @@ -276,17 +239,59 @@ public class SkillZipParser { } } } + + // 如果根目录没有找到md文件,检查根目录下的文件夹 + if (!foundMdFile) { + // 收集根目录下的文件夹 + List rootFolders = new ArrayList<>(); + entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + String path = entry.getName(); + // 确保是根目录下的文件夹(路径中不包含/,或者以/结尾且前面没有/) + if (path.endsWith("/") && !path.substring(0, path.length() - 1).contains("/")) { + rootFolders.add(path.substring(0, path.length() - 1)); + } + } + } + + // 检查每个根目录文件夹下的md文件 + for (String folder : rootFolders) { + entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!entry.isDirectory()) { + String path = entry.getName(); + // 检查是否是该文件夹下的md文件 + if (path.endsWith(".md") && (path.equals(folder + "/skill.md") || path.equals(folder + "/SKILL.md") || + path.equals(folder + "/readme.md") || path.equals(folder + "/README.md"))) { + try (InputStream inputStream = zipFile.getInputStream(entry); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder content = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + + // 存储md文件的完整内容 + skillInfo.put("skillMdText", content.toString()); + + // 解析md内容 + parseSkillsMd(content.toString(), skillInfo); + foundMdFile = true; + break; // 找到一个md文件后就停止 + } + } + } + } + if (foundMdFile) break; // 找到一个md文件后就停止 + } + } // 如果没有找到 md 文件,使用默认值 if (!foundMdFile) { skillInfo.put("name", defaultSkillName); - skillInfo.put("description", "Skill uploaded "); - skillInfo.put("tags", Arrays.asList("10001", "10002")); - } else { - // 确保 tags 不为 null - if (!skillInfo.containsKey("tags")) { - skillInfo.put("tags", Arrays.asList("10001")); - } } return skillInfo; diff --git a/src/main/java/com/kexue/skills/controller/CmsContentController.java b/src/main/java/com/kexue/skills/controller/CmsContentController.java index 75b94ac..ae7fef4 100644 --- a/src/main/java/com/kexue/skills/controller/CmsContentController.java +++ b/src/main/java/com/kexue/skills/controller/CmsContentController.java @@ -8,6 +8,7 @@ import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.base.IdDto; import com.kexue.skills.entity.dto.CmsContentDto; import com.kexue.skills.entity.dto.QueryContentDto; +import com.kexue.skills.entity.request.ImportPathDto; import com.kexue.skills.service.CmsContentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -312,4 +313,24 @@ public class CmsContentController { String title = cmsContentService.getTitle(contentId); return CommonResult.success(title); } + + /** + * 从指定目录导入Excel数据到CmsContent + * + * @param importPathDto 导入路径请求参数 + * @param createBy 创建人 + * @return 导入结果 + */ + @PostMapping("/importFromPath") + @Operation(summary = "从目录导入Excel数据", description = "从指定目录导入Excel数据到CmsContent") + @RequireAuth + public CommonResult importFromPath(@RequestBody ImportPathDto importPathDto, @RequestParam("createBy") String createBy) { + try { + int successCount = cmsContentService.importFromPath(importPathDto, createBy); + return CommonResult.success(successCount); + } catch (Exception e) { + e.printStackTrace(); + return CommonResult.failed("导入失败:" + e.getMessage()); + } + } } \ 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 9ab4f2d..3a6656b 100644 --- a/src/main/java/com/kexue/skills/controller/PayController.java +++ b/src/main/java/com/kexue/skills/controller/PayController.java @@ -10,8 +10,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.util.Map; /** diff --git a/src/main/java/com/kexue/skills/controller/SkillGenController.java b/src/main/java/com/kexue/skills/controller/SkillGenController.java index 46fcc55..5472534 100644 --- a/src/main/java/com/kexue/skills/controller/SkillGenController.java +++ b/src/main/java/com/kexue/skills/controller/SkillGenController.java @@ -42,18 +42,6 @@ public class SkillGenController { 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 = "生成技能", description = "生成技能") public CommonResult generate(@RequestBody SkillGenRequest request) { diff --git a/src/main/java/com/kexue/skills/controller/SysUserController.java b/src/main/java/com/kexue/skills/controller/SysUserController.java index 72f2499..e3b32a5 100644 --- a/src/main/java/com/kexue/skills/controller/SysUserController.java +++ b/src/main/java/com/kexue/skills/controller/SysUserController.java @@ -3,9 +3,7 @@ package com.kexue.skills.controller; import com.kexue.skills.annotation.RequireAuth; import com.kexue.skills.entity.SysUser; import com.kexue.skills.entity.dto.SysUserDto; -import com.kexue.skills.entity.request.ResetPasswordDto; -import com.kexue.skills.entity.request.ResetPwdDto; -import com.kexue.skills.entity.request.AdminResetPasswordDto; +import com.kexue.skills.entity.request.*; import com.kexue.skills.exception.BizException; import com.kexue.skills.service.SysUserService; import org.springframework.web.bind.annotation.*; @@ -17,7 +15,6 @@ import com.kexue.skills.common.CacheManager; import com.github.pagehelper.PageInfo; import com.kexue.skills.common.CommonResult; import com.kexue.skills.entity.base.IdDto; -import com.kexue.skills.entity.request.LoginUserDto; import org.redisson.api.RedissonClient; /** @@ -86,7 +83,7 @@ public class SysUserController { */ @PostMapping("/update") @Operation(summary = "更新用户", description = "更新用户") - public CommonResult update(@RequestBody SysUser SysUser) { + public CommonResult update(@RequestBody SysUserUpdateDto SysUser) { return CommonResult.success(sysUserService.update(SysUser)); } diff --git a/src/main/java/com/kexue/skills/entity/CmsContent.java b/src/main/java/com/kexue/skills/entity/CmsContent.java index 3b71248..b5314e6 100644 --- a/src/main/java/com/kexue/skills/entity/CmsContent.java +++ b/src/main/java/com/kexue/skills/entity/CmsContent.java @@ -97,14 +97,14 @@ public class CmsContent extends BaseEntity implements Serializable { @Schema(description ="审核人名称") private String reviewerName; - @Schema(description ="审核状态(1草稿,2待审核,3审核通过,4审核拒绝)") + @Schema(description ="审核状态(1未发布,2待审核,3审核通过,4审核未通过)") private Integer auditStatus; @Schema(description ="审核意见") private String auditComment; - @Schema(description ="发布状态(1未发布,2已发布,3已下架)") - private Integer publishStatus; + @Schema(description ="发布状态(1未发布,2已发布,3已下架)--> 公有还是私有:1私有,2公有") + private Integer publishStatus; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Schema(description ="发布时间") diff --git a/src/main/java/com/kexue/skills/entity/request/ImportPathDto.java b/src/main/java/com/kexue/skills/entity/request/ImportPathDto.java new file mode 100644 index 0000000..29716bf --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/ImportPathDto.java @@ -0,0 +1,26 @@ +package com.kexue.skills.entity.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 导入路径请求参数 + * + * @author 王志维 + * @since 2025-02-21 23:01:48 + */ +@Data +@Schema(name = "ImportPathDto", description = "导入路径请求参数") +public class ImportPathDto implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "是否追加,true表示追加,false表示清空表") + private boolean append; + + @Schema(description = "文件目录") + private String filePath; + +} diff --git a/src/main/java/com/kexue/skills/entity/request/SysUserUpdateDto.java b/src/main/java/com/kexue/skills/entity/request/SysUserUpdateDto.java new file mode 100644 index 0000000..6fcd206 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/SysUserUpdateDto.java @@ -0,0 +1,38 @@ +package com.kexue.skills.entity.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户更新请求参数 + * + * @author 王志维 + * @since 2025-02-21 23:01:48 + */ +@Data +@Schema(name = "SysUserUpdateDto", description = "用户更新请求参数") +public class SysUserUpdateDto implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户名") + private String userName; + + @Schema(description = "密码") + private String password; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "手机号") + private String tel; + + @Schema(description = "状态(1-正常,0-禁用)") + private Integer enable; + +} diff --git a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java index c05c1f8..7898f6c 100644 --- a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java +++ b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java @@ -225,4 +225,11 @@ public interface CmsContentMapper { * @return 总记录数 */ int getPageListByUserCreatedCount(@Param("userId") Long userId, @Param("publishStatus") Integer publishStatus); + + /** + * 清空表数据 + * + * @return 影响行数 + */ + int truncateTable(); } \ 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 44c80c4..a1abb98 100644 --- a/src/main/java/com/kexue/skills/service/CmsContentService.java +++ b/src/main/java/com/kexue/skills/service/CmsContentService.java @@ -3,6 +3,7 @@ package com.kexue.skills.service; import com.github.pagehelper.PageInfo; import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.dto.CmsContentDto; +import com.kexue.skills.entity.request.ImportPathDto; import java.util.List; @@ -191,4 +192,13 @@ public interface CmsContentService extends BaseService { * @return title字段的内容 */ String getTitle(Long contentId); + + /** + * 从指定目录导入Excel数据到CmsContent + * + * @param importPathDto 导入路径请求参数 + * @param createBy 创建人 + * @return 导入结果 + */ + int importFromPath(ImportPathDto importPathDto, String createBy); } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/PayService.java b/src/main/java/com/kexue/skills/service/PayService.java index 93db4f1..f62750e 100644 --- a/src/main/java/com/kexue/skills/service/PayService.java +++ b/src/main/java/com/kexue/skills/service/PayService.java @@ -1,7 +1,7 @@ package com.kexue.skills.service; import com.kexue.skills.entity.PaymentOrder; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Map; /** diff --git a/src/main/java/com/kexue/skills/service/SysUserService.java b/src/main/java/com/kexue/skills/service/SysUserService.java index bebcf3e..734d418 100644 --- a/src/main/java/com/kexue/skills/service/SysUserService.java +++ b/src/main/java/com/kexue/skills/service/SysUserService.java @@ -7,6 +7,7 @@ import com.kexue.skills.entity.request.LoginDto; import com.kexue.skills.entity.request.LoginUserDto; import com.kexue.skills.entity.request.PhoneLoginDto; import com.kexue.skills.entity.request.ResetPasswordDto; +import com.kexue.skills.entity.request.SysUserUpdateDto; import java.util.List; @@ -59,6 +60,14 @@ public interface SysUserService extends BaseService { */ SysUser update(SysUser sysUser); + /** + * 修改用户数据 + * + * @param sysUserUpdateDto 用户更新请求参数 + * @return 实例对象 + */ + SysUser update(SysUserUpdateDto sysUserUpdateDto); + /** * 通过主键删除数据 * 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 6aeabd1..cbd47e4 100644 --- a/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java @@ -10,6 +10,7 @@ import com.kexue.skills.entity.CmsContentView; import com.kexue.skills.entity.CmsContentLike; import com.kexue.skills.entity.base.BaseQueryDto; import com.kexue.skills.entity.dto.CmsContentDto; +import com.kexue.skills.entity.request.ImportPathDto; import com.kexue.skills.mapper.CmsContentMapper; import com.kexue.skills.mapper.CmsContentViewMapper; import com.kexue.skills.mapper.CmsContentLikeMapper; @@ -21,6 +22,8 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.math.BigDecimal; import java.util.*; @@ -311,9 +314,16 @@ public class CmsContentServiceImpl implements CmsContentService { if (cmsContent.getCommentCount() == null) { cmsContent.setCommentCount(0); } + if (cmsContent.getShareCount() == null) { + cmsContent.setShareCount(0); + } if (cmsContent.getSort() == null) { cmsContent.setSort(0); } + if (cmsContent.getIsOfficial() == null) { + cmsContent.setIsOfficial(false); + } + cmsContent.setAuthorId(StpUtil.getLoginIdAsLong()); // 保存数据 this.cmsContentMapper.insert(cmsContent); return cmsContent; @@ -729,7 +739,7 @@ public class CmsContentServiceImpl implements CmsContentService { @Override public int importFromExcel(byte[] fileBytes, String createBy) { int successCount = 0; - final int BATCH_SIZE = 100; // 批量处理大小 + final int BATCH_SIZE = 10; // 批量处理大小 try (InputStream inputStream = new ByteArrayInputStream(fileBytes); ExcelReader reader = ExcelUtil.getReader(inputStream)) { @@ -757,76 +767,91 @@ public class CmsContentServiceImpl implements CmsContentService { // 从第二行开始读取数据(第一行为标题行) for (int rowIndex = 1; rowIndex < totalRows; rowIndex++) { - List rowList = reader.readRow(rowIndex); - if (rowList == null || rowList.isEmpty()) { - continue; - } - - // 转换为Map - Map row = new HashMap<>(); - for (int i = 0; i < headerList.size() && i < rowList.size(); i++) { - row.put(headerList.get(i), rowList.get(i)); - } - if (row.isEmpty()) { - continue; - } - - CmsContent cmsContent = new CmsContent(); - - // 设置创建时间和更新时间 - cmsContent.setCreateTime(now); - cmsContent.setUpdateTime(now); - cmsContent.setCreateBy(createBy); - cmsContent.setUpdateBy(createBy); - - // 设置默认值 - cmsContent.setDeleteFlag(0); - cmsContent.setAuditStatus(1); // 默认草稿状态 - cmsContent.setPublishStatus(1); // 默认未发布状态 - cmsContent.setViewCount(0); - cmsContent.setLikeCount(0); - cmsContent.setCommentCount(0); - cmsContent.setSort(0); - - // 读取Excel中的字段 - // content_id - 数据库自动生成,不需要读取 - cmsContent.setTitle(getStringValue(row, "title")); - cmsContent.setTitleEn(getStringValue(row, "title_en")); - cmsContent.setOrigin(getStringValue(row, "origin")); - cmsContent.setTags(Objects.requireNonNull(getStringValue(row, "tags")).replaceAll(" ","")); - cmsContent.setIcon(getStringValue(row, "icon")); - cmsContent.setIsOfficial(getBooleanValue(row, "is_official")); - cmsContent.setPrice(getBigDecimalValue(row, "price")); - cmsContent.setLikeCount(getIntegerValue(row, "like_count")); - cmsContent.setShareCount(getIntegerValue(row, "share_count")); - cmsContent.setContentType(getIntegerValue(row, "content_type")); - cmsContent.setContent(getStringValue(row, "content")); - cmsContent.setContentEn(getStringValue(row, "content_en")); - cmsContent.setAuditStatus(getIntegerValue(row, "audit_status")); - cmsContent.setPublishStatus(getIntegerValue(row, "publish_status")); - cmsContent.setPublishTime(getDateValue(row, "publish_time")); - cmsContent.setViewCount(getIntegerValue(row, "view_count")); - cmsContent.setCommentCount(getIntegerValue(row, "comment_count")); - cmsContent.setIsPaid(getIntegerValue(row, "is_paid")); - cmsContent.setSupportPointsPay(getIntegerValue(row, "support_points_pay")); - cmsContent.setDescription(getStringValue(row, "description")); - cmsContent.setDescriptionEn(getStringValue(row, "description_en")); - cmsContent.setIntroduce(getStringValue(row, "introduce")); - cmsContent.setIntroduceEn(getStringValue(row, "introduce_en")); - // create_time - 由系统生成,不需要读取 - // update_time - 由系统生成,不需要读取 - cmsContent.setDeleteFlag(getIntegerValue(row, "delete_flag")); - - batchList.add(cmsContent); - - // 达到批量大小或最后一行时,执行批量插入 - if (batchList.size() >= BATCH_SIZE || rowIndex == totalRows - 1) { - if (!batchList.isEmpty()) { - // 执行批量插入 - this.cmsContentMapper.batchInsert(batchList); - successCount += batchList.size(); - batchList.clear(); // 清空批次 + try { + List rowList = reader.readRow(rowIndex); + if (rowList == null || rowList.isEmpty()) { + continue; } + + // 转换为Map + Map row = new HashMap<>(); + for (int i = 0; i < headerList.size() && i < rowList.size(); i++) { + row.put(headerList.get(i), rowList.get(i)); + } + if (row.isEmpty()) { + continue; + } + + CmsContent cmsContent = new CmsContent(); + + // 设置创建时间和更新时间 + cmsContent.setCreateTime(now); + cmsContent.setUpdateTime(now); + cmsContent.setCreateBy(createBy); + cmsContent.setUpdateBy(createBy); + + // 设置默认值 + cmsContent.setDeleteFlag(0); + cmsContent.setAuditStatus(1); // 默认草稿状态 + cmsContent.setPublishStatus(1); // 默认未发布状态 + cmsContent.setViewCount(0); + cmsContent.setLikeCount(0); + cmsContent.setCommentCount(0); + cmsContent.setSort(0); + + // 读取Excel中的字段 + // content_id - 数据库自动生成,不需要读取 + cmsContent.setTitle(getStringValue(row, "title")); + cmsContent.setTitleEn(getStringValue(row, "title_en")); + cmsContent.setOrigin(getStringValue(row, "origin")); + + // 处理tags字段,避免NullPointerException + String tags = getStringValue(row, "tags"); + if (tags == null) { + System.err.println("第 " + rowIndex + " 行数据的tags字段为null,跳过该条记录"); + continue; + } + cmsContent.setTags(tags.replaceAll(" ","")); + + cmsContent.setIcon(getStringValue(row, "icon")); + cmsContent.setIsOfficial(getBooleanValue(row, "is_official")); + cmsContent.setPrice(getBigDecimalValue(row, "price")); + cmsContent.setLikeCount(getIntegerValue(row, "like_count")); + cmsContent.setShareCount(getIntegerValue(row, "share_count")); + cmsContent.setContentType(getIntegerValue(row, "content_type")); + cmsContent.setContent(getStringValue(row, "content")); + cmsContent.setContentEn(getStringValue(row, "content_en")); + cmsContent.setAuditStatus(getIntegerValue(row, "audit_status")); + cmsContent.setPublishStatus(getIntegerValue(row, "publish_status")); + cmsContent.setPublishTime(getDateValue(row, "publish_time")); + cmsContent.setViewCount(getIntegerValue(row, "view_count")); + cmsContent.setCommentCount(getIntegerValue(row, "comment_count")); + cmsContent.setIsPaid(getIntegerValue(row, "is_paid")); + cmsContent.setSupportPointsPay(getIntegerValue(row, "support_points_pay")); + cmsContent.setDescription(getStringValue(row, "description")); + cmsContent.setDescriptionEn(getStringValue(row, "description_en")); + cmsContent.setIntroduce(getStringValue(row, "introduce")); + cmsContent.setIntroduceEn(getStringValue(row, "introduce_en")); + // create_time - 由系统生成,不需要读取 + // update_time - 由系统生成,不需要读取 + cmsContent.setDeleteFlag(getIntegerValue(row, "delete_flag")); + + batchList.add(cmsContent); + + // 达到批量大小或最后一行时,执行批量插入 + if (batchList.size() >= BATCH_SIZE || rowIndex == totalRows - 1) { + if (!batchList.isEmpty()) { + // 执行批量插入 + this.cmsContentMapper.batchInsert(batchList); + successCount += batchList.size(); + batchList.clear(); // 清空批次 + } + } + } catch (Exception e) { + System.err.println("处理第 " + rowIndex + " 行数据时出错: " + e.getMessage()); + e.printStackTrace(); + // 跳过当前行,继续处理下一行 + continue; } } } catch (Exception e) { @@ -942,4 +967,66 @@ public class CmsContentServiceImpl implements CmsContentService { } return cmsContent.getTitle(); } + + @Override + public int importFromPath(ImportPathDto importPathDto, String createBy) { + int totalSuccessCount = 0; + + try { + // 检查目录是否存在 + File directory = new File(importPathDto.getFilePath()); + if (!directory.exists() || !directory.isDirectory()) { + System.err.println("目录不存在或不是有效目录: " + importPathDto.getFilePath()); + return 0; + } + + // 如果不是追加模式,清空表 + if (!importPathDto.isAppend()) { + cmsContentMapper.truncateTable(); + } + + // 读取目录下所有 Excel 文件(排除 Office 临时锁定文件) + File[] files = directory.listFiles((dir, name) -> { + // 跳过以 ~$ 开头的临时锁定文件 + if (name.startsWith("~$")) { + return false; + } + return name.endsWith(".xls") || name.endsWith(".xlsx"); + }); + if (files == null || files.length == 0) { + return 0; + } + + // 记录文件总数 + int totalFiles = files.length; + System.out.println("总共发现 " + totalFiles + " 个 Excel 文件需要导入"); + + // 遍历所有 Excel 文件并导入 + for (int i = 0; i < files.length; i++) { + File file = files[i]; + System.out.println("当前处理第 " + (i + 1) + " 个文件,文件名称是:" + file.getName()); + try (FileInputStream fis = new FileInputStream(file)) { + // 读取文件内容到字节数组 + byte[] fileBytes = new byte[(int) file.length()]; + fis.read(fileBytes); + + // 调用现有的 importFromExcel 方法进行导入 + int successCount = importFromExcel(fileBytes, createBy); + totalSuccessCount += successCount; + System.out.println("第 " + (i + 1) + " 个文件导入成功,导入了 " + successCount + " 条记录"); + } catch (Exception e) { + System.err.println("导入文件失败: " + file.getAbsolutePath()); + e.printStackTrace(); + // 单个文件导入失败不影响其他文件 + continue; + } + } + System.out.println("导入完成,共处理 " + totalFiles + " 个文件,成功导入 " + totalSuccessCount + " 条记录"); + } catch (Exception e) { + System.err.println("导入操作失败: " + importPathDto.getFilePath()); + e.printStackTrace(); + } + + return totalSuccessCount; + } } \ No newline at end of file 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 d56c554..241c3e7 100644 --- a/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java @@ -14,8 +14,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; 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 8064039..0bc8a99 100644 --- a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java @@ -4,6 +4,7 @@ import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.kexue.skills.common.Assert; import com.kexue.skills.common.util.HttpUtil; import com.kexue.skills.config.DeepSeekConfig; import com.kexue.skills.config.GlmConfig; @@ -140,14 +141,14 @@ public class SkillGenServiceImpl implements SkillGenService { StringBuilder tagsList = new StringBuilder(); for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); - tagsList.append(tag.getTagName()); + tagsList.append(tag.getTagId()+"."+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格式输出,仅包含要求的字段,以中文形式返回。"; + String systemContent = "你是一个专业的AI技能设计助手。请根据agent skills撰写规范,按照用户提出的主题描述及参考文件,生成这个skill的名称、描述,并从以下标签列表中选择至少3个标签:\"" + tagsList.toString() + "\",tags只需要返回序号数组,并简述这个skill的具体价值点。输出json格式,仅输出以上所提到的名称、描述、标签、价值点,节点名称分别为name、description、tags、value_point,节点内容以中文形式返回。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。"; // 准备文件URL列表 List fileUrls = new ArrayList<>(); @@ -198,6 +199,11 @@ public class SkillGenServiceImpl implements SkillGenService { @Override public CmsContent generate(SkillGenRequest request) { log.info("生成技能请求: {}", request); + // 参数验证,确保每个参数都必须传递 + Assert.notEmpty(request.getName(), "技能名称不能为空"); + Assert.notEmpty(request.getDescription(), "技能描述不能为空"); + Assert.notEmpty(request.getTags(), "技能标签不能为空"); + Assert.notEmpty(request.getRequirement(), "技能摘要不能为空"); String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; // 从数据库中读取cms_tag表的标签信息 @@ -223,7 +229,7 @@ public class SkillGenServiceImpl implements SkillGenService { } } } - String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,概要含核心属性:name、version、description、author、created、tags 等,其中 structure 为核心节点,用于描述 skill 包的文件目录结构;structure 下的每个节点均含基础属性:name、type、path、format、description,content 和 children 为互选属性,由 type 决定:type 为 file 时,展示文件具体内容在 content 字段;type 为 directory 时,展示子目录 / 文件节点在 children [] 数组中,无需其他额外说明。"; + String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,概要含核心属性:name、version、description、author、created、tags 等,其中 structure 为核心节点,用于描述 skill 包的文件目录结构;structure 下的每个节点均含基础属性:name、type、path、format、description,content 和 children 为互选属性,由 type 决定:type 为 file 时,展示文件具体内容在 content 字段,必须保证content内容的缩进为2个字符;type 为 directory 时,展示子目录 / 文件节点在 children [] 数组中,无需其他额外说明。"; 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"); @@ -467,7 +473,7 @@ public class SkillGenServiceImpl implements SkillGenService { request.setName(defaultSkillName); request.setDescription("未知"); request.setIntroduce("未知"); - request.setTags(Arrays.asList("10001", "10002")); + request.setTags(Arrays.asList("1001", "1000")); } // 3. 使用SkillGenRequest中的信息生成yaml @@ -482,9 +488,19 @@ public class SkillGenServiceImpl implements SkillGenService { // 4. 生成CmsContent对象 CmsContent cmsContent = getCmsContent(request, yamlContent, StpUtil.getLoginIdAsLong(), ""); - List list = tags.stream().filter(tag -> tag.getTagId() == Long.parseLong(cmsContent.getTags().split(",")[0])).toList(); - if (CollectionUtil.isNotEmpty(list)){ - cmsContent.setIcon(list.get(0).getIcon()); + if (Objects.nonNull(cmsContent.getTags())) { + String s = cmsContent.getTags().split(",")[0]; + long l = 1000;// 默认值 + try { + l = Long.parseLong(s); + } catch (NumberFormatException e) { + // 异常是因为大模型返回的tag带中文了,忽略 + } + long finalL = l; + List list = tags.stream().filter(tag -> tag.getTagId() == finalL).toList(); + if (CollectionUtil.isNotEmpty(list)){ + cmsContent.setIcon(list.get(0).getIcon()); + } } // 删除临时文件 tempFile.delete(); @@ -518,7 +534,7 @@ public class SkillGenServiceImpl implements SkillGenService { - skill的核心描述(description); - 详细功能介绍(introduce); - 从指定标签列表中选取至少3个适配标签(tagList),标签范围:TAG_LIST; - - 选择标签的时候只需要输出对应的标签编号 + - 选择标签的时候只需要输出对应的标签编号,特别注意:只需要标签编号 请将最终结果以JSON格式输出,JSON结构必须包含:name(技能名称),description(技能描述)、introduce(功能介绍)、tagList(标签列表),无需额外说明,仅输出符合要求的JSON内容。 """; systemContent = systemContent.replace("TAG_LIST", tagsList.toString()); @@ -554,7 +570,7 @@ public class SkillGenServiceImpl implements SkillGenService { if (skillJson.containsKey("tagList")) { request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class)); } else { - request.setTags(Arrays.asList("10001", "10002")); + request.setTags(Arrays.asList("1001", "1002")); } return request; } 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 08c1338..5f0d230 100644 --- a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java @@ -16,6 +16,7 @@ import com.kexue.skills.entity.dto.ContentPurchaseDto; import com.kexue.skills.entity.dto.SessionDto; import com.kexue.skills.entity.dto.SysUserDto; import com.kexue.skills.entity.request.*; +import com.kexue.skills.entity.request.SysUserUpdateDto; import com.kexue.skills.mapper.*; import com.kexue.skills.service.SysUserService; import com.kexue.skills.utils.MD5Util; @@ -194,6 +195,69 @@ public class SysUserServiceImpl implements SysUserService { return queryById(sysUser.getUserId()); } + /** + * 修改用户数据 + * + * @param sysUserUpdateDto 用户更新请求参数 + * @return 实例对象 + */ + @Override + @CacheInvalidate(name = "sysUser:", key = "#sysUserUpdateDto.userId") + @CacheInvalidate(name = "sysUser:username:", key = "#sysUserUpdateDto.userName") + public SysUser update(SysUserUpdateDto sysUserUpdateDto) { + if (Objects.nonNull(sysUserUpdateDto.getUserId())){ + // 查询用户信息 + SysUser sysUser = sysUserMapper.queryById(sysUserUpdateDto.getUserId()); + Assert.notNull(sysUser, "用户不存在"); + + // 校验用户名是否已经存在 + if (sysUserUpdateDto.getUserName() != null && !sysUserUpdateDto.getUserName().isEmpty()) { + SysUser existingUser = sysUserMapper.getByUsername(sysUserUpdateDto.getUserName()); + Assert.isTrue(existingUser == null || existingUser.getUserId().equals(sysUserUpdateDto.getUserId()), "用户名已存在"); + sysUser.setUserName(sysUserUpdateDto.getUserName()); + } + + // 更新邮箱 + if (sysUserUpdateDto.getEmail() != null) { + sysUser.setEmail(sysUserUpdateDto.getEmail()); + } + + // 更新手机号 + if (sysUserUpdateDto.getTel() != null) { + sysUser.setTel(sysUserUpdateDto.getTel()); + } + + // 更新状态 + if (sysUserUpdateDto.getEnable() != null) { + sysUser.setEnable(sysUserUpdateDto.getEnable()); + } + + // 更新密码(如果有) + if (sysUserUpdateDto.getPassword() != null && !sysUserUpdateDto.getPassword().isEmpty()) { + try { + // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 + String encryptedPwd = MD5Util.doubleEncrypt(sysUserUpdateDto.getPassword(), sysUser.getSalt()); + sysUser.setPwd(encryptedPwd); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + // 执行更新 + sysUserMapper.update(sysUser); + }else { + // 如果没有用户ID,创建新用户 + SysUser sysUser = new SysUser(); + sysUser.setUserName(sysUserUpdateDto.getUserName()); + sysUser.setEmail(sysUserUpdateDto.getEmail()); + sysUser.setTel(sysUserUpdateDto.getTel()); + sysUser.setEnable(sysUserUpdateDto.getEnable()); + sysUser.setPwd(sysUserUpdateDto.getPassword()); + insert(sysUser); + } + return queryById(sysUserUpdateDto.getUserId()); + } + /** * 通过主键删除数据 * @@ -980,7 +1044,7 @@ public class SysUserServiceImpl implements SysUserService { sessionDto.setNew(false); } else { // 没有会话,创建新的会话ID - sessionId = java.util.UUID.randomUUID().toString().replaceAll("-",""); + sessionId = java.util.UUID.randomUUID().toString(); sysUser.setSessionId(sessionId); sysUserMapper.update(sysUser); sessionDto.setSessionId(sessionId); diff --git a/src/main/resources/mapper/CmsContentMapper.xml b/src/main/resources/mapper/CmsContentMapper.xml index ba5cb35..af064b4 100644 --- a/src/main/resources/mapper/CmsContentMapper.xml +++ b/src/main/resources/mapper/CmsContentMapper.xml @@ -592,5 +592,10 @@ and publish_status = #{publishStatus} + + + + truncate table cms_content +