mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-12 21:02:08 +08:00
1、系统登录改造:首页移除机构、新用户用手机号登录(机构新增组织管理员移除账号密码、新增用户移除账号密码)、需要验证码的部分(首页忘记密码、修改密保手机);2、组织权限菜单树增加visiable属性过滤;3、支持钉钉、阿里云短信配置加密、对接系统发送短信;
This commit is contained in:
+1
-4
@@ -7,7 +7,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "RPC 服务 - 短信验证码的发送 Request DTO")
|
||||
@Data
|
||||
@@ -19,12 +18,10 @@ public class SmsCodeSendReqDTO {
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "发送场景", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "发送场景不能为空")
|
||||
@InEnum(SmsSceneEnum.class)
|
||||
|
||||
private Integer scene;
|
||||
|
||||
@Schema(description = "发送 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.20.30.40")
|
||||
@NotEmpty(message = "发送 IP 不能为空")
|
||||
private String createIp;
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import com.cf.imes.framework.common.validation.InEnum;
|
||||
import com.cf.imes.framework.common.validation.Mobile;
|
||||
import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
@@ -11,6 +12,7 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "RPC 服务 - 短信验证码的使用 Request DTO")
|
||||
@Data
|
||||
@Builder
|
||||
public class SmsCodeUseReqDTO {
|
||||
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
|
||||
@@ -28,7 +30,6 @@ public class SmsCodeUseReqDTO {
|
||||
private String code;
|
||||
|
||||
@Schema(description = "发送 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.20.30.40")
|
||||
@NotEmpty(message = "使用 IP 不能为空")
|
||||
private String usedIp;
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -4,6 +4,7 @@ import com.cf.imes.framework.common.validation.InEnum;
|
||||
import com.cf.imes.framework.common.validation.Mobile;
|
||||
import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
@@ -11,6 +12,7 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "RPC 服务 - 短信验证码的校验 Request DTO")
|
||||
@Data
|
||||
@Builder
|
||||
public class SmsCodeValidateReqDTO {
|
||||
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
|
||||
|
||||
+6
-3
@@ -16,6 +16,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "未绑定账号,需要进行绑定");
|
||||
ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1_002_000_006, "Token 已经过期");
|
||||
ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "手机号不存在");
|
||||
ErrorCode AUTH_LOGIN_FIRST_LOGIN = new ErrorCode(1_002_000_008, "请通过验证码登录");
|
||||
|
||||
// ========== 菜单模块 1-002-001-000 ==========
|
||||
ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "已经存在该名字的菜单");
|
||||
@@ -45,6 +46,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用");
|
||||
ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过组织最大组织配额({})!");
|
||||
ErrorCode USER_ME_ERROR = new ErrorCode(1_002_003_009, "不可操作用户自身");
|
||||
ErrorCode USERNAME_MULTIPLE_ERROR = new ErrorCode(1_002_003_010,"存在名字为【{}】的重复用户,请联系系统管理员");
|
||||
|
||||
ErrorCode THE_PASSWORD_LENGTH_MUST_BE_AT_LEAST_6_CHARACTERS = new ErrorCode(1_002_003_010, "密码长度必须至少为6个字符");
|
||||
|
||||
@@ -105,14 +107,15 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, "短信模板不存在");
|
||||
|
||||
// ========== 短信验证码 1-002-014-000 ==========
|
||||
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
|
||||
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期");
|
||||
ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用");
|
||||
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在,请点击获取");
|
||||
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期,请重新获取");
|
||||
ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用,请重新获取");
|
||||
ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1_002_014_003, "验证码不正确");
|
||||
ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量");
|
||||
ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频率");
|
||||
ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1_002_014_006, "手机号已被使用");
|
||||
ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1_002_014_007, "验证码未被使用");
|
||||
ErrorCode SMS_CODE_IS_VALID = new ErrorCode(1_002_014_008, "验证码在有效期内,5分钟内请勿重复发送!");
|
||||
|
||||
// ========== 组织信息 1-002-015-000 ==========
|
||||
ErrorCode ORGAN_NOT_EXISTS = new ErrorCode(1_002_015_000, "组织不存在");
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ public enum LoginLogTypeEnum {
|
||||
LOGIN_SOCIAL(101), // 使用社交登录
|
||||
LOGIN_MOBILE(103), // 使用手机登陆
|
||||
LOGIN_SMS(104), // 使用短信登陆
|
||||
LOGIN_FORGET_PWD(105), // 登录忘记密码
|
||||
|
||||
LOGOUT_SELF(200), // 自己主动登出
|
||||
LOGOUT_DELETE(202), // 强制退出
|
||||
|
||||
+1
-2
@@ -15,8 +15,7 @@ public enum LoginResultEnum {
|
||||
USER_DISABLED(20), // 用户被禁用
|
||||
CAPTCHA_NOT_FOUND(30), // 图片验证码不存在
|
||||
CAPTCHA_CODE_ERROR(31), // 图片验证码不正确
|
||||
|
||||
;
|
||||
FIRST_LOGIN(32); // 首次登录
|
||||
|
||||
/**
|
||||
* 结果
|
||||
|
||||
+7
-6
@@ -16,12 +16,13 @@ import java.util.Arrays;
|
||||
@AllArgsConstructor
|
||||
public enum SmsSceneEnum implements IntArrayValuable {
|
||||
|
||||
MEMBER_LOGIN(1, "user-sms-login", "会员用户 - 手机号登陆"),
|
||||
MEMBER_UPDATE_MOBILE(2, "user-update-mobile", "会员用户 - 修改手机"),
|
||||
MEMBER_UPDATE_PASSWORD(3, "user-update-password", "会员用户 - 修改密码"),
|
||||
MEMBER_RESET_PASSWORD(4, "user-reset-password", "会员用户 - 忘记密码"),
|
||||
|
||||
ADMIN_MEMBER_LOGIN(21, "admin-sms-login", "后台用户 - 手机号登录");
|
||||
USER_UPDATE_MOBILE(1, "user-update-mobile", "修改手机"),
|
||||
USER_RESET_PASSWORD(2, "user-reset-password", "忘记密码"),
|
||||
USER_UPDATE_PASSWORD(3, "user-update-password", "修改密码"),
|
||||
USER_LOGIN_CAPTCHAVERIFICATION(4, "user-login-captchaverification", "登录验证码"),
|
||||
ADMIN_LOGIN(20, "admin-sms-login", "后台用户 - 手机号登录"),
|
||||
ADMIN_MEMBER_LOGIN(21, "admin-sms-login", "后台用户 - 手机号登录"),
|
||||
MEMBER_LOGIN(22, "user-sms-login", "会员用户 - 手机号登陆");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsSceneEnum::getScene).toArray();
|
||||
|
||||
|
||||
+13
@@ -22,4 +22,17 @@ public enum SmsTemplateTypeEnum {
|
||||
*/
|
||||
private final int type;
|
||||
|
||||
|
||||
public static SmsTemplateTypeEnum valueOf(Integer type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
for (SmsTemplateTypeEnum value : values()) {
|
||||
if (type == value.getType()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -2,12 +2,14 @@ package com.cf.imes.module.system;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
|
||||
/**
|
||||
* 项目的启动类
|
||||
* @author 晨丰科技
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@RefreshScope
|
||||
public class SystemServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
+21
-4
@@ -1,13 +1,13 @@
|
||||
package com.cf.imes.module.system.controller.admin.auth;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||
import com.cf.imes.framework.common.pojo.CommonResult;
|
||||
import com.cf.imes.framework.operatelog.core.annotations.OperateLog;
|
||||
import com.cf.imes.framework.security.config.SecurityProperties;
|
||||
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthPermissionInfoRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO;
|
||||
@@ -20,6 +20,7 @@ import com.cf.imes.module.system.service.auth.AdminAuthService;
|
||||
import com.cf.imes.module.system.service.permission.MenuService;
|
||||
import com.cf.imes.module.system.service.permission.PermissionService;
|
||||
import com.cf.imes.module.system.service.permission.RoleService;
|
||||
import com.cf.imes.module.system.service.sms.SmsCodeService;
|
||||
import com.cf.imes.module.system.service.social.SocialClientService;
|
||||
import com.cf.imes.module.system.service.user.AdminUserService;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO;
|
||||
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -43,6 +45,7 @@ import java.util.Set;
|
||||
|
||||
import static com.cf.imes.framework.common.pojo.CommonResult.success;
|
||||
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static com.cf.imes.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - 认证")
|
||||
@@ -68,11 +71,14 @@ public class AuthController {
|
||||
@Resource
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
@Resource
|
||||
private SmsCodeService smsCodeService;
|
||||
|
||||
@PostMapping("/login")
|
||||
@PermitAll
|
||||
@Operation(summary = "使用账号密码登录")
|
||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
|
||||
public CommonResult<AuthLoginRespVO> login(@Valid @RequestBody AuthLoginReqVO reqVO) {
|
||||
return success(authService.login(reqVO));
|
||||
}
|
||||
|
||||
@@ -83,7 +89,7 @@ public class AuthController {
|
||||
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
||||
String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
||||
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
if (StringUtils.isNotBlank(token)) {
|
||||
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
|
||||
}
|
||||
return success(true);
|
||||
@@ -112,7 +118,6 @@ public class AuthController {
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return success(AuthConvert.INSTANCE.convert(user, Collections.emptyList(), Collections.emptyList()));
|
||||
}
|
||||
//List<RoleDO> roles = roleService.getRoleList(roleIds);
|
||||
List<RoleDO> roles = roleService.getRoleList1(roleIds);
|
||||
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
|
||||
|
||||
@@ -144,6 +149,18 @@ public class AuthController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/sms-code")
|
||||
@Operation(summary = "发送短信验证码")
|
||||
@PermitAll
|
||||
@OperateLog(enable = false)
|
||||
public CommonResult<Boolean> sendSmsCode(@Valid @RequestBody SmsCodeSendReqDTO reqDTO) {
|
||||
// 获取请求ip
|
||||
reqDTO.setCreateIp(getClientIP());
|
||||
// 发送验证码
|
||||
smsCodeService.sendSmsCode(reqDTO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ========== 社交登录相关 ==========
|
||||
|
||||
@GetMapping("/social-auth-redirect")
|
||||
|
||||
+13
-6
@@ -1,6 +1,6 @@
|
||||
package com.cf.imes.module.system.controller.admin.auth.vo;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.cf.imes.framework.common.validation.InEnum;
|
||||
import com.cf.imes.module.system.enums.social.SocialTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -8,10 +8,10 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@@ -29,12 +29,10 @@ public class AuthLoginReqVO {
|
||||
private String username;
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "组织名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰科技")
|
||||
@NotBlank(message = "组织名称不能空")
|
||||
private String organName;
|
||||
|
||||
// ========== 图片验证码相关 ==========
|
||||
@@ -44,6 +42,10 @@ public class AuthLoginReqVO {
|
||||
@NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class)
|
||||
private String captchaVerification;
|
||||
|
||||
@Schema(description = "短信验证码", example = "123456")
|
||||
@Length(min = 6, max = 6, message = "短信验证码长度为 6 位")
|
||||
private String smsCaptchaVerification;
|
||||
|
||||
// ========== 绑定社交登录时,需要传递如下参数 ==========
|
||||
|
||||
@Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
@@ -63,12 +65,17 @@ public class AuthLoginReqVO {
|
||||
|
||||
@AssertTrue(message = "授权码不能为空")
|
||||
public boolean isSocialCodeValid() {
|
||||
return socialType == null || StrUtil.isNotEmpty(socialCode);
|
||||
return socialType == null || StringUtils.isNotEmpty(socialCode);
|
||||
}
|
||||
|
||||
@AssertTrue(message = "授权 state 不能为空")
|
||||
public boolean isSocialState() {
|
||||
return socialType == null || StrUtil.isNotEmpty(socialState);
|
||||
return socialType == null || StringUtils.isNotEmpty(socialState);
|
||||
}
|
||||
|
||||
@AssertTrue(message = "请输入密码后登陆")
|
||||
public boolean isPasswordValid() {
|
||||
// 要么是密码登录、要么是验证码登录
|
||||
return ObjectUtil.isNotNull(password) || ObjectUtil.isNotNull(captchaVerification);
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -46,6 +46,9 @@ public class AuthPermissionInfoRespVO {
|
||||
|
||||
@Schema(description = "组织id")
|
||||
private Long organId;
|
||||
|
||||
@Schema(description = "是否需要设置密码")
|
||||
private boolean needSetPwd;
|
||||
}
|
||||
|
||||
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
|
||||
|
||||
+1
-10
@@ -1,13 +1,10 @@
|
||||
package com.cf.imes.module.system.controller.admin.organ.vo.organ;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.cf.imes.framework.common.validation.Mobile;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
@@ -30,6 +27,7 @@ public class OrganSaveReqVO {
|
||||
|
||||
@Schema(description = "联系手机", example = "15601691300")
|
||||
@Mobile
|
||||
@NotNull(message = "联系人手机不能为空")
|
||||
private String contactMobile;
|
||||
|
||||
@Schema(description = "组织状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@@ -62,13 +60,6 @@ public class OrganSaveReqVO {
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@AssertTrue(message = "用户账号、密码不能为空")
|
||||
@JsonIgnore
|
||||
public boolean isUsernameValid() {
|
||||
return id != null // 修改时,不需要传递
|
||||
|| (ObjectUtil.isAllNotEmpty(username, password)); // 新增时,必须都传递 username、password
|
||||
}
|
||||
|
||||
@Schema(description = "是否大型组织")
|
||||
private Boolean large;
|
||||
|
||||
|
||||
+1
-3
@@ -5,12 +5,10 @@ import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.pojo.CommonResult;
|
||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||
import com.cf.imes.framework.security.core.LoginUser;
|
||||
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSimpleRespVO;
|
||||
import com.cf.imes.module.system.convert.auth.AuthConvert;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
@@ -111,7 +109,7 @@ public class MenuController {
|
||||
"在多组织的场景下,会只返回组织所在套餐有的菜单")
|
||||
public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() {
|
||||
List<MenuDO> list = menuService.getMenuListByOrgan(
|
||||
new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()).setVisiable(CommonStatusEnum.ENABLE.getStatus()));
|
||||
list.sort(Comparator.comparing(MenuDO::getSort));
|
||||
return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
|
||||
}
|
||||
|
||||
+3
-1
@@ -10,7 +10,9 @@ public class MenuListReqVO {
|
||||
@Schema(description = "菜单名称,模糊匹配", example = "晨丰")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
|
||||
@Schema(description = "菜单状态,参见 CommonStatusEnum 枚举类", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
|
||||
private Integer visiable;
|
||||
}
|
||||
|
||||
+10
@@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
@@ -100,6 +101,15 @@ public class UserController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/reset-password")
|
||||
@Operation(summary = "忘记密码重置")
|
||||
@PermitAll
|
||||
@OperateLog(enable = false)
|
||||
public CommonResult<Boolean> resetUserPassword(@Valid @RequestBody UserResetPasswordReqVO reqVO) {
|
||||
userService.resetUserPassword(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "修改用户状态")
|
||||
// @PreAuthorize("@ss.hasPermission('system:user:update')")
|
||||
|
||||
+7
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||
import com.cf.imes.framework.common.pojo.CommonResult;
|
||||
import com.cf.imes.framework.datapermission.core.annotation.DataPermission;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserMobileUpdateReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
|
||||
@@ -97,4 +98,10 @@ public class UserProfileController {
|
||||
return success(avatar);
|
||||
}
|
||||
|
||||
@PostMapping("/security/phone")
|
||||
@Operation(summary = "修改密保手机")
|
||||
public CommonResult<Boolean> updateUserSecurityPhone(@Valid @RequestBody UserMobileUpdateReqVO reqVO) {
|
||||
userService.updateUserSecurityPhone(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.cf.imes.module.system.controller.admin.user.vo.profile;
|
||||
|
||||
import com.cf.imes.framework.common.validation.Mobile;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
|
||||
@Schema(description = "管理后台 - 用户密保手机更新 Request VO")
|
||||
@Data
|
||||
public class UserMobileUpdateReqVO {
|
||||
|
||||
|
||||
@Schema(description = "手机号码", example = "15601691300")
|
||||
@Length(min = 11, max = 11, message = "手机号长度必须 11 位")
|
||||
@Mobile
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "验证码", example = "123456")
|
||||
@Length(min = 6, max = 6, message = "验证码长度必须 6 位")
|
||||
@NotEmpty(message = "短信验证码不能为空")
|
||||
private String smsCaptchaVerification;
|
||||
}
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
package com.cf.imes.module.system.controller.admin.user.vo.profile;
|
||||
|
||||
import com.cf.imes.framework.common.validation.Mobile;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
@@ -23,6 +24,7 @@ public class UserProfileUpdateReqVO {
|
||||
|
||||
@Schema(description = "手机号码", example = "15601691300")
|
||||
@Length(min = 11, max = 11, message = "手机号长度必须 11 位")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "用户性别,参见 SexEnum 枚举类", example = "1")
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.cf.imes.module.system.controller.admin.user.vo.user;
|
||||
|
||||
import com.cf.imes.framework.common.validation.Mobile;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Schema(description = "管理后台 - 用户重置密码 Request VO")
|
||||
@Data
|
||||
public class UserResetPasswordReqVO {
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "短信验证码", example = "123456")
|
||||
@Length(min = 6, max = 6, message = "短信验证码长度为 6 位")
|
||||
@NotEmpty(message = "短信验证码不能为空")
|
||||
private String smsCaptchaVerification;
|
||||
|
||||
@Schema(description = "手机号", example = "15601691300")
|
||||
@Mobile
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
private String mobile;
|
||||
}
|
||||
+1
-8
@@ -18,7 +18,6 @@ public class UserSaveReqVO {
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "a", requiredMode = Schema.RequiredMode.REQUIRED, example = "chenfeng")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户名由 数字、字母 组成")
|
||||
@Size(min = 4, max = 30, message = "用户名长度为 4-30 个字符")
|
||||
private String username;
|
||||
@@ -43,6 +42,7 @@ public class UserSaveReqVO {
|
||||
|
||||
@Schema(description = "手机号码", example = "15601691300")
|
||||
@Mobile
|
||||
@NotBlank(message = "手机号码不能为空")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "用户性别,参见 SexEnum 枚举类", example = "1")
|
||||
@@ -57,13 +57,6 @@ public class UserSaveReqVO {
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@AssertTrue(message = "密码不能为空")
|
||||
@JsonIgnore
|
||||
public boolean isPasswordValid() {
|
||||
return id != null // 修改时,不需要传递
|
||||
|| (ObjectUtil.isAllNotEmpty(password)); // 新增时,必须都传递 password
|
||||
}
|
||||
|
||||
@Schema(description = "帐号状态(0正常 1停用)")
|
||||
@NotNull(message = "账号状态不能空")
|
||||
private Integer status;
|
||||
|
||||
+3
-2
@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import com.cf.imes.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.*;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthPermissionInfoRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
|
||||
@@ -15,6 +14,7 @@ import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.cf.imes.module.system.enums.permission.MenuTypeEnum;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -33,7 +33,8 @@ public interface AuthConvert {
|
||||
|
||||
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
|
||||
return AuthPermissionInfoRespVO.builder()
|
||||
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).organId(user.getOrganId()).build())
|
||||
.user(AuthPermissionInfoRespVO.UserVO.builder()
|
||||
.id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).organId(user.getOrganId()).needSetPwd(StringUtils.isEmpty(user.getPassword())).build())
|
||||
.roles(convertSet(roleList, RoleDO::getCode))
|
||||
// 权限标识信息
|
||||
.permissions(convertSet(menuList, MenuDO::getPermission))
|
||||
|
||||
+2
-3
@@ -17,9 +17,8 @@ public interface OrganConvert {
|
||||
|
||||
default UserSaveReqVO convert02(OrganSaveReqVO bean) {
|
||||
UserSaveReqVO reqVO = new UserSaveReqVO();
|
||||
reqVO.setUsername(bean.getUsername());
|
||||
reqVO.setPassword(bean.getPassword());
|
||||
reqVO.setNickname(bean.getContactName()).setMobile(bean.getContactMobile());
|
||||
reqVO.setMobile(bean.getContactMobile());
|
||||
reqVO.setNickname(bean.getContactName());
|
||||
return reqVO;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
|
||||
default List<MenuDO> selectList(MenuListReqVO reqVO) {
|
||||
return selectList(new LambdaQueryWrapperX<MenuDO>()
|
||||
.likeIfPresent(MenuDO::getName, reqVO.getName())
|
||||
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
|
||||
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(MenuDO::getVisible, reqVO.getVisiable()));
|
||||
}
|
||||
|
||||
default List<MenuDO> selectListByPermission(String permission) {
|
||||
|
||||
+24
-3
@@ -1,8 +1,9 @@
|
||||
package com.cf.imes.module.system.dal.mysql.user;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
@@ -11,7 +12,6 @@ import com.cf.imes.framework.security.core.LoginUser;
|
||||
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.cf.imes.module.executor.enums.OrderDeletedEnum;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserPageReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -22,7 +22,8 @@ import java.time.LocalTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.USERNAME_MULTIPLE_ERROR;
|
||||
|
||||
@Mapper
|
||||
public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
|
||||
@@ -35,6 +36,26 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名查询唯一用户,存在多个用户抛出业务异常
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
@OrganIgnore
|
||||
default AdminUserDO selectByUsernameUnique(String userName) {
|
||||
AdminUserDO adminUserDO = null;
|
||||
List<AdminUserDO> adminUserDOS = selectList(new LambdaQueryWrapperX<AdminUserDO>()
|
||||
.eq(AdminUserDO::getUsername, userName));
|
||||
if (CollectionUtil.isNotEmpty(adminUserDOS)) {
|
||||
if (adminUserDOS.size() > 1) {
|
||||
ServiceExceptionUtil.exception(USERNAME_MULTIPLE_ERROR, userName);
|
||||
}
|
||||
adminUserDO = adminUserDOS.get(0);
|
||||
}
|
||||
return adminUserDO;
|
||||
}
|
||||
|
||||
default AdminUserDO selectByEmail(String email) {
|
||||
return selectOne(AdminUserDO::getEmail, email);
|
||||
}
|
||||
|
||||
+10
@@ -98,4 +98,14 @@ public interface RedisKeyConstants {
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String SMS_TEMPLATE = "sms_template";
|
||||
|
||||
/**
|
||||
* 短信验证码的缓存
|
||||
*/
|
||||
String SMS_CAPTCHA_VERIFICATION = "sms_captcha_verification:%s";
|
||||
|
||||
/**
|
||||
* 短信验证码次数上限的缓存
|
||||
*/
|
||||
String SMS_CAPTCHA_VERIFICATION_LIMIT = "sms_captcha_verification_limit:%s";
|
||||
}
|
||||
|
||||
+11
@@ -38,4 +38,15 @@ public class SmsCodeProperties {
|
||||
@NotNull(message = "验证码最大值不能为空")
|
||||
private Integer endCode;
|
||||
|
||||
/**
|
||||
* 窗口时间
|
||||
*/
|
||||
@NotNull(message = "窗口时间不能为空")
|
||||
private Duration sendWindowSizeMin;
|
||||
|
||||
/**
|
||||
* 窗口最大请求次数
|
||||
*/
|
||||
@NotNull(message = "窗口最大请求次数不能为空")
|
||||
private Integer sendMaxSendTimesPerWindow;
|
||||
}
|
||||
|
||||
+5
@@ -39,4 +39,9 @@ public class SmsSendMessage {
|
||||
*/
|
||||
private List<KeyValue<String, Object>> templateParams;
|
||||
|
||||
/**
|
||||
* 短信消息模板类型:system_sms_template.type
|
||||
* 不同的消息模板类型有不同的消息发送后处理器
|
||||
*/
|
||||
private Integer templateType;
|
||||
}
|
||||
|
||||
+9
-7
@@ -1,10 +1,12 @@
|
||||
package com.cf.imes.module.system.mq.producer.sms;
|
||||
|
||||
import com.cf.imes.framework.common.core.KeyValue;
|
||||
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import com.cf.imes.module.system.mq.message.sms.SmsSendMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
@@ -25,16 +27,16 @@ public class SmsProducer {
|
||||
/**
|
||||
* 发送 {@link SmsSendMessage} 消息
|
||||
*
|
||||
* @param logId 短信日志编号
|
||||
* @param mobile 手机号
|
||||
* @param channelId 渠道编号
|
||||
* @param apiTemplateId 短信模板编号
|
||||
* @param logId 短信日志编号
|
||||
* @param mobile 手机号
|
||||
* @param template 模板信息
|
||||
* @param templateParams 短信模板参数
|
||||
*/
|
||||
public void sendSmsSendMessage(Long logId, String mobile,
|
||||
Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
|
||||
public void sendSmsSendMessage(Long logId, String mobile, SmsTemplateDO template, List<KeyValue<String, Object>> templateParams) {
|
||||
SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
|
||||
message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);
|
||||
message.setChannelId(template.getChannelId()).setApiTemplateId(template.getApiTemplateId()).setTemplateType(template.getType()).setTemplateParams(templateParams);
|
||||
// event异步发送短信,保证子线程内部request不为空
|
||||
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
|
||||
applicationContext.publishEvent(message);
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -1,6 +1,5 @@
|
||||
package com.cf.imes.module.system.service.auth;
|
||||
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.*;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
|
||||
@@ -22,11 +21,10 @@ public interface AdminAuthService {
|
||||
/**
|
||||
* 验证账号 + 密码。如果通过,则返回用户
|
||||
*
|
||||
* @param username 账号
|
||||
* @param password 密码
|
||||
* @param reqVO
|
||||
* @return 用户
|
||||
*/
|
||||
AdminUserDO authenticate(String username, String password, String organName);
|
||||
AdminUserDO authenticate(AuthLoginReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 账号登录
|
||||
|
||||
+28
-12
@@ -9,6 +9,7 @@ import com.cf.imes.framework.common.util.servlet.ServletUtils;
|
||||
import com.cf.imes.framework.common.util.validation.ValidationUtils;
|
||||
import com.cf.imes.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||
import com.cf.imes.module.system.api.sms.SmsCodeApi;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import com.cf.imes.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import com.cf.imes.module.system.api.social.dto.SocialUserRespDTO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO;
|
||||
@@ -27,6 +28,7 @@ import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.cf.imes.module.system.service.logger.LoginLogService;
|
||||
import com.cf.imes.module.system.service.member.MemberService;
|
||||
import com.cf.imes.module.system.service.oauth2.OAuth2TokenService;
|
||||
import com.cf.imes.module.system.service.sms.SmsCodeService;
|
||||
import com.cf.imes.module.system.service.social.SocialUserService;
|
||||
import com.cf.imes.module.system.service.organ.OrganService;
|
||||
import com.cf.imes.module.system.service.user.AdminUserService;
|
||||
@@ -47,7 +49,6 @@ import java.util.Objects;
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.cf.imes.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORGAN_DATA_CODE_NOT_EXISTS;
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORGAN_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* Auth Service 实现类
|
||||
@@ -77,6 +78,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
@Resource
|
||||
private OrganService organService;
|
||||
|
||||
@Resource
|
||||
private SmsCodeService smsCodeService;
|
||||
|
||||
/**
|
||||
* 验证码的开关,默认为 true
|
||||
*/
|
||||
@@ -84,19 +88,23 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
private Boolean captchaEnable;
|
||||
|
||||
@Override
|
||||
public AdminUserDO authenticate(String username, String password, String organName) {
|
||||
public AdminUserDO authenticate(AuthLoginReqVO reqVO) {
|
||||
String username = reqVO.getUsername();
|
||||
String password = reqVO.getPassword();
|
||||
String smsCaptchaVerification = reqVO.getSmsCaptchaVerification();
|
||||
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
|
||||
OrganizationDO organ = organService.getOrganByName(organName);
|
||||
if(Objects.isNull(organ)) {
|
||||
throw ServiceExceptionUtil.exception(ORGAN_NOT_EXISTS);
|
||||
}
|
||||
// 校验账号是否存在
|
||||
AdminUserDO user = userService.getUserByUsernameAndOrganId(username, organ.getId());
|
||||
// 查询账号
|
||||
AdminUserDO user = userService.getUserUniqueByUserName(username);
|
||||
// 校验用户是否存在
|
||||
if (user == null) {
|
||||
createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
if (!userService.isPasswordMatch(password, user.getPassword())) {
|
||||
// 校验机构有效性
|
||||
organService.validOrgan(user.getOrganId());
|
||||
// 校验密码
|
||||
String userPassword = user.getPassword();
|
||||
if (StringUtils.isNotEmpty(userPassword) && !userService.isPasswordMatch(password, userPassword)) {
|
||||
createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
}
|
||||
@@ -105,6 +113,11 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED);
|
||||
}
|
||||
// 校验没有密码并且没有传验证码,告诉前端需要验证码登录
|
||||
if (StringUtils.isEmpty(userPassword) && StringUtils.isEmpty(smsCaptchaVerification)) {
|
||||
createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.FIRST_LOGIN);
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.AUTH_LOGIN_FIRST_LOGIN);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -114,7 +127,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
validateCaptcha(reqVO);
|
||||
|
||||
// 使用账号密码,进行登录
|
||||
AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword(), reqVO.getOrganName());
|
||||
AdminUserDO user = authenticate(reqVO);
|
||||
|
||||
// 如果 socialType 非空,说明需要绑定社交用户
|
||||
if (reqVO.getSocialType() != null) {
|
||||
@@ -122,14 +135,17 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
|
||||
}
|
||||
Long organId = user.getOrganId();
|
||||
// 校验机构有效性
|
||||
organService.validOrgan(organId);
|
||||
OrganizationDO organ = organService.getOrgan(organId);
|
||||
String dataSourceCode = organ.getDataSourceCode();
|
||||
// 校验机构数据源
|
||||
if (StringUtils.isBlank(dataSourceCode)) {
|
||||
throw exception(ORGAN_DATA_CODE_NOT_EXISTS);
|
||||
}
|
||||
// 验证并消耗验证码
|
||||
String smsCaptchaVerification = reqVO.getSmsCaptchaVerification();
|
||||
if (StringUtils.isNotEmpty(smsCaptchaVerification)) {
|
||||
smsCodeService.useSmsCode(SmsCodeUseReqDTO.builder().mobile(user.getUsername()).code(smsCaptchaVerification).scene(SmsSceneEnum.USER_LOGIN_CAPTCHAVERIFICATION.getScene()).build());
|
||||
}
|
||||
// 创建 Token 令牌,记录登录日志
|
||||
return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME,organ.getLarge(), dataSourceCode, user.getOrganId(), user.getNickname());
|
||||
}
|
||||
|
||||
+7
-10
@@ -1,14 +1,12 @@
|
||||
package com.cf.imes.module.system.service.oauth2;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||
import com.cf.imes.framework.common.exception.ServerException;
|
||||
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2CodeDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.cf.imes.module.system.enums.ErrorCodeConstants;
|
||||
import com.cf.imes.module.system.service.auth.AdminAuthService;
|
||||
@@ -17,7 +15,6 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
@@ -64,16 +61,16 @@ public class OAuth2GrantServiceImpl implements OAuth2GrantService {
|
||||
OAuth2CodeDO codeDO = oauth2CodeService.consumeAuthorizationCode(code);
|
||||
Assert.notNull(codeDO, "授权码不能为空"); // 防御性编程
|
||||
// 校验 clientId 是否匹配
|
||||
if (!StrUtil.equals(clientId, codeDO.getClientId())) {
|
||||
if (!CharSequenceUtil.equals(clientId, codeDO.getClientId())) {
|
||||
throw exception(ErrorCodeConstants.OAUTH2_GRANT_CLIENT_ID_MISMATCH);
|
||||
}
|
||||
// 校验 redirectUri 是否匹配
|
||||
if (!StrUtil.equals(redirectUri, codeDO.getRedirectUri())) {
|
||||
if (!CharSequenceUtil.equals(redirectUri, codeDO.getRedirectUri())) {
|
||||
throw exception(ErrorCodeConstants.OAUTH2_GRANT_REDIRECT_URI_MISMATCH);
|
||||
}
|
||||
// 校验 state 是否匹配
|
||||
state = StrUtil.nullToDefault(state, ""); // 数据库 state 为 null 时,会设置为 "" 空串
|
||||
if (!StrUtil.equals(state, codeDO.getState())) {
|
||||
state = CharSequenceUtil.nullToDefault(state, ""); // 数据库 state 为 null 时,会设置为 "" 空串
|
||||
if (!CharSequenceUtil.equals(state, codeDO.getState())) {
|
||||
throw exception(ErrorCodeConstants.OAUTH2_GRANT_STATE_MISMATCH);
|
||||
}
|
||||
|
||||
@@ -91,7 +88,7 @@ public class OAuth2GrantServiceImpl implements OAuth2GrantService {
|
||||
@Override
|
||||
public OAuth2AccessTokenDO grantPassword(String username, String password, String clientId, List<String> scopes) {
|
||||
// 使用账号 + 密码进行登录
|
||||
AdminUserDO user = adminAuthService.authenticate(username, password, null);
|
||||
AdminUserDO user = adminAuthService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build());
|
||||
Assert.notNull(user, "用户不能为空!"); // 防御性编程
|
||||
/*Long organId = OrganContextHolder.getOrganId();
|
||||
OrganizationDO organ = organService.getOrgan(organId);
|
||||
|
||||
+7
@@ -78,4 +78,11 @@ public interface SmsChannelService {
|
||||
*/
|
||||
SmsClient getSmsClient(String code);
|
||||
|
||||
/**
|
||||
* 根据配置获得短信客户端
|
||||
*
|
||||
* @param
|
||||
* @return 短信客户端
|
||||
*/
|
||||
SmsClient getSmsClient();
|
||||
}
|
||||
|
||||
+23
-28
@@ -6,7 +6,7 @@ import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||
import com.cf.imes.framework.sms.core.client.SmsClient;
|
||||
import com.cf.imes.framework.sms.core.client.SmsClientFactory;
|
||||
import com.cf.imes.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.cf.imes.framework.sms.core.property.SmsProperties;
|
||||
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
@@ -22,7 +22,6 @@ import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.cf.imes.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||
|
||||
/**
|
||||
@@ -34,22 +33,28 @@ import static com.cf.imes.framework.common.util.cache.CacheUtils.buildAsyncReloa
|
||||
@Slf4j
|
||||
public class SmsChannelServiceImpl implements SmsChannelService {
|
||||
|
||||
@Resource
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
@Resource
|
||||
private SmsChannelMapper smsChannelMapper;
|
||||
|
||||
@Resource
|
||||
private SmsTemplateService smsTemplateService;
|
||||
|
||||
@Resource
|
||||
private SmsProperties smsProperties;
|
||||
|
||||
/**
|
||||
* {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
|
||||
*/
|
||||
@Getter
|
||||
private final LoadingCache<Long, SmsClient> idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
|
||||
new CacheLoader<Long, SmsClient>() {
|
||||
new CacheLoader<>() {
|
||||
|
||||
@Override
|
||||
public SmsClient load(Long id) {
|
||||
// 查询,然后尝试刷新
|
||||
SmsChannelDO channel = smsChannelMapper.selectById(id);
|
||||
if (channel != null) {
|
||||
SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);
|
||||
smsClientFactory.createOrUpdateSmsClient(properties);
|
||||
}
|
||||
return smsClientFactory.getSmsClient(id);
|
||||
return smsClientFactory.getSmsClient(smsProperties);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -59,30 +64,15 @@ public class SmsChannelServiceImpl implements SmsChannelService {
|
||||
*/
|
||||
@Getter
|
||||
private final LoadingCache<String, SmsClient> codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L),
|
||||
new CacheLoader<String, SmsClient>() {
|
||||
new CacheLoader<>() {
|
||||
|
||||
@Override
|
||||
public SmsClient load(String code) {
|
||||
// 查询,然后尝试刷新
|
||||
SmsChannelDO channel = smsChannelMapper.selectByCode(code);
|
||||
if (channel != null) {
|
||||
SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);
|
||||
smsClientFactory.createOrUpdateSmsClient(properties);
|
||||
}
|
||||
return smsClientFactory.getSmsClient(code);
|
||||
return smsClientFactory.getSmsClient(smsProperties);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Resource
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
@Resource
|
||||
private SmsChannelMapper smsChannelMapper;
|
||||
|
||||
@Resource
|
||||
private SmsTemplateService smsTemplateService;
|
||||
|
||||
@Override
|
||||
public Long createSmsChannel(SmsChannelSaveReqVO createReqVO) {
|
||||
SmsChannelDO channel = BeanUtils.toBean(createReqVO, SmsChannelDO.class);
|
||||
@@ -160,7 +150,12 @@ public class SmsChannelServiceImpl implements SmsChannelService {
|
||||
|
||||
@Override
|
||||
public SmsClient getSmsClient(String code) {
|
||||
return codeClientCache.getUnchecked(code);
|
||||
return smsClientFactory.getSmsClient(smsProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsClient getSmsClient() {
|
||||
return smsClientFactory.getSmsClient(smsProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+101
-27
@@ -3,24 +3,29 @@ package com.cf.imes.module.system.service.sms;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
||||
import com.cf.imes.module.system.dal.dataobject.sms.SmsCodeDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.cf.imes.module.system.dal.mysql.sms.SmsCodeMapper;
|
||||
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.cf.imes.module.system.framework.sms.SmsCodeProperties;
|
||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.cf.imes.module.system.enums.ErrorCodeConstants;
|
||||
import com.cf.imes.module.system.service.user.AdminUserService;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomInt;
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.cf.imes.framework.common.util.date.DateUtils.isToday;
|
||||
|
||||
/**
|
||||
* 短信验证码 Service 实现类
|
||||
@@ -40,49 +45,118 @@ public class SmsCodeServiceImpl implements SmsCodeService {
|
||||
@Resource
|
||||
private SmsSendService smsSendService;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Resource
|
||||
private AdminUserService userService;
|
||||
|
||||
@Override
|
||||
public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
|
||||
// 非修改手机号的场景时校验手机号是否存在
|
||||
AdminUserDO user = userService.getUserByUsernameAndOrganId(reqDTO.getMobile(), null);
|
||||
if (ObjectUtil.isNull(user) && ObjectUtil.notEqual(SmsSceneEnum.USER_UPDATE_MOBILE.getScene(), reqDTO.getScene())) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.AUTH_MOBILE_NOT_EXISTS);
|
||||
}
|
||||
SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
|
||||
Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
|
||||
// 创建验证码
|
||||
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
|
||||
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getCreateIp());
|
||||
// 发送验证码
|
||||
HashMap<String, Object> map = MapUtil.of("code", code);
|
||||
map.put("product","晨丰科技");
|
||||
smsSendService.sendSingleSms(reqDTO.getMobile(), null, null,
|
||||
sceneEnum.getTemplateCode(), MapUtil.of("code", code));
|
||||
sceneEnum.getTemplateCode(), map);
|
||||
}
|
||||
|
||||
private String createSmsCode(String mobile, Integer scene, String ip) {
|
||||
// 校验是否可以发送验证码,不用筛选场景
|
||||
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);
|
||||
if (lastSmsCode != null) {
|
||||
if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
|
||||
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param ip 请求来源ip
|
||||
* @return
|
||||
*/
|
||||
private String createSmsCode(String mobile, String ip) {
|
||||
String redisMobileKey = String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, mobile);
|
||||
String redisMobileCountKey = String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION_LIMIT, mobile);
|
||||
String redisIpCountKey = String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION_LIMIT, ip);
|
||||
boolean hasCode = redisTemplate.hasKey(redisMobileKey);
|
||||
if (!hasCode) {
|
||||
// 验证码不存在
|
||||
Duration sendWindowSizeMin = smsCodeProperties.getSendWindowSizeMin();
|
||||
Integer sendMaxSendTimesPerWindow = smsCodeProperties.getSendMaxSendTimesPerWindow();
|
||||
// 限制手机号请求次数:10分钟5次
|
||||
boolean mobileCountRateLimit = slidingWindowRateLimit(redisMobileCountKey, sendWindowSizeMin.toMinutesPart(), sendMaxSendTimesPerWindow);
|
||||
if (!mobileCountRateLimit) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_SEND_TOO_FAST);
|
||||
}
|
||||
if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限
|
||||
lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
|
||||
// 限制ip请求次数
|
||||
boolean ipCountRateLimit = slidingWindowRateLimit(redisIpCountKey, sendWindowSizeMin.toMinutesPart(), sendMaxSendTimesPerWindow);
|
||||
if (!ipCountRateLimit) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_SEND_TOO_FAST);
|
||||
}
|
||||
// TODO 晨丰:提升,每个 IP 每天可发送数量
|
||||
// TODO 晨丰:提升,每个 IP 每小时可发送数量
|
||||
} else {
|
||||
// 验证码存在提示请勿重复发送
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_IS_VALID);
|
||||
}
|
||||
|
||||
// 创建验证码记录
|
||||
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
|
||||
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
|
||||
.todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
|
||||
.createIp(ip).used(false).build();
|
||||
smsCodeMapper.insert(newSmsCode);
|
||||
return code;
|
||||
// 生成6位随机验证码
|
||||
StringBuffer code = new StringBuffer(6);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
code.append(randomInt(0, 9));
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 滑动窗口限制发送次数
|
||||
*
|
||||
* @param key
|
||||
* @param winSizeMin 窗口时间大小
|
||||
* @param maxSendTimesPerWin 窗口内最大请求次数
|
||||
* @return false没有达到发送频繁告警要求
|
||||
*/
|
||||
public boolean slidingWindowRateLimit(String key, int winSizeMin, int maxSendTimesPerWin) {
|
||||
// 获取当前时间戳
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
// 清除过期的时间片
|
||||
redisTemplate.opsForZSet().removeRangeByScore(key, 0L, currentTime - (winSizeMin * 60 * 1000));
|
||||
|
||||
// 检查过去 winSizeMin 分钟内的请求次数
|
||||
long requestCount = redisTemplate.opsForZSet().zCard(key);
|
||||
if (requestCount >= maxSendTimesPerWin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 窗口足够添加当前请求的时间戳
|
||||
redisTemplate.opsForZSet().add(key, currentTime, currentTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
|
||||
// 检测验证码是否有效
|
||||
SmsCodeDO lastSmsCode = validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
|
||||
// 使用验证码
|
||||
smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
|
||||
.used(true).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build());
|
||||
// 非修改手机号的场景时校验手机号是否存在
|
||||
AdminUserDO user = userService.getUserByUsernameAndOrganId(reqDTO.getMobile(), null);
|
||||
if (ObjectUtil.isNull(user) && ObjectUtil.notEqual(SmsSceneEnum.USER_UPDATE_MOBILE.getScene(), reqDTO.getScene())) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.AUTH_MOBILE_NOT_EXISTS);
|
||||
}
|
||||
String smsCodeKey = String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, reqDTO.getMobile());
|
||||
Object keyObj = redisTemplate.opsForValue().get(smsCodeKey);
|
||||
if (ObjectUtil.isNotNull(keyObj)) {
|
||||
// 校验码存在
|
||||
if (ObjectUtil.notEqual(String.valueOf(keyObj), reqDTO.getCode())) {
|
||||
// 验证码不正确
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_NOT_CORRECT);
|
||||
} else {
|
||||
// 消耗验证码
|
||||
redisTemplate.delete(smsCodeKey);
|
||||
}
|
||||
} else {
|
||||
// 提示验证码不存在
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -40,10 +40,11 @@ public interface SmsLogService {
|
||||
* @param apiSendMsg 短信 API 发送失败的提示
|
||||
* @param apiRequestId 短信 API 发送返回的唯一请求 ID
|
||||
* @param apiSerialNo 短信 API 发送返回的序号
|
||||
* @param channelCode 短信 API 渠道编码
|
||||
*/
|
||||
void updateSmsSendResult(Long id, Boolean success,
|
||||
String apiSendCode, String apiSendMsg,
|
||||
String apiRequestId, String apiSerialNo);
|
||||
String apiRequestId, String apiSerialNo, String channelCode);
|
||||
|
||||
/**
|
||||
* 更新日志的接收结果
|
||||
|
||||
+2
-4
@@ -40,8 +40,6 @@ public class SmsLogServiceImpl implements SmsLogService {
|
||||
logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType());
|
||||
logBuilder.templateContent(templateContent).templateParams(templateParams)
|
||||
.apiTemplateId(template.getApiTemplateId());
|
||||
// 设置渠道相关字段
|
||||
logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode());
|
||||
// 设置接收相关字段
|
||||
logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus());
|
||||
|
||||
@@ -54,12 +52,12 @@ public class SmsLogServiceImpl implements SmsLogService {
|
||||
@Override
|
||||
public void updateSmsSendResult(Long id, Boolean success,
|
||||
String apiSendCode, String apiSendMsg,
|
||||
String apiRequestId, String apiSerialNo) {
|
||||
String apiRequestId, String apiSerialNo, String channelCode) {
|
||||
SmsSendStatusEnum sendStatus = success ? SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;
|
||||
smsLogMapper.updateById(SmsLogDO.builder().id(id)
|
||||
.sendStatus(sendStatus.getStatus()).sendTime(LocalDateTime.now())
|
||||
.apiSendCode(apiSendCode).apiSendMsg(apiSendMsg)
|
||||
.apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build());
|
||||
.apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).channelCode(channelCode).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+24
-28
@@ -3,7 +3,6 @@ package com.cf.imes.module.system.service.sms;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.cf.imes.framework.common.core.KeyValue;
|
||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||
@@ -12,16 +11,17 @@ import com.cf.imes.framework.datapermission.core.annotation.DataPermission;
|
||||
import com.cf.imes.framework.sms.core.client.SmsClient;
|
||||
import com.cf.imes.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import com.cf.imes.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.cf.imes.module.system.mq.message.sms.SmsSendMessage;
|
||||
import com.cf.imes.module.system.mq.producer.sms.SmsProducer;
|
||||
import com.cf.imes.module.system.service.member.MemberService;
|
||||
import com.cf.imes.module.system.service.sms.handler.SmsSendAfterSendHandler;
|
||||
import com.cf.imes.module.system.service.user.AdminUserService;
|
||||
import com.cf.imes.module.system.enums.ErrorCodeConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -29,8 +29,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 短信发送 Service 发送的实现
|
||||
*
|
||||
@@ -54,11 +52,14 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
@Resource
|
||||
private SmsProducer smsProducer;
|
||||
|
||||
@Resource
|
||||
private List<SmsSendAfterSendHandler> smsSendAfterSendHandlers;
|
||||
|
||||
@Override
|
||||
@DataPermission(enable = false) // 发送短信时,无需考虑数据权限
|
||||
public Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
|
||||
// 如果 mobile 为空,则加载用户编号对应的手机号
|
||||
if (StrUtil.isEmpty(mobile)) {
|
||||
if (StringUtils.isEmpty(mobile)) {
|
||||
AdminUserDO user = adminUserService.getUser(userId);
|
||||
if (user != null) {
|
||||
mobile = user.getMobile();
|
||||
@@ -71,7 +72,7 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
@Override
|
||||
public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
|
||||
// 如果 mobile 为空,则加载用户编号对应的手机号
|
||||
if (StrUtil.isEmpty(mobile)) {
|
||||
if (StringUtils.isEmpty(mobile)) {
|
||||
mobile = memberService.getMemberUserMobile(userId);
|
||||
}
|
||||
// 执行发送
|
||||
@@ -83,8 +84,6 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
String templateCode, Map<String, Object> templateParams) {
|
||||
// 校验短信模板是否合法
|
||||
SmsTemplateDO template = validateSmsTemplate(templateCode);
|
||||
// 校验短信渠道是否合法
|
||||
SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId());
|
||||
|
||||
// 校验手机号码是否存在
|
||||
mobile = validateMobile(mobile);
|
||||
@@ -92,30 +91,17 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
List<KeyValue<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);
|
||||
|
||||
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
|
||||
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())
|
||||
&& CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());
|
||||
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
|
||||
String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);
|
||||
Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);
|
||||
|
||||
// 发送 MQ 消息,异步执行发送短信
|
||||
if (isSend) {
|
||||
smsProducer.sendSmsSendMessage(sendLogId, mobile, template.getChannelId(),
|
||||
template.getApiTemplateId(), newTemplateParams);
|
||||
smsProducer.sendSmsSendMessage(sendLogId, mobile, template, newTemplateParams);
|
||||
}
|
||||
return sendLogId;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SmsChannelDO validateSmsChannel(Long channelId) {
|
||||
// 获得短信模板。考虑到效率,从缓存中获取
|
||||
SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);
|
||||
// 短信模板不存在
|
||||
if (channelDO == null) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS);
|
||||
}
|
||||
return channelDO;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SmsTemplateDO validateSmsTemplate(String templateCode) {
|
||||
// 获得短信模板。考虑到效率,从缓存中获取
|
||||
@@ -149,7 +135,7 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
|
||||
@VisibleForTesting
|
||||
public String validateMobile(String mobile) {
|
||||
if (StrUtil.isEmpty(mobile)) {
|
||||
if (StringUtils.isEmpty(mobile)) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_SEND_MOBILE_NOT_EXISTS);
|
||||
}
|
||||
return mobile;
|
||||
@@ -158,19 +144,29 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
@Override
|
||||
public void doSendSms(SmsSendMessage message) {
|
||||
// 获得渠道对应的 SmsClient 客户端
|
||||
SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId());
|
||||
SmsClient smsClient = smsChannelService.getSmsClient();
|
||||
Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
|
||||
|
||||
List<KeyValue<String, Object>> templateParams = message.getTemplateParams();
|
||||
String channelCode = null;
|
||||
// 发送短信
|
||||
try {
|
||||
SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(),
|
||||
message.getApiTemplateId(), message.getTemplateParams());
|
||||
message.getApiTemplateId(), templateParams);
|
||||
channelCode = sendResponse.getChannelCode();
|
||||
// 发送成功后操作
|
||||
for (SmsSendAfterSendHandler afterSendHandler : smsSendAfterSendHandlers) {
|
||||
if (afterSendHandler.checkTemplateType(message.getTemplateType())) {
|
||||
afterSendHandler.afterSend(sendResponse, templateParams);
|
||||
}
|
||||
}
|
||||
smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),
|
||||
sendResponse.getApiCode(), sendResponse.getApiMsg(),
|
||||
sendResponse.getApiRequestId(), sendResponse.getSerialNo());
|
||||
sendResponse.getApiRequestId(), sendResponse.getSerialNo(), channelCode);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[doSendSms][发送短信异常,日志编号({})]", message.getLogId(), ex);
|
||||
smsLogService.updateSmsSendResult(message.getLogId(), false,
|
||||
"EXCEPTION", ExceptionUtil.getRootCauseMessage(ex), null, null);
|
||||
"EXCEPTION", ExceptionUtil.getRootCauseMessage(ex), null, null, channelCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
||||
@VisibleForTesting
|
||||
void validateApiTemplate(Long channelId, String apiTemplateId) {
|
||||
// 获得短信模板
|
||||
SmsClient smsClient = smsChannelService.getSmsClient(channelId);
|
||||
SmsClient smsClient = smsChannelService.getSmsClient();
|
||||
Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
|
||||
SmsTemplateRespDTO template;
|
||||
try {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.cf.imes.module.system.service.sms.handler;
|
||||
|
||||
import com.cf.imes.framework.common.core.KeyValue;
|
||||
import com.cf.imes.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 短信发送后处理器接口
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2024/8/12 18:42
|
||||
*/
|
||||
public interface SmsSendAfterSendHandler {
|
||||
/**
|
||||
* 校验消息模板类型
|
||||
*
|
||||
* @param templateType
|
||||
* @return
|
||||
*/
|
||||
boolean checkTemplateType(Integer templateType);
|
||||
|
||||
|
||||
void afterSend(SmsSendRespDTO sendResponse, List<KeyValue<String, Object>> params);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.cf.imes.module.system.service.sms.handler.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.cf.imes.framework.common.core.KeyValue;
|
||||
import com.cf.imes.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.cf.imes.module.system.enums.sms.SmsTemplateTypeEnum;
|
||||
import com.cf.imes.module.system.framework.sms.SmsCodeProperties;
|
||||
import com.cf.imes.module.system.service.sms.handler.SmsSendAfterSendHandler;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Gqr
|
||||
* @since 2024/8/12 18:49
|
||||
*/
|
||||
@Service
|
||||
public class SmsSendAfterSendHandlerImpl implements SmsSendAfterSendHandler {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Resource
|
||||
private SmsCodeProperties smsCodeProperties;
|
||||
|
||||
@Override
|
||||
public boolean checkTemplateType(Integer templateType) {
|
||||
SmsTemplateTypeEnum smsTemplateTypeEnum = SmsTemplateTypeEnum.valueOf(templateType);
|
||||
return ObjectUtil.isNotNull(smsTemplateTypeEnum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSend(SmsSendRespDTO sendResponse, List<KeyValue<String, Object>> params) {
|
||||
// 发送成功把验证码存入redis
|
||||
if (sendResponse.getSuccess()) {
|
||||
for (KeyValue<String, Object> keyValue : params) {
|
||||
if ("code".equals(keyValue.getKey())) {
|
||||
redisTemplate.opsForValue().set(String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, sendResponse.getMobile()), keyValue.getValue(), smsCodeProperties.getExpireTimes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-1
@@ -4,11 +4,13 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.framework.common.util.collection.CollectionUtils;
|
||||
import com.cf.imes.module.system.api.user.dto.OrganAdminUserRespDTO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserMobileUpdateReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserImportExcelVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserImportRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserPageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserResetPasswordReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserSaveReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
|
||||
@@ -71,7 +73,7 @@ public interface AdminUserService {
|
||||
* @param id 用户 id
|
||||
* @param avatarFile 头像文件
|
||||
*/
|
||||
String updateUserAvatar(Long id, InputStream avatarFile) throws Exception;
|
||||
String updateUserAvatar(Long id, InputStream avatarFile);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
@@ -81,6 +83,13 @@ public interface AdminUserService {
|
||||
*/
|
||||
void updateUserPassword(Long id, String password);
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*
|
||||
* @param reqVO
|
||||
*/
|
||||
void resetUserPassword(UserResetPasswordReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 修改状态
|
||||
*
|
||||
@@ -113,6 +122,14 @@ public interface AdminUserService {
|
||||
*/
|
||||
AdminUserDO getUserByUsernameAndOrganId(String username, Long organId);
|
||||
|
||||
/**
|
||||
* 用户名查询唯一用户
|
||||
*
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
AdminUserDO getUserUniqueByUserName(String userName);
|
||||
|
||||
/**
|
||||
* 通过手机号获取用户
|
||||
*
|
||||
@@ -242,4 +259,12 @@ public interface AdminUserService {
|
||||
* 用户活跃数统计
|
||||
*/
|
||||
Map<String, Integer> userActTotal();
|
||||
|
||||
/**
|
||||
* 修改密保手机
|
||||
*
|
||||
* @param id 用户编号
|
||||
* @param reqVO 用户个人信息
|
||||
*/
|
||||
void updateUserSecurityPhone(Long id, @Valid UserMobileUpdateReqVO reqVO);
|
||||
}
|
||||
|
||||
+106
-15
@@ -4,13 +4,17 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||
import com.cf.imes.framework.common.exception.ServiceException;
|
||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.framework.common.util.collection.CollectionUtils;
|
||||
import com.cf.imes.framework.common.util.monitor.TracerUtils;
|
||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||
import com.cf.imes.framework.common.util.pinyin.PinYinUtils;
|
||||
import com.cf.imes.framework.common.util.servlet.ServletUtils;
|
||||
import com.cf.imes.framework.datapermission.core.util.DataPermissionUtils;
|
||||
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.cf.imes.framework.mybatis.core.query.MPJLambdaWrapperX;
|
||||
@@ -18,13 +22,17 @@ import com.cf.imes.framework.organ.core.aop.OrganIgnore;
|
||||
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
|
||||
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.cf.imes.module.infra.api.file.FileApi;
|
||||
import com.cf.imes.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import com.cf.imes.module.system.api.user.dto.OrganAdminUserRespDTO;
|
||||
import com.cf.imes.module.system.constants.permission.InternalRoleConstants;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserMobileUpdateReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserImportExcelVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserImportRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserPageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserResetPasswordReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.user.vo.user.UserSaveReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dept.DeptDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dept.UserPostDO;
|
||||
@@ -32,11 +40,16 @@ import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.cf.imes.module.system.dal.mysql.dept.UserPostMapper;
|
||||
import com.cf.imes.module.system.dal.mysql.user.AdminUserMapper;
|
||||
import com.cf.imes.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import com.cf.imes.module.system.enums.logger.LoginResultEnum;
|
||||
import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.cf.imes.module.system.service.dept.DeptService;
|
||||
import com.cf.imes.module.system.service.dept.PostService;
|
||||
import com.cf.imes.module.system.service.logger.LoginLogService;
|
||||
import com.cf.imes.module.system.service.permission.PermissionService;
|
||||
import com.cf.imes.module.system.service.organ.OrganService;
|
||||
import com.cf.imes.module.system.enums.ErrorCodeConstants;
|
||||
import com.cf.imes.module.system.service.sms.SmsCodeService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -92,6 +105,12 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
@Resource
|
||||
private FileApi fileApi;
|
||||
|
||||
@Resource
|
||||
private SmsCodeService smsCodeService;
|
||||
|
||||
@Resource
|
||||
private LoginLogService loginLogService;
|
||||
|
||||
|
||||
// 定义密码校验的正则表达式
|
||||
private static final String LENGTH_PATTERN = ".{6,}";
|
||||
@@ -110,15 +129,14 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_COUNT_MAX, organ.getAccountCount());
|
||||
}
|
||||
});
|
||||
// 改造:手机号直接当做用户名
|
||||
createReqVO.setUsername(createReqVO.getMobile());
|
||||
// 校验正确性
|
||||
validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
|
||||
createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds(),createReqVO.getPassword(), organId);
|
||||
createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds(), null, organId);
|
||||
// 插入用户
|
||||
AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
|
||||
user.setOrganId(createReqVO.getOrganId());
|
||||
//user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
|
||||
user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
|
||||
|
||||
String pinyinFull = PinYinUtils.convertToPinyin(user.getNickname());
|
||||
if (StringUtils.isNotBlank(pinyinFull)) {
|
||||
pinyinFull = pinyinFull.replace(" ", "");
|
||||
@@ -138,12 +156,11 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateUser(UserSaveReqVO updateReqVO) {
|
||||
Long organId = updateReqVO.getOrganId() == null ? OrganContextHolder.getOrganId(): updateReqVO.getOrganId();
|
||||
updateReqVO.setPassword(null); // 特殊:此处不更新密码
|
||||
// 不允许自身操作
|
||||
checkCurrentWhenOperate(updateReqVO.getId());
|
||||
// 校验正确性
|
||||
validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),
|
||||
updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds(),null, organId);
|
||||
updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds(), null, organId);
|
||||
// 更新用户
|
||||
AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class);
|
||||
|
||||
@@ -190,7 +207,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
// 校验正确性
|
||||
validateUserExists(id);
|
||||
validateEmailUnique(id, reqVO.getEmail(), organId);
|
||||
validateMobileUnique(id, reqVO.getMobile(), organId);
|
||||
validateMobileUnique(id, reqVO.getMobile());
|
||||
// 执行更新
|
||||
userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));
|
||||
}
|
||||
@@ -231,6 +248,46 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
userMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void resetUserPassword(UserResetPasswordReqVO reqVO) {
|
||||
String mobile = reqVO.getMobile();
|
||||
String password = reqVO.getPassword();
|
||||
// 校验用户名
|
||||
AdminUserDO adminUserDO = validateUserNameExists(mobile);
|
||||
Long userId = adminUserDO.getId();
|
||||
String username = adminUserDO.getUsername();
|
||||
// 校验密码
|
||||
validatePassword(password);
|
||||
// 忘记密码消耗验证码
|
||||
smsCodeService.useSmsCode(SmsCodeUseReqDTO.builder()
|
||||
.mobile(mobile).code(reqVO.getSmsCaptchaVerification()).scene(SmsSceneEnum.USER_RESET_PASSWORD.getScene())
|
||||
.build());
|
||||
// 更新密码
|
||||
AdminUserDO updateObj = new AdminUserDO();
|
||||
updateObj.setId(userId);
|
||||
updateObj.setPassword(encodePassword(password)); // 加密密码
|
||||
userMapper.updateById(updateObj);
|
||||
|
||||
// 记录日志
|
||||
createLoginLog(userId, username, LoginLogTypeEnum.LOGIN_FORGET_PWD, LoginResultEnum.SUCCESS);
|
||||
}
|
||||
|
||||
private void createLoginLog(Long userId, String username,
|
||||
LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
|
||||
// 插入登录日志
|
||||
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
|
||||
reqDTO.setLogType(logTypeEnum.getType());
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
reqDTO.setUserId(userId);
|
||||
reqDTO.setUserType(UserTypeEnum.ADMIN.getValue());
|
||||
reqDTO.setUsername(username);
|
||||
reqDTO.setUserAgent(ServletUtils.getUserAgent());
|
||||
reqDTO.setUserIp(ServletUtils.getClientIP());
|
||||
reqDTO.setResult(loginResult.getResult());
|
||||
loginLogService.createLoginLog(reqDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserStatus(Long id, Integer status) {
|
||||
// 不允许自身操作
|
||||
@@ -269,10 +326,15 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
public AdminUserDO getUserByUsernameAndOrganId(String username, Long organId) {
|
||||
return userMapper.selectOne(new LambdaQueryWrapperX<AdminUserDO>()
|
||||
.eq(AdminUserDO::getUsername, username)
|
||||
.eq(AdminUserDO::getOrganId, organId)
|
||||
.eqIfPresent(AdminUserDO::getOrganId, organId)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminUserDO getUserUniqueByUserName(String userName) {
|
||||
return userMapper.selectByUsernameUnique(userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminUserDO getUserByMobile(String mobile) {
|
||||
return userMapper.selectByMobile(mobile);
|
||||
@@ -363,7 +425,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
return deptIds;
|
||||
}
|
||||
|
||||
private void validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
|
||||
private void validateUserForCreateOrUpdate(Long id, String username, String email,
|
||||
Long deptId, Set<Long> postIds,String password ,Long organId) {
|
||||
// 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
|
||||
DataPermissionUtils.executeIgnore(() -> {
|
||||
@@ -375,8 +437,6 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
validateUserExists(id);
|
||||
// 校验用户名唯一
|
||||
validateUsernameUnique(id, username, organId);
|
||||
// 校验手机号唯一
|
||||
validateMobileUnique(id, mobile, organId);
|
||||
// 校验邮箱唯一
|
||||
validateEmailUnique(id, email, organId);
|
||||
// 校验部门处于开启状态
|
||||
@@ -420,11 +480,21 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
private AdminUserDO validateUserNameExists(String userName) {
|
||||
AdminUserDO user = userMapper.selectByUsernameUnique(userName);
|
||||
if (user == null) {
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.USER_NOT_EXISTS);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@OrganIgnore
|
||||
void validateUsernameUnique(Long id, String username, Long organId) {
|
||||
if (StringUtils.isBlank(username)) {
|
||||
return;
|
||||
}
|
||||
AdminUserDO user = userMapper.selectOne(AdminUserDO::getUsername, username, AdminUserDO::getOrganId, organId);
|
||||
AdminUserDO user = userMapper.selectOne(AdminUserDO::getUsername, username);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
@@ -456,11 +526,12 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMobileUnique(Long id, String mobile, Long organId) {
|
||||
@OrganIgnore
|
||||
void validateMobileUnique(Long id, String mobile) {
|
||||
if (StringUtils.isBlank(mobile)) {
|
||||
return;
|
||||
}
|
||||
AdminUserDO user = userMapper.selectOne(AdminUserDO::getOrganId, organId, AdminUserDO::getMobile, mobile);
|
||||
AdminUserDO user = userMapper.selectOne(AdminUserDO::getUsername, mobile);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
@@ -501,7 +572,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
importUsers.forEach(importUser -> {
|
||||
// 校验,判断是否有不符合的原因
|
||||
try {
|
||||
validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
|
||||
validateUserForCreateOrUpdate(null, null, importUser.getEmail(),
|
||||
importUser.getDeptId(), null,null, organId);
|
||||
} catch (ServiceException ex) {
|
||||
respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
|
||||
@@ -510,6 +581,8 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
// 判断如果不存在,在进行插入
|
||||
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername(), organId);
|
||||
if (existUser == null) {
|
||||
// 改造:手机号直接当做用户名
|
||||
importUser.setUsername(importUser.getMobile());
|
||||
userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
|
||||
.setPassword(encodePassword(userInitPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组
|
||||
respVO.getCreateUsernames().add(importUser.getUsername());
|
||||
@@ -563,6 +636,24 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
return organAdminUserRespDTOS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateUserSecurityPhone(Long id, UserMobileUpdateReqVO reqVO) {
|
||||
String mobile = reqVO.getMobile();
|
||||
// 校验手机号唯一
|
||||
validateMobileUnique(id, mobile);
|
||||
|
||||
// 校验和消耗验证码
|
||||
smsCodeService.useSmsCode(SmsCodeUseReqDTO.builder()
|
||||
.mobile(reqVO.getMobile()).code(reqVO.getSmsCaptchaVerification()).scene(SmsSceneEnum.USER_UPDATE_MOBILE.getScene())
|
||||
.build());
|
||||
|
||||
// 新手机号入库 -> setUsername
|
||||
userMapper.update(new LambdaUpdateWrapper<AdminUserDO>()
|
||||
.set(AdminUserDO::getUsername, mobile)
|
||||
.eq(AdminUserDO::getId, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对密码进行加密
|
||||
*
|
||||
|
||||
@@ -159,6 +159,8 @@ chenfeng:
|
||||
- /admin-api/system/data-source/*
|
||||
- /admin-api/system/auth/login
|
||||
- /admin-api/system/auth/logout
|
||||
- /admin-api/system/auth/sms-code
|
||||
- /admin-api/system/user/reset-password
|
||||
- /rpc-api/**
|
||||
#- /admin-api/system/**
|
||||
#- /rpc-api/system/organ/valid # 防止递归。避免调用 /rpc-api/system/organ/valid 接口时,又去触发 /rpc-api/system/organ/valid 去校验
|
||||
@@ -193,10 +195,19 @@ chenfeng:
|
||||
use-data-code: imes_prod #动态数据源标识,后期添加的数据源需要修改此值
|
||||
|
||||
sms-code: # 短信验证码相关的配置项
|
||||
expire-times: 10m
|
||||
expire-times: 5m
|
||||
send-frequency: 1m
|
||||
send-maximum-quantity-per-day: 10
|
||||
send-max-send-times-per-window: 5 #窗口最大请求次数
|
||||
send-window-size-min: 10m #窗口时间
|
||||
begin-code: 9999 # 这里配置 9999 的原因是,测试方便。
|
||||
end-code: 9999 # 这里配置 9999 的原因是,测试方便。
|
||||
|
||||
sms:
|
||||
channel: DEBUG_DING_TALK
|
||||
signature: AKhcjogF4mzWTn5G0I0sfQ==
|
||||
apiKey: Bw9mvVZZ8cLBLoPFl9yY/AI1xOUCEALEtftakrgiv9BT/dOuKUhWsXuFr9UvxRlMSRporf8Wcd7C6IFB9ukUR0IWgKTelx2vv2Lhzdnz4ac=
|
||||
apiSecret: Um956u5zFSl0tu8imFB/TOZyK7xysT5JMLY17mZIwXH+eh4BjZEmsjmFRfkcXin0A/GNhRzcUurJfm+N/S42uJZ9G9aCCIoPSjpnF9SXwt0=
|
||||
encrypt:
|
||||
enable: true
|
||||
publicKey: cfimes
|
||||
debug: false
|
||||
|
||||
+4
-10
@@ -5,20 +5,14 @@ import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||
import com.cf.imes.framework.test.core.ut.BaseDbUnitTest;
|
||||
import com.cf.imes.module.system.api.sms.SmsCodeApi;
|
||||
import com.cf.imes.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import com.cf.imes.module.system.api.social.dto.SocialUserRespDTO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.*;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.cf.imes.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import com.cf.imes.module.system.enums.logger.LoginResultEnum;
|
||||
import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
|
||||
import com.cf.imes.module.system.enums.social.SocialTypeEnum;
|
||||
import com.cf.imes.module.system.service.logger.LoginLogService;
|
||||
import com.cf.imes.module.system.service.member.MemberService;
|
||||
import com.cf.imes.module.system.service.oauth2.OAuth2TokenService;
|
||||
@@ -91,7 +85,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
||||
when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
|
||||
|
||||
// 调用
|
||||
AdminUserDO loginUser = authService.authenticate(username, password, null);
|
||||
AdminUserDO loginUser = authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build());
|
||||
// 校验
|
||||
assertPojoEquals(user, loginUser);
|
||||
}
|
||||
@@ -103,7 +97,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
||||
String password = randomString();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> authService.authenticate(username, password, null),
|
||||
assertServiceException(() -> authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()),
|
||||
AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
verify(loginLogService).createLoginLog(
|
||||
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
|
||||
@@ -123,7 +117,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
||||
when(userService.getUserByUsername(eq(username), null)).thenReturn(user);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> authService.authenticate(username, password, null),
|
||||
assertServiceException(() -> authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()),
|
||||
AUTH_LOGIN_BAD_CREDENTIALS);
|
||||
verify(loginLogService).createLoginLog(
|
||||
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
|
||||
@@ -145,7 +139,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
||||
when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> authService.authenticate(username, password, null),
|
||||
assertServiceException(() -> authService.authenticate(AuthLoginReqVO.builder().username(username).password(password).build()),
|
||||
AUTH_LOGIN_USER_DISABLED);
|
||||
verify(loginLogService).createLoginLog(
|
||||
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
|
||||
|
||||
+2
-9
@@ -5,6 +5,7 @@ import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||
import com.cf.imes.framework.sms.core.client.SmsClient;
|
||||
import com.cf.imes.framework.sms.core.client.SmsClientFactory;
|
||||
import com.cf.imes.framework.sms.core.enums.SmsChannelEnum;
|
||||
import com.cf.imes.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.cf.imes.framework.test.core.ut.BaseDbUnitTest;
|
||||
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
|
||||
@@ -200,16 +201,12 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
|
||||
Long id = channel.getId();
|
||||
// mock 方法
|
||||
SmsClient mockClient = mock(SmsClient.class);
|
||||
when(smsClientFactory.getSmsClient(eq(id))).thenReturn(mockClient);
|
||||
when(smsClientFactory.getSmsClient(eq(SmsChannelEnum.ALIYUN.getCode()))).thenReturn(mockClient);
|
||||
|
||||
// 调用
|
||||
SmsClient client = smsChannelService.getSmsClient(id);
|
||||
// 断言
|
||||
assertSame(client, mockClient);
|
||||
verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> {
|
||||
SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);
|
||||
return properties.equals(arg);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,10 +224,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
|
||||
SmsClient client = smsChannelService.getSmsClient(code);
|
||||
// 断言
|
||||
assertSame(client, mockClient);
|
||||
verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> {
|
||||
SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);
|
||||
return properties.equals(arg);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -130,10 +130,11 @@ public class SmsLogServiceImplTest extends BaseDbUnitTest {
|
||||
String apiSendMsg = randomString();
|
||||
String apiRequestId = randomString();
|
||||
String apiSerialNo = randomString();
|
||||
String channelCode = randomString();
|
||||
|
||||
// 调用
|
||||
smsLogService.updateSmsSendResult(id, success,
|
||||
apiSendCode, apiSendMsg, apiRequestId, apiSerialNo);
|
||||
apiSendCode, apiSendMsg, apiRequestId, apiSerialNo, channelCode);
|
||||
// 断言
|
||||
dbSmsLog = smsLogMapper.selectById(id);
|
||||
assertEquals(success ? SmsSendStatusEnum.SUCCESS.getStatus() : SmsSendStatusEnum.FAILURE.getStatus(),
|
||||
|
||||
+5
-9
@@ -85,8 +85,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
// 断言
|
||||
assertEquals(smsLogId, resultSmsLogId);
|
||||
// 断言调用
|
||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(user.getMobile()),
|
||||
eq(template.getChannelId()), eq(template.getApiTemplateId()),
|
||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(user.getMobile()), eq(template),
|
||||
eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
|
||||
}
|
||||
|
||||
@@ -124,8 +123,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
// 断言
|
||||
assertEquals(smsLogId, resultSmsLogId);
|
||||
// 断言调用
|
||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile),
|
||||
eq(template.getChannelId()), eq(template.getApiTemplateId()),
|
||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(template),
|
||||
eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
|
||||
}
|
||||
|
||||
@@ -164,8 +162,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
// 断言
|
||||
assertEquals(smsLogId, resultSmsLogId);
|
||||
// 断言调用
|
||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile),
|
||||
eq(template.getChannelId()), eq(template.getApiTemplateId()),
|
||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(template),
|
||||
eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
|
||||
}
|
||||
|
||||
@@ -204,8 +201,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
// 断言
|
||||
assertEquals(smsLogId, resultSmsLogId);
|
||||
// 断言调用
|
||||
verify(smsProducer, times(0)).sendSmsSendMessage(anyLong(), anyString(),
|
||||
anyLong(), any(), anyList());
|
||||
verify(smsProducer, times(0)).sendSmsSendMessage(anyLong(), any(), any(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -274,7 +270,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
||||
// 断言
|
||||
verify(smsLogService).updateSmsSendResult(eq(message.getLogId()),
|
||||
eq(sendResult.getSuccess()), eq(sendResult.getApiCode()),
|
||||
eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getSerialNo()));
|
||||
eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getSerialNo()), eq(sendResult.getChannelCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -588,7 +588,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
userMapper.insert(randomAdminUserDO(o -> o.setMobile(mobile)));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> userService.validateMobileUnique(null, mobile, 1L),
|
||||
assertServiceException(() -> userService.validateMobileUnique(null, mobile),
|
||||
USER_MOBILE_EXISTS);
|
||||
}
|
||||
|
||||
@@ -601,7 +601,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
userMapper.insert(randomAdminUserDO(o -> o.setMobile(mobile)));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> userService.validateMobileUnique(id, mobile, 1L),
|
||||
assertServiceException(() -> userService.validateMobileUnique(id, mobile),
|
||||
USER_MOBILE_EXISTS);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user