From 7fc636a020acb40604f99129b73ebdd2e6909ca5 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Wed, 22 Jul 2026 20:11:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=A0=81=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 UserCaptchaConfigEntity 实体类,含 side(admin/app) 端标识 - 新增 Admin/App Controller、Service、Mapper - BaseSysLoginServiceImpl 新增 isCaptchaEnabled() 查询数据库配置 - login/register 改用数据库配置替代 cool.captcha.enable 硬编码 - AdminBaseOpenController 新增 /captchaEnabled 接口 - menu.json 新增验证码管理菜单及权限 - base.json 新增 5 条预置数据(3条app+2条admin) --- .../admin/AdminBaseOpenController.java | 6 ++ .../base/service/sys/BaseSysLoginService.java | 2 + .../sys/impl/BaseSysLoginServiceImpl.java | 25 ++++-- .../AdminUserCaptchaConfigController.java | 22 +++++ .../app/AppUserCaptchaConfigController.java | 26 ++++++ .../user/entity/UserCaptchaConfigEntity.java | 52 ++++++++++++ .../user/mapper/UserCaptchaConfigMapper.java | 7 ++ .../service/UserCaptchaConfigService.java | 10 +++ .../impl/UserCaptchaConfigServiceImpl.java | 22 +++++ src/main/resources/cool/data/db/base.json | 82 ++++++++++++++++++ src/main/resources/cool/data/menu/menu.json | 85 +++++++++++++++++++ 11 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/cool/modules/user/controller/admin/AdminUserCaptchaConfigController.java create mode 100644 src/main/java/com/cool/modules/user/controller/app/AppUserCaptchaConfigController.java create mode 100644 src/main/java/com/cool/modules/user/entity/UserCaptchaConfigEntity.java create mode 100644 src/main/java/com/cool/modules/user/mapper/UserCaptchaConfigMapper.java create mode 100644 src/main/java/com/cool/modules/user/service/UserCaptchaConfigService.java create mode 100644 src/main/java/com/cool/modules/user/service/impl/UserCaptchaConfigServiceImpl.java diff --git a/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java index 279d830..ce36e32 100644 --- a/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java +++ b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseOpenController.java @@ -125,4 +125,10 @@ public class AdminBaseOpenController { } return R.ok(Map.of("mode", "common")); } + + @Operation(summary = "管理端验证码是否启用") + @GetMapping("/captchaEnabled") + public R captchaEnabled() { + return R.ok(Map.of("enabled", baseSysLoginService.isCaptchaEnabled("admin", "adminLogin"))); + } } diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java index 3a0e7b0..7f52a16 100644 --- a/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java +++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysLoginService.java @@ -23,6 +23,8 @@ public interface BaseSysLoginService { */ void captchaCheck(String captchaId, String code); + boolean isCaptchaEnabled(String side, String sceneKey); + /** * 登录 * diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java index 066dbbe..f254825 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java @@ -23,6 +23,8 @@ import com.cool.modules.base.mapper.sys.BaseSysRoleMapper; import com.cool.modules.base.mapper.sys.BaseSysUserMapper; import com.cool.modules.base.service.sys.BaseSysLoginService; import com.cool.modules.base.service.sys.BaseSysPermsService; +import com.cool.modules.user.entity.UserCaptchaConfigEntity; +import com.cool.modules.user.service.UserCaptchaConfigService; import com.mybatisflex.core.query.QueryWrapper; import java.awt.Color; import java.util.HashMap; @@ -52,6 +54,8 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { private final BaseSysPermsService baseSysPermsService; + private final UserCaptchaConfigService userCaptchaConfigService; + private final org.springframework.security.crypto.password.PasswordEncoder passwordEncoder; @org.springframework.beans.factory.annotation.Value("${cool.captcha.enable:true}") @@ -90,10 +94,21 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { } } + @Override + public boolean isCaptchaEnabled(String side, String sceneKey) { + UserCaptchaConfigEntity config = userCaptchaConfigService.getOne( + QueryWrapper.create() + .eq(UserCaptchaConfigEntity::getSide, side) + .eq(UserCaptchaConfigEntity::getSceneKey, sceneKey)); + if (config != null) { + return ObjUtil.equals(config.getEnabled(), 1); + } + return captchaEnable; + } + @Override public Object login(BaseSysLoginDto baseSysLoginDto) { - // 1、检查验证码是否正确 2、执行登录操作 - if (captchaEnable) { + if (isCaptchaEnabled("admin", "adminLogin")) { captchaCheck(baseSysLoginDto.getCaptchaId(), baseSysLoginDto.getVerifyCode()); } UsernamePasswordAuthenticationToken upToken = @@ -109,7 +124,7 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { CoolPreconditions.check( ObjectUtil.isEmpty(baseSysUserEntity) || baseSysUserEntity.getStatus() == 0, "用户已禁用"); Long[] roleIds = baseSysPermsService.getRoles(baseSysUserEntity); - if (captchaEnable) { + if (isCaptchaEnabled("admin", "adminLogin")) { coolCache.del("verify:img:" + baseSysLoginDto.getCaptchaId()); } return generateToken(roleIds, baseSysUserEntity, null); @@ -134,7 +149,7 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { @Override public Object register(BaseSysRegisterDto baseSysRegisterDto) { - if (captchaEnable) { + if (isCaptchaEnabled("admin", "adminRegister")) { captchaCheck(baseSysRegisterDto.getCaptchaId(), baseSysRegisterDto.getVerifyCode()); } BaseSysUserEntity existUser = baseSysUserMapper.selectOneByQuery( @@ -160,7 +175,7 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { userEntity.setDepartmentId(defaultDepartment.getId()); baseSysUserMapper.insertSelective(userEntity); baseSysPermsService.updateUserRole(userEntity.getId(), new Long[]{normalRole.getId()}); - if (captchaEnable) { + if (isCaptchaEnabled("admin", "adminRegister")) { coolCache.del("verify:img:" + baseSysRegisterDto.getCaptchaId()); } return Dict.create().set("message", "注册成功"); diff --git a/src/main/java/com/cool/modules/user/controller/admin/AdminUserCaptchaConfigController.java b/src/main/java/com/cool/modules/user/controller/admin/AdminUserCaptchaConfigController.java new file mode 100644 index 0000000..42c7b96 --- /dev/null +++ b/src/main/java/com/cool/modules/user/controller/admin/AdminUserCaptchaConfigController.java @@ -0,0 +1,22 @@ +package com.cool.modules.user.controller.admin; + +import static com.cool.modules.user.entity.table.UserCaptchaConfigEntityTableDef.USER_CAPTCHA_CONFIG_ENTITY; + +import cn.hutool.json.JSONObject; +import com.cool.core.annotation.CoolRestController; +import com.cool.core.base.BaseController; +import com.cool.modules.user.entity.UserCaptchaConfigEntity; +import com.cool.modules.user.service.UserCaptchaConfigService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; + +@Tag(name = "验证码配置", description = "管理验证码配置") +@CoolRestController(api = {"add", "delete", "update", "page", "list", "info"}, cname = "captchaConfig") +public class AdminUserCaptchaConfigController extends BaseController { + + @Override + protected void init(HttpServletRequest request, JSONObject requestParams) { + setPageOption(createOp().fieldEq(USER_CAPTCHA_CONFIG_ENTITY.SIDE, USER_CAPTCHA_CONFIG_ENTITY.CAPTCHA_TYPE, USER_CAPTCHA_CONFIG_ENTITY.ENABLED) + .keyWordLikeFields(USER_CAPTCHA_CONFIG_ENTITY.LABEL)); + } +} diff --git a/src/main/java/com/cool/modules/user/controller/app/AppUserCaptchaConfigController.java b/src/main/java/com/cool/modules/user/controller/app/AppUserCaptchaConfigController.java new file mode 100644 index 0000000..663f87a --- /dev/null +++ b/src/main/java/com/cool/modules/user/controller/app/AppUserCaptchaConfigController.java @@ -0,0 +1,26 @@ +package com.cool.modules.user.controller.app; + +import com.cool.core.annotation.CoolRestController; +import com.cool.core.annotation.TokenIgnore; +import com.cool.modules.user.entity.UserCaptchaConfigEntity; +import com.cool.modules.user.service.UserCaptchaConfigService; +import com.cool.core.request.R; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "App验证码配置", description = "App端获取验证码配置") +@RestController +@RequiredArgsConstructor +public class AppUserCaptchaConfigController { + + private final UserCaptchaConfigService userCaptchaConfigService; + + @TokenIgnore + @GetMapping("/app/user/captcha/config") + public R> getConfig() { + return R.ok(userCaptchaConfigService.getEnabledConfigs("app")); + } +} diff --git a/src/main/java/com/cool/modules/user/entity/UserCaptchaConfigEntity.java b/src/main/java/com/cool/modules/user/entity/UserCaptchaConfigEntity.java new file mode 100644 index 0000000..64700cc --- /dev/null +++ b/src/main/java/com/cool/modules/user/entity/UserCaptchaConfigEntity.java @@ -0,0 +1,52 @@ +package com.cool.modules.user.entity; + +import com.cool.core.base.BaseEntity; +import com.mybatisflex.annotation.Table; +import com.cool.core.annotation.ColumnDefine; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Table(value = "user_captcha_config", comment = "验证码配置") +public class UserCaptchaConfigEntity extends BaseEntity { + + @ColumnDefine(comment = "端标识 admin/app", notNull = true, length = 20) + private String side; + + @ColumnDefine(comment = "场景标识 smsLogin/smsRegister/bindPhone/passwordLogin/adminLogin/adminRegister", notNull = true, length = 50) + private String sceneKey; + + @ColumnDefine(comment = "场景名称", notNull = true, length = 50) + private String label; + + @ColumnDefine(comment = "验证码类型 image/sms/slider/rotate/wordClick", notNull = true, length = 30) + private String captchaType; + + @ColumnDefine(comment = "是否启用验证码 0-禁用 1-启用", defaultValue = "1") + private Integer enabled; + + @ColumnDefine(comment = "验证码长度", defaultValue = "4") + private Integer codeLength; + + @ColumnDefine(comment = "验证码过期时间(秒)", defaultValue = "1800") + private Integer expireSeconds; + + @ColumnDefine(comment = "验证码宽度(图片验证码)", defaultValue = "150") + private Integer width; + + @ColumnDefine(comment = "验证码高度(图片验证码)", defaultValue = "50") + private Integer height; + + @ColumnDefine(comment = "短信发送间隔(秒,短信验证码)", defaultValue = "60") + private Integer smsInterval; + + @ColumnDefine(comment = "每日发送上限(短信验证码)", defaultValue = "10") + private Integer smsDailyLimit; + + @ColumnDefine(comment = "排序号,越小越靠前", defaultValue = "0") + private Integer orderNum; + + @ColumnDefine(comment = "备注", length = 200) + private String remark; +} diff --git a/src/main/java/com/cool/modules/user/mapper/UserCaptchaConfigMapper.java b/src/main/java/com/cool/modules/user/mapper/UserCaptchaConfigMapper.java new file mode 100644 index 0000000..082ff2b --- /dev/null +++ b/src/main/java/com/cool/modules/user/mapper/UserCaptchaConfigMapper.java @@ -0,0 +1,7 @@ +package com.cool.modules.user.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.cool.modules.user.entity.UserCaptchaConfigEntity; + +public interface UserCaptchaConfigMapper extends BaseMapper { +} diff --git a/src/main/java/com/cool/modules/user/service/UserCaptchaConfigService.java b/src/main/java/com/cool/modules/user/service/UserCaptchaConfigService.java new file mode 100644 index 0000000..4542a8b --- /dev/null +++ b/src/main/java/com/cool/modules/user/service/UserCaptchaConfigService.java @@ -0,0 +1,10 @@ +package com.cool.modules.user.service; + +import com.cool.core.base.BaseService; +import com.cool.modules.user.entity.UserCaptchaConfigEntity; +import java.util.List; + +public interface UserCaptchaConfigService extends BaseService { + + List getEnabledConfigs(String side); +} diff --git a/src/main/java/com/cool/modules/user/service/impl/UserCaptchaConfigServiceImpl.java b/src/main/java/com/cool/modules/user/service/impl/UserCaptchaConfigServiceImpl.java new file mode 100644 index 0000000..0bf10ac --- /dev/null +++ b/src/main/java/com/cool/modules/user/service/impl/UserCaptchaConfigServiceImpl.java @@ -0,0 +1,22 @@ +package com.cool.modules.user.service.impl; + +import com.cool.core.base.BaseServiceImpl; +import com.cool.modules.user.entity.UserCaptchaConfigEntity; +import com.cool.modules.user.mapper.UserCaptchaConfigMapper; +import com.cool.modules.user.service.UserCaptchaConfigService; +import com.mybatisflex.core.query.QueryWrapper; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class UserCaptchaConfigServiceImpl extends BaseServiceImpl + implements UserCaptchaConfigService { + + @Override + public List getEnabledConfigs(String side) { + return list(QueryWrapper.create() + .eq(UserCaptchaConfigEntity::getSide, side) + .eq(UserCaptchaConfigEntity::getEnabled, 1) + .orderBy(UserCaptchaConfigEntity::getOrderNum, true)); + } +} diff --git a/src/main/resources/cool/data/db/base.json b/src/main/resources/cool/data/db/base.json index 04f0a38..9cd47ee 100644 --- a/src/main/resources/cool/data/db/base.json +++ b/src/main/resources/cool/data/db/base.json @@ -209,5 +209,87 @@ "orderNum": 2, "isDefault": 0 } + ], + "user_captcha_config": [ + { + "id": 1, + "side": "app", + "sceneKey": "smsLogin", + "label": "短信登录验证码", + "captchaType": "image", + "enabled": 1, + "codeLength": 4, + "expireSeconds": 1800, + "width": 150, + "height": 50, + "smsInterval": 60, + "smsDailyLimit": 10, + "orderNum": 1, + "remark": "手机验证码登录前的图片验证码" + }, + { + "id": 2, + "side": "app", + "sceneKey": "smsRegister", + "label": "短信注册验证码", + "captchaType": "image", + "enabled": 1, + "codeLength": 4, + "expireSeconds": 1800, + "width": 150, + "height": 50, + "smsInterval": 60, + "smsDailyLimit": 10, + "orderNum": 2, + "remark": "注册时发送短信前的图片验证码" + }, + { + "id": 3, + "side": "app", + "sceneKey": "bindPhone", + "label": "绑定手机验证码", + "captchaType": "image", + "enabled": 1, + "codeLength": 4, + "expireSeconds": 1800, + "width": 150, + "height": 50, + "smsInterval": 60, + "smsDailyLimit": 10, + "orderNum": 3, + "remark": "绑定手机号时的短信验证码" + }, + { + "id": 4, + "side": "admin", + "sceneKey": "adminLogin", + "label": "管理员登录验证码", + "captchaType": "image", + "enabled": 1, + "codeLength": 4, + "expireSeconds": 1800, + "width": 150, + "height": 50, + "smsInterval": 60, + "smsDailyLimit": 10, + "orderNum": 1, + "remark": "管理端登录时的图片验证码" + }, + { + "id": 5, + "side": "admin", + "sceneKey": "adminRegister", + "label": "管理员注册验证码", + "captchaType": "image", + "enabled": 1, + "codeLength": 4, + "expireSeconds": 1800, + "width": 150, + "height": 50, + "smsInterval": 60, + "smsDailyLimit": 10, + "orderNum": 2, + "remark": "管理端注册时的图片验证码" + } ] } \ No newline at end of file diff --git a/src/main/resources/cool/data/menu/menu.json b/src/main/resources/cool/data/menu/menu.json index d2379af..98223d9 100644 --- a/src/main/resources/cool/data/menu/menu.json +++ b/src/main/resources/cool/data/menu/menu.json @@ -992,6 +992,91 @@ "childMenus": [] } ] + }, + { + "name": "验证码管理", + "router": "/user/captcha-config", + "perms": null, + "type": 1, + "icon": "icon-menu", + "orderNum": 3, + "viewPath": "modules/user/views/captcha-config.vue", + "keepAlive": true, + "isShow": true, + "childMenus": [ + { + "name": "删除", + "router": null, + "perms": "user:captchaConfig:delete", + "type": 2, + "icon": null, + "orderNum": 0, + "viewPath": null, + "keepAlive": true, + "isShow": true, + "childMenus": [] + }, + { + "name": "修改", + "router": null, + "perms": "user:captchaConfig:update,user:captchaConfig:info", + "type": 2, + "icon": null, + "orderNum": 0, + "viewPath": null, + "keepAlive": true, + "isShow": true, + "childMenus": [] + }, + { + "name": "单个信息", + "router": null, + "perms": "user:captchaConfig:info", + "type": 2, + "icon": null, + "orderNum": 0, + "viewPath": null, + "keepAlive": true, + "isShow": true, + "childMenus": [] + }, + { + "name": "列表查询", + "router": null, + "perms": "user:captchaConfig:list", + "type": 2, + "icon": null, + "orderNum": 0, + "viewPath": null, + "keepAlive": true, + "isShow": true, + "childMenus": [] + }, + { + "name": "分页查询", + "router": null, + "perms": "user:captchaConfig:page", + "type": 2, + "icon": null, + "orderNum": 0, + "viewPath": null, + "keepAlive": true, + "isShow": true, + "childMenus": [] + }, + { + "name": "新增", + "router": null, + "perms": "user:captchaConfig:add", + "type": 2, + "icon": null, + "orderNum": 0, + "viewPath": null, + "keepAlive": true, + "isShow": true, + "childMenus": [] + } + ] } ] }