Commit ff3adc54 authored by 唐峰's avatar 唐峰

Merge branch 'feature-migration-20230628' of...

Merge branch 'feature-migration-20230628' of git.quantgroup.cn:head_group/xyqb-user2 into feature-migration-20230628
parents ae29eb07 737ac45e
......@@ -37,6 +37,7 @@ public interface Constants {
String X_AUTH_APP_ID = "qg-app-id";
String X_AUTH_FROM = "qg-registered-from";
String X_STMS_TOKEN = "qg-stms-token";
String X_BEHAVIOR_TYPE = "qg-behavior-type";
String X_BEHAVIOR_ID = "qg-behavior-id";
......
package cn.quantgroup.xyqb.config.http;
import cn.quantgroup.xyqb.filter.BehaviorInterceptor;
import cn.quantgroup.xyqb.filter.InnerInterceptor;
import cn.quantgroup.xyqb.filter.TokenInterceptor;
import cn.quantgroup.xyqb.service.captcha.IQuantgroupCaptchaService;
import cn.quantgroup.xyqb.remote.StmsRemoteService;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.v2.BehaviorContext;
import cn.quantgroup.xyqb.thirdparty.jcaptcha.AbstractManageableImageCaptchaService;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.format.FormatterRegistry;
import org.springframework.stereotype.Component;
......@@ -23,39 +22,44 @@ import javax.validation.ValidatorFactory;
@Component
public class MyWebMvcConfigurer extends WebMvcConfigurerAdapter {
@Autowired
private ISessionService sessionService;
@Autowired
private BehaviorContext behaviorContext;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, Enum.class);
registry.addConverterFactory(new IntegerToEnumConverterFactory());
}
/**
* 快速校验参数. 第一个出错后面的不用再校验了
*
* @return
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
postProcessor.setValidator(validator);
return postProcessor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor(sessionService)).addPathPatterns("/v2/**")
.excludePathPatterns("/v2/behavior/code");
registry.addInterceptor(new BehaviorInterceptor(behaviorContext)).addPathPatterns("/v2/**","/api/v2")
.excludePathPatterns("/v2/behavior/code");
}
@Autowired
private ISessionService sessionService;
@Autowired
private BehaviorContext behaviorContext;
@Autowired
private StmsRemoteService stmsRemoteService;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, Enum.class);
registry.addConverterFactory(new IntegerToEnumConverterFactory());
}
/**
* 快速校验参数. 第一个出错后面的不用再校验了
*
* @return
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
postProcessor.setValidator(validator);
return postProcessor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor(sessionService)).addPathPatterns("/v2/**")
.excludePathPatterns("/v2/behavior/code");
registry.addInterceptor(new BehaviorInterceptor(behaviorContext)).addPathPatterns("/v2/**", "/api/v2/**")
.excludePathPatterns("/v2/behavior/code");
registry.addInterceptor(new InnerInterceptor(sessionService, stmsRemoteService)).addPathPatterns("/api/v2/**");
}
}
\ No newline at end of file
package cn.quantgroup.xyqb.controller.api.v2;
import cn.quantgroup.xyqb.controller.req.v2.LoginReq;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginBean;
import cn.quantgroup.xyqb.service.v2.LoginContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v2/oauth")
public class OauthApiV2Controller {
private final LoginContext loginContext;
public OauthApiV2Controller(LoginContext loginContext) {
this.loginContext = loginContext;
}
/**
* 对外内部登录接口
* @param loginReq LoginReq
* @return JsonResult<LoginBean>
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65779">对外统一登录接口</a>
*/
@PostMapping("/login")
public JsonResult<LoginBean> login(@RequestBody LoginReq loginReq){
return JsonResult.buildSuccessResultGeneric(loginContext.login(loginReq));
}
}
package cn.quantgroup.xyqb.controller.api.v2;
import cn.quantgroup.xyqb.controller.req.v2.BatchInfoReq;
import cn.quantgroup.xyqb.controller.req.v2.UserInfoReq;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.WechatUserInfo;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.repository.IUserRepository;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/v2/user")
public class UserApiV2Controller {
private final IUserRepository userRepository;
private final IWechatService wechatService;
public UserApiV2Controller(IUserRepository userRepository, IWechatService wechatService) {
this.userRepository = userRepository;
this.wechatService = wechatService;
}
/**
* 查询用户信息
*
* @param userInfoReq 查询条件
* @return JsonResult<User>
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65724">查询用户信息</a>
*/
@PostMapping("info")
public JsonResult<User> info(@RequestBody UserInfoReq userInfoReq) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
User user = null;
//1、校验
if (userInfoReq.getUserId() == null && StringUtils.isEmpty(userInfoReq.getUuid())
&& StringUtils.isEmpty(userInfoReq.getPhoneNo()) && (StringUtils.isEmpty(userInfoReq.getAppId()) && StringUtils.isEmpty(userInfoReq.getOpenId()))
&& (StringUtils.isEmpty(userInfoReq.getAppId()) && StringUtils.isEmpty(userInfoReq.getUnionId()))) {
throw new BizException(BizExceptionEnum.ERROR_PARAM);
}
//2、查询
if (userInfoReq.getUserId() != null) {
user = userRepository.findByIdAndTenantId(userInfoReq.getUserId(), sessionStruct.getTenantId());
}
if( StringUtils.isNotEmpty(userInfoReq.getUuid())){
user = userRepository.findByUuidAndTenantId(userInfoReq.getUuid(), sessionStruct.getTenantId());
}
if(StringUtils.isNotEmpty(userInfoReq.getPhoneNo())){
user = userRepository.findByPhoneNoAndTenantId(userInfoReq.getPhoneNo(), sessionStruct.getTenantId());
}
if(StringUtils.isNotEmpty(userInfoReq.getAppId()) && StringUtils.isNotEmpty(userInfoReq.getOpenId())){
WechatUserInfo wechatUserInfo = wechatService.findWechatUserInfoFromDb(userInfoReq.getOpenId(), userInfoReq.getAppId(), sessionStruct.getTenantId());
if (wechatUserInfo == null || wechatUserInfo.getUserId() == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
user = userRepository.findByIdAndTenantId(wechatUserInfo.getUserId(), sessionStruct.getTenantId());
}
if(StringUtils.isNotEmpty(userInfoReq.getAppId()) && StringUtils.isNotEmpty(userInfoReq.getUnionId())){
WechatUserInfo wechatUserInfo = wechatService.findByUnionIdAndAppIdAndTenantId(userInfoReq.getUnionId(), userInfoReq.getAppId(), sessionStruct.getTenantId());
if (wechatUserInfo == null || wechatUserInfo.getUserId() == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
user = userRepository.findByIdAndTenantId(wechatUserInfo.getUserId(), sessionStruct.getTenantId());
}
//3、异常处理
if (user == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
return JsonResult.buildSuccessResultGeneric(user);
}
/**
* 批量查询用户信息
*
* @param batchInfoReq 批量查询条件
* @return JsonResult<List < User>>
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65734">批量查询用户信息</a>
*/
@PostMapping("/batchInfo")
public JsonResult<List<User>> batchInfo(@RequestBody BatchInfoReq batchInfoReq) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
List<User> userList = null;
//1、校验
if (CollectionUtils.isEmpty(batchInfoReq.getUserIds())
&& CollectionUtils.isEmpty(batchInfoReq.getUuids())
&& CollectionUtils.isEmpty(batchInfoReq.getPhoneNos())
&& (StringUtils.isEmpty(batchInfoReq.getAppId()) && CollectionUtils.isEmpty(batchInfoReq.getOpenIds()))
&& (StringUtils.isEmpty(batchInfoReq.getAppId()) && CollectionUtils.isEmpty(batchInfoReq.getUnionIds()))) {
throw new BizException(BizExceptionEnum.ERROR_PARAM);
}
//2、查询
if (CollectionUtils.isNotEmpty(batchInfoReq.getUserIds())) {
userList = userRepository.findByIdInAndTenantId(batchInfoReq.getUserIds(), sessionStruct.getTenantId());
}
if( CollectionUtils.isNotEmpty(batchInfoReq.getUuids())){
userList = userRepository.findByUuidInAndTenantId(batchInfoReq.getUuids(), sessionStruct.getTenantId());
}
if(CollectionUtils.isNotEmpty(batchInfoReq.getPhoneNos())){
userList = userRepository.findByPhoneNoInAndTenantId(batchInfoReq.getPhoneNos(), sessionStruct.getTenantId());
}
if(StringUtils.isNotEmpty(batchInfoReq.getAppId()) && CollectionUtils.isNotEmpty(batchInfoReq.getOpenIds())){
List<WechatUserInfo> wechatUserInfo = wechatService.findWechatUserInfoFromDb(batchInfoReq.getOpenIds(), batchInfoReq.getAppId(), sessionStruct.getTenantId());
List<Long> userIds = wechatUserInfo.stream().map(WechatUserInfo::getUserId).collect(Collectors.toList());;
if (CollectionUtils.isEmpty(wechatUserInfo) || CollectionUtils.isEmpty(userIds)) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
userList = userRepository.findByIdInAndTenantId(userIds, sessionStruct.getTenantId());
}
if(StringUtils.isNotEmpty(batchInfoReq.getAppId()) && CollectionUtils.isNotEmpty(batchInfoReq.getUnionIds())){
List<WechatUserInfo> wechatUserInfo = wechatService.findUnionIdsAndOpenIdAndTenantId(batchInfoReq.getUnionIds(), batchInfoReq.getAppId(), sessionStruct.getTenantId());
List<Long> userIds = wechatUserInfo.stream().map(WechatUserInfo::getUserId).collect(Collectors.toList());;
if (CollectionUtils.isEmpty(wechatUserInfo) || CollectionUtils.isEmpty(userIds)) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
userList = userRepository.findByIdInAndTenantId(userIds, sessionStruct.getTenantId());
}
//3、异常处理
if (CollectionUtils.isEmpty(userList)) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
return JsonResult.buildSuccessResultGeneric(userList);
}
}
......@@ -4,10 +4,13 @@ import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaNewValidator;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.sms.SmsMerchant;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
......@@ -16,7 +19,6 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -44,19 +46,33 @@ public class SmsController implements IBaseController {
private static final String IMAGE_IP_COUNT = "image:ip";
private static final String IMAGE_PHONE_COUNT = "image:phone";
private static final String IMAGE_DEVICEID_COUNT = "image:deviceId:";
/** ip上限 */
/**
* ip上限
*/
private static final Long IP_MAX_PER_DAY = 5000L;
/** 手机号短信上限 */
/**
* 手机号短信上限
*/
private static final Long PHONE_MAX_PER_DAY = 20L;
/** 手机号语音上限 */
/**
* 手机号语音上限
*/
private static final Long PHONE_VOICE_MAX_PER_DAY = 5L;
/** 设备每天上限 */
/**
* 设备每天上限
*/
private static final Long DEVICE_MAX_PER_DAY = 20L;
/** 手机号语音上限-KEY-4 */
/**
* 手机号语音上限-KEY-4
*/
private static final String PHONE_VOICE_MAX_PER_DAY_KEY_4 = "4";
/** 手机号语音上限-KEY-5 */
/**
* 手机号语音上限-KEY-5
*/
private static final String PHONE_VOICE_MAX_PER_DAY_KEY_5 = "5";
/** 手机号语音上限-KEY-6 */
/**
* 手机号语音上限-KEY-6
*/
private static final String PHONE_VOICE_MAX_PER_DAY_KEY_6 = "6";
/**
......@@ -198,8 +214,13 @@ public class SmsController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
......@@ -207,9 +228,6 @@ public class SmsController implements IBaseController {
String uniqueId = phoneNo + UUID.randomUUID().toString().replaceAll("-", "");
List<String> newList = new ArrayList<>();
newList.add(randomCode);
/*ConfirmableMsg confirmableMsg = new ConfirmableMsg(
uniqueId, newList, "1", "1", phoneNo
);*/
MsgParams message = new MsgParams.Builder()
.typeList(Collections.singletonList(2))
.phoneNo(phoneNo)
......@@ -220,7 +238,6 @@ public class SmsController implements IBaseController {
.channel(appName)
.build();
try {
//smsService.getSmsSender().sendConfirmableMessage(confirmableMsg);
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
deleteRetSendCode(phoneNo);//删除用户重置密码,多次错误逻辑
......@@ -242,7 +259,13 @@ public class SmsController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
......@@ -330,20 +353,20 @@ public class SmsController implements IBaseController {
@CaptchaNewValidator
@RequestMapping("/send_vcc_sms_code")
public JsonResult sendVccSmsCode(@RequestParam String phoneNo,
@RequestParam(required = false) String registerFrom,
@RequestParam(required = false) String deviceId,
@RequestParam(required = false, defaultValue = "") String appName,
@RequestParam(required = false) String smsMerchant) {
@RequestParam(required = false) String registerFrom,
@RequestParam(required = false) String deviceId,
@RequestParam(required = false, defaultValue = "") String appName,
@RequestParam(required = false) String smsMerchant) {
log.info("VCC-发送验证码, phoneNo:{}, registerFrom:{}", phoneNo, registerFrom);
String randomCode = getRandomCode(Constants.SMS_CODE_LEN_6);
return sendVerificationCode2New(phoneNo, randomCode, deviceId, true, appName, smsMerchant);
}
public String getRandomCode(int count) {
if(TechEnvironment.isPro()) {
if (TechEnvironment.isPro()) {
return RandomStringUtils.random(count, RANDOM_CHARS);
}
switch (count){
switch (count) {
case 6:
return "000000";
default:
......@@ -398,7 +421,13 @@ public class SmsController implements IBaseController {
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天短信验证码上限", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
log.info("sendVerificationCode2New1分钟内不能重复获取验证码:phoneNo:{},deviceId:{},ip:{}", phoneNo, deviceId, clientIp);
......@@ -479,7 +508,13 @@ public class SmsController implements IBaseController {
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天语音验证码上限", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
......
package cn.quantgroup.xyqb.controller.req.v2;
import lombok.Data;
import lombok.Getter;
import java.util.List;
/**
* 每组有且仅能有一组userId、phoneNo、uuid、openId和appId、unionId和appId
*/
@Data
public class BatchInfoReq {
/**
* 用户id
*/
private List<Long> userIds;
/**
* 手机号码
*/
private List<String> phoneNos;
/**
* 用户uuid
*/
private List<String> uuids;
/**
* 微信openId
*/
private List<String> openIds;
/**
* 微信unionId
*/
private List<String> unionIds;
/**
* 微信appid
*/
private String appId;
}
......@@ -18,7 +18,7 @@ public class BehaviorReq {
/**
* 手机号码
*/
@Size(min = 11,max = 20)
@Size(max = 20)
private String phone;
/**
......
package cn.quantgroup.xyqb.controller.req.v2;
import lombok.Data;
import javax.validation.constraints.Size;
@Data
public class SMSReq {
/**
* 0:是短信,1是语音,默认值是0
*/
private Integer type = 0;
/**
* 手机号码,必填
*/
@Size(min = 11,max = 18)
private String phoneNo;
/**
* 场景类型,0:是普通默认短信,1是登录,2是重置密码
*/
private Integer sceneType = 0;
/**
* 指定验证码长度,可选,4-10位
*/
private Integer codeLength;
/**
* 指定模版参数,可选,需要自己指定模版时选用
*/
private String smsMerchant;
}
package cn.quantgroup.xyqb.controller.req.v2;
import lombok.Data;
import javax.validation.constraints.Size;
@Data
public class SMSVerifyReq {
/**
* 手机号码,必填
*/
@Size(min = 11,max = 18)
private String phoneNo;
/**
* 验证码,必填,4-10位
*/
@Size(min = 4,max = 10)
private String code;
}
package cn.quantgroup.xyqb.controller.req.v2;
import lombok.Data;
/**
* 每组有且仅能有一组userId、phoneNo、uuid、openId和appId、unionId和appId
*/
@Data
public class UserInfoReq {
/**
* 用户id
*/
private Long userId;
/**
* 手机号码
*/
private String phoneNo;
/**
* 用户uuid
*/
private String uuid;
/**
* 微信openId
*/
private String openId;
/**
* 微信unionId
*/
private String unionId;
/**
* 微信appid
*/
private String appId;
}
......@@ -10,6 +10,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/v2/behavior")
public class BehaviorV2Controller {
......@@ -22,7 +24,7 @@ public class BehaviorV2Controller {
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65824">统一获取行为验证码</a>
*/
@PostMapping("/code")
public JsonResult<BehaviorCodeBean> getCode(@RequestBody BehaviorReq behaviorReq){
public JsonResult<BehaviorCodeBean> getCode(@Valid @RequestBody BehaviorReq behaviorReq){
return JsonResult.buildSuccessResultGeneric(behaviorContext.generate(behaviorReq));
}
}
package cn.quantgroup.xyqb.controller.v2;
import cn.quantgroup.xyqb.controller.req.v2.LoginReq;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginBean;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.service.v2.VerificationCodeContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -13,15 +14,20 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/v2/sms")
public class SMSV2controller {
private final VerificationCodeContext verificationCodeContext;
public SMSV2controller(VerificationCodeContext verificationCodeContext) {
this.verificationCodeContext = verificationCodeContext;
}
/**
* 统一获取短信验证码
* @return JsonResult<SMSCodeBean>
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65709">统一获取短信验证码</a>
*/
@PostMapping("/code")
public JsonResult<SMSCodeBean> getCode(){
return null;
public JsonResult<SMSCodeBean> getCode(@RequestBody SMSReq smsReq){
return JsonResult.buildSuccessResultGeneric(verificationCodeContext.send(smsReq));
}
/**
......@@ -30,9 +36,9 @@ public class SMSV2controller {
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65829">...</a>
*/
@PostMapping("/verify")
public JsonResult<Boolean> verify(){
public JsonResult<Boolean> verify(@RequestBody SMSVerifyReq smsVerifyReq){
return null;
return JsonResult.buildSuccessResultGeneric(verificationCodeContext.verify(smsVerifyReq));
}
......
......@@ -19,6 +19,12 @@ public enum BizExceptionEnum {
ERROR_DIRECT_LOGIN("1008","极验登录验证失败"),
ERROR_WECHAT_APP_ID("1009","请先配置微信appId"),
ERROR_WECHAT_LOGIN("1010","微信登录失败"),
ERROR_INTERNAL_LOGIN("1011","公开接口不能调用内部登录方式"),
ERROR_LOGIN_PARAM("1012","登录参数异常,请按接口文档对接"),
UN_EXIT_STMS_TOKEN("1013","stms的token不存在"),
UN_VALID_STMS_TOKEN("1014","无效的stms的token"),
UN_PERMISSION_STMS("1015","没有内部接口访问权限"),
......@@ -28,8 +34,16 @@ public enum BizExceptionEnum {
UN_EXIT_VERIFY_TYPE("2003","不存在的验证码方式"),
UN_EXIT_VERIFY_CODE("2004","验证模式下验证码参数不能为空"),
EX_GET_VERIFY_CODE("2005","获取验证码失败"),
UN_EXIT_GEETEST_LOG("2006","极验记录不存在");
UN_EXIT_GEETEST_LOG("2006","极验记录不存在"),
PHONE_MAX_PER_DAY("2007","今天已获取20次短信验证码,请使用语音验证码或明天再试"),
DEVICE_MAX_PER_DAY("2008","您设备已经达到获取今天验证码上限"),
IP_MAX_PER_DAY("2009","您当前ip已经达到获取今天短信验证码上限"),
DUPLICATE_MIN("2010","1分钟内不能重复获取验证码"),
PHONE_VOICE_MAX_PER_DAY("2011","今天已获取5次语音验证码,请使用短信验证码或明天再试"),
ERROR_USAGE("2022","参数校验失败,用户登录语音验证码usage不正确"),
ERROR_SEND_SMS("2023","验证码发送失败"),
//通用记录
ERROR_PARAM("4000","参数不错误");
private final String businessCode;
private final String msg;
......
package cn.quantgroup.xyqb.filter;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.OauthResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.remote.StmsRemoteService;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
public class InnerInterceptor implements HandlerInterceptor {
private final ISessionService sessionService;
private final StmsRemoteService stmsRemoteService;
public InnerInterceptor(ISessionService sessionService, StmsRemoteService stmsRemoteService) {
this.sessionService = sessionService;
this.stmsRemoteService = stmsRemoteService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//所有开放出去的外部接口,都需要验证租户id和注册来源
String tenantId = request.getHeader(Constants.X_AUTH_TENANT);
String registeredFrom = request.getHeader(Constants.X_AUTH_FROM);
String stmsToken = request.getHeader(Constants.X_AUTH_TOKEN);
if (StringUtils.isEmpty(tenantId)) {
throw new BizException(BizExceptionEnum.UN_EXIT_TENANT_ID);
}
if (StringUtils.isEmpty(registeredFrom)) {
throw new BizException(BizExceptionEnum.UN_EXIT_REGISTERED_FROM);
}
if (StringUtils.isEmpty(stmsToken)) {
throw new BizException(BizExceptionEnum.UN_EXIT_STMS_TOKEN);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSessionFromRedis(stmsToken);
if (sessionStruct == null) {
OauthResult oauthResult = stmsRemoteService.checkToken(stmsToken);
if (oauthResult != null && 2000 == oauthResult.getCode()) {
LinkedHashMap<String,Object> linkedHashMap = (LinkedHashMap<String, Object>) oauthResult.getData();
String userId = String.valueOf(linkedHashMap.get("id"));
String userName = String.valueOf(linkedHashMap.get("name"));
sessionStruct = XyqbSessionContextHolder.initSTMSSession(stmsToken, userId, userName);
sessionService.persistSession(sessionStruct.getSid(), sessionStruct.getValues(), sessionStruct.getTenantId());
} else {
throw new BizException(BizExceptionEnum.UN_PERMISSION_STMS);
}
} else {
//session续期
sessionService.persistSession(sessionStruct.getSid(), sessionStruct.getValues(), sessionStruct.getTenantId());
}
//校验接口权限
OauthResult permissionResult = stmsRemoteService.checkPermission(stmsToken,
request.getRequestURI(), tenantId);
if (permissionResult != null && 2000 == permissionResult.getCode()) {
return true;
} else {
throw new BizException(BizExceptionEnum.UN_VALID_STMS_TOKEN);
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
XyqbSessionContextHolder.releaseSession();
}
}
......@@ -21,12 +21,14 @@ public class TokenInterceptor implements HandlerInterceptor {
public TokenInterceptor(ISessionService sessionService) {
this.sessionService = sessionService;
excludes.add("/v2/oauth/login");
excludes.add("/v2/sms/code");
excludes.add("/v2/sms/verify");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//所有开放出去的外部接口,都需要验证租户id和注册来源
//所有开放出去的外部接口,都需要验证租户id和注册来源
String tenantId = request.getHeader(Constants.X_AUTH_TENANT);
String registered_from = request.getHeader(Constants.X_AUTH_FROM);
if (StringUtils.isEmpty(tenantId)) {
......@@ -39,7 +41,7 @@ public class TokenInterceptor implements HandlerInterceptor {
//如果不再排查excludes的接口,需要进一步校验token(说明这些接口需要登录)
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSessionFromRedis();
if (!excludes.contains(request.getRequestURI())) {
if (sessionStruct == null) {
if (sessionStruct == null) {
throw new BizException(BizExceptionEnum.UN_AUTHORIZED);
} else {
//session续期
......@@ -48,7 +50,9 @@ public class TokenInterceptor implements HandlerInterceptor {
} else {
//如果是排除的接口,同时sessionStruct为空,这时候初始化租户和来源字段
if (sessionStruct == null) {
XyqbSessionContextHolder.initXSession();
sessionStruct = XyqbSessionContextHolder.initXSession();
sessionService.persistSession(sessionStruct.getSid(), sessionStruct.getValues(), sessionStruct.getTenantId());
}
}
......
......@@ -13,7 +13,13 @@ public class BehaviorCodeBean {
* 当客户端给的是极验,但是由于极验不可用的时候,会返回降级量化派图形验证码
*/
private Integer type;
/**
* 量化派图形验证码时返回
*/
private QtCode qtCode;
/**
* 极光验证时返回
*/
private GeetestCode geetestCode;
private String id;
......
......@@ -38,10 +38,24 @@ public class LoginProperties {
//租户ID
private Integer tenantId;
private String stmsUserId;
private String stmsUserName;
public LoginProperties(int action, Long createdFrom, Integer tenantId) {
this.action = action;
this.createdFrom = createdFrom;
this.tenantId = tenantId;
}
public LoginProperties(String dimension, int action, Long channelId, Long createdFrom, String appChannel, Long merchantId, String merchantName, Integer tenantId) {
this.dimension = dimension;
this.action = action;
this.channelId = channelId;
this.createdFrom = createdFrom;
this.appChannel = appChannel;
this.merchantId = merchantId;
this.merchantName = merchantName;
this.tenantId = tenantId;
}
}
......@@ -4,4 +4,5 @@ import lombok.Data;
@Data
public class SMSCodeBean {
private String code;
}
......@@ -28,6 +28,9 @@ public class SessionStruct implements Serializable {
private String scDeviceId;
private String terminal;
private String stmsUserId;
private String stmsUserName;
public void setAttribute(String key, String value) {
if (value == null) {
......
package cn.quantgroup.xyqb.model.v2.login;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* userId、uuid、openId与appId、unionId与appId有且只能有一组
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class InterLoginParam extends BaseLoginParam{
//用户id
private Long userId;
private String uuid;
private String openId;
private String unionId;
private String appId;
}
......@@ -2,11 +2,18 @@ package cn.quantgroup.xyqb.remote;
import cn.quantgroup.xyqb.model.OauthResult;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "stms", url = "${stms.https}")
public interface StmsRemoteService {
@PostMapping("/v2/oauth/currentuserinfo")
OauthResult checkToken(@RequestHeader("Access-Token") String accessToken);
@GetMapping(value = "/v2/oauth/permission")
OauthResult checkPermission(@RequestHeader("Access-Token") String accessToken,
@RequestHeader("Referer") String referer, @RequestParam("tenantId") String tenantId);
}
......@@ -16,6 +16,7 @@ import java.util.List;
public interface IUserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
User findByPhoneNoAndTenantId(String phoneNo,Integer tenantId);
List<User> findByPhoneNoInAndTenantId(List<String> phoneNo,Integer tenantId);
User findByEncryptedPhoneNoAndTenantId(String phoneNo,Integer tenantId);
......
......@@ -16,8 +16,15 @@ import static org.springframework.transaction.annotation.Propagation.MANDATORY;
* modify by djh 20200527 http://confluence.quantgroup.cn/pages/viewpage.action?pageId=30657427
*/
public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Long> {
WechatUserInfo findByOpenIdAndAppNameAndAppIdAndTenantId(String openId, String appName,String appId,Integer tenantId);
WechatUserInfo findByOpenIdAndAppIdAndTenantId(String openId,String appId,Integer tenantId);
WechatUserInfo findByOpenIdAndAppNameAndAppIdAndTenantId(String openId, String appName, String appId, Integer tenantId);
List<WechatUserInfo> findByOpenIdInAndAppIdAndTenantId(List<String> openId, String appId, Integer tenantId);
List<WechatUserInfo> findByUnionIdInAndAppIdAndTenantId(List<String> unionIds, String appId, Integer tenantId);
WechatUserInfo findByUnionIdAndAppIdAndTenantId(String openId, String appId, Integer tenantId);
WechatUserInfo findByOpenIdAndAppIdAndTenantId(String openId, String appId, Integer tenantId);
WechatUserInfo findByPhoneNoAndAppNameAndTenantId(String phoneNo, String appName, Integer tenantId);
......@@ -25,7 +32,7 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
WechatUserInfo findFirstByUserIdAndAppNameAndTenantIdOrderByCreatedAtDesc(Long userId, String appName, Integer tenantId);
long countByOpenIdAndAppNameAndAppIdAndTenantId(String openId, String appName,String appId,Integer tenantId);
long countByOpenIdAndAppNameAndAppIdAndTenantId(String openId, String appName, String appId, Integer tenantId);
/**
* 解除关联关系 -- 当前用户的已关联微信
......@@ -50,7 +57,7 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
@Transactional(rollbackFor = Exception.class)
@Modifying
@Query(value = "update wechat_userinfo set user_id=?1,phone_no=?2 where open_id=?3 and user_id is null and app_name=?4 and app_id=?5 and tenant_id=?6", nativeQuery = true)
int relateUser(Long userId, String phoneNo, String openId, String appName,String appId,Integer tenantId);
int relateUser(Long userId, String phoneNo, String openId, String appName, String appId, Integer tenantId);
/**
* 解除关联关系 -- 包括:1、当前微信旧的关联用户;2、当前用户旧的关联微信
......@@ -63,11 +70,12 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
@Transactional(propagation = MANDATORY, rollbackFor = Exception.class)
@Modifying
@Query(value = "update wechat_userinfo set user_id=null,phone_no='*' where (open_id=?1 or user_id=?2) and app_name=?3 and app_id=?4 and tenant_id=?5", nativeQuery = true)
int dissociateUser(String openId, Long userId, String appName,String appId,Integer tenantId);
int dissociateUser(String openId, Long userId, String appName, String appId, Integer tenantId);
/**
* 更新unionId
*
* @param userId
* @param appName
* @param unionId
......@@ -76,7 +84,7 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
@Transactional(rollbackFor = Exception.class)
@Modifying
@Query(value = "update wechat_userinfo set union_id=?3 where user_id=?1 and app_name=?2 and app_id=?3 and tenant_id=?4", nativeQuery = true)
int updateUserUnionId(Long userId, String appName, String unionId,String appId,Integer tenantId);
int updateUserUnionId(Long userId, String appName, String unionId, String appId, Integer tenantId);
@Transactional
void deleteByUserIdAndTenantId(Long userId, Integer tenantId);
......@@ -84,9 +92,9 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
/**
* 通过userId查询相关绑定微信记录
*/
List<WechatUserInfo> findByUserIdAndTenantId(Long userId,Integer tenantId);
List<WechatUserInfo> findByUserIdAndTenantId(Long userId, Integer tenantId);
List<WechatUserInfo> findByPhoneNoAndTenantId(String phoneNo,Integer tenantId);
List<WechatUserInfo> findByPhoneNoAndTenantId(String phoneNo, Integer tenantId);
/**
* 通过unionId查询相关绑定微信记录
......
package cn.quantgroup.xyqb.service.session.impl;
import cn.quantgroup.xyqb.constant.enums.RecordType;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.enums.RecordType;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.UserTag;
import cn.quantgroup.xyqb.event.UserLoginEvent;
......@@ -36,7 +36,6 @@ import java.util.concurrent.TimeUnit;
/**
* session创建和续期
*
*/
@Slf4j
@Service
......@@ -64,22 +63,22 @@ public class SessionServiceImpl implements ISessionService {
* @return
*/
@Override
public AuthBean createSession(User user, LoginProperties properties, int loginType,Integer tenantId) {
public AuthBean createSession(User user, LoginProperties properties, int loginType, Integer tenantId) {
//找到用户
//TODO: 使用userId
String sessionId = findSessionIdByUserIdLoginProperties(user.getId(), properties,tenantId);
String sessionId = findSessionIdByUserIdLoginProperties(user.getId(), properties, tenantId);
SessionStruct sessionStruct = null;
if (StringUtils.isNotEmpty(sessionId)) {
sessionStruct = findSessionBySessionId(sessionId,tenantId);
sessionStruct = findSessionBySessionId(sessionId, tenantId);
}
if (Objects.nonNull(sessionStruct) && Objects.nonNull(properties)) {
sessionStruct.setAttribute("channelId", String.valueOf(properties.getChannelId()));
sessionStruct.setAttribute("createdFrom", String.valueOf(properties.getCreatedFrom()));
sessionStruct.setAttribute("appChannel", String.valueOf(properties.getAppChannel()));
sessionStruct.getValues().setLoginProperties(properties);
persistSession(sessionStruct.getSid(), sessionStruct.getValues(),tenantId);
persistSession(sessionStruct.getSid(), sessionStruct.getValues(), tenantId);
} else {
sessionStruct = createSessionAndPersist(user, properties,tenantId);
sessionStruct = createSessionAndPersist(user, properties, tenantId);
}
String uuid = user.getUuid();
AuthBean authBean = new AuthBean();
......@@ -100,27 +99,27 @@ public class SessionServiceImpl implements ISessionService {
}
@Override
public SessionStruct createSessionAndPersist(User user, LoginProperties properties,Integer tenantId) {
public SessionStruct createSessionAndPersist(User user, LoginProperties properties, Integer tenantId) {
SessionStruct sessionStruct;
//获取sessionid
String sessionId = findSessionIdByUserIdLoginProperties(user.getId(), properties,tenantId);
String sessionId = findSessionIdByUserIdLoginProperties(user.getId(), properties, tenantId);
if (StringUtils.length(sessionId) > 0) {
sessionStruct = findSessionBySessionId(sessionId,tenantId);
sessionStruct = findSessionBySessionId(sessionId, tenantId);
if (sessionStruct == null) {
sessionStruct = newSession(user, properties);
} else {
sessionStruct.getValues().setLoginProperties(properties);
}
persistSession(sessionStruct.getSid(), sessionStruct.getValues(),tenantId);
persistSession(sessionStruct.getSid(), sessionStruct.getValues(), tenantId);
} else {
sessionStruct = newSession(user, properties);
persistSession(sessionStruct.getSid(), sessionStruct.getValues(),tenantId);
persistSession(sessionStruct.getSid(), sessionStruct.getValues(), tenantId);
}
return sessionStruct;
}
private String findSessionIdByUserIdLoginProperties(Long userId, LoginProperties properties,Integer tenantId) {
return stringRedisTemplate.opsForValue().get(generateLoginPropertiesKey(userId, properties,tenantId));
private String findSessionIdByUserIdLoginProperties(Long userId, LoginProperties properties, Integer tenantId) {
return stringRedisTemplate.opsForValue().get(generateLoginPropertiesKey(userId, properties, tenantId));
}
/**
......@@ -130,17 +129,17 @@ public class SessionServiceImpl implements ISessionService {
* @param properties baitiao/xyqb/vcc ... + 用户注册来源
* @return redisKey. 用来标识这个渠道的用户 Session 是否存在
*/
private String generateLoginPropertiesKey(Long userId, LoginProperties properties,Integer tenantId) {
private String generateLoginPropertiesKey(Long userId, LoginProperties properties, Integer tenantId) {
if (ObjectUtils.isEmpty(properties.getTenantId())) {
return Constants.Session.USER_SESSION_ID_CACHE + ":" + userId + ":" + properties.getMerchantName() + ":" + properties.getCreatedFrom();
}else if (properties.getTenantId().equals(0) || TenantUtil.TENANT_DEFAULT.equals(properties.getTenantId())) {
} else if (properties.getTenantId().equals(0) || TenantUtil.TENANT_DEFAULT.equals(properties.getTenantId())) {
return Constants.Session.USER_SESSION_ID_CACHE + ":" + userId + ":" + properties.getMerchantName() + ":" + properties.getCreatedFrom();
}else {
} else {
return Constants.Session.USER_SESSION_ID_CACHE + ":" + userId + ":" + properties.getMerchantName() + ":" + properties.getCreatedFrom() + ":" + properties.getTenantId();
}
}
private String findSessionValueBySessionId(String sessionId,Integer tenantId) {
private String findSessionValueBySessionId(String sessionId, Integer tenantId) {
String result = stringRedisTemplate.opsForValue().get(Constants.Session.USER_SESSION_CACHE + sessionId);
return StringUtils.defaultString(result, "");
}
......@@ -173,21 +172,36 @@ public class SessionServiceImpl implements ISessionService {
@Override
@UserBtRegisterFill
public void persistSession(String token, SessionValue sessionValue,Integer tenantId) {
persistSession(token, sessionValue, Constants.Session.SESSION_VALID_TIME,tenantId);
public void persistSession(String token, SessionValue sessionValue, Integer tenantId) {
persistSession(token, sessionValue, Constants.Session.SESSION_VALID_TIME, tenantId);
}
@Override
public void persistSession(String token, SessionValue sessionValue, Long time,Integer tenantId) {
public void persistSession(String token, SessionValue sessionValue, Long time, Integer tenantId) {
Timestamp current = new Timestamp(System.currentTimeMillis());
if (sessionValue == null) {
sessionValue = new SessionValue();
}
if(sessionValue.getLoginProperties()==null){
sessionValue.setLoginProperties(new LoginProperties());
}
LoginProperties loginProperties= sessionValue.getLoginProperties();
loginProperties.setTenantId(tenantId);
sessionValue.setLoginProperties(loginProperties);
sessionValue.setLastAccessTime(current);
String json = JSON.toJSONString(sessionValue);
stringRedisTemplate.opsForValue().set(Constants.Session.USER_SESSION_CACHE + token, json,
time, TimeUnit.SECONDS);
String key = generateLoginPropertiesKey(sessionValue.getUser().getId(), sessionValue.getLoginProperties(),tenantId);
stringRedisTemplate.opsForValue().set(key, token, time, TimeUnit.SECONDS);
log.info("[Session生命期延续],token:{},有效期:[24Hour]", token);
setUserIdTokenKeys(sessionValue.getUser().getId(), key,tenantId);
if(sessionValue.getUser()!=null){
String key = generateLoginPropertiesKey(sessionValue.getUser().getId(), sessionValue.getLoginProperties(), tenantId);
stringRedisTemplate.opsForValue().set(key, token, time, TimeUnit.SECONDS);
log.info("[Session生命期延续],token:{},有效期:[24Hour]", token);
setUserIdTokenKeys(sessionValue.getUser().getId(), key, tenantId);
}
}
/**
......@@ -196,9 +210,9 @@ public class SessionServiceImpl implements ISessionService {
* @param userId
* @param key
*/
private void setUserIdTokenKeys(long userId, String key,Integer tenantId) {
private void setUserIdTokenKeys(long userId, String key, Integer tenantId) {
if (0L != userId) {
String setKey = getUserSessionSetKey(userId,tenantId);
String setKey = getUserSessionSetKey(userId, tenantId);
try {
stringRedisTemplate.opsForSet().add(setKey, key);
stringRedisTemplate.expire(setKey, Constants.Session.SESSION_VALID_TIME, TimeUnit.SECONDS);
......@@ -209,8 +223,8 @@ public class SessionServiceImpl implements ISessionService {
}
}
private SessionStruct findSessionBySessionId(String sessionId,Integer tenantId) {
String sessionValue = findSessionValueBySessionId(sessionId,tenantId);
private SessionStruct findSessionBySessionId(String sessionId, Integer tenantId) {
String sessionValue = findSessionValueBySessionId(sessionId, tenantId);
if (StringUtils.isEmpty(sessionValue)) {
log.warn("[SessionServiceImpl][findSessionBySessionId] session data 未找到:sid:{}", sessionId);
return null;
......@@ -234,15 +248,15 @@ public class SessionServiceImpl implements ISessionService {
@Override
@CacheEvict(value = "userextinfocache", key = "'extinfo' + #userId", cacheManager = "cacheManager")
public void deleteByUserId(long userId,Integer tenantId) {
public void deleteByUserId(long userId, Integer tenantId) {
//1.删除session关联
String setKey = getUserSessionSetKey(userId,tenantId);
String setKey = getUserSessionSetKey(userId, tenantId);
Set useIdKeys = stringRedisTemplate.opsForSet().members(setKey);
if (!CollectionUtils.isEmpty(useIdKeys)) {
useIdKeys.forEach(key -> {
log.info("删除用户userId={}的缓存信息", userId);
String token = stringRedisTemplate.opsForValue().get(String.valueOf(key));
stringRedisTemplate.delete(getUserTokenKey(token,tenantId));
stringRedisTemplate.delete(getUserTokenKey(token, tenantId));
stringRedisTemplate.delete(String.valueOf(key));
});
//2.删除session缓存健
......@@ -250,7 +264,7 @@ public class SessionServiceImpl implements ISessionService {
}
}
private String getUserTokenKey(String token,Integer tenantId) {
private String getUserTokenKey(String token, Integer tenantId) {
return Constants.Session.USER_SESSION_CACHE + token;
}
......@@ -266,39 +280,39 @@ public class SessionServiceImpl implements ISessionService {
@CacheEvict(value = "userSpouseCache", key = "'spouse' + #user.id", cacheManager = "cacheManager"),
@CacheEvict(value = "btRegisterCache", key = "'userId' + #user.id", cacheManager = "cacheManager")})
@Override
public void deleteUserCatch(User user,Integer tenantId) {
public void deleteUserCatch(User user, Integer tenantId) {
}
@Override
public List<SessionStruct> findByUserId(long userId,Integer tenantId) {
public List<SessionStruct> findByUserId(long userId, Integer tenantId) {
List<SessionStruct> sessionStructList = new ArrayList<>();
String setKey = getUserSessionSetKey(userId,tenantId);
String setKey = getUserSessionSetKey(userId, tenantId);
Set<String> userIdKeys = stringRedisTemplate.opsForSet().members(setKey);
if (CollectionUtils.isEmpty(userIdKeys)) {
return sessionStructList;
}
for (String userIdKey : userIdKeys) {
String sessionId = stringRedisTemplate.opsForValue().get(userIdKey);
SessionStruct sessionStruct = findSessionBySessionId(sessionId,tenantId);
SessionStruct sessionStruct = findSessionBySessionId(sessionId, tenantId);
sessionStructList.add(sessionStruct);
}
return sessionStructList;
}
@Override
public void persistSession(List<SessionStruct> sessionStructList,Integer tenantId) {
public void persistSession(List<SessionStruct> sessionStructList, Integer tenantId) {
for (SessionStruct sessionStruct : sessionStructList) {
String sid = sessionStruct.getSid();
SessionValue values = sessionStruct.getValues();
persistSession(sid, values,tenantId);
persistSession(sid, values, tenantId);
}
}
@Override
public void deleteSession(String token,Integer tenantId) {
public void deleteSession(String token, Integer tenantId) {
SessionStruct sessionStruct = findSessionBySessionId(token,tenantId);
SessionStruct sessionStruct = findSessionBySessionId(token, tenantId);
if (null != sessionStruct) {
......@@ -313,7 +327,7 @@ public class SessionServiceImpl implements ISessionService {
String key = generateLoginPropertiesKey(user.getId(), values.getLoginProperties(),tenantId);
String key = generateLoginPropertiesKey(user.getId(), values.getLoginProperties(), tenantId);
stringRedisTemplate.delete(key);
......@@ -322,37 +336,36 @@ public class SessionServiceImpl implements ISessionService {
}
@Override
public void persistSessionExchange(String token, SessionValue sessionValue, long expire,Integer tenantId) {
public void persistSessionExchange(String token, SessionValue sessionValue, long expire, Integer tenantId) {
Timestamp current = new Timestamp(System.currentTimeMillis());
sessionValue.setLastAccessTime(current);
String json = JSON.toJSONString(sessionValue);
stringRedisTemplate.opsForValue().set(Constants.Session.USER_SESSION_CACHE + token, json,
Constants.Session.SESSION_VALID_TIME, TimeUnit.SECONDS);
String key = generateLoginPropertiesKey(sessionValue.getUser().getId(), sessionValue.getLoginProperties(),tenantId);
String key = generateLoginPropertiesKey(sessionValue.getUser().getId(), sessionValue.getLoginProperties(), tenantId);
stringRedisTemplate.opsForValue().set(key, token, expire, TimeUnit.SECONDS);
setUserIdTokenKeys(sessionValue.getUser().getId(), key,tenantId);
setUserIdTokenKeys(sessionValue.getUser().getId(), key, tenantId);
}
public void kdspDeleteSession(Long userId, LoginProperties loginProperties,Integer tenantId) {
public void kdspDeleteSession(Long userId, LoginProperties loginProperties, Integer tenantId) {
List<Long> createFromList = Arrays.asList(214L, 217L);
for (Long createFrom : createFromList) {
loginProperties.setCreatedFrom(createFrom);
String key = generateLoginPropertiesKey(userId, loginProperties,tenantId);
String key = generateLoginPropertiesKey(userId, loginProperties, tenantId);
String token = stringRedisTemplate.opsForValue().get(key);
deleteSession(token,tenantId);
deleteSession(token, tenantId);
}
}
/**
* 获取用户的会话缓存Set的Redis-Key
*
* @param userId - 用户主键
* @return
*/
private String getUserSessionSetKey(long userId,Integer tenantId) {
private String getUserSessionSetKey(long userId, Integer tenantId) {
return Constants.Session.USER_SESSION_KEY_SET + userId;
}
}
package cn.quantgroup.xyqb.service.sms;
import cn.quantgroup.sms.SmsSender;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
/**
* 短信发送服务
......@@ -25,6 +26,8 @@ public interface ISmsService {
*/
boolean verifyPhoneAndCode(String phoneNo, String verificationCode);
boolean verifyPhoneAndCode(SMSVerifyReq smsVerifyReq);
/**
* 是否需要重新发送短信验证码
*
......
......@@ -4,13 +4,18 @@ import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.sms.SmsSender;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
......@@ -98,6 +103,37 @@ public class SmsServiceImpl implements ISmsService {
return confirmSms(verificationCode, uniqueId, code);
}
public boolean verifyPhoneAndCode(SMSVerifyReq smsVerifyReq) {
// 非生产环境直接跳过验证码检验
if (!TechEnvironment.isPro()) {
return true;
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key;
if (sessionStruct == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_TENANT_ID);
}
if (UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + smsVerifyReq.getPhoneNo();
} else {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" + smsVerifyReq.getPhoneNo();
}
String randomCode = stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isBlank(randomCode)) {
return false;
}
String[] arr = randomCode.split(":");
if (arr.length != Constants.VERIFICATION_LEN) {
return false;
}
String uniqueId = arr[0];
String code = arr[1];
return confirmSms(smsVerifyReq.getCode(), uniqueId, code);
}
private boolean confirmSms(String smsVerificationCode, String unqiueId, String code) {
try {
MsgParams message = new MsgParams(Collections.singletonList(2), unqiueId);
......@@ -114,11 +150,11 @@ public class SmsServiceImpl implements ISmsService {
@Override
public boolean needResendCode(String phoneNo) {
return needResendCode(phoneNo, Constants.VERIFICATION_CODE_FINITE_COUNT,true);
return needResendCode(phoneNo, Constants.VERIFICATION_CODE_FINITE_COUNT, true);
}
@Override
public boolean needResendCode(String phoneNo, Long threshold,boolean isDelTryCount) {
public boolean needResendCode(String phoneNo, Long threshold, boolean isDelTryCount) {
String verificationCountKey = Constants.REDIS_VERIFICATION_COUNT + phoneNo;
Long getVerificationCount = stringRedisTemplate.opsForHash().increment(verificationCountKey, Constants.REDIS_VERIFICATION_COUNT, 1);
......@@ -143,7 +179,12 @@ public class SmsServiceImpl implements ISmsService {
@Override
public void deleteOnlyCodeFromCache(String phoneNo) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
stringRedisTemplate.delete(key);
}
......
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.xyqb.constant.enums.LoginType;
import cn.quantgroup.xyqb.controller.req.v2.LoginReq;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.WechatUserInfo;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.AuthBean;
import cn.quantgroup.xyqb.model.LoginBean;
import cn.quantgroup.xyqb.model.LoginProperties;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.v2.login.BaseLoginParam;
import cn.quantgroup.xyqb.model.v2.login.InterLoginParam;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.user.ILockIpv4Service;
import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
* 短信验证码登录
*/
@Service
@Slf4j
public class InterLoginStrategy implements LoginStrategy {
private final ILockIpv4Service lockIpv4Service;
private final IUserService userService;
private final IWechatService wechatService;
private final ISessionService sessionService;
public InterLoginStrategy(ILockIpv4Service lockIpv4Service, IUserService userService, IWechatService wechatService, ISessionService sessionService) {
this.lockIpv4Service = lockIpv4Service;
this.userService = userService;
this.wechatService = wechatService;
this.sessionService = sessionService;
}
@Override
public Integer getType() {
return 9000;
}
@Override
public LoginBean login(BaseLoginParam param) {
InterLoginParam interLoginParam = (InterLoginParam) param;
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
User user = null;
//1、根据前端的条件,进行查询用户信息
if (interLoginParam.getUserId() != null) {
user = userService.findById(interLoginParam.getUserId(), sessionStruct.getTenantId());
}
if (StringUtils.isNotEmpty(interLoginParam.getUuid())) {
user = userService.findByUuidInDb(interLoginParam.getUuid(), sessionStruct.getTenantId());
}
if (StringUtils.isNotEmpty(interLoginParam.getAppId()) && StringUtils.isNotEmpty(interLoginParam.getOpenId())) {
WechatUserInfo wechatUserInfo = wechatService.findWechatUserInfoFromDb(interLoginParam.getOpenId(), interLoginParam.getAppId(), sessionStruct.getTenantId());
if (wechatUserInfo == null || wechatUserInfo.getUserId() == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
user = userService.findById(wechatUserInfo.getUserId(), sessionStruct.getTenantId());
}
if (StringUtils.isNotEmpty(interLoginParam.getAppId()) && StringUtils.isNotEmpty(interLoginParam.getUnionId())) {
WechatUserInfo wechatUserInfo = wechatService.findByUnionIdAndAppIdAndTenantId(interLoginParam.getUnionId(), interLoginParam.getAppId(), sessionStruct.getTenantId());
if (wechatUserInfo == null || wechatUserInfo.getUserId() == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
user = userService.findById(wechatUserInfo.getUserId(), sessionStruct.getTenantId());
}
if (user == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_USER);
}
//2、需要判断是否是否启用
if (!user.getEnable()) {
log.info("用户不存在,或者已经注销,phoneNo:{}", user.getPhoneNo());
throw new BizException(BizExceptionEnum.ERROR_OR_ENABLE_ERROR);
}
LoginProperties loginProperties = new LoginProperties(1, sessionStruct.getRegisteredFrom(), sessionStruct.getTenantId());
AuthBean authBean = sessionService.createSession(user, loginProperties, LoginType.SUPERLOGIN.ordinal(), sessionStruct.getTenantId());
LoginBean loginBean = new LoginBean();
if (authBean != null) {
loginBean.setToken(authBean.getToken());
loginBean.setPhoneNo(authBean.getPhoneNo());
loginBean.setUuid(authBean.getUuid());
loginBean.setHasPassword(authBean.isHasPassword());
loginBean.setUserId(user.getId());
loginBean.setTenantId(user.getTenantId());
}
loginBean.setRegister(false);
lockIpv4Service.unLockPhone(user.getPhoneNo());
return loginBean;
}
@Override
public BaseLoginParam checkParam(LoginReq loginReq) {
InterLoginParam param = loginReq.getData().toJavaObject(InterLoginParam.class);
if (param.getUserId() == null && StringUtils.isEmpty(param.getUuid())
&& (StringUtils.isEmpty(param.getOpenId()) && StringUtils.isEmpty(param.getAppId())) && (StringUtils.isEmpty(param.getUnionId()) && StringUtils.isEmpty(param.getAppId()))) {
throw new BizException(BizExceptionEnum.ERROR_LOGIN_PARAM);
}
return param;
}
}
......@@ -6,6 +6,8 @@ import cn.quantgroup.xyqb.entity.UserInfoEntity;
import cn.quantgroup.xyqb.entity.UserTag;
import cn.quantgroup.xyqb.entity.WechatUserInfo;
import cn.quantgroup.xyqb.event.UserLoginEvent;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.LoginBean;
import cn.quantgroup.xyqb.model.UserInfo;
import cn.quantgroup.xyqb.model.session.SessionStruct;
......@@ -25,7 +27,6 @@ import java.util.Map;
@Service
public class LoginContext {
private final Map<Integer, LoginStrategy> loginStrategyMap = new HashMap<>();
private final ApplicationEventPublisher applicationEventPublisher;
private final IWechatService wechatService;
......@@ -33,17 +34,33 @@ public class LoginContext {
private final IUserInfoRepository userInfoRepository;
private final Map<Integer, LoginStrategy> interloginStrategyMap = new HashMap<>();
public LoginContext(List<LoginStrategy> loginStrategies, ApplicationEventPublisher applicationEventPublisher, IWechatService wechatService, IUserRepository userRepository, IUserInfoRepository userInfoRepository) {
this.applicationEventPublisher = applicationEventPublisher;
this.wechatService = wechatService;
this.userRepository = userRepository;
this.userInfoRepository = userInfoRepository;
loginStrategies.forEach(i -> loginStrategyMap.put(i.getType(), i));
loginStrategies.forEach(i -> interloginStrategyMap.put(i.getType(), i));
}
public LoginBean login(LoginReq loginReq) {
if(loginReq.getType()!=null && loginReq.getType()==9000){
throw new BizException(BizExceptionEnum.ERROR_INTERNAL_LOGIN);
}
return this.interLogin(loginReq);
}
/**
* 内部统一登录
* @param loginReq LoginReq
* @return LoginBean
*/
public LoginBean interLogin(LoginReq loginReq){
//第一步、校验参数
LoginStrategy loginStrategy = loginStrategyMap.get(loginReq.getType());
LoginStrategy loginStrategy = interloginStrategyMap.get(loginReq.getType());
BaseLoginParam loginParam = loginStrategy.checkParam(loginReq);
......
......@@ -41,7 +41,7 @@ public class QuantGroupBehaviorStrategy implements BehaviorStrategy {
@Override
public boolean validate(ValidateBehavior validateBehavior) {
boolean result = quantgroupCaptchaService.validQuantgroupCaptcha(validateBehavior.getData().get("id"),validateBehavior.getData().get(Constants.X_BEHAVIOR_IMAGEVALUE));
boolean result = quantgroupCaptchaService.validQuantgroupCaptcha(validateBehavior.getData().get(Constants.X_BEHAVIOR_ID),validateBehavior.getData().get(Constants.X_BEHAVIOR_IMAGEVALUE));
if(result){
return true;
}else{
......
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.sms.SmsMerchant;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class SMSVerificationCodeStrategy implements VerificationCodeStrategy {
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ISmsService smsService;
private static final long EXPIRE_MINUTES = 10;
private static final Long PHONE_MAX_PER_DAY = 20L;
/**
* 设备每天上限
*/
private static final Long DEVICE_MAX_PER_DAY = 20L;
/**
* ip上限
*/
private static final Long IP_MAX_PER_DAY = 5000L;
@Override
public Integer getType() {
return 0;
}
@Override
public SMSCodeBean send(SMSReq smsReq) {
SMSCodeBean smsCodeBean = new SMSCodeBean();
int codeLength = Constants.SMS_CODE_LEN_4;
if (smsReq.getCodeLength() != null) {
codeLength = smsReq.getCodeLength();
}
String randomCode = getRandomCode(codeLength);
if (sendVerificationCode2New(smsReq.getPhoneNo(), randomCode, smsReq.getSmsMerchant())) {
smsCodeBean.setCode(randomCode);
return smsCodeBean;
} else {
throw new BizException(BizExceptionEnum.ERROR_SEND_SMS);
}
}
private final static String RANDOM_CHARS = "0123456789";
private String getRandomCode(int count) {
if (TechEnvironment.isPro()) {
return RandomStringUtils.random(count, RANDOM_CHARS);
}
if (count == 6) {
return "000000";
}
return Constants.SUCCESS_CODE;
}
/**
* 新版本短信验证码
*
* @param phoneNo 手机号码
* @param randomCode 随机码
* @param smsMerchant 模版
* @return true成功,false失败
*/
private boolean sendVerificationCode2New(String phoneNo, String randomCode, String smsMerchant) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String clientIp = sessionStruct.getIp();
// 手机号计数器
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_CODE_COUNT, phoneNo, 1);
redisTemplate.expire(Constants.REDIS_SMS_CODE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
// 设备号计数器
Long getDeviceVerificationCount = 0L;
if (StringUtils.isNotBlank(sessionStruct.getScDeviceId())) {
getDeviceVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_DEVICE_COUNT, sessionStruct.getScDeviceId(), 1);
redisTemplate.expire(Constants.REDIS_SMS_DEVICE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// IP计数器
Long getIPVerificationCount = 0L;
if (StringUtils.isNotBlank(clientIp)) {
getIPVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_IP_COUNT, clientIp, 1);
redisTemplate.expire(Constants.REDIS_SMS_IP_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 手机号上限检查
if (getPhoneVerificationCount > PHONE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.PHONE_MAX_PER_DAY);
}
// 设备号上限检查
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.DEVICE_MAX_PER_DAY);
}
// IP上限检查
if (!IpUtil.whiteOf(clientIp) && getIPVerificationCount > IP_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.IP_MAX_PER_DAY);
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (!UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" + phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
throw new BizException(BizExceptionEnum.DUPLICATE_MIN);
}
String uniqueId = phoneNo + UUID.randomUUID().toString().replaceAll("-", "");
MsgParams message = new MsgParams.Builder()
.typeList(Collections.singletonList(2))
.phoneNo(phoneNo)
.merchantId(SmsMerchant.of(smsMerchant).getMerchantId())
.contentId(SmsMerchant.of(smsMerchant).getContentId())
.uniqueId(uniqueId)
.contentArgs(Collections.singletonList(randomCode))
.channel("")
.build();
try {
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
//删除用户重置密码,多次错误逻辑
deleteRetSendCode(phoneNo);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 删除用户重置密码时短信验证错误
*
* @param phoneNo 手机号码
*/
private void deleteRetSendCode(String phoneNo) {
String verificationCountKey = Constants.REDIS_VERIFICATION_COUNT + phoneNo;
redisTemplate.opsForHash().delete(verificationCountKey, Constants.REDIS_VERIFICATION_COUNT);
}
}
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class SMSVoiceVerificationCodeStrategy implements VerificationCodeStrategy {
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ISmsService smsService;
private static final long EXPIRE_MINUTES = 10;
/**
* 手机号语音上限
*/
private static final Long PHONE_VOICE_MAX_PER_DAY = 5L;
/**
* 设备每天上限
*/
private static final Long DEVICE_MAX_PER_DAY = 20L;
/**
* ip上限
*/
private static final Long IP_MAX_PER_DAY = 5000L;
@Override
public Integer getType() {
return 1;
}
@Override
public SMSCodeBean send(SMSReq smsReq) {
SMSCodeBean smsCodeBean = new SMSCodeBean();
String usage = null;
switch (smsReq.getSceneType()) {
case 0:
usage = "4";
break;
case 1:
usage = "6";
break;
case 2:
usage = "5";
break;
}
int codeLength = Constants.SMS_CODE_LEN_4;
if (smsReq.getCodeLength() != null) {
codeLength = smsReq.getCodeLength();
}
String randomCode = getRandomCode(codeLength);
if (usage == null) {
throw new BizException(BizExceptionEnum.ERROR_USAGE);
}
if (sendVerificationCode2VoiceNew(smsReq.getPhoneNo(), randomCode, usage)) {
smsCodeBean.setCode(randomCode);
return smsCodeBean;
} else {
throw new BizException(BizExceptionEnum.ERROR_SEND_SMS);
}
}
/**
* 新版本语音验证码
*/
private boolean sendVerificationCode2VoiceNew(String phoneNo, String randomCode, String usage) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String clientIp = sessionStruct.getIp();
// 手机号计数器
String verificationCountKey = Constants.REDIS_VOICE_CODE_COUNT + usage;
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(verificationCountKey, phoneNo, 1);
redisTemplate.expire(verificationCountKey, DateUtils.getSeconds(), TimeUnit.SECONDS);
// 设备号计数器
Long getDeviceVerificationCount = 0L;
if (StringUtils.isNotBlank(sessionStruct.getScDeviceId())) {
String verificationDeviceCountKey = Constants.REDIS_VOICE_DEVICE_COUNT + sessionStruct.getScDeviceId();
getDeviceVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_VOICE_DEVICE_COUNT, verificationDeviceCountKey, 1);
redisTemplate.expire(Constants.REDIS_VOICE_DEVICE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// IP计数器
Long getIPVerificationCount = 0L;
if (StringUtils.isNotBlank(clientIp)) {
String verificationIPCountKey = Constants.REDIS_VOICE_IP_COUNT + clientIp;
getIPVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_VOICE_IP_COUNT, verificationIPCountKey, 1);
redisTemplate.expire(Constants.REDIS_VOICE_IP_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 手机号上限检查
if (getPhoneVerificationCount > PHONE_VOICE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.PHONE_VOICE_MAX_PER_DAY);
}
// 设备号上限检查
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.DEVICE_MAX_PER_DAY);
}
// IP上限检查
if (!IpUtil.whiteOf(clientIp) && getIPVerificationCount > IP_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.IP_MAX_PER_DAY);
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (!UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" + phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
throw new BizException(BizExceptionEnum.DUPLICATE_MIN);
}
String uniqueId = phoneNo + UUID.randomUUID().toString().replaceAll("-", "");
MsgParams message = new MsgParams(Collections.singletonList(4), phoneNo, "1", "4", Collections.singletonList(randomCode), uniqueId);
try {
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
//删除用户重置密码,多次错误逻辑
deleteRetSendCode(phoneNo);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 删除用户重置密码时短信验证错误
*
* @param phoneNo 手机号码
*/
private void deleteRetSendCode(String phoneNo) {
String verificationCountKey = Constants.REDIS_VERIFICATION_COUNT + phoneNo;
redisTemplate.opsForHash().delete(verificationCountKey, Constants.REDIS_VERIFICATION_COUNT);
}
private final static String RANDOM_CHARS = "0123456789";
private String getRandomCode(int count) {
if (TechEnvironment.isPro()) {
return RandomStringUtils.random(count, RANDOM_CHARS);
}
if (count == 6) {
return "000000";
}
return Constants.SUCCESS_CODE;
}
}
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class VerificationCodeContext {
private final ISmsService smsService;
private final Map<Integer, VerificationCodeStrategy> verificationCodeStrategyHashMap = new HashMap<>();
public VerificationCodeContext(List<VerificationCodeStrategy> verificationCodeStrategyList, ISmsService smsService) {
this.smsService = smsService;
verificationCodeStrategyList.forEach(i -> verificationCodeStrategyHashMap.put(i.getType(), i));
}
public SMSCodeBean send(SMSReq smsReq) {
VerificationCodeStrategy verificationCodeStrategy = verificationCodeStrategyHashMap.get(smsReq.getType());
if (verificationCodeStrategy == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_VERIFY_TYPE);
}
return verificationCodeStrategy.send(smsReq);
}
public boolean verify(SMSVerifyReq smsVerifyReq) {
return smsService.verifyPhoneAndCode(smsVerifyReq);
}
}
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.model.SMSCodeBean;
public interface VerificationCodeStrategy {
Integer getType();
SMSCodeBean send(SMSReq smsReq);
}
......@@ -14,6 +14,11 @@ public interface IWechatService {
WechatUserInfo getWechatUserInfoFromWechatServer(String token, String openId,String appId,Integer tenantId);
WechatUserInfo findWechatUserInfoFromDb(String openId,String appId,Integer tenantId);
List<WechatUserInfo> findWechatUserInfoFromDb(List<String> openId,String appId,Integer tenantId);
List<WechatUserInfo> findUnionIdsAndOpenIdAndTenantId(List<String> uuids,String appId,Integer tenantId);
WechatUserInfo findByUnionIdAndAppIdAndTenantId(String unionId,String appId,Integer tenantId);
WechatUserInfo findWechatUserInfoByPhoneNo(String phoneNo, Integer tenantId);
......
......@@ -116,6 +116,23 @@ public class WechatServiceImpl implements IWechatService {
return weChatUserRepository.findByOpenIdAndAppNameAndAppIdAndTenantId(openId, "xyqb",appId,tenantId);
}
@Override
public List<WechatUserInfo> findWechatUserInfoFromDb(List<String> openId,String appId,Integer tenantId){
return weChatUserRepository.findByOpenIdInAndAppIdAndTenantId(openId,appId,tenantId);
}
public List<WechatUserInfo> findUnionIdsAndOpenIdAndTenantId(List<String> unionIds, String appId, Integer tenantId){
return weChatUserRepository.findByUnionIdInAndAppIdAndTenantId(unionIds,appId,tenantId);
}
@Override
public WechatUserInfo findByUnionIdAndAppIdAndTenantId(String openId,String appId,Integer tenantId) {
return weChatUserRepository.findByUnionIdAndAppIdAndTenantId(openId,appId,tenantId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public WechatUserInfo saveWechatUserInfo(WechatUserInfo userInfo) {
......
package cn.quantgroup.xyqb.session;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.LoginProperties;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.session.SessionValue;
import cn.quantgroup.xyqb.util.IpUtil;
......@@ -34,12 +35,17 @@ public class XyqbSessionContextHolder {
sessionStruct = getXSessionFromRedis();
threadSession.set(sessionStruct);
}
if(sessionStruct!=null && sessionStruct.getValues()!=null && sessionStruct.getValues().getLoginProperties()!=null){
sessionStruct.setStmsUserId(sessionStruct.getValues().getLoginProperties().getStmsUserId());
sessionStruct.setStmsUserName(sessionStruct.getValues().getLoginProperties().getStmsUserName());
}
return sessionStruct;
}
public static void initXSession() {
SessionStruct sessionStruct;
if (threadSession.get() == null) {
public static SessionStruct initXSession() {
SessionStruct sessionStruct = threadSession.get();
if (sessionStruct == null) {
sessionStruct = new SessionStruct();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
......@@ -56,9 +62,53 @@ public class XyqbSessionContextHolder {
sessionStruct.setGeetestId(geetestId);
}
sessionStruct.setScDeviceId(request.getHeader("scDeviceId"));
sessionStruct.setTerminal( request.getHeader("terminal"));
sessionStruct.setTerminal(request.getHeader("terminal"));
threadSession.set(sessionStruct);
} else {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//如果是极验,那赋值
if (request.getHeader(Constants.X_BEHAVIOR_TYPE) != null && "1".equals(request.getHeader(Constants.X_BEHAVIOR_TYPE))) {
String geetestId = request.getHeader(Constants.X_BEHAVIOR_ID);
sessionStruct.setGeetestId(geetestId);
}
}
return sessionStruct;
}
public static SessionStruct initSTMSSession(String token, String userId, String userName) {
SessionStruct sessionStruct = threadSession.get();
if (sessionStruct == null) {
sessionStruct = new SessionStruct();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String tenantId = request.getHeader(Constants.X_AUTH_TENANT);
String registered_from = request.getHeader(Constants.X_AUTH_FROM);
String ip = IpUtil.getRemoteIP(request);
sessionStruct.setTenantId(Integer.valueOf(tenantId));
sessionStruct.setRegisteredFrom(Long.valueOf(registered_from));
sessionStruct.setIp(ip);
sessionStruct.setScDeviceId(request.getHeader("scDeviceId"));
sessionStruct.setTerminal(request.getHeader("terminal"));
sessionStruct.setSid(token);
sessionStruct.setStmsUserId(userId);
sessionStruct.setStmsUserName(userName);
threadSession.set(sessionStruct);
} else {
sessionStruct.setStmsUserId(userId);
sessionStruct.setStmsUserName(userName);
}
SessionValue sessionValue = sessionStruct.getValues();
if (sessionValue == null) {
sessionValue = new SessionValue();
LoginProperties loginProperties = new LoginProperties();
loginProperties.setStmsUserId(userId);
loginProperties.setStmsUserName(userName);
sessionValue.setLoginProperties(loginProperties);
}
sessionStruct.setValues(sessionValue);
return sessionStruct;
}
public static SessionStruct getXSessionFromRedis() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment