diff --git a/chenhai-admin/src/main/java/com/chenhai/web/controller/auth/MultiAuthController.java b/chenhai-admin/src/main/java/com/chenhai/web/controller/auth/MultiAuthController.java index 5159962..6112818 100644 --- a/chenhai-admin/src/main/java/com/chenhai/web/controller/auth/MultiAuthController.java +++ b/chenhai-admin/src/main/java/com/chenhai/web/controller/auth/MultiAuthController.java @@ -5,7 +5,6 @@ import com.chenhai.common.core.domain.AjaxResult; import com.chenhai.common.core.domain.model.*; import com.chenhai.framework.web.service.SysLoginService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -83,4 +82,29 @@ public class MultiAuthController { return AjaxResult.error(e.getMessage()); } } + + /** + * 牧户PC端注册 + */ + @PostMapping("/vet/register") + public AjaxResult vetRegister(@Valid @RequestBody VetRegisterBody registerBody) { + try { + // 验证确认密码 + if (!registerBody.getPassword().equals(registerBody.getConfirmPassword())) { + return AjaxResult.error("两次输入的密码不一致"); + } + + // 调用注册服务 + String token = loginService.vetRegister( + registerBody.getPhone(), + registerBody.getPassword(), + registerBody.getClientType() + ); + + return AjaxResult.success("注册成功").put(Constants.TOKEN, token); + + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } } \ No newline at end of file diff --git a/chenhai-common/src/main/java/com/chenhai/common/constant/Constants.java b/chenhai-common/src/main/java/com/chenhai/common/constant/Constants.java index 13ef9b7..fc8c326 100644 --- a/chenhai-common/src/main/java/com/chenhai/common/constant/Constants.java +++ b/chenhai-common/src/main/java/com/chenhai/common/constant/Constants.java @@ -170,4 +170,9 @@ public class Constants */ public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.chenhai.common.utils.file", "com.chenhai.common.config", "com.chenhai.generator" }; + + /** + * 密码必须包含大写、小写、数字和特殊字符,且长度是8位以上 + */ + public static final String PWD_REGEX = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()=_+;':,.?]).{8,20}$"; } diff --git a/chenhai-common/src/main/java/com/chenhai/common/core/domain/model/VetRegisterBody.java b/chenhai-common/src/main/java/com/chenhai/common/core/domain/model/VetRegisterBody.java new file mode 100644 index 0000000..954c10f --- /dev/null +++ b/chenhai-common/src/main/java/com/chenhai/common/core/domain/model/VetRegisterBody.java @@ -0,0 +1,58 @@ +package com.chenhai.common.core.domain.model; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** + * 兽医PC端注册请求 + */ +public class VetRegisterBody { + + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 20, message = "密码长度6-20位") + private String password; + + @NotBlank(message = "确认密码不能为空") + private String confirmPassword; + + // 客户端类型固定为 vet-pc + private String clientType = "vet-pc"; + + // getters and setters + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConfirmPassword() { + return confirmPassword; + } + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + public String getClientType() { + return clientType; + } + + public void setClientType(String clientType) { + this.clientType = clientType; + } +} \ No newline at end of file diff --git a/chenhai-framework/src/main/java/com/chenhai/framework/security/provider/PhoneAuthenticationProvider.java b/chenhai-framework/src/main/java/com/chenhai/framework/security/provider/PhoneAuthenticationProvider.java index df54b92..2926e26 100644 --- a/chenhai-framework/src/main/java/com/chenhai/framework/security/provider/PhoneAuthenticationProvider.java +++ b/chenhai-framework/src/main/java/com/chenhai/framework/security/provider/PhoneAuthenticationProvider.java @@ -40,23 +40,32 @@ public class PhoneAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { PhoneAuthenticationToken authToken = (PhoneAuthenticationToken) authentication; - + String phone = authToken.getPhone(); String password = authToken.getPassword(); String clientType = authToken.getClientType(); - + // 1. 验证参数 if (phone == null || password == null) { throw new BadCredentialsException("手机号或密码不能为空"); } - - // 2. 根据手机号查找用户 - SysUser user = userAuthService.findUserByAuth("phone", phone); - + + // 2. 根据手机号查找用户(扩展查询逻辑) + SysUser user = null; + + // 扩展认证方式(两步查询) + // 第一步:先查 user_auth 表(小程序注册的用户) + user = userAuthService.findUserByAuth("phone", phone); + + // 第二步:如果没找到,再查 sys_user 表(管理端创建的用户) + if (user == null) { + user = userService.selectUserByPhone(phone); + } + if (user == null) { throw new BadCredentialsException("手机号未注册"); } - + // 3. 验证用户状态 if ("1".equals(user.getStatus())) { throw new ServiceException(MessageUtils.message("user.blocked")); @@ -64,13 +73,13 @@ public class PhoneAuthenticationProvider implements AuthenticationProvider { if ("2".equals(user.getDelFlag())) { throw new ServiceException(MessageUtils.message("user.password.delete")); } - + // 4. 验证密码 if (!passwordEncoder.matches(password, user.getPassword())) { // 密码错误计数逻辑(复用原有的) throw new BadCredentialsException("密码错误"); } - + // 5. 创建LoginUser LoginUser loginUser = new LoginUser( user.getUserId(), @@ -79,7 +88,7 @@ public class PhoneAuthenticationProvider implements AuthenticationProvider { permissionService.getMenuPermission(user), clientType ); - + return new PhoneAuthenticationToken(loginUser, loginUser.getAuthorities()); } diff --git a/chenhai-framework/src/main/java/com/chenhai/framework/web/service/SysLoginService.java b/chenhai-framework/src/main/java/com/chenhai/framework/web/service/SysLoginService.java index 2fbff31..56fd9e9 100644 --- a/chenhai-framework/src/main/java/com/chenhai/framework/web/service/SysLoginService.java +++ b/chenhai-framework/src/main/java/com/chenhai/framework/web/service/SysLoginService.java @@ -1,6 +1,8 @@ package com.chenhai.framework.web.service; +import com.chenhai.common.utils.*; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; @@ -19,9 +21,6 @@ import com.chenhai.common.exception.user.CaptchaException; import com.chenhai.common.exception.user.CaptchaExpireException; import com.chenhai.common.exception.user.UserNotExistsException; import com.chenhai.common.exception.user.UserPasswordNotMatchException; -import com.chenhai.common.utils.DateUtils; -import com.chenhai.common.utils.MessageUtils; -import com.chenhai.common.utils.StringUtils; import com.chenhai.common.utils.ip.IpUtils; import com.chenhai.framework.manager.AsyncManager; import com.chenhai.framework.manager.factory.AsyncFactory; @@ -31,7 +30,6 @@ import com.chenhai.framework.security.context.AuthenticationContextHolder; import com.alibaba.fastjson2.JSONObject; import com.chenhai.common.core.domain.entity.SysUser; import com.chenhai.common.core.domain.model.*; -import com.chenhai.common.utils.WechatDecryptUtil; import com.chenhai.framework.security.exception.WechatNeedBindException; import com.chenhai.framework.security.token.PhoneAuthenticationToken; import com.chenhai.framework.security.token.WechatAuthenticationToken; @@ -50,6 +48,7 @@ import java.util.Map; * * @author ruoyi */ +@Slf4j @Component public class SysLoginService { @@ -410,4 +409,148 @@ public class SysLoginService // ==================================================================== // =================== 多端登录方法结束 =============================== // ==================================================================== + + + // ==================================================================== + // =================== 以下是牧户PC端注册方法 ======================== + // ==================================================================== + + /** + * 牧户PC端注册 + * + * @param phone 手机号 + * @param password 密码 + * @param clientType 客户端类型 + * @return token + */ + @Transactional + public String vetRegister(String phone, String password, String clientType) { + try { + // 1. 验证手机号格式 + if (!isValidPhone(phone)) { + throw new ServiceException("手机号格式不正确"); + } + + // 2. 验证密码强度 + validatePassword(password); + + // 3. 检查手机号是否已注册 + checkPhoneRegistered(phone); + + // 4. 创建牧户用户 + SysUser user = createVetUser(phone, password, clientType); + + // 5. 创建登录用户 + LoginUser loginUser = new LoginUser( + user.getUserId(), + user.getDeptId(), + user, + permissionService.getMenuPermission(user), + clientType + ); + + // 6. 记录登录信息 + recordLoginInfo(loginUser.getUserId()); + + // 7. 生成token + return tokenService.createToken(loginUser); + + } catch (Exception e) { + throw new ServiceException("注册失败: " + e.getMessage()); + } + } + + /** + * 验证手机号格式 + */ + private boolean isValidPhone(String phone) { + String regex = "^1[3-9]\\d{9}$"; + return phone != null && phone.matches(regex); + } + + /** + * 验证密码强度 + */ + private void validatePassword(String password) { + if (password == null || password.length() < 6 || password.length() > 20) { + throw new ServiceException("密码长度需6-20位"); + } + + if (StringUtils.isBlank(password)) { + throw new ServiceException("密码不能为空"); + } + if (!password.matches(Constants.PWD_REGEX)) { + throw new ServiceException("长度在 8 到 20 个字符且包含大小写字母、数字以及特殊符号"); + } + } + + /** + * 检查手机号是否已注册 + */ + private void checkPhoneRegistered(String phone) { + // 检查 sys_user 表 + SysUser existingUser = userService.selectUserByPhone(phone); + if (existingUser != null) { + // 检查是否是牧户 + if ("02".equals(existingUser.getUserType())) { + throw new ServiceException("该手机号已注册为兽医用户"); + } else { + throw new ServiceException("该手机号已注册为其他类型用户"); + } + } + + // 可选:检查 user_auth 表 + SysUser authUser = userAuthService.findUserByAuth("phone", phone); + if (authUser != null) { + throw new ServiceException("该手机号已注册"); + } + } + + /** + * 创建牧户用户 + */ + private SysUser createVetUser(String phone, String password, String clientType) { + SysUser user = new SysUser(); + + // 生成用户名:muhu_ + 手机后4位 + 4位随机数 + String phoneSuffix = phone.length() > 4 ? phone.substring(phone.length() - 4) : phone; + String randomSuffix = String.format("%04d", (int)(Math.random() * 10000)); + String username = "vet_" + phoneSuffix + "_" + randomSuffix; + user.setUserName(username); + + // 设置昵称:牧户 + 手机后4位 + user.setNickName("用户" + phoneSuffix); + + user.setUserType("01"); // 牧户 + user.setEmail(""); + user.setPhonenumber(phone); + user.setSex("0"); // 未知性别 + user.setAvatar(""); // 默认头像 + + // 加密密码 + user.setPassword(com.chenhai.common.utils.SecurityUtils.encryptPassword(password)); + + user.setStatus("0"); // 正常状态 + user.setDelFlag("0"); + user.setCreateTime(new Date()); + + // 这里在最初PC端注册,因为兽医用户涉及审核,因此先加入角色“兽医未审核角色” + Long[] roleIds = {6L}; // 兽医未审核角色ID + user.setRoleIds(roleIds); + + // 插入用户 + userService.insertUser(user); + + // 重新查询获取完整用户信息 + SysUser savedUser = userService.selectUserByUserName(username); + + // 可选:创建手机号认证记录 + try { + userAuthService.bindAuth(savedUser.getUserId(), "phone", phone, null); + } catch (Exception e) { + log.warn("创建用户认证记录失败: userId={}, phone={}", savedUser.getUserId(), phone); + } + + return savedUser; + } } \ No newline at end of file