feat: complete aihr mvp mobile workflow
This commit is contained in:
@@ -7,13 +7,17 @@ import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.request.AuthRequest;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
import org.dromara.common.core.constant.RegexConstants;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.constant.TenantConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.domain.model.LoginBody;
|
||||
import org.dromara.common.core.domain.model.RegisterBody;
|
||||
@@ -67,6 +71,9 @@ import java.util.concurrent.TimeUnit;
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
private static final String MOBILE_CLIENT_ID = "428a8310cd442757ae699df5d894f051";
|
||||
private static final String MOBILE_GRANT_TYPE = "sms";
|
||||
|
||||
private final SocialProperties socialProperties;
|
||||
private final SysLoginService loginService;
|
||||
private final SysRegisterService registerService;
|
||||
@@ -114,6 +121,31 @@ public class AuthController {
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端短信登录,复用 app 客户端和 sms 授权策略。
|
||||
*/
|
||||
@PostMapping("/mobile/sms-login")
|
||||
public R<LoginVo> mobileSmsLogin(@Validated @RequestBody MobileSmsLoginBody body) {
|
||||
SysClientVo client = clientService.queryByClientId(MOBILE_CLIENT_ID);
|
||||
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), MOBILE_GRANT_TYPE)) {
|
||||
log.info("移动端客户端id: {} 认证类型:{} 异常!.", MOBILE_CLIENT_ID, MOBILE_GRANT_TYPE);
|
||||
return R.fail(MessageUtils.message("auth.grant.type.error"));
|
||||
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
|
||||
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
|
||||
}
|
||||
|
||||
String tenantId = StringUtils.blankToDefault(body.tenantId(), TenantConstants.DEFAULT_TENANT_ID);
|
||||
loginService.checkTenant(tenantId);
|
||||
|
||||
Map<String, String> loginBody = new HashMap<>();
|
||||
loginBody.put("clientId", MOBILE_CLIENT_ID);
|
||||
loginBody.put("grantType", MOBILE_GRANT_TYPE);
|
||||
loginBody.put("tenantId", tenantId);
|
||||
loginBody.put("phonenumber", body.phonenumber());
|
||||
loginBody.put("smsCode", body.smsCode());
|
||||
return R.ok(IAuthStrategy.login(JsonUtils.toJsonString(loginBody), client, MOBILE_GRANT_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取跳转URL
|
||||
*
|
||||
@@ -240,4 +272,14 @@ public class AuthController {
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
public record MobileSmsLoginBody(
|
||||
@NotBlank(message = "{user.phonenumber.not.blank}")
|
||||
@Pattern(regexp = RegexConstants.MOBILE, message = "{user.mobile.phone.number.not.valid}")
|
||||
String phonenumber,
|
||||
@NotBlank(message = "{sms.code.not.blank}")
|
||||
String smsCode,
|
||||
String tenantId
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-6
@@ -7,10 +7,12 @@ import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
import org.dromara.common.core.constant.RegexConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
@@ -26,6 +28,7 @@ import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.dromara.web.domain.vo.CaptchaVo;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@@ -52,6 +55,9 @@ public class CaptchaController {
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final MailProperties mailProperties;
|
||||
|
||||
@Value("${aihr.sms.login-template-id:}")
|
||||
private String smsLoginTemplateId;
|
||||
|
||||
/**
|
||||
* 短信验证码
|
||||
*
|
||||
@@ -59,20 +65,23 @@ public class CaptchaController {
|
||||
*/
|
||||
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
||||
@GetMapping("/resource/sms/code")
|
||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
||||
public R<Void> smsCode(
|
||||
@NotBlank(message = "{user.phonenumber.not.blank}")
|
||||
@Pattern(regexp = RegexConstants.MOBILE, message = "{user.mobile.phone.number.not.valid}") String phonenumber) {
|
||||
if (StringUtils.isBlank(smsLoginTemplateId)) {
|
||||
return R.fail("短信模板未配置:请设置 aihr.sms.login-template-id");
|
||||
}
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||
String templateId = "";
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
map.put("code", code);
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, smsLoginTemplateId, map);
|
||||
if (!smsResponse.isSuccess()) {
|
||||
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||
return R.fail(smsResponse.getData().toString());
|
||||
return R.fail(String.valueOf(smsResponse.getData()));
|
||||
}
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
+15
-5
@@ -12,6 +12,7 @@ import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.core.domain.model.SmsLoginBody;
|
||||
import org.dromara.common.core.enums.LoginType;
|
||||
import org.dromara.common.core.enums.UserType;
|
||||
import org.dromara.common.core.exception.user.CaptchaExpireException;
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
@@ -51,8 +52,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
||||
String phonenumber = loginBody.getPhonenumber();
|
||||
String smsCode = loginBody.getSmsCode();
|
||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||
SysUserVo user = loadUserByPhonenumber(phonenumber);
|
||||
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
|
||||
loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode));
|
||||
SysUserVo user = loadOrRegisterUserByPhonenumber(tenantId, phonenumber);
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
return loginService.buildLoginUser(user);
|
||||
});
|
||||
@@ -87,11 +88,20 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
||||
return code.equals(smsCode);
|
||||
}
|
||||
|
||||
private SysUserVo loadUserByPhonenumber(String phonenumber) {
|
||||
private SysUserVo loadOrRegisterUserByPhonenumber(String tenantId, String phonenumber) {
|
||||
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", phonenumber);
|
||||
throw new UserException("user.not.exists", phonenumber);
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setTenantId(tenantId);
|
||||
sysUser.setUserName(phonenumber);
|
||||
sysUser.setNickName("手机用户" + phonenumber.substring(7));
|
||||
sysUser.setUserType(UserType.APP_USER.getUserType());
|
||||
sysUser.setPhonenumber(phonenumber);
|
||||
sysUser.setStatus(SystemConstants.NORMAL);
|
||||
sysUser.setRemark("移动端短信自动注册");
|
||||
userMapper.insert(sysUser);
|
||||
log.info("短信登录自动注册用户:{}", phonenumber);
|
||||
user = userMapper.selectVoById(sysUser.getUserId());
|
||||
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", phonenumber);
|
||||
throw new UserException("user.blocked", phonenumber);
|
||||
|
||||
@@ -155,6 +155,9 @@ mail:
|
||||
|
||||
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
|
||||
aihr:
|
||||
sms:
|
||||
login-template-id: ${AIHR_SMS_LOGIN_TEMPLATE_ID:}
|
||||
sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
@@ -172,11 +175,11 @@ sms:
|
||||
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: alibaba
|
||||
# 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
|
||||
access-key-id: 您的accessKey
|
||||
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID:您的accessKey}
|
||||
# 称为accessSecret有些称之为apiSecret
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET:您的accessKeySecret}
|
||||
signature: ${ALIYUN_SMS_SIGN_NAME:您的短信签名}
|
||||
sdk-app-id: ${ALIYUN_SMS_SDK_APP_ID:您的sdkAppId}
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: tencent
|
||||
|
||||
@@ -158,6 +158,9 @@ mail:
|
||||
|
||||
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
|
||||
aihr:
|
||||
sms:
|
||||
login-template-id: ${AIHR_SMS_LOGIN_TEMPLATE_ID:}
|
||||
sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
@@ -175,11 +178,11 @@ sms:
|
||||
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: alibaba
|
||||
# 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
|
||||
access-key-id: 您的accessKey
|
||||
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID:您的accessKey}
|
||||
# 称为accessSecret有些称之为apiSecret
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET:您的accessKeySecret}
|
||||
signature: ${ALIYUN_SMS_SIGN_NAME:您的短信签名}
|
||||
sdk-app-id: ${ALIYUN_SMS_SDK_APP_ID:您的sdkAppId}
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: tencent
|
||||
|
||||
Reference in New Issue
Block a user