From 4fecb777c87dd73de98b912cfd4f5c757758e16d Mon Sep 17 00:00:00 2001 From: zhouhaibin Date: Tue, 11 Jun 2024 09:55:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BD=9C=E8=80=85=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 2 +- .../main/resources/application-platform.yml | 11 +- .../common/exception/CommonException.java | 5 +- .../properties/system/SystemProperties.java | 1 - .../security/UserDetailsServiceImpl.java | 4 + platform-system/pom.xml | 6 + .../platform/system/config/SystemConfig.java | 5 + .../system/controller/UserController.java | 53 ++++- .../system/exception/UserExceptionEnum.java | 18 ++ .../platform/system/service/UserService.java | 45 +++- .../system/service/impl/UserServiceImpl.java | 213 ++++++++++++++---- .../platform/system/vo/RegisterUserVO.java | 46 ++++ 12 files changed, 359 insertions(+), 50 deletions(-) create mode 100644 platform-system/src/main/java/tech/abc/platform/system/vo/RegisterUserVO.java diff --git a/platform-boot-starter-demo/src/main/resources/application.yml b/platform-boot-starter-demo/src/main/resources/application.yml index 4609b98..0cafb8b 100644 --- a/platform-boot-starter-demo/src/main/resources/application.yml +++ b/platform-boot-starter-demo/src/main/resources/application.yml @@ -1,5 +1,5 @@ spring: profiles: - include: platform + include: platform,dev diff --git a/platform-boot-starter/src/main/resources/application-platform.yml b/platform-boot-starter/src/main/resources/application-platform.yml index ac2b8f0..07fcfd4 100644 --- a/platform-boot-starter/src/main/resources/application-platform.yml +++ b/platform-boot-starter/src/main/resources/application-platform.yml @@ -120,6 +120,7 @@ platform-config: userInitPassword: 12345678 tokenSecret: wqliu exportDataPageSize: 2 + systemUrl: http://localhost:4000 notification: serverPort: 9997 message: @@ -136,7 +137,7 @@ platform-config: # 集成minio模式 # storeClass: tech.abc.platform.oss.service.impl.MinioStoreServiceImpl # 本地磁盘需要给出路径,如c:/attachment/或/data;minio因使用桶作为逻辑存储,无根路径,需留空 - basePath: d:/ce/ + basePath: c:/attachment/ minio: server: http://127.0.0.1:9000 accessKey: admin @@ -179,10 +180,10 @@ platform-config: #工作流配置 camunda: bpm: -# admin-user: -# id: admin -# password: admin -# firstName: Kermit + # admin-user: + # id: admin + # password: admin + # firstName: Kermit database: type: mysql schema-update: true diff --git a/platform-common/src/main/java/tech/abc/platform/common/exception/CommonException.java b/platform-common/src/main/java/tech/abc/platform/common/exception/CommonException.java index 5efe7a2..ec3cffd 100644 --- a/platform-common/src/main/java/tech/abc/platform/common/exception/CommonException.java +++ b/platform-common/src/main/java/tech/abc/platform/common/exception/CommonException.java @@ -108,7 +108,10 @@ public enum CommonException implements ExceptionInterface { */ ENUM_TYPE_NOT_FOUNT("未找到对应的枚举类型:【{0}】"), - ; + /** + * 无效的枚举值 + */ + INVALID_ENUM("无效的枚举值:{0}"); private String message; diff --git a/platform-framework/src/main/java/tech/abc/platform/framework/config/properties/system/SystemProperties.java b/platform-framework/src/main/java/tech/abc/platform/framework/config/properties/system/SystemProperties.java index 332db66..1510fb3 100644 --- a/platform-framework/src/main/java/tech/abc/platform/framework/config/properties/system/SystemProperties.java +++ b/platform-framework/src/main/java/tech/abc/platform/framework/config/properties/system/SystemProperties.java @@ -31,5 +31,4 @@ public class SystemProperties { */ private String tokenSecret = "wqliu"; - } diff --git a/platform-framework/src/main/java/tech/abc/platform/framework/security/UserDetailsServiceImpl.java b/platform-framework/src/main/java/tech/abc/platform/framework/security/UserDetailsServiceImpl.java index f9ba6f8..c8308a3 100644 --- a/platform-framework/src/main/java/tech/abc/platform/framework/security/UserDetailsServiceImpl.java +++ b/platform-framework/src/main/java/tech/abc/platform/framework/security/UserDetailsServiceImpl.java @@ -1,15 +1,19 @@ package tech.abc.platform.framework.security; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; +import tech.abc.platform.common.constant.TreeDefaultConstant; import tech.abc.platform.common.entity.MyGrantedAuthority; import tech.abc.platform.common.entity.MyUserDetails; import tech.abc.platform.common.utils.CacheUtil; +import tech.abc.platform.system.entity.Organization; import tech.abc.platform.system.entity.User; +import tech.abc.platform.system.enums.OrganizationTypeEnum; import tech.abc.platform.system.enums.UserStatusEnum; import tech.abc.platform.system.service.OrganizationService; import tech.abc.platform.system.service.UserService; diff --git a/platform-system/pom.xml b/platform-system/pom.xml index e293847..5418c72 100644 --- a/platform-system/pom.xml +++ b/platform-system/pom.xml @@ -38,6 +38,12 @@ tech.abc platform-boot-starter-notification + + + + tech.abc + platform-boot-starter-mail + diff --git a/platform-system/src/main/java/tech/abc/platform/system/config/SystemConfig.java b/platform-system/src/main/java/tech/abc/platform/system/config/SystemConfig.java index fadabbc..6a5dcbf 100644 --- a/platform-system/src/main/java/tech/abc/platform/system/config/SystemConfig.java +++ b/platform-system/src/main/java/tech/abc/platform/system/config/SystemConfig.java @@ -23,5 +23,10 @@ public class SystemConfig { */ private String userInitPassword = "654321"; + /** + * 系统地址 + */ + private String systemUrl = "http://localhost:4000"; + } diff --git a/platform-system/src/main/java/tech/abc/platform/system/controller/UserController.java b/platform-system/src/main/java/tech/abc/platform/system/controller/UserController.java index 60252c8..d40e54d 100644 --- a/platform-system/src/main/java/tech/abc/platform/system/controller/UserController.java +++ b/platform-system/src/main/java/tech/abc/platform/system/controller/UserController.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import tech.abc.platform.common.annotation.AllowAll; import tech.abc.platform.common.annotation.AllowAuthenticated; import tech.abc.platform.common.annotation.SystemLog; import tech.abc.platform.common.base.BaseController; @@ -26,6 +27,7 @@ import tech.abc.platform.system.entity.User; import tech.abc.platform.system.service.GroupUserService; import tech.abc.platform.system.service.OrganizationService; import tech.abc.platform.system.service.UserService; +import tech.abc.platform.system.vo.RegisterUserVO; import tech.abc.platform.system.vo.UserChangePasswordVO; import tech.abc.platform.system.vo.UserVO; @@ -230,7 +232,7 @@ public class UserController extends BaseController { @AllowAuthenticated public ResponseEntity changePassword(@RequestBody UserChangePasswordVO vo) { - userService.changePassword(vo.getUserId(), vo.getOldPassword(), vo.getNewPassword()); + userService.changeUserPassword(vo.getUserId(), vo.getOldPassword(), vo.getNewPassword()); return ResultUtil.success(); } @@ -349,6 +351,55 @@ public class UserController extends BaseController { return ResultUtil.success(new ArrayList()); } + /** + * 注册 + */ + @PostMapping("/register") + @SystemLog(value = "用户-注册") + @AllowAll + public ResponseEntity register(@Validated @RequestBody RegisterUserVO vo) { + User entity = new User(); + mapperFacade.map(vo, entity); + userService.register(entity); + return ResultUtil.success(); + } + + /** + * 注册 + */ + @PostMapping("/retrievePassword") + @SystemLog(value = "用户-找回密码") + @AllowAll + public ResponseEntity retrievePassword(String email) { + userService.retrievePassword(email); + return ResultUtil.success(); + } + + + /** + * 根据授权码获取账号 + */ + @GetMapping("/getAccoutByCode") + @SystemLog(value = "用户-根据授权码获取账号") + @AllowAll + public ResponseEntity getAccoutByCode(String code) { + String account = userService.getAccoutByCode(code); + return ResultUtil.success(account); + } + + /** + * 用户自助重设密码 + */ + @PutMapping("/selfResetPassword") + @SystemLog(value = "用户自助重设密码", logType = LogTypeEnum.AUDIT) + @AllowAll + public ResponseEntity selfResetPassword(String code, String password) { + userService.selfResetPassword(code, password); + return ResultUtil.success(); + } + + // endregion + // region 辅助操作 private UserVO convert2VO(User entity) { UserVO vo = mapperFacade.map(entity, UserVO.class); diff --git a/platform-system/src/main/java/tech/abc/platform/system/exception/UserExceptionEnum.java b/platform-system/src/main/java/tech/abc/platform/system/exception/UserExceptionEnum.java index ff2930e..3fad3bb 100644 --- a/platform-system/src/main/java/tech/abc/platform/system/exception/UserExceptionEnum.java +++ b/platform-system/src/main/java/tech/abc/platform/system/exception/UserExceptionEnum.java @@ -62,6 +62,24 @@ public enum UserExceptionEnum implements ExceptionInterface { */ ORGANIZATION_NAME_AND_CODE_CANOT_NULL("组织机构名称和组织机构编码不能同时为空"), + /** + * 账号与邮箱不匹配 + */ + ACCOUNT_EMAIL_NOT_MATCH("账号与邮箱不匹配,请确认"), + + /** + * 该邮箱未注册 + */ + EMAIl_NOT_REGISTER("该邮箱未注册,请确认"), + + /** + * 授权码已失效 + */ + AUTHORIZATION_CODE_EXPIRED("授权码已失效,请重新获取"), + /** + * 账号不存在 + */ + ACCOUNT_NOT_EXIST("账号不存在"), ; private String message; diff --git a/platform-system/src/main/java/tech/abc/platform/system/service/UserService.java b/platform-system/src/main/java/tech/abc/platform/system/service/UserService.java index 8b74f50..b196290 100644 --- a/platform-system/src/main/java/tech/abc/platform/system/service/UserService.java +++ b/platform-system/src/main/java/tech/abc/platform/system/service/UserService.java @@ -22,7 +22,7 @@ public interface UserService extends BaseService { * @return 集合 */ Map getNameMap(List idList); - + /** * 启用 * @@ -61,11 +61,11 @@ public interface UserService extends BaseService { /** * 用户修改密码 * - * @param account 账号 + * @param id 用户标识 * @param oldPassword 旧密码 * @param newPassword 新密码 */ - void changePassword(String account, String oldPassword, String newPassword); + void changeUserPassword(String id, String oldPassword, String newPassword); /** * 更新登录失败次数 @@ -119,8 +119,47 @@ public interface UserService extends BaseService { /** * 获取姓名 + * * @param id 用户标识 * @return {@link String} 姓名 */ String getNameById(String id); + + /** + * 注册用户 + * + * @param entity + */ + void register(User entity); + + /** + * 找回密码 + * + * @param email 邮箱 + */ + void retrievePassword(String email); + + /** + * 根据授权码获取账号 + * + * @param code 授权码 + * @return {@link String} 账号 + */ + String getAccoutByCode(String code); + + /** + * 自助重设密码 + * + * @param code 授权码 + * @param password 新密码 + */ + void selfResetPassword(String code, String password); + + /** + * 根据账号获取用户标识 + * + * @param account 账号 + * @return {@link String} 用户标识 + */ + String getIdByAccount(String account); } diff --git a/platform-system/src/main/java/tech/abc/platform/system/service/impl/UserServiceImpl.java b/platform-system/src/main/java/tech/abc/platform/system/service/impl/UserServiceImpl.java index 8278a1c..f1f7971 100644 --- a/platform-system/src/main/java/tech/abc/platform/system/service/impl/UserServiceImpl.java +++ b/platform-system/src/main/java/tech/abc/platform/system/service/impl/UserServiceImpl.java @@ -9,19 +9,20 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tech.abc.platform.common.annotation.SystemLog; import tech.abc.platform.common.base.BaseServiceImpl; +import tech.abc.platform.common.enums.ExecuteResultEnum; import tech.abc.platform.common.enums.LogTypeEnum; import tech.abc.platform.common.enums.StatusEnum; import tech.abc.platform.common.enums.YesOrNoEnum; import tech.abc.platform.common.exception.CustomException; import tech.abc.platform.common.utils.CacheUtil; import tech.abc.platform.common.utils.EncryptUtil; +import tech.abc.platform.mail.service.MailService; import tech.abc.platform.system.config.SystemConfig; import tech.abc.platform.system.constant.SystemConstant; import tech.abc.platform.system.entity.GroupUser; import tech.abc.platform.system.entity.PermissionItem; import tech.abc.platform.system.entity.User; import tech.abc.platform.system.entity.UserPasswordChangeLog; -import tech.abc.platform.common.enums.ExecuteResultEnum; import tech.abc.platform.system.enums.UserStatusEnum; import tech.abc.platform.system.exception.PermissionItemExceptionEnum; import tech.abc.platform.system.exception.UserExceptionEnum; @@ -31,9 +32,8 @@ import tech.abc.platform.system.utils.PasswordUtil; import java.time.Duration; import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.TimeUnit; /** * 用户 服务实现类 @@ -66,6 +66,9 @@ public class UserServiceImpl extends BaseServiceImpl implement @Autowired private PermissionItemService permissionItemService; + @Autowired + private MailService mailService; + @Override public User init() { User entity = new User(); @@ -212,36 +215,42 @@ public class UserServiceImpl extends BaseServiceImpl implement @Override @Transactional(rollbackFor = Exception.class) - public void changePassword(String id, String oldPassword, String newPassword) { - // 检验账号以及旧密码,验证新密码强度 - User user = checkAccountPassword(id, oldPassword, newPassword); + public void changeUserPassword(String id, String oldPassword, String newPassword) { + // 检验旧密码是否正确,以及新旧密码是否相同 + checkAccountPassword(id, oldPassword, newPassword); // 校验密码安全规则 - checkSafePolicy(user, newPassword); + checkSafePolicy(id, newPassword); + // 修改密码 + changePassword(id, newPassword); + } + + private void changePassword(String id, String password) { + User user = query(id); + // 缓存原始密码 + String originPassword = user.getPassword(); // 修改密码 // 设置密码加密 - String encrtyPassword = EncryptUtil.bCryptPasswordEncode(newPassword); + String encrtyPassword = EncryptUtil.bCryptPasswordEncode(password); user.setPassword(encrtyPassword); // 将强制修改密码标识位设置为否 user.setForceChangePasswordFlag(YesOrNoEnum.NO.name()); // 修改 modify(user); - // 记录日志 UserPasswordChangeLog log = new UserPasswordChangeLog(); log.setAccount(user.getAccount()); log.setUserId(user.getId()); log.setChangeTime(LocalDateTime.now()); - log.setOriginPassword(EncryptUtil.bCryptPasswordEncode(oldPassword)); + log.setOriginPassword(originPassword); userPasswordChangeLogService.add(log); } /** * 校验账号账号以及旧密码校验 */ - public User checkAccountPassword(String id, String oldPassword, String newPassword) { + public void checkAccountPassword(String id, String oldPassword, String newPassword) { // 获取用户 User entity = getEntity(id); - // 验证旧密码 if (!EncryptUtil.bCryptPasswordMatches(oldPassword, entity.getPassword())) { // 更新累计错误次数 @@ -254,15 +263,7 @@ public class UserServiceImpl extends BaseServiceImpl implement if (oldPassword.equals(newPassword)) { throw new CustomException(UserExceptionEnum.PWD_OLD_NEW_SAME); } - // 新密码复杂度验证:包括大写字母、小写字母、数字、特殊符号这4种类型中的3种 - if (!PasswordUtil.isComplexPassword(newPassword)) { - throw new CustomException(UserExceptionEnum.PWD_CHANGE_NOT_STRONG); - } - // 验证新密码不能包含账号、电话号码或出生日期三者中任何一项 - if (!checkPasswordSimple(newPassword, entity)) { - throw new CustomException(UserExceptionEnum.PWD_CHANGE_EASY); - } - return entity; + } /** @@ -359,6 +360,125 @@ public class UserServiceImpl extends BaseServiceImpl implement return user.getName(); } + @Override + @Transactional(rollbackFor = Exception.class) + public void register(User entity) { + // 验证账号全局唯一 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(User::getAccount, entity.getAccount()); + long count = count(queryWrapper); + if (count > 0) { + throw new CustomException(UserExceptionEnum.ACCOUNT_EXIST); + } + + // 验证密码基本要求 + checkPasswordBasicRequire(entity.getPassword()); + + // 设置密码 + entity.setPassword(EncryptUtil.bCryptPasswordEncode(entity.getPassword())); + // 强制修改密码状态位置为否 + entity.setForceChangePasswordFlag(YesOrNoEnum.NO.toString()); + // 状态初始化为正常 + entity.setStatus(UserStatusEnum.NORMAL.toString()); + // 设置性别 + entity.setGender("MALE"); + + // 设置默认部门为 遇见 应用 + entity.setOrganization("999"); + + + // 调用父类保存 + super.save(entity); + + // 设置通用角色 + List userIdList = new ArrayList<>(); + userIdList.add(entity.getId()); + groupUserService.addUser("999", userIdList); + + + } + + @Override + public void retrievePassword(String email) { + // 验证邮箱是否注册 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(User::getEmail, email); + List list = list(queryWrapper); + if (CollectionUtils.isEmpty(list)) { + throw new CustomException(UserExceptionEnum.EMAIl_NOT_REGISTER); + } + User user = list.get(0); + // 发送重设邮件 + String account = user.getAccount(); + sendResetPasswordEmail(email, account); + + } + + private void sendResetPasswordEmail(String email, String account) { + // 生成唯一性编码 + String code = UUID.randomUUID().toString(); + // 存入redis,24小时后失效 + cacheUtil.set(code, account, 24, TimeUnit.HOURS); + + + // 生成内容 + String systemUrl = systemConfig.getSystemUrl(); + String content = "重设【遇见】应用密码:" + systemUrl + "/#/selfResetPassword?code=" + code; + + + // 使用线程异步发送邮件 + Runnable runnable = new Runnable() { + @Override + public void run() { + mailService.sendHtmlMail(email, "重设【遇见】应用密码", content); + } + }; + // noinspection AlibabaAvoidManuallyCreateThread + Thread thread = new Thread(runnable); + // 启动 + thread.start(); + + } + + @Override + public String getAccoutByCode(String code) { + Object object = cacheUtil.get(code); + if (object != null) { + return object.toString(); + } else { + throw new CustomException(UserExceptionEnum.AUTHORIZATION_CODE_EXPIRED); + } + + } + + @Override + public void selfResetPassword(String code, String password) { + // 获取账号 + String account = getAccoutByCode(code); + String id = getIdByAccount(account); + User user = query(id); + // 验证密码 + checkSafePolicy(id, password); + // 修改密码 + changePassword(id, password); + // 删除缓存授权码 + cacheUtil.remove(code); + + } + + @Override + public String getIdByAccount(String account) { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(User::getAccount, account); + List list = list(queryWrapper); + if (CollectionUtils.isEmpty(list)) { + throw new CustomException(UserExceptionEnum.ACCOUNT_NOT_EXIST); + } + User user = list.get(0); + return user.getId(); + } + /** * 安全认证校验 @@ -367,23 +487,22 @@ public class UserServiceImpl extends BaseServiceImpl implement * @param password * @return */ - public void checkSafePolicy(User user, String password) { - // 密码长度 - // 系统配置密码最小长度 - Integer passwordLength = Integer.parseInt(paramService.getParamValue(SystemConstant.PASSWORD_LENGTH)); - // 比较密码长度 - if (password.length() < passwordLength) { - throw new CustomException(UserExceptionEnum.PWD_CHANGE_NEED_LENGTH); + public void checkSafePolicy(String id, String password) { + // 验证密码基本要求(长度及复杂度) + checkPasswordBasicRequire(password); + + // 验证新密码不能包含账号、电话号码或出生日期三者中任何一项 + if (!checkPasswordSimple(password, id)) { + throw new CustomException(UserExceptionEnum.PWD_CHANGE_EASY); } - // n次以内不得设置相同的密码 - // 系统配置密码最小长度 + // 验证n次以内不得设置相同的密码 Integer passwordCount = Integer.parseInt(paramService.getParamValue(SystemConstant.PASSWORD_UPDATE_SAME_TIMES)); - // 从密码修改日志中获取最后N次修改的密码 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.lambda().eq(UserPasswordChangeLog::getId, user.getId()) + queryWrapper.lambda().eq(UserPasswordChangeLog::getUserId, id) .orderByDesc(UserPasswordChangeLog::getChangeTime); queryWrapper.last("limit 0," + passwordCount); + List logList = userPasswordChangeLogService.list(queryWrapper); for (UserPasswordChangeLog log : logList) { if (EncryptUtil.bCryptPasswordMatches(password, log.getOriginPassword())) { @@ -393,16 +512,34 @@ public class UserServiceImpl extends BaseServiceImpl implement } + /** + * 验证密码基本要求(长度及复杂度) + * + * @param password 密码 + */ + private void checkPasswordBasicRequire(String password) { + // 验证密码长度 + Integer passwordLength = Integer.parseInt(paramService.getParamValue(SystemConstant.PASSWORD_LENGTH)); + if (password.length() < passwordLength) { + throw new CustomException(UserExceptionEnum.PWD_CHANGE_NEED_LENGTH); + } + + // 验证密码复杂度:包括大写字母、小写字母、数字、特殊符号这4种类型中的3种 + if (!PasswordUtil.isComplexPassword(password)) { + throw new CustomException(UserExceptionEnum.PWD_CHANGE_NOT_STRONG); + } + } + /** - * 验证新密码不能包含账号、电话号码或出生日期三者中任何一项 + * 验证密码不能包含账号、电话号码或出生日期三者中任何一项 * - * @param password - * @param user + * @param password 密码 + * @param id 用户标识 * @return */ - private boolean checkPasswordSimple(String password, User user) { - + private boolean checkPasswordSimple(String password, String id) { + User user = query(id); if (password.contains(user.getAccount())) { return false; } diff --git a/platform-system/src/main/java/tech/abc/platform/system/vo/RegisterUserVO.java b/platform-system/src/main/java/tech/abc/platform/system/vo/RegisterUserVO.java new file mode 100644 index 0000000..d4466a5 --- /dev/null +++ b/platform-system/src/main/java/tech/abc/platform/system/vo/RegisterUserVO.java @@ -0,0 +1,46 @@ +package tech.abc.platform.system.vo; + + +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; + +/** + * 注册用户 视图对象类 + * + * @author wqliu + * @date 2023-05-24 + */ +@Data +@Accessors(chain = true) +public class RegisterUserVO { + + /** + * 姓名 + */ + @NotBlank(message = "【姓名】不能为空") + private String name; + + /** + * 账号 + */ + @NotBlank(message = "【账号】不能为空") + private String account; + + /** + * 密码 + */ + private String password; + + + /** + * 邮箱 + */ + @NotBlank(message = "【邮箱】不能为空") + private String email; + + +} + +