diff --git a/src/main/java/com/youlai/boot/config/property/CaptchaProperties.java b/src/main/java/com/youlai/boot/config/property/CaptchaProperties.java index fae6c447..4067d51e 100644 --- a/src/main/java/com/youlai/boot/config/property/CaptchaProperties.java +++ b/src/main/java/com/youlai/boot/config/property/CaptchaProperties.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Component; /** * 验证码 属性配置 * - * @author haoxr + * @author Ray.Hao * @since 2023/11/24 */ @Component diff --git a/src/main/java/com/youlai/boot/platform/ai/controller/AiAssistantController.java b/src/main/java/com/youlai/boot/platform/ai/controller/AiAssistantController.java index 040db52c..792ae741 100644 --- a/src/main/java/com/youlai/boot/platform/ai/controller/AiAssistantController.java +++ b/src/main/java/com/youlai/boot/platform/ai/controller/AiAssistantController.java @@ -10,27 +10,21 @@ import com.youlai.boot.platform.ai.model.query.AiAssistantQuery; import com.youlai.boot.platform.ai.model.vo.AiAssistantRecordVO; import com.youlai.boot.platform.ai.service.AiAssistantRecordService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - /** * AI 助手控制器 *

- * 负责 AI 命令的解析、执行、记录管理及回滚操作, - * 表示一次 AI 助手完整的指令生命周期。 + * 负责 AI 命令的解析、执行与记录查询。 * * @author Ray.Hao * @since 3.0.0 */ -@Tag(name = "AI 助手接口") +@Tag(name = "13.AI 助手接口") @RestController @RequestMapping("/api/v1/ai/assistant") @RequiredArgsConstructor @@ -81,29 +75,5 @@ public class AiAssistantController { return PageResult.success(page); } - @Operation(summary = "删除 AI 命令记录") - @DeleteMapping("/records/{ids}") - public Result deleteRecords( - @Parameter(description = "记录ID,多个以英文逗号(,)分割") - @PathVariable String ids - ) { - List idList = Arrays.stream(ids.split(",")) - .filter(s -> s != null && !s.isBlank()) - .map(String::trim) - .map(Long::valueOf) - .collect(Collectors.toList()); - - boolean removed = aiAssistantRecordService.deleteRecords(idList); - return Result.judge(removed); - } - - @Operation(summary = "撤销命令执行") - @PostMapping("/records/{recordId}/rollback") - public Result rollbackCommand( - @Parameter(description = "记录ID") - @PathVariable String recordId - ) { - aiAssistantRecordService.rollbackCommand(recordId); - return Result.success(); - } + // 记录类接口按需扩展,当前开放 parse/execute/records } diff --git a/src/main/java/com/youlai/boot/platform/ai/service/AiAssistantRecordService.java b/src/main/java/com/youlai/boot/platform/ai/service/AiAssistantRecordService.java index e542025c..38340bdb 100644 --- a/src/main/java/com/youlai/boot/platform/ai/service/AiAssistantRecordService.java +++ b/src/main/java/com/youlai/boot/platform/ai/service/AiAssistantRecordService.java @@ -49,18 +49,4 @@ public interface AiAssistantRecordService extends IService { */ IPage getRecordPage(AiAssistantQuery queryParams); - /** - * 删除 AI 助手行为记录。 - * - * @param ids 记录ID列表 - * @return 是否删除成功 - */ - boolean deleteRecords(List ids); - - /** - * 撤销命令执行 - * - * @param logId 记录ID - */ - void rollbackCommand(String logId); } diff --git a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiAssistantRecordServiceImpl.java b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiAssistantRecordServiceImpl.java index 66b0a690..7791a323 100644 --- a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiAssistantRecordServiceImpl.java +++ b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiAssistantRecordServiceImpl.java @@ -299,26 +299,4 @@ public class AiAssistantRecordServiceImpl return this.baseMapper.getRecordPage(page, queryParams); } - @Override - public boolean deleteRecords(List ids) { - if (ids == null || ids.isEmpty()) { - return true; - } - return this.removeByIds(ids); - } - - @Override - public void rollbackCommand(String logId) { - AiAssistantRecord commandRecord = this.getById(logId); - if (commandRecord == null) { - throw new RuntimeException("命令记录不存在"); - } - - if (commandRecord.getExecuteStatus() == null || commandRecord.getExecuteStatus() != 1) { - throw new RuntimeException("只能撤销成功执行的命令"); - } - - log.info("撤销命令执行: logId={}, function={}", logId, commandRecord.getFunctionName()); - throw new UnsupportedOperationException("回滚功能尚未实现"); - } } diff --git a/src/main/java/com/youlai/boot/platform/codegen/controller/CodegenController.java b/src/main/java/com/youlai/boot/platform/codegen/controller/CodegenController.java index de890da4..1f483e60 100644 --- a/src/main/java/com/youlai/boot/platform/codegen/controller/CodegenController.java +++ b/src/main/java/com/youlai/boot/platform/codegen/controller/CodegenController.java @@ -83,8 +83,9 @@ public class CodegenController { @GetMapping("/{tableName}/preview") @Log(value = "预览生成代码", module = LogModuleEnum.OTHER) public Result> getTablePreviewData(@PathVariable String tableName, - @RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType) { - List list = codegenService.getCodegenPreviewData(tableName, pageType); + @RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType, + @RequestParam(value = "type", required = false, defaultValue = "ts") String type) { + List list = codegenService.getCodegenPreviewData(tableName, pageType, type); return Result.success(list); } @@ -92,9 +93,10 @@ public class CodegenController { @GetMapping("/{tableName}/download") @Log(value = "下载代码", module = LogModuleEnum.OTHER) public void downloadZip(HttpServletResponse response, @PathVariable String tableName, - @RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType) { + @RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType, + @RequestParam(value = "type", required = false, defaultValue = "ts") String type) { String[] tableNames = tableName.split(","); - byte[] data = codegenService.downloadCode(tableNames, pageType); + byte[] data = codegenService.downloadCode(tableNames, pageType, type); response.reset(); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(codegenProperties.getDownloadFileName(), StandardCharsets.UTF_8)); diff --git a/src/main/java/com/youlai/boot/platform/codegen/service/CodegenService.java b/src/main/java/com/youlai/boot/platform/codegen/service/CodegenService.java index aa1f4a58..8a028158 100644 --- a/src/main/java/com/youlai/boot/platform/codegen/service/CodegenService.java +++ b/src/main/java/com/youlai/boot/platform/codegen/service/CodegenService.java @@ -29,12 +29,12 @@ public interface CodegenService { * @param tableName 表名 * @return */ - List getCodegenPreviewData(String tableName, String pageType); + List getCodegenPreviewData(String tableName, String pageType, String type); /** * 下载代码 * @param tableNames 表名 * @return */ - byte[] downloadCode(String[] tableNames, String pageType); + byte[] downloadCode(String[] tableNames, String pageType, String type); } diff --git a/src/main/java/com/youlai/boot/platform/codegen/service/impl/CodegenServiceImpl.java b/src/main/java/com/youlai/boot/platform/codegen/service/impl/CodegenServiceImpl.java index 20309ff5..6c2c6d44 100644 --- a/src/main/java/com/youlai/boot/platform/codegen/service/impl/CodegenServiceImpl.java +++ b/src/main/java/com/youlai/boot/platform/codegen/service/impl/CodegenServiceImpl.java @@ -70,6 +70,52 @@ public class CodegenServiceImpl implements CodegenService { return databaseMapper.getTablePage(page, queryParams); } + /** + * 解析前端模板路径 + * + * @param templateName 模板标识 + * @param templateConfig 模板配置 + * @param frontendType 前端类型 + * @return 模板路径 + */ + private String resolveFrontendTemplatePath(String templateName, + CodegenProperties.TemplateConfig templateConfig, + String frontendType) { + if (!"js".equals(frontendType)) { + return templateConfig.getTemplatePath(); + } + if ("API".equals(templateName)) { + return "codegen/api.js.vm"; + } + if ("VIEW".equals(templateName)) { + return "codegen/index.js.vue.vm"; + } + if ("API_TYPES".equals(templateName)) { + return "codegen/api-types.js.vm"; + } + return templateConfig.getTemplatePath(); + } + + /** + * 解析前端文件后缀 + * + * @param templateName 模板标识 + * @param templateConfig 模板配置 + * @param frontendType 前端类型 + * @return 文件后缀 + */ + private String resolveFrontendExtension(String templateName, + CodegenProperties.TemplateConfig templateConfig, + String frontendType) { + if (!"js".equals(frontendType)) { + return templateConfig.getExtension(); + } + if ("API".equals(templateName) || "API_TYPES".equals(templateName)) { + return ".js"; + } + return templateConfig.getExtension(); + } + /** * 获取预览生成代码 * @@ -77,7 +123,7 @@ public class CodegenServiceImpl implements CodegenService { * @return 预览数据 */ @Override - public List getCodegenPreviewData(String tableName, String pageType) { + public List getCodegenPreviewData(String tableName, String pageType, String type) { List list = new ArrayList<>(); @@ -99,18 +145,25 @@ public class CodegenServiceImpl implements CodegenService { // 遍历模板配置 Map templateConfigs = codegenProperties.getTemplateConfigs(); + String frontendType = StrUtil.blankToDefault(type, "ts").toLowerCase(); for (Map.Entry templateConfigEntry : templateConfigs.entrySet()) { CodegenPreviewVO previewVo = new CodegenPreviewVO(); CodegenProperties.TemplateConfig templateConfig = templateConfigEntry.getValue(); + String templateName = templateConfigEntry.getKey(); + if ("js".equals(frontendType) && "API_TYPES".equals(templateName)) { + continue; + } + + String effectiveTemplatePath = resolveFrontendTemplatePath(templateName, templateConfig, frontendType); + String extension = resolveFrontendExtension(templateName, templateConfig, frontendType); + /* 1. 生成文件名 UserController */ // User Role Menu Dept String entityName = genTable.getEntityName(); // Controller Service Mapper Entity - String templateName = templateConfigEntry.getKey(); // .java .ts .vue - String extension = templateConfig.getExtension(); // 文件名 UserController.java String fileName = getFileName(entityName, templateName, extension); @@ -131,7 +184,13 @@ public class CodegenServiceImpl implements CodegenService { // 将模板文件中的变量替换为具体的值 生成代码内容 // 优先使用保存的 ui,没有则使用请求参数 String finalType = StrUtil.blankToDefault(genTable.getPageType(), pageType); - String content = getCodeContent(templateConfig, genTable, fieldConfigs, finalType); + String content = getCodeContent( + effectiveTemplatePath, + templateConfig.getSubpackageName(), + genTable, + fieldConfigs, + finalType + ); previewVo.setContent(content); list.add(previewVo); @@ -232,7 +291,11 @@ public class CodegenServiceImpl implements CodegenService { * @param pageType 前端页面类型 * @return 渲染后的代码内容 */ - private String getCodeContent(CodegenProperties.TemplateConfig templateConfig, GenTable genTable, List fieldConfigs, String pageType) { + private String getCodeContent(String templatePath, + String subpackageName, + GenTable genTable, + List fieldConfigs, + String pageType) { Map bindMap = new HashMap<>(); @@ -240,7 +303,7 @@ public class CodegenServiceImpl implements CodegenService { bindMap.put("packageName", genTable.getPackageName()); bindMap.put("moduleName", genTable.getModuleName()); - bindMap.put("subpackageName", templateConfig.getSubpackageName()); + bindMap.put("subpackageName", subpackageName); bindMap.put("date", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm")); bindMap.put("entityName", entityName); bindMap.put("tableName", genTable.getTableName()); @@ -282,9 +345,13 @@ public class CodegenServiceImpl implements CodegenService { TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH)); // 根据 ui 选择不同的前端页面模板:默认 index.vue.vm;封装版使用 index.curd.vue.vm - String path = templateConfig.getTemplatePath(); - if ("curd".equalsIgnoreCase(pageType) && path.endsWith("index.vue.vm")) { - path = path.replace("index.vue.vm", "index.curd.vue.vm"); + String path = templatePath; + if ("curd".equalsIgnoreCase(pageType)) { + if (path.endsWith("index.js.vue.vm")) { + path = path.replace("index.js.vue.vm", "index.curd.js.vue.vm"); + } else if (path.endsWith("index.vue.vm")) { + path = path.replace("index.vue.vm", "index.curd.vue.vm"); + } } Template template = templateEngine.getTemplate(path); @@ -299,13 +366,13 @@ public class CodegenServiceImpl implements CodegenService { * @return zip 压缩文件字节数组 */ @Override - public byte[] downloadCode(String[] tableNames, String ui) { + public byte[] downloadCode(String[] tableNames, String ui, String type) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zip = new ZipOutputStream(outputStream)) { // 遍历每个表名,生成对应的代码并压缩到 zip 文件中 for (String tableName : tableNames) { - generateAndZipCode(tableName, zip, ui); + generateAndZipCode(tableName, zip, ui, type); } // 确保所有压缩数据写入输出流,避免数据残留在内存缓冲区引发的数据不完整 zip.finish(); @@ -324,8 +391,8 @@ public class CodegenServiceImpl implements CodegenService { * @param zip 压缩文件输出流 * @param ui 页面类型 */ - private void generateAndZipCode(String tableName, ZipOutputStream zip, String ui) { - List codePreviewList = getCodegenPreviewData(tableName, ui); + private void generateAndZipCode(String tableName, ZipOutputStream zip, String ui, String type) { + List codePreviewList = getCodegenPreviewData(tableName, ui, type); for (CodegenPreviewVO codePreview : codePreviewList) { String fileName = codePreview.getFileName(); diff --git a/src/main/java/com/youlai/boot/platform/file/controller/FileController.java b/src/main/java/com/youlai/boot/platform/file/controller/FileController.java index a6b63feb..55923b2a 100644 --- a/src/main/java/com/youlai/boot/platform/file/controller/FileController.java +++ b/src/main/java/com/youlai/boot/platform/file/controller/FileController.java @@ -19,7 +19,7 @@ import org.springframework.web.multipart.MultipartFile; * @author Ray.Hao * @since 2022/10/16 */ -@Tag(name = "07.文件接口") +@Tag(name = "10.文件接口") @RestController @RequestMapping("/api/v1/files") @RequiredArgsConstructor diff --git a/src/main/java/com/youlai/boot/system/controller/ConfigController.java b/src/main/java/com/youlai/boot/system/controller/ConfigController.java index 280a4e32..113a6c59 100644 --- a/src/main/java/com/youlai/boot/system/controller/ConfigController.java +++ b/src/main/java/com/youlai/boot/system/controller/ConfigController.java @@ -28,7 +28,7 @@ import org.springframework.security.access.prepost.PreAuthorize; @Slf4j @RestController @RequiredArgsConstructor -@Tag(name = "08.系统配置") +@Tag(name = "07.系统配置") @RequestMapping("/api/v1/configs") public class ConfigController { diff --git a/src/main/java/com/youlai/boot/system/controller/LogController.java b/src/main/java/com/youlai/boot/system/controller/LogController.java index 89d0a85c..eae8cd31 100644 --- a/src/main/java/com/youlai/boot/system/controller/LogController.java +++ b/src/main/java/com/youlai/boot/system/controller/LogController.java @@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*; * @author Ray.Hao * @since 2.10.0 */ -@Tag(name = "10.日志接口") +@Tag(name = "09.日志接口") @RestController @RequestMapping("/api/v1/logs") @RequiredArgsConstructor diff --git a/src/main/java/com/youlai/boot/system/controller/NoticeController.java b/src/main/java/com/youlai/boot/system/controller/NoticeController.java index e2700d85..04bfeecf 100644 --- a/src/main/java/com/youlai/boot/system/controller/NoticeController.java +++ b/src/main/java/com/youlai/boot/system/controller/NoticeController.java @@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.*; * @author youlaitech * @since 2024-08-27 10:31 */ -@Tag(name = "09.通知公告") +@Tag(name = "08.通知公告") @RestController @RequestMapping("/api/v1/notices") @RequiredArgsConstructor diff --git a/src/main/java/com/youlai/boot/system/controller/StatisticsController.java b/src/main/java/com/youlai/boot/system/controller/StatisticsController.java index 50184c5b..3014c230 100644 --- a/src/main/java/com/youlai/boot/system/controller/StatisticsController.java +++ b/src/main/java/com/youlai/boot/system/controller/StatisticsController.java @@ -18,7 +18,7 @@ import java.time.LocalDate; * @author Ray.Hao * @since 2025-12-15 */ -@Tag(name = "11.统计分析") +@Tag(name = "12.统计分析") @RestController @RequestMapping("/api/v1/statistics") @RequiredArgsConstructor diff --git a/src/main/java/com/youlai/boot/system/controller/UserController.java b/src/main/java/com/youlai/boot/system/controller/UserController.java index 8705986c..a2909079 100644 --- a/src/main/java/com/youlai/boot/system/controller/UserController.java +++ b/src/main/java/com/youlai/boot/system/controller/UserController.java @@ -125,6 +125,17 @@ public class UserController { return Result.judge(result); } + @Operation(summary = "重置指定用户密码") + @PutMapping(value = "/{userId}/password/reset") + @PreAuthorize("@ss.hasPerm('sys:user:reset-password')") + public Result resetUserPassword( + @Parameter(description = "用户ID") @PathVariable Long userId, + @RequestParam String password + ) { + boolean result = userService.resetUserPassword(userId, password); + return Result.judge(result); + } + @Operation(summary = "获取当前登录用户信息") @GetMapping("/me") @Log(value = "获取当前登录用户信息", module = LogModuleEnum.USER) @@ -176,6 +187,13 @@ public class UserController { .doWrite(exportUserList); } + @Operation(summary = "获取用户下拉选项") + @GetMapping("/options") + public Result>> listUserOptions() { + List> list = userService.listUserOptions(); + return Result.success(list); + } + @Operation(summary = "获取个人中心用户信息") @GetMapping("/profile") @Log(value = "获取个人中心用户信息", module = LogModuleEnum.USER) @@ -193,16 +211,6 @@ public class UserController { return Result.judge(result); } - @Operation(summary = "重置指定用户密码") - @PutMapping(value = "/{userId}/password/reset") - @PreAuthorize("@ss.hasPerm('sys:user:reset-password')") - public Result resetUserPassword( - @Parameter(description = "用户ID") @PathVariable Long userId, - @RequestParam String password - ) { - boolean result = userService.resetUserPassword(userId, password); - return Result.judge(result); - } @Operation(summary = "当前用户修改密码") @PutMapping(value = "/password") @@ -232,6 +240,15 @@ public class UserController { return Result.judge(result); } + @Operation(summary = "解绑手机号") + @DeleteMapping(value = "/mobile") + public Result unbindMobile( + @RequestBody @Validated PasswordVerifyForm data + ) { + boolean result = userService.unbindMobile(data); + return Result.judge(result); + } + @Operation(summary = "发送邮箱验证码(绑定或更换邮箱)") @PostMapping(value = "/email/code") public Result sendEmailCode( @@ -250,10 +267,14 @@ public class UserController { return Result.judge(result); } - @Operation(summary = "获取用户下拉选项") - @GetMapping("/options") - public Result>> listUserOptions() { - List> list = userService.listUserOptions(); - return Result.success(list); + @Operation(summary = "解绑邮箱") + @DeleteMapping(value = "/email") + public Result unbindEmail( + @RequestBody @Validated PasswordVerifyForm data + ) { + boolean result = userService.unbindEmail(data); + return Result.judge(result); } + + } diff --git a/src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java b/src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java index 6019fa3e..62495312 100644 --- a/src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java @@ -1,6 +1,7 @@ package com.youlai.boot.system.model.form; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import lombok.Data; @@ -16,10 +17,15 @@ public class EmailUpdateForm { @Schema(description = "邮箱") @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") private String email; @Schema(description = "验证码") @NotBlank(message = "验证码不能为空") private String code; + @Schema(description = "当前密码") + @NotBlank(message = "当前密码不能为空") + private String password; + } diff --git a/src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java b/src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java index d15bf824..be620791 100644 --- a/src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java @@ -2,6 +2,7 @@ package com.youlai.boot.system.model.form; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.Data; /** @@ -16,10 +17,15 @@ public class MobileUpdateForm { @Schema(description = "手机号码") @NotBlank(message = "手机号码不能为空") + @Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号码格式不正确") private String mobile; @Schema(description = "验证码") @NotBlank(message = "验证码不能为空") private String code; + @Schema(description = "当前密码") + @NotBlank(message = "当前密码不能为空") + private String password; + } diff --git a/src/main/java/com/youlai/boot/system/model/form/PasswordVerifyForm.java b/src/main/java/com/youlai/boot/system/model/form/PasswordVerifyForm.java new file mode 100644 index 00000000..674043f2 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/form/PasswordVerifyForm.java @@ -0,0 +1,14 @@ +package com.youlai.boot.system.model.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Schema(description = "密码校验表单") +@Data +public class PasswordVerifyForm { + + @Schema(description = "当前密码") + @NotBlank(message = "当前密码不能为空") + private String password; +} diff --git a/src/main/java/com/youlai/boot/system/model/form/UserProfileForm.java b/src/main/java/com/youlai/boot/system/model/form/UserProfileForm.java index 5c607cd2..25953345 100644 --- a/src/main/java/com/youlai/boot/system/model/form/UserProfileForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/UserProfileForm.java @@ -12,13 +12,6 @@ import lombok.Data; @Schema(description = "个人中心用户信息") @Data public class UserProfileForm { - - @Schema(description = "用户ID") - private Long id; - - @Schema(description = "用户名") - private String username; - @Schema(description = "用户昵称") private String nickname; @@ -28,11 +21,4 @@ public class UserProfileForm { @Schema(description = "性别") private Integer gender; - @Schema(description = "手机号") - private String mobile; - - @Schema(description = "邮箱") - private String email; - - } diff --git a/src/main/java/com/youlai/boot/system/service/UserService.java b/src/main/java/com/youlai/boot/system/service/UserService.java index b77da7ad..93a2b683 100644 --- a/src/main/java/com/youlai/boot/system/service/UserService.java +++ b/src/main/java/com/youlai/boot/system/service/UserService.java @@ -158,6 +158,22 @@ public interface UserService extends IService { */ boolean bindOrChangeEmail(EmailUpdateForm data); + /** + * 解绑手机号 + * + * @param data 表单数据 + * @return {@link Boolean} 是否解绑成功 + */ + boolean unbindMobile(PasswordVerifyForm data); + + /** + * 解绑邮箱 + * + * @param data 表单数据 + * @return {@link Boolean} 是否解绑成功 + */ + boolean unbindEmail(PasswordVerifyForm data); + /** * 获取用户选项列表 * diff --git a/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java index 65a6cac0..108de0fa 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java @@ -491,9 +491,17 @@ public class UserServiceImpl extends ServiceImpl implements Us @Override public boolean updateUserProfile(UserProfileForm formData) { Long userId = SecurityUtils.getUserId(); - User entity = userConverter.toEntity(formData); - entity.setId(userId); - return this.updateById(entity); + + if (formData.getNickname() == null && formData.getAvatar() == null && formData.getGender() == null) { + throw new BusinessException("请修改至少一个字段"); + } + + return this.update(new LambdaUpdateWrapper() + .eq(User::getId, userId) + .set(formData.getNickname() != null, User::getNickname, formData.getNickname()) + .set(formData.getAvatar() != null, User::getAvatar, formData.getAvatar()) + .set(formData.getGender() != null, User::getGender, formData.getGender()) + ); } /** @@ -523,7 +531,7 @@ public class UserServiceImpl extends ServiceImpl implements Us } // 判断新密码和确认密码是否一致 - if (passwordEncoder.matches(data.getNewPassword(), data.getConfirmPassword())) { + if (!Objects.equals(data.getNewPassword(), data.getConfirmPassword())) { throw new BusinessException("新密码和确认密码不一致"); } @@ -569,6 +577,15 @@ public class UserServiceImpl extends ServiceImpl implements Us @Override public boolean sendMobileCode(String mobile) { + Long currentUserId = SecurityUtils.getUserId(); + long mobileCount = this.count(new LambdaQueryWrapper() + .eq(User::getMobile, mobile) + .ne(User::getId, currentUserId) + ); + if (mobileCount > 0) { + throw new BusinessException("手机号已被其他账号绑定"); + } + // String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); // TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码 String code = "1234"; @@ -600,6 +617,10 @@ public class UserServiceImpl extends ServiceImpl implements Us throw new BusinessException("用户不存在"); } + if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) { + throw new BusinessException("当前密码错误"); + } + // 校验验证码 String inputVerifyCode = form.getCode(); String mobile = form.getMobile(); @@ -614,7 +635,15 @@ public class UserServiceImpl extends ServiceImpl implements Us if (!inputVerifyCode.equals(cachedVerifyCode)) { throw new BusinessException("验证码错误"); } - // 验证完成删除验证码 + + long mobileCount = this.count(new LambdaQueryWrapper() + .eq(User::getMobile, mobile) + .ne(User::getId, currentUserId) + ); + if (mobileCount > 0) { + throw new BusinessException("手机号已被其他账号绑定"); + } + redisTemplate.delete(cacheKey); // 更新手机号码 @@ -633,6 +662,15 @@ public class UserServiceImpl extends ServiceImpl implements Us @Override public void sendEmailCode(String email) { + Long currentUserId = SecurityUtils.getUserId(); + long emailCount = this.count(new LambdaQueryWrapper() + .eq(User::getEmail, email) + .ne(User::getId, currentUserId) + ); + if (emailCount > 0) { + throw new BusinessException("邮箱已被其他账号绑定"); + } + // String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); // TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码 String code = "1234"; @@ -659,6 +697,10 @@ public class UserServiceImpl extends ServiceImpl implements Us throw new BusinessException("用户不存在"); } + if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) { + throw new BusinessException("当前密码错误"); + } + // 获取前端输入的验证码 String inputVerifyCode = form.getCode(); @@ -674,7 +716,15 @@ public class UserServiceImpl extends ServiceImpl implements Us if (!inputVerifyCode.equals(cachedVerifyCode)) { throw new BusinessException("验证码错误"); } - // 验证完成删除验证码 + + long emailCount = this.count(new LambdaQueryWrapper() + .eq(User::getEmail, email) + .ne(User::getId, currentUserId) + ); + if (emailCount > 0) { + throw new BusinessException("邮箱已被其他账号绑定"); + } + redisTemplate.delete(redisCacheKey); // 更新邮箱地址 @@ -685,6 +735,66 @@ public class UserServiceImpl extends ServiceImpl implements Us ); } + /** + * 解绑手机号 + * + * @param form 表单数据 + * @return true|false + */ + @Override + public boolean unbindMobile(PasswordVerifyForm form) { + + Long currentUserId = SecurityUtils.getUserId(); + User currentUser = this.getById(currentUserId); + + if (currentUser == null) { + throw new BusinessException("用户不存在"); + } + + if (StrUtil.isBlank(currentUser.getMobile())) { + throw new BusinessException("当前账号未绑定手机号"); + } + + if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) { + throw new BusinessException("当前密码错误"); + } + + return this.update(new LambdaUpdateWrapper() + .eq(User::getId, currentUserId) + .set(User::getMobile, null) + ); + } + + /** + * 解绑邮箱 + * + * @param form 表单数据 + * @return true|false + */ + @Override + public boolean unbindEmail(PasswordVerifyForm form) { + + Long currentUserId = SecurityUtils.getUserId(); + User currentUser = this.getById(currentUserId); + + if (currentUser == null) { + throw new BusinessException("用户不存在"); + } + + if (StrUtil.isBlank(currentUser.getEmail())) { + throw new BusinessException("当前账号未绑定邮箱"); + } + + if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) { + throw new BusinessException("当前密码错误"); + } + + return this.update(new LambdaUpdateWrapper() + .eq(User::getId, currentUserId) + .set(User::getEmail, null) + ); + } + /** * 获取用户选项列表 * diff --git a/src/main/resources/mapper/system/UserMapper.xml b/src/main/resources/mapper/system/UserMapper.xml index 9ddac65b..72d23d6f 100644 --- a/src/main/resources/mapper/system/UserMapper.xml +++ b/src/main/resources/mapper/system/UserMapper.xml @@ -27,17 +27,15 @@ u.is_deleted = 0 - - AND NOT EXISTS ( - SELECT - 1 - FROM sys_user_role sur - INNER JOIN sys_role r ON sur.role_id = r.id - WHERE - sur.user_id = u.id - AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}' - ) - + AND NOT EXISTS ( + SELECT + 1 + FROM sys_user_role sur + INNER JOIN sys_role r ON sur.role_id = r.id + WHERE + sur.user_id = u.id + AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}' + ) AND ( u.username LIKE CONCAT('%',#{queryParams.keywords},'%') @@ -199,7 +197,6 @@ LEFT JOIN sys_dept d ON u.dept_id = d.id u.is_deleted = 0 - AND NOT EXISTS ( SELECT 1 @@ -209,7 +206,6 @@ sur.user_id = u.id AND r.code = '${@com.youlai.boot.common.constant.SystemConstants@ROOT_ROLE_CODE}' ) - AND (u.username LIKE CONCAT('%',#{keywords},'%') OR u.nickname LIKE CONCAT('%',#{keywords},'%') diff --git a/src/main/resources/templates/codegen/api.js.vm b/src/main/resources/templates/codegen/api.js.vm new file mode 100644 index 00000000..4b9cc527 --- /dev/null +++ b/src/main/resources/templates/codegen/api.js.vm @@ -0,0 +1,63 @@ +import request from "@/utils/request"; + +const ${entityUpperSnake}_BASE_URL = "/api/v1/${entityKebab}"; + +const ${entityName}API = { + /** 获取${businessName}分页数据 */ + getPage(queryParams) { + return request({ + url: `${${entityUpperSnake}_BASE_URL}`, + method: "get", + params: queryParams, + }); + }, + + /** + * 获取${businessName}表单数据 + * @param {string} id ${businessName}ID + */ + getFormData(id) { + return request({ + url: `${${entityUpperSnake}_BASE_URL}/${id}/form`, + method: "get", + }); + }, + + /** + * 新增${businessName} + * @param {Object} data ${businessName}表单数据 + */ + create(data) { + return request({ + url: `${${entityUpperSnake}_BASE_URL}`, + method: "post", + data, + }); + }, + + /** + * 更新${businessName} + * @param {string} id ${businessName}ID + * @param {Object} data ${businessName}表单数据 + */ + update(id, data) { + return request({ + url: `${${entityUpperSnake}_BASE_URL}/${id}`, + method: "put", + data, + }); + }, + + /** + * 批量删除${businessName},多个以英文逗号分割 + * @param {string} ids ${businessName}ID字符串 + */ + deleteByIds(ids) { + return request({ + url: `${${entityUpperSnake}_BASE_URL}/${ids}`, + method: "delete", + }); + }, +}; + +export default ${entityName}API; diff --git a/src/main/resources/templates/codegen/index.curd.js.vue.vm b/src/main/resources/templates/codegen/index.curd.js.vue.vm new file mode 100644 index 00000000..8e4bd380 --- /dev/null +++ b/src/main/resources/templates/codegen/index.curd.js.vue.vm @@ -0,0 +1,369 @@ + + + diff --git a/src/main/resources/templates/codegen/index.js.vue.vm b/src/main/resources/templates/codegen/index.js.vue.vm new file mode 100644 index 00000000..10d0139b --- /dev/null +++ b/src/main/resources/templates/codegen/index.js.vue.vm @@ -0,0 +1,417 @@ + + + diff --git a/src/main/resources/templates/codegen/index.vue.vm b/src/main/resources/templates/codegen/index.vue.vm index 813eeb89..164bf7b3 100644 --- a/src/main/resources/templates/codegen/index.vue.vm +++ b/src/main/resources/templates/codegen/index.vue.vm @@ -23,7 +23,7 @@ #end #elseif($fieldConfig.formType == "RADIO") #if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "") - + #else 选项一 @@ -32,7 +32,7 @@ #end #elseif($fieldConfig.formType == "CHECK_BOX") #if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "") - + #else 选项一