添加施工人员手机号登录方式

This commit is contained in:
方亮 2025-10-23 14:31:38 +08:00
parent 7a1556ba5a
commit df96dc0f18
19 changed files with 400 additions and 26 deletions

View File

@ -53,6 +53,16 @@ public interface RemoteUserService {
public R<LoginUser> getUserInfoByPhone(@PathVariable("phone") String phone, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过用户名查询用户信息
*
* @param phone 手机号
* @param source 请求来源
* @return 结果
*/
@GetMapping("/user/isWorkerPhone/{photoNumber}")
public boolean isWorkerPhone(@PathVariable("photoNumber") String photoNumber, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过用户名查询用户信息
*

View File

@ -19,7 +19,7 @@ import java.util.List;
/**
* 用户服务降级处理
*
*
* @author bonus
*/
@Component
@ -56,6 +56,11 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
return R.fail("获取用户失败:" + throwable.getMessage());
}
@Override
public boolean isWorkerPhone(String phone, String source) {
return false;
}
/**
* 通过用户名查询用户信息
*

View File

@ -19,6 +19,10 @@ public enum LoginType {
* 手机号验证码
*/
PHONE_OTP,
/**
* 手机号验证码-工人出场专用
*/
PHONE_OTP_WORKER,
/**
* 邮箱验证码
*/

View File

@ -11,6 +11,10 @@ public enum VerificationCodeType {
* 登录
*/
LOGIN,
/**
* 工人验证码
*/
WORKER_LOGIN,
/**
* 注册
*/

View File

@ -1,6 +1,5 @@
package com.bonus.auth.controller;
import com.alibaba.fastjson.JSONObject;
import com.bonus.auth.config.LoginType;
import com.bonus.auth.factory.LoginStrategyFactory;
import com.bonus.auth.form.LoginBody;
@ -19,25 +18,19 @@ import com.bonus.common.security.service.TokenService;
import com.bonus.common.security.utils.SecurityUtils;
import com.bonus.config.SystemConfig;
import com.bonus.system.api.RemoteConfigService;
import com.bonus.system.api.RemoteLogService;
import com.bonus.system.api.RemoteUserService;
import com.bonus.system.api.domain.SysUser;
import com.bonus.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
@ -122,7 +115,7 @@ public class TokenController {
if (strategy == null) {
return R.fail("不支持的登录方式");
}
if (form.getLoginType()== LoginType.EMAIL_OTP || form.getLoginType()== LoginType.PHONE_OTP ){
if (form.getLoginType()== LoginType.EMAIL_OTP || form.getLoginType()== LoginType.PHONE_OTP || form.getLoginType()== LoginType.PHONE_OTP_WORKER){
form.setPassword(form.getVerificationCode());
}
LoginUser login = strategy.login(form.getUsername(), form.getPassword());
@ -136,7 +129,7 @@ public class TokenController {
if (strategy == null) {
return R.fail("不支持的登录方式");
}
if (form.getLoginType()== LoginType.EMAIL_OTP || form.getLoginType()== LoginType.PHONE_OTP ){
if (form.getLoginType()== LoginType.EMAIL_OTP || form.getLoginType()== LoginType.PHONE_OTP || form.getLoginType()== LoginType.PHONE_OTP_WORKER){
form.setPassword(form.getVerificationCode());
}

View File

@ -31,6 +31,8 @@ public class LoginStrategyFactory {
strategyMap.put(LoginType.EMAIL_PASSWORD, strategy);
} else if (strategy instanceof EmailOtpLoginStrategy) {
strategyMap.put(LoginType.EMAIL_OTP, strategy);
} else if (strategy instanceof PhoneOtpWorkerLoginStrategy) {
strategyMap.put(LoginType.PHONE_OTP_WORKER, strategy);
}
// 继续添加其他策略
});

View File

@ -4,6 +4,7 @@ import com.bonus.auth.config.VerificationCodeType;
import com.bonus.auth.service.LoginVerificationCodeSender;
import com.bonus.auth.service.RegisterVerificationCodeSender;
import com.bonus.auth.service.VerificationCodeStrategy;
import com.bonus.auth.service.WorkerLoginVerificationCodeSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -27,6 +28,8 @@ public class VerificationCodeStrategyFactory {
strategyMap.put(VerificationCodeType.LOGIN, strategy);
} else if (strategy instanceof RegisterVerificationCodeSender) {
strategyMap.put(VerificationCodeType.REGISTER, strategy);
} else if (strategy instanceof WorkerLoginVerificationCodeSender) {
strategyMap.put(VerificationCodeType.WORKER_LOGIN, strategy);
}
// 继续添加其他策略
});

View File

@ -0,0 +1,54 @@
package com.bonus.auth.service;
import com.bonus.common.core.constant.SecurityConstants;
import com.bonus.common.core.domain.R;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.config.SystemConfig;
import com.bonus.system.api.RemoteUserService;
import com.bonus.system.api.domain.SysUser;
import com.bonus.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author bonus
*/
@Service
public class PhoneOtpWorkerLoginStrategy implements LoginStrategy {
@Resource
private SystemConfig systemConfig;
@Resource
private RemoteUserService remoteUserService;
@Resource
private PasswordValidatorService passwordValidatorService;
@Value("${worker.phone}")
private String workerPhone;
@Override
public LoginUser login(String phone, String otp) {
if (!systemConfig.getLoginConfig().isPhoneCode()) {
throw new ServiceException("用户不存在/验证码错误");
}
//校验验证码
passwordValidatorService.checkPhoneCaptcha(phone, otp);
//施工人员没有登录权限给一个固定账号登录
phone = workerPhone;
R<LoginUser> userResult = remoteUserService.getUserInfoByPhone(phone, SecurityConstants.INNER);
//验证用户是否存在
passwordValidatorService.validateUserResult(phone, userResult);
LoginUser userInfo = userResult.getData();
SysUser user = userInfo.getSysUser();
passwordValidatorService.validateApprovalStatus(phone, user);
// 验证用户状态
passwordValidatorService.validateUserStatus(phone, user);
passwordValidatorService.processLoginBlackList(user);
//返回信息
return userInfo;
}
}

View File

@ -10,7 +10,6 @@ import com.bonus.common.core.constant.UserConstants;
import com.bonus.common.core.domain.R;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.core.utils.StringUtils;
import com.bonus.common.core.utils.encryption.Sm4Utils;
import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.common.security.utils.SecurityUtils;
import com.bonus.config.SystemConfig;
@ -55,6 +54,8 @@ public class SysLoginService {
@Resource
private RemoteConfigService configService;
// @Resource
// private RedisService redisService;
/**
* 获取验证码
*
@ -68,6 +69,13 @@ public class SysLoginService {
if (strategyFactory == null) {
return R.fail("不支持的方式");
}
//工人登录时加一个验证手机号
if (verificationCodeType == VerificationCodeType.WORKER_LOGIN) {
boolean userResult = remoteUserService.isWorkerPhone(username, SecurityConstants.INNER);
if (!userResult) {
return R.fail("手机号不存在");
}
}
strategyFactory.sendVerificationCode(username);
return R.ok();
}
@ -87,6 +95,53 @@ public class SysLoginService {
}
}
// /**
// * 下发短信
// * @param phone
// * @return
// */
// public AjaxResult sendPhone(String phone) {
// phone= Sm4Utils.decrypt(phone);
// //验证手机手机号是否存在
// String thisUser=userService.getUserInfo(phone);
// if(!phone.equals(thisUser)){
// AjaxResult ajax = AjaxResult.error();
// ajax.put("msg", "手机号不存在或手机号不正确");
// return ajax;
// }
// Integer num=redisService.getCacheObject(CacheConstants.PHONE_NUM+phone);
// if(num==null){
// num=1;
// }else{
// num++;
// }
// if(num>10){
// return AjaxResult.error("请勿频繁发送验证码!");
// }
// StringBuilder sb=new StringBuilder();
//// String code = getSixBitCode();
// String code = VerificationCodeUtils.generateVerificationCode(NUMERIC);
// sb.append("【博诺思】验证码:").append(code).append(",验证码有效期").append(CacheConstants.TIMES).append("分钟,切勿将验证码泄漏于他人。");
// sb.append("发送时间:").append(DateUtils.getTime());
// sb.append("。(实名制)");
// Map<String,String> map= PhoneUtils.sendPhoneMsg(phone,sb.toString());
// if ("200".equals(map.get("code"))){
// AjaxResult ajax = AjaxResult.success();
// redisService.setCacheObject(CacheConstants.PHONE_CODE+phone,code,CacheConstants.TIMES, TimeUnit.MINUTES);
// redisService.setCacheObject(CacheConstants.PHONE_NUM+phone,num,5, TimeUnit.MINUTES);
// ajax.put("times", CacheConstants.TIMES);
// ajax.put("msg", "发送成功");
// return ajax;
// }else{
// AjaxResult ajax = AjaxResult.error();
// ajax.put("times", CacheConstants.TIMES);
// ajax.put("msg", "发送失败,请检查网络!");
// return ajax;
// }
//
// }
/**
* 用户注册
*

View File

@ -0,0 +1,95 @@
package com.bonus.auth.service;
import com.bonus.common.core.constant.SecurityConstants;
import com.bonus.common.core.domain.R;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.security.service.EmailService;
import com.bonus.common.security.service.SmsService;
import com.bonus.system.api.RemoteUserService;
import com.bonus.system.api.model.LoginUser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 验证码发送服务
* 可发送到邮箱或手机
*
* @author bonus
*/
@Service
public class WorkerLoginVerificationCodeSender implements VerificationCodeStrategy {
private static final String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
@Resource
private EmailService emailService;
@Resource
private SmsService smsService;
@Resource
private RemoteUserService remoteUserService;
/**
* 发送验证码到邮箱或手机
*
* @param contactInfo 可以是邮箱地址或手机号码
* @return 验证码发送的结果
*/
@Override
public void sendVerificationCode(String contactInfo) {
if (isEmail(contactInfo)) {
emailService.sendSimpleEmail(contactInfo);
} else if (isPhone(contactInfo)) {
smsService.sendSimplePhone(contactInfo);
} else {
handleUsernameLogin(contactInfo);
}
}
/**
* 检查是否是邮箱
*
* @param contactInfo 输入信息
* @return 是否为邮箱
*/
private boolean isEmail(String contactInfo) {
return contactInfo.matches(EMAIL_REGEX);
}
/**
* 检查是否是手机号
*
* @param contactInfo 输入信息
* @return 是否为手机号
*/
private boolean isPhone(String contactInfo) {
return contactInfo.matches(PHONE_REGEX);
}
/**
* 处理用户名登录逻辑
*
* @param username 用户名
* @return 验证码发送的结果
*/
private void handleUsernameLogin(String username) {
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (userResult == null || userResult.getData() == null || R.FAIL == userResult.getCode()) {
throw new ServiceException("用户名/密码错误");
}
LoginUser user = userResult.getData();
// 如果用户是管理员则发送手机验证码
if (user.getRoles().contains("admin")) {
if (StringUtils.isEmpty(user.getSysUser().getPhonenumber())) {
throw new ServiceException("此账号未绑定手机号,请先绑定手机号");
}
smsService.sendSimplePhone(user.getSysUser().getPhonenumber());
} else {
throw new ServiceException("不支持的登录方式");
}
}
}

View File

@ -67,4 +67,10 @@ public class CacheConstants {
* 登录IP黑名单 cache key
*/
public static final String SYS_LOGIN_BLACKIPLIST ="blackIPList";
public static final String PHONE_CODE = "phone_code:";
public static final String PHONE_NUM = "phone_num:";
public static final Long TIMES = 5L;
}

View File

@ -0,0 +1,101 @@
package com.bonus.common.core.utils;
import cn.hutool.http.HttpRequest;
import org.hibernate.validator.internal.util.StringHelper;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* 第三方依赖
* <dependency>
* <groupId>cn.hutool</groupId>
* <artifactId>hutool-all</artifactId>
* <version>5.8.22</version>
* </dependency>
* 手机 短信 下发工具类
* @author 黑子
*/
public class PhoneUtils {
/**
* 短信验证码 发送地址 及账号
*/
public static String url="http://api.ktsms.cn/sms_token?ddtkey=bonus&secretkey=KtyBns@Admin2023!";
public static final String STRING_OK = "ok";
/**
* 验证码时长
* 分钟
*/
public static final String TIMES = "5";
/**
* 系统平台名称
*/
public static final String SYSTEM_NAME = "实名制";
/**
* 短信签名
*/
public static final String PHONE_HEAD = "【博诺思】";
/**
* 发送短信验证吗
* @param phone 手机号码
* @param msg 消息内容
* 如果msg不传 会使用默认值
* @return
*/
public static Map<String,String> sendPhoneMsg(String phone, String msg){
Map<String,String> map=new HashMap<>(4);
map.put("phone",phone);
if (!isValidPhoneNumber(phone)) {
map.put("code","201");
map.put("msg","手机号格式错误请输入11位数字号码");
}
StringBuilder sb=new StringBuilder();
sb.append(url).append("&mobile=").append(phone);
sb.append("&content=");
if(StringHelper.isNullOrEmptyString(msg)){
String code = getSixBitCode();
map.put("captcha",code);
sb.append(PHONE_HEAD);
sb.append("您正在进行短信验证,验证码:").append(code).append(",请在").append(TIMES).append("分钟内完成验证,切勿将验证码泄漏于他人。");
map.put("times",TIMES);
sb.append("发送时间:").append(DateUtils.getTime()).append("。(");
sb.append(SYSTEM_NAME).append("");
}else{
sb.append(msg);
}
String body = HttpRequest.post(sb.toString()).execute(false).body();
if (body == null || !body.contains(STRING_OK)) {
map.put("code","201");
map.put("msg","短信发送失败");
}else{
; map.put("code","200");
map.put("msg","发送成功");
}
return map;
}
public static boolean isValidPhoneNumber(String phoneNumber) {
// 定义中国的手机号正则表达式
String regex = "^1[3-9]\\d{9}$";
return phoneNumber.matches(regex);
}
private static String getSixBitCode() {
//随机数
Random random = new Random();
return String.valueOf(random.nextInt(900000) + 100000);
}
}

View File

@ -16,7 +16,6 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
@ -39,6 +38,10 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
private static final String UUID = "uuid";
private static final String WORKER_VERIFY = "verificationCodeType";
private static final String WORKER_LOGIN = "loginType";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
@ -59,8 +62,12 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
throw new CaptchaException("请求参数异常");
}
JSONObject obj = JSON.parseObject(rspStr);
//人员登录不要验证码后面我会去验证手机号是否有效
if ("WORKER_LOGIN".equals(obj.getString(WORKER_VERIFY)) || "PHONE_OTP_WORKER".equals(obj.getString(WORKER_LOGIN))) {
// 直接放行不需要验证码验证
return chain.filter(exchange);
}
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
} catch (Exception e) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
@ -68,15 +75,30 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
};
}
// private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
// // 获取请求体
// Flux<DataBuffer> body = serverHttpRequest.getBody();
// AtomicReference<String> bodyRef = new AtomicReference<>();
// body.subscribe(buffer -> {
// CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
// DataBufferUtils.release(buffer);
// bodyRef.set(charBuffer.toString());
// });
// return bodyRef.get();
// }
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
bodyRef.set(new String(bytes, StandardCharsets.UTF_8));
});
return bodyRef.get();
}
}

View File

@ -45,7 +45,6 @@ public class AppRecognitionController extends BaseController {
/**
* 人脸识别-识别人脸
*/
@RequiresPermissionsOrInnerAuth(innerAuth = @InnerAuth(isUser = false))
@PostMapping("/getFaceRecognition")
@SysLog(title = "识别人脸", businessType = OperaType.QUERY, logType = 0, module = "识别服务接口->识别人脸", details = "识别人脸")

View File

@ -12,7 +12,6 @@ import com.bonus.common.log.annotation.SysLog;
import com.bonus.common.log.enums.OperaType;
import com.bonus.common.redis.service.RedisService;
import com.bonus.common.security.annotation.InnerAuth;
import com.bonus.common.security.annotation.PreventRepeatSubmit;
import com.bonus.common.security.annotation.RequiresPermissions;
import com.bonus.common.security.annotation.RequiresPermissionsOrInnerAuth;
import com.bonus.common.security.utils.SecurityUtils;
@ -23,7 +22,6 @@ import com.bonus.system.api.domain.SysUser;
import com.bonus.system.api.model.LoginUser;
import com.bonus.system.domain.UserPasswordHistory;
import com.bonus.system.service.*;
import com.bonus.system.warning.WebSocketHandler;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -184,6 +182,16 @@ public class SysUserController extends BaseController {
return R.ok(sysUserVo);
}
/**
* 获取获取是否为施工人员
*/
@InnerAuth
@GetMapping("/isWorkerPhone/{photoNumber}")
public boolean isWorkerPhone(@PathVariable("photoNumber") String photoNumber) {
int sysUserVo = userService.selectWorkerByPhotoNumber(photoNumber);
return sysUserVo > 0;
}
/**
* 获取当前用户信息
*/

View File

@ -167,4 +167,6 @@ public interface SysUserMapper {
* @date 2025/8/18 16:15
*/
Long getAffCompany(SysUser user);
int selectWorkerByPhotoNumber(String photoNumber);
}

View File

@ -5,7 +5,6 @@ import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.system.api.domain.SysRole;
import com.bonus.system.api.domain.SysUser;
import org.apache.poi.ss.formula.functions.T;
import org.aspectj.weaver.loadtime.Aj;
import java.util.List;
@ -239,4 +238,12 @@ public interface ISysUserService {
public AjaxResult systemUpdateUser(SysUser user);
AjaxResult getRoleList(SysRole role);
/**
* 根据手机号查询用户
*
* @param photoNumber 手机号
* @return 用户对象信息
*/
int selectWorkerByPhotoNumber(String photoNumber);
}

View File

@ -1,6 +1,5 @@
package com.bonus.system.service.impl;
import com.bonus.common.core.constant.Constants;
import com.bonus.common.core.constant.UserConstants;
import com.bonus.common.core.domain.R;
import com.bonus.common.core.exception.ServiceException;
@ -11,7 +10,6 @@ import com.bonus.common.core.utils.encryption.Sm4Utils;
import com.bonus.common.core.utils.sms.SmsUtils;
import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.common.core.web.domain.BaseEntity;
import com.bonus.common.core.web.domain.TreeEntity;
import com.bonus.common.datascope.annotation.DataScope;
import com.bonus.common.datascope.utils.CommonDataPermissionInfo;
import com.bonus.common.security.config.VerificationCodeConfig;
@ -21,13 +19,11 @@ import com.bonus.system.api.domain.SysRole;
import com.bonus.system.api.domain.SysUser;
import com.bonus.system.api.domain.SysUserRole;
import com.bonus.system.domain.SysUserPost;
import com.bonus.system.domain.vo.TreeSelect;
import com.bonus.system.mapper.*;
import com.bonus.system.service.ISysConfigService;
import com.bonus.system.service.ISysDeptService;
import com.bonus.system.service.ISysUserService;
import org.apache.poi.ss.formula.functions.T;
import org.checkerframework.checker.units.qual.A;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
@ -42,7 +38,6 @@ import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@ -702,4 +697,9 @@ public class SysUserServiceImpl implements ISysUserService {
return AjaxResult.error();
}
}
@Override
public int selectWorkerByPhotoNumber(String photoNumber) {
return userMapper.selectWorkerByPhotoNumber(photoNumber);
}
}

View File

@ -437,5 +437,9 @@
</foreach>
</delete>
<select id="selectWorkerByPhotoNumber" resultType="int">
select count(1)
from pm_worker
where phone = #{photoNumber}
</select>
</mapper>