Commit d8591f38 by kenzo

add login

parents b92a3e31 3bc7fe88
package com.cnooc.expert.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.time.format.DateTimeFormatter;
@Service
public class AccountLockService {
@Autowired
private StringRedisTemplate redisTemplate;
// 锁定策略:3次错误 -> 10分钟,4次错误 -> 30分钟,5次及以上错误 -> 1小时
private static final int[] LOCK_DURATIONS = {10, 30, 60}; // 分钟
private static final int MAX_ATTEMPTS = 3;
private String getLockKey(String account) {
return "login:lock:" + account;
}
private String getAttemptKey(String account) {
return "login:attempt:" + account;
}
private int calculateLockMinutes(long failureCount) {
if (failureCount < MAX_ATTEMPTS) {
return 0; // 不锁定
} else if (failureCount == MAX_ATTEMPTS) {
return LOCK_DURATIONS[0]; // 10分钟
} else if (failureCount == MAX_ATTEMPTS + 1) {
return LOCK_DURATIONS[1]; // 30分钟
} else {
return LOCK_DURATIONS[2]; // 60分钟
}
}
private void lockAccount(String account, int lockMinutes) {
String lockKey = getLockKey(account);
String lockValue = LocalDateTime.now().toString() + "|" + lockMinutes;
redisTemplate.opsForValue().set(lockKey, lockValue, lockMinutes*60, TimeUnit.SECONDS);//这个超时时间需要考虑一下
}
/**
* 手动解锁账号
*/
public void unlockAccount(String account) {
String attemptKey = getAttemptKey(account);
String lockKey = getLockKey(account);
redisTemplate.delete(attemptKey);
redisTemplate.delete(lockKey);
}
/**
* 处理登录成功(重置失败计数和锁定)
*/
public void handleLoginSuccess(String account) {
String attemptKey = getAttemptKey(account);
String lockKey = getLockKey(account);
redisTemplate.delete(attemptKey);
redisTemplate.delete(lockKey);
}
/**
* 处理登录失败
*/
public void handleLoginFailure(String account) {
String attemptKey = getAttemptKey(account);
String lockKey = getLockKey(account);
// 如果已经锁定,不再增加计数
if (Boolean.TRUE.equals(redisTemplate.hasKey(lockKey))) {
return;
}
// 增加失败计数
//long failureCount = jedis.incr(attemptKey);
String failureCountStr = redisTemplate.opsForValue().get(attemptKey)!=null?redisTemplate.opsForValue().get(attemptKey).toString():"0";
long failureCount = Long.parseLong(failureCountStr)+1;
LocalDateTime now = LocalDateTime.now();
LocalDateTime midnight = now.toLocalDate().atTime(LocalTime.MAX);
redisTemplate.opsForValue().set(attemptKey,String.valueOf(failureCount),ChronoUnit.SECONDS.between(now, midnight), TimeUnit.SECONDS);
// 设置计数过期时间(避免无限增长)
if (failureCount == 1) {
redisTemplate.expire(attemptKey, 24 * 60 * 60, TimeUnit.SECONDS); // 24小时过期
}
// 根据失败次数确定锁定时间
int lockMinutes = calculateLockMinutes(failureCount);
if (lockMinutes > 0) {
lockAccount(account, lockMinutes);
// 重置失败计数(锁定期间不需要继续计数)
redisTemplate.delete(attemptKey);
}
}
/**
* 检查账号是否被锁定
*/
public boolean isAccountLocked(String account) {
String lockKey = getLockKey(account);
return Boolean.TRUE.equals(redisTemplate.hasKey(lockKey));
}
/**
* 获取锁定剩余时间
*/
public String getLockRemainingTime(String account) {
String lockKey = getLockKey(account);
boolean isExist = Boolean.TRUE.equals(redisTemplate.hasKey(lockKey));
if(isExist){
LocalDateTime now = LocalDateTime.now();
LocalDateTime futureTime = now.plusSeconds(redisTemplate.getExpire(lockKey,TimeUnit.SECONDS));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
return futureTime.format(formatter);
}else{
return "";
}
}
}
package com.cnooc.expert.auth.service; package com.cnooc.expert.auth.service;
import com.cnooc.expert.system.entity.pojo.ZhuanJiaUser;
import com.cnooc.expert.system.entity.vo.LoginVO; import com.cnooc.expert.system.entity.vo.LoginVO;
import com.cnooc.expert.system.entity.vo.VerifyCodeVO; import com.cnooc.expert.system.entity.vo.VerifyCodeVO;
public interface LoginService { public interface LoginService {
/**
* 登录,兼容短信登录和密码登录
* @param loginVO
* @return
*/
String login(LoginVO loginVO); String login(LoginVO loginVO);
/**
* 发送短信验证码
* @param vo
* @return
*/
String sendPhoneCode(LoginVO vo); String sendPhoneCode(LoginVO vo);
/**
* 修改密码
* @param loginVO
* @return
*/
String changePass(LoginVO loginVO); String changePass(LoginVO loginVO);
/**
* 验证码验证接口
* @param codeVO
* @return
*/
String verifyCode(VerifyCodeVO codeVO); String verifyCode(VerifyCodeVO codeVO);
/**
* 根据token查询专家基础信息
* @param token
* @return
*/
ZhuanJiaUser getZhuanJiaUserByToken(String token);
} }
package com.cnooc.expert.auth.service;
import com.cnooc.expert.system.entity.pojo.SlideImageResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.concurrent.ThreadLocalRandom;
@Service
public class SlideCaptchaService {
private static final int SLIDER_WIDTH = 50;
private static final int SLIDER_HEIGHT = 50;
@Value("classpath:images/backgrounds/*")
private Resource[] backgroundResources;
/**
* 使用预设背景图片生成验证码
*/
public SlideImageResult generateSlideImages() throws IOException {
// 随机选择背景图片
Resource bgResource = backgroundResources[
ThreadLocalRandom.current().nextInt(backgroundResources.length)
];
BufferedImage background = ImageIO.read(bgResource.getInputStream());
// 调整背景图尺寸
background = resizeImage(background, 300, 200);
// 生成滑块位置
int sliderX = ThreadLocalRandom.current().nextInt(50, 250);
int sliderY = ThreadLocalRandom.current().nextInt(10, 150);
// 创建滑块
BufferedImage sliderImage = createSliderWithShadow(background, sliderX, sliderY);
drawSliderHole(background, sliderX, sliderY);
// 转换为Base64
String bgBase64 = imageToBase64(background, "png");
String sliderBase64 = imageToBase64(sliderImage, "png");
SlideImageResult result = new SlideImageResult();
result.setBackgroundBase64(bgBase64);
result.setSlideBase64(sliderBase64);
result.setSliderX(sliderX);
result.setSliderY(sliderY);
return result;
}
/**
* 图片转换为Base64
*/
private String imageToBase64(BufferedImage image, String format) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, format, baos);
byte[] imageBytes = baos.toByteArray();
return Base64.getEncoder().encodeToString(imageBytes);
}
/**
* 在背景图上绘制滑块挖空效果
*/
private void drawSliderHole(BufferedImage background, int x, int y) {
Graphics2D g2d = background.createGraphics();
// 设置挖空区域
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(x, y, SLIDER_WIDTH, SLIDER_HEIGHT);
// 重置合成规则,绘制边框
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setColor(Color.GRAY);
g2d.setStroke(new BasicStroke(2));
g2d.drawRect(x, y, SLIDER_WIDTH, SLIDER_HEIGHT);
g2d.dispose();
}
/**
* 创建带阴影效果的滑块
*/
private BufferedImage createSliderWithShadow(BufferedImage background, int x, int y) {
int width = 50;
int height = 50;
int shadowSize = 3;
BufferedImage slider = new BufferedImage(
width + shadowSize, height + shadowSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = slider.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制阴影
g2d.setColor(new Color(0, 0, 0, 100));
g2d.fillRoundRect(shadowSize, shadowSize, width, height, 10, 10);
// 绘制滑块主体
Shape sliderShape = new RoundRectangle2D.Double(0, 0, width, height, 10, 10);
g2d.setClip(sliderShape);
BufferedImage subImage = background.getSubimage(x, y, width, height);
g2d.drawImage(subImage, 0, 0, null);
g2d.setClip(null);
// 绘制边框
g2d.setColor(new Color(64, 158, 255));
g2d.setStroke(new BasicStroke(2));
g2d.draw(sliderShape);
g2d.dispose();
return slider;
}
/**
* 调整图片尺寸
*/
private BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = resizedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
g2d.dispose();
return resizedImage;
}
}
package com.cnooc.expert.auth.service; package com.cnooc.expert.auth.service;
import com.cnooc.expert.controller.auth.model.response.SlideCaptchaVO;
import com.cnooc.expert.system.entity.vo.SysCaptchaVO; import com.cnooc.expert.system.entity.vo.SysCaptchaVO;
public interface SysCaptchaService { public interface SysCaptchaService {
...@@ -9,6 +10,7 @@ public interface SysCaptchaService { ...@@ -9,6 +10,7 @@ public interface SysCaptchaService {
*/ */
SysCaptchaVO generate(); SysCaptchaVO generate();
SlideCaptchaVO generateSlide();
/** /**
* 验证码效验 * 验证码效验
* *
...@@ -18,6 +20,8 @@ public interface SysCaptchaService { ...@@ -18,6 +20,8 @@ public interface SysCaptchaService {
*/ */
boolean validate(String key, String code); boolean validate(String key, String code);
boolean validateSlide(String code, Integer moveX);
/** /**
* 是否开启登录验证码 * 是否开启登录验证码
* *
......
...@@ -2,6 +2,7 @@ package com.cnooc.expert.auth.service.impl; ...@@ -2,6 +2,7 @@ package com.cnooc.expert.auth.service.impl;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdcardUtil; import cn.hutool.core.util.IdcardUtil;
import com.cnooc.expert.auth.service.AccountLockService;
import com.cnooc.expert.common.constant.TokenConstants; import com.cnooc.expert.common.constant.TokenConstants;
import com.cnooc.expert.common.exception.BusinessException; import com.cnooc.expert.common.exception.BusinessException;
import com.cnooc.expert.common.exception.GlobalErrorCodeConstants; import com.cnooc.expert.common.exception.GlobalErrorCodeConstants;
...@@ -21,12 +22,12 @@ import com.cnooc.expert.auth.service.SysCaptchaService; ...@@ -21,12 +22,12 @@ import com.cnooc.expert.auth.service.SysCaptchaService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.Duration; import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -45,6 +46,9 @@ public class LoginServiceImpl implements LoginService { ...@@ -45,6 +46,9 @@ public class LoginServiceImpl implements LoginService {
private SmsService smsService; private SmsService smsService;
@Autowired @Autowired
private AccountLockService accountLockService;
@Autowired
private SysCaptchaService sysCaptchaService; private SysCaptchaService sysCaptchaService;
@Autowired @Autowired
...@@ -94,17 +98,17 @@ public class LoginServiceImpl implements LoginService { ...@@ -94,17 +98,17 @@ public class LoginServiceImpl implements LoginService {
//待确认,验证码下发是调用第三方平台吗 //待确认,验证码下发是调用第三方平台吗
//return Result.success("验证码已发送"); //return Result.success("验证码已发送");
boolean flag = sysCaptchaService.validate(vo.getKey(), vo.getCaptcha()); /* boolean flag = sysCaptchaService.validate(vo.getKey(), vo.getCaptcha());
if(!flag){ if(!flag){
throw new BusinessException(GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getCode(),GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getCode(),GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getMsg());
}else{ }else{*/
boolean smsfalg = smsService.sendSmsCode(vo.getPhoneNumber()); boolean smsfalg = smsService.sendSmsCode(vo.getPhoneNumber());
if(!smsfalg) { if(!smsfalg) {
throw new BusinessException(GlobalErrorCodeConstants.SEND_SMS_ERROR.getCode(),GlobalErrorCodeConstants.SEND_SMS_ERROR.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.SEND_SMS_ERROR.getCode(),GlobalErrorCodeConstants.SEND_SMS_ERROR.getMsg());
} else { } else {
return "短信验证码发送成功"; return "短信验证码发送成功";
} }
} //}
} }
@Override @Override
...@@ -167,26 +171,30 @@ public class LoginServiceImpl implements LoginService { ...@@ -167,26 +171,30 @@ public class LoginServiceImpl implements LoginService {
if(expertInfoResp == null){ if(expertInfoResp == null){
throw new BusinessException(GlobalErrorCodeConstants.USER_NOT_EXISTS.getCode(),GlobalErrorCodeConstants.USER_NOT_EXISTS.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.USER_NOT_EXISTS.getCode(),GlobalErrorCodeConstants.USER_NOT_EXISTS.getMsg());
} }
boolean isAccountLocked = accountLockService.isAccountLocked(expertInfoResp.getZhuanJiaGuid());
if(isAccountLocked){
//如果账号锁定了,返回错误信息
Map<String, Object> errorData = new HashMap<>();
errorData.put("dueDate", accountLockService.getLockRemainingTime(expertInfoResp.getZhuanJiaGuid()));
throw new BusinessException(GlobalErrorCodeConstants.USER_LOCKED.getCode(),GlobalErrorCodeConstants.USER_LOCKED.getMsg(), "user_login", errorData);
}
ExpertInfoAppResp expertInfoAppResp = loginServicesClient.getZhuanJiaInfoAppById(expertInfoResp.getZhuanJiaGuid()); ExpertInfoAppResp expertInfoAppResp = loginServicesClient.getZhuanJiaInfoAppById(expertInfoResp.getZhuanJiaGuid());
if(expertInfoAppResp == null){ if(expertInfoAppResp == null){
throw new BusinessException(GlobalErrorCodeConstants.PASSWORD_NOT_EXIST.getCode(),GlobalErrorCodeConstants.PASSWORD_NOT_EXIST.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.PASSWORD_NOT_EXIST.getCode(),GlobalErrorCodeConstants.PASSWORD_NOT_EXIST.getMsg());
} }
// 验证码效验 // 验证码效验
boolean flag = sysCaptchaService.validate(loginVO.getKey(), loginVO.getCaptcha()); /* boolean flag = sysCaptchaService.validate(loginVO.getKey(), loginVO.getCaptcha());
if (!flag) { if (!flag) {
// 保存登录日志
//sysLogLoginService.save(login.getUsername(), Constant.FAIL, LoginOperationEnum.CAPTCHA_FAIL.getValue());
throw new BusinessException(GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getCode(),GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getCode(),GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getMsg());
} }*/
//1.需要去库中查询,是否存在 //1.需要去库中查询,是否存在
//2.存在校验密码 //2.存在校验密码
//解密 //解密
String pwd = Sm2Util.decrypt(loginVO.getPassword()); String pwd = Sm2Util.decrypt(loginVO.getPassword());
flag = passwordEncoder.matches(pwd, expertInfoAppResp.getPassword()); boolean flag = passwordEncoder.matches(pwd, expertInfoAppResp.getPassword());
if (!flag) { if (!flag) {
// 登录日志 // 登录日志
//sysLogLoginService.savePortal(login.getAccountName(), Constant.FAIL, LoginOperationEnum.ACCOUNT_FAIL.getValue(), 1); accountLockService.handleLoginFailure(expertInfoResp.getZhuanJiaGuid());
throw new BusinessException(GlobalErrorCodeConstants.PASSWORD_ERROR.getCode(),GlobalErrorCodeConstants.PASSWORD_ERROR.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.PASSWORD_ERROR.getCode(),GlobalErrorCodeConstants.PASSWORD_ERROR.getMsg());
} }
...@@ -198,6 +206,7 @@ public class LoginServiceImpl implements LoginService { ...@@ -198,6 +206,7 @@ public class LoginServiceImpl implements LoginService {
UserUtils.setUserId(zhuanJiaUser); UserUtils.setUserId(zhuanJiaUser);
String token = JwtUtils.createToken(expertInfoResp.getZhuanJiaGuid(),uuidKey); String token = JwtUtils.createToken(expertInfoResp.getZhuanJiaGuid(),uuidKey);
//6.返回token //6.返回token
accountLockService.handleLoginSuccess(expertInfoResp.getZhuanJiaGuid());
return token; return token;
} }
...@@ -216,35 +225,31 @@ public class LoginServiceImpl implements LoginService { ...@@ -216,35 +225,31 @@ public class LoginServiceImpl implements LoginService {
if(expertInfoResp == null){ if(expertInfoResp == null){
throw new BusinessException(GlobalErrorCodeConstants.USER_NOT_EXISTS.getCode(),GlobalErrorCodeConstants.USER_NOT_EXISTS.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.USER_NOT_EXISTS.getCode(),GlobalErrorCodeConstants.USER_NOT_EXISTS.getMsg());
} }
//expertInfoResp = new ExpertInfoResp();
//expertInfoResp.setZhuanJiaGuid("1234");
boolean isAccountLocked = accountLockService.isAccountLocked(expertInfoResp.getZhuanJiaGuid());
if(isAccountLocked){
//如果账号锁定了,返回错误信息
Map<String, Object> errorData = new HashMap<>();
errorData.put("dueDate", accountLockService.getLockRemainingTime(expertInfoResp.getZhuanJiaGuid()));
throw new BusinessException(GlobalErrorCodeConstants.USER_LOCKED.getCode(),GlobalErrorCodeConstants.USER_LOCKED.getMsg(), "user_login", errorData);
}
//2.存在校验验证码 //2.存在校验验证码
if (!smsService.verifySmsCode(loginVO.getPhoneNumber(), loginVO.getPhoneCode())) { if (!smsService.verifySmsCode(loginVO.getPhoneNumber(), loginVO.getPhoneCode())) {
//登录日志 //登录日志
//sysLogLoginService.savePortal(login.getPhone(), Constant.FAIL, LoginOperationEnum.CAPTCHA_FAIL.getValue(), 1); accountLockService.handleLoginFailure(expertInfoResp.getZhuanJiaGuid());
throw new BusinessException(GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getCode(),GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getMsg()); throw new BusinessException(GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getCode(),GlobalErrorCodeConstants.CAPTCHA_EXPIRED.getMsg());
} }
//3.生成相应的uuid作为redis的key //3.生成相应的uuid作为redis的key
String uuidKey = UUID.randomUUID().toString(); String uuidKey = UUID.randomUUID().toString();
//expertInfoResp = new ExpertInfoResp();
//expertInfoResp.setZhuanJiaGuid("1234");
ZhuanJiaUser zhuanJiaUser = convert2ZhuanjiaUser( expertInfoResp ); ZhuanJiaUser zhuanJiaUser = convert2ZhuanjiaUser( expertInfoResp );
redisTemplate.opsForValue().set(TokenConstants.LOGIN_USER_KEY_ + expertInfoResp.getZhuanJiaGuid(), zhuanJiaUser, 48, TimeUnit.HOURS); redisTemplate.opsForValue().set(TokenConstants.LOGIN_USER_KEY_ + expertInfoResp.getZhuanJiaGuid(), zhuanJiaUser, 48, TimeUnit.HOURS);
UserUtils.setUserId(zhuanJiaUser); UserUtils.setUserId(zhuanJiaUser);
String token = JwtUtils.createToken(expertInfoResp.getZhuanJiaGuid(),uuidKey); String token = JwtUtils.createToken(expertInfoResp.getZhuanJiaGuid(),uuidKey);
accountLockService.handleLoginSuccess(expertInfoResp.getZhuanJiaGuid());
return token; return token;
} }
// /**
// * 缓存存入token
// * @param token token
// * @param uuidKey uuidKey
// */
// private void tokenSetRedis(String token, String uuidKey) {
// String redisTokenKey = TokenConstants.TOKEN_KEY_ + uuidKey;
// redisTemplate.opsForValue().set(redisTokenKey, token, Duration.ofHours(24));
// }
private ZhuanJiaUser convert2ZhuanjiaUser(ExpertInfoResp expertInfoResp){ private ZhuanJiaUser convert2ZhuanjiaUser(ExpertInfoResp expertInfoResp){
if( expertInfoResp == null ){ if( expertInfoResp == null ){
return null; return null;
...@@ -260,7 +265,24 @@ public class LoginServiceImpl implements LoginService { ...@@ -260,7 +265,24 @@ public class LoginServiceImpl implements LoginService {
zhuanJiaUser.setZhuanJiaName(expertInfoResp.getZhuanJiaName()); zhuanJiaUser.setZhuanJiaName(expertInfoResp.getZhuanJiaName());
zhuanJiaUser.setZhuanJiaShiXiangGuid(expertInfoResp.getZhuanJiaShiXiangGuid()); zhuanJiaUser.setZhuanJiaShiXiangGuid(expertInfoResp.getZhuanJiaShiXiangGuid());
zhuanJiaUser.setZhuanJiaZhuangTai(expertInfoResp.getZhuanJiaZhuangTai()); zhuanJiaUser.setZhuanJiaZhuangTai(expertInfoResp.getZhuanJiaZhuangTai());
zhuanJiaUser.setAdAccount(expertInfoResp.getAdAccount());
return zhuanJiaUser; return zhuanJiaUser;
} }
@Override
public ZhuanJiaUser getZhuanJiaUserByToken(String token){
try {
Map<String, String> userMap = JwtUtils.getTokenInfo(token);
String userId = userMap.get(TokenConstants.USER_ID);
ZhuanJiaUser zhuanjiaUser = (ZhuanJiaUser)redisTemplate.opsForValue().get(TokenConstants.LOGIN_USER_KEY_ + userId);
if(zhuanjiaUser!=null) {
UserUtils.setUserId(zhuanjiaUser);
}
return zhuanjiaUser;
} catch (Exception e) {
log.info("token解析异常 {}",e.getMessage(),e);
return null;
}
}
} }
...@@ -2,15 +2,19 @@ package com.cnooc.expert.auth.service.impl; ...@@ -2,15 +2,19 @@ package com.cnooc.expert.auth.service.impl;
import cn.hutool.core.lang.UUID; import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.cnooc.expert.common.cache.RedisKeys; import com.cnooc.expert.common.cache.RedisKeys;
import com.cnooc.expert.auth.service.SysCaptchaService; import com.cnooc.expert.auth.service.*;
import lombok.AllArgsConstructor; import com.cnooc.expert.controller.auth.model.response.SlideCaptchaVO;
import com.cnooc.expert.system.entity.pojo.CaptchaData;
import com.cnooc.expert.system.entity.pojo.SlideImageResult;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.cnooc.expert.system.entity.vo.SysCaptchaVO; import com.cnooc.expert.system.entity.vo.SysCaptchaVO;
import com.wf.captcha.SpecCaptcha; import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha; import com.wf.captcha.base.Captcha;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -19,6 +23,9 @@ public class SysCaptchaServiceImpl implements SysCaptchaService { ...@@ -19,6 +23,9 @@ public class SysCaptchaServiceImpl implements SysCaptchaService {
private static final int EXPIRE_MINUTES = 5; private static final int EXPIRE_MINUTES = 5;
@Autowired @Autowired
private SlideCaptchaService slideCaptchaService;
@Autowired
private StringRedisTemplate redisTemplate; private StringRedisTemplate redisTemplate;
@Override @Override
public SysCaptchaVO generate() { public SysCaptchaVO generate() {
...@@ -44,6 +51,43 @@ public class SysCaptchaServiceImpl implements SysCaptchaService { ...@@ -44,6 +51,43 @@ public class SysCaptchaServiceImpl implements SysCaptchaService {
} }
@Override @Override
public SlideCaptchaVO generateSlide(){
try {
// 1. 生成背景图和滑块图
SlideImageResult imageResult = slideCaptchaService.generateSlideImages();
// 2. 生成随机位置
// int x = ThreadLocalRandom.current().nextInt(50, 250);
// int y = ThreadLocalRandom.current().nextInt(10, 150);
int x = imageResult.getSliderX();
int y = imageResult.getSliderY();
// 3. 生成唯一标识
String token = UUID.randomUUID().toString();
// 4. 存储验证数据到Redis (5分钟过期)
CaptchaData captchaData = new CaptchaData(x, y, System.currentTimeMillis());
redisTemplate.opsForValue().set(
"captcha:" + token,
JSON.toJSONString(captchaData),
5, TimeUnit.MINUTES
);
// 5. 返回前端所需数据
SlideCaptchaVO vo = new SlideCaptchaVO();
vo.setToken(token);
vo.setBackgroundImage(imageResult.getBackgroundBase64());
vo.setSlideImage(imageResult.getSlideBase64());
vo.setStartY(y);
return vo;
} catch (Exception e) {
return null;
}
}
@Override
public boolean validate(String key, String code) { public boolean validate(String key, String code) {
// 如果关闭了验证码,则直接效验通过 // 如果关闭了验证码,则直接效验通过
if (!isCaptchaEnabled()) { if (!isCaptchaEnabled()) {
...@@ -62,6 +106,41 @@ public class SysCaptchaServiceImpl implements SysCaptchaService { ...@@ -62,6 +106,41 @@ public class SysCaptchaServiceImpl implements SysCaptchaService {
} }
@Override @Override
public boolean validateSlide(String token, Integer moveX) {
// 如果关闭了验证码,则直接效验通过
if (!isCaptchaEnabled()) {
return true;
}
if (StrUtil.isBlank(token)) {
return false;
}
// 1. 从Redis获取验证数据
String newkey = "captcha:" + token;
String dataStr = redisTemplate.opsForValue().get(newkey);
if (StringUtils.isEmpty(dataStr)) {
return false;
}
// 2. 解析数据
CaptchaData captchaData = JSON.parseObject(dataStr, CaptchaData.class);
// 3. 验证滑动距离 (允许±3像素的误差)
int expectedX = captchaData.getX();
int actualX = moveX;
if (Math.abs(actualX - expectedX) <= 3) {
// 验证成功,删除Redis中的key
redisTemplate.delete(newkey);
return true;
} else {
return false;
}
}
@Override
public boolean isCaptchaEnabled() { public boolean isCaptchaEnabled() {
return true; return true;
} }
......
...@@ -26,6 +26,7 @@ public interface GlobalErrorCodeConstants { ...@@ -26,6 +26,7 @@ public interface GlobalErrorCodeConstants {
ErrorCode PASSWORD_ERROR = new ErrorCode(2003, "密码错误"); ErrorCode PASSWORD_ERROR = new ErrorCode(2003, "密码错误");
ErrorCode USER_DISABLED = new ErrorCode(2004, "用户已被禁用"); ErrorCode USER_DISABLED = new ErrorCode(2004, "用户已被禁用");
ErrorCode PASSWORD_NOT_EXIST = new ErrorCode(2005, "密码不存在,请先重置密码"); ErrorCode PASSWORD_NOT_EXIST = new ErrorCode(2005, "密码不存在,请先重置密码");
ErrorCode USER_LOCKED = new ErrorCode(2006, "账号已锁定");
// ========== 参数校验错误 (3000-3999) ========== // ========== 参数校验错误 (3000-3999) ==========
ErrorCode PARAM_REQUIRED = new ErrorCode(3001, "必填字段不能为空"); ErrorCode PARAM_REQUIRED = new ErrorCode(3001, "必填字段不能为空");
......
...@@ -51,6 +51,10 @@ public class GlobalExceptionHandler { ...@@ -51,6 +51,10 @@ public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class) @ExceptionHandler(BusinessException.class)
public ApiResult<String> handleBusinessException(BusinessException e, HttpServletRequest request) { public ApiResult<String> handleBusinessException(BusinessException e, HttpServletRequest request) {
log.error("业务异常: {},请求URL: {}", e.getMessage(), request.getRequestURI(), e); log.error("业务异常: {},请求URL: {}", e.getMessage(), request.getRequestURI(), e);
if(e.getErrorCode()==GlobalErrorCodeConstants.USER_LOCKED.getCode()){
//这个需要特殊处理
return ApiResult.errorWithResult(GlobalErrorCodeConstants.USER_LOCKED.getCode(),GlobalErrorCodeConstants.USER_LOCKED.getMsg(),String.valueOf(e.getErrorData().get("dueDate")));
}
return ApiResult.error(e.getErrorCode(), e.getMessage()); return ApiResult.error(e.getErrorCode(), e.getMessage());
} }
......
...@@ -66,6 +66,13 @@ public class ApiResult<T> implements Serializable { ...@@ -66,6 +66,13 @@ public class ApiResult<T> implements Serializable {
return new ApiResult<>(httpCode, message, null); return new ApiResult<>(httpCode, message, null);
} }
/**
* 失败返回结果,并带有数据
*/
public static <T> ApiResult<T> errorWithResult(int httpCode, String message, T result) {
return new ApiResult<>(httpCode, message, result);
}
// Getters and Setters // Getters and Setters
} }
...@@ -11,6 +11,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; ...@@ -11,6 +11,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class TheWebMvcConfigurer implements WebMvcConfigurer { public class TheWebMvcConfigurer implements WebMvcConfigurer {
<<<<<<< HEAD
// @Autowired // @Autowired
// private LoginInterceptor loginInterceptor; // private LoginInterceptor loginInterceptor;
// //
...@@ -36,5 +37,32 @@ public class TheWebMvcConfigurer implements WebMvcConfigurer { ...@@ -36,5 +37,32 @@ public class TheWebMvcConfigurer implements WebMvcConfigurer {
// public WebAuthInterceptor webAuthInterceptor() { // public WebAuthInterceptor webAuthInterceptor() {
// return new WebAuthInterceptor(); // return new WebAuthInterceptor();
// } // }
=======
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册登录拦截器,并设置拦截路径和排除路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns( // 排除一些路径
"/sys/**",
"/verify/**",
"/person/**",
"/text/**",
"/error/**",
"/error"
);
registry.addInterceptor(webAuthInterceptor());
}
@Bean
public WebAuthInterceptor webAuthInterceptor() {
return new WebAuthInterceptor();
}
>>>>>>> 3bc7fe8847766d39bf419c764c9495af3590dfa2
} }
...@@ -7,6 +7,8 @@ import com.cnooc.expert.common.response.ApiResult; ...@@ -7,6 +7,8 @@ import com.cnooc.expert.common.response.ApiResult;
import com.cnooc.expert.auth.service.LoginService; import com.cnooc.expert.auth.service.LoginService;
import com.cnooc.expert.common.utils.JwtUtils; import com.cnooc.expert.common.utils.JwtUtils;
import com.cnooc.expert.common.utils.KafkaProducerUtil; import com.cnooc.expert.common.utils.KafkaProducerUtil;
import com.cnooc.expert.controller.auth.model.request.VerifyRequest;
import com.cnooc.expert.controller.auth.model.response.SlideCaptchaVO;
import com.cnooc.expert.external.expert.model.response.ExpertInfoResp; import com.cnooc.expert.external.expert.model.response.ExpertInfoResp;
import com.cnooc.expert.system.entity.pojo.ZhuanJiaUser; import com.cnooc.expert.system.entity.pojo.ZhuanJiaUser;
import com.cnooc.expert.system.entity.vo.LoginVO; import com.cnooc.expert.system.entity.vo.LoginVO;
...@@ -146,6 +148,32 @@ public class LoginController { ...@@ -146,6 +148,32 @@ public class LoginController {
return ApiResult.successWithResult(response); return ApiResult.successWithResult(response);
} }
@GetMapping("/slideCaptcha")
public ApiResult<Map<String, String>> slideCaptcha() {
SlideCaptchaVO captchaVO = sysCaptchaService.generateSlide();
// 5. 构建响应
Map<String, String> response = new HashMap<>();
response.put("backgroundImage", captchaVO.getBackgroundImage());
response.put("slideImage", captchaVO.getSlideImage());
response.put("token", captchaVO.getToken());
response.put("startY",String.valueOf(captchaVO.getStartY()));
return ApiResult.successWithResult(response);
}
@PostMapping("/verifySlideCaptcha")
public ApiResult<Map<String, Object>> verifySlideCaptcha(
@RequestBody VerifyRequest request) {
Map<String, Object> result = new HashMap<>();
boolean flag = sysCaptchaService.validateSlide(request.getToken(),request.getMoveX());
String msg = "验证成功";
if (!flag) {
msg = "验证失败";
}
result.put("success", flag);
result.put("message", msg);
return ApiResult.successWithResult(result);
}
/** /**
* 验证图片验证码 * 验证图片验证码
*/ */
......
package com.cnooc.expert.controller.auth.model.request;
import lombok.Data;
@Data
public class VerifyRequest {
private String token;
private Integer moveX; // 滑块移动的X距离
}
package com.cnooc.expert.controller.auth.model.response;
import lombok.Data;
@Data
public class SlideCaptchaVO {
private String token;
private String backgroundImage; // base64格式
private String slideImage; // base64格式
private Integer startY; // 滑块初始Y坐标
}
package com.cnooc.expert.controller.auth.model.response;
import lombok.Data;
@Data
public class VerifyResult {
private Boolean success;
private String message;
public VerifyResult(Boolean success, String message) {
this.success = success;
this.message = message;
}
}
\ No newline at end of file
package com.cnooc.expert.external.expert.api; package com.cnooc.expert.external.expert.api;
<<<<<<< HEAD
import com.cnooc.expert.external.expert.model.request.ExpertInfoGetReq; import com.cnooc.expert.external.expert.model.request.ExpertInfoGetReq;
import com.cnooc.expert.external.expert.model.response.ExpertInfoGetTestResp; import com.cnooc.expert.external.expert.model.response.ExpertInfoGetTestResp;
=======
>>>>>>> 3bc7fe8847766d39bf419c764c9495af3590dfa2
import com.cnooc.expert.external.expert.model.request.ExpertInfoAppReq; import com.cnooc.expert.external.expert.model.request.ExpertInfoAppReq;
import com.cnooc.expert.external.expert.model.request.ExpertInfoGetReq;
import com.cnooc.expert.external.expert.model.request.ExpertInfoReq; import com.cnooc.expert.external.expert.model.request.ExpertInfoReq;
import com.cnooc.expert.external.expert.model.response.ExpertInfoAppResp; import com.cnooc.expert.external.expert.model.response.ExpertInfoAppResp;
import com.cnooc.expert.external.expert.model.response.ExpertInfoGetTestResp;
import com.cnooc.expert.external.expert.model.response.ExpertInfoResp; import com.cnooc.expert.external.expert.model.response.ExpertInfoResp;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.Body; import retrofit2.http.Body;
...@@ -29,4 +34,8 @@ public interface LoginServiceApi { ...@@ -29,4 +34,8 @@ public interface LoginServiceApi {
@POST("/zjfw/zggrxxgl/updateZhuanJiaInfoApp") @POST("/zjfw/zggrxxgl/updateZhuanJiaInfoApp")
Call<ExpertInfoAppResp> updateZhuanJiaInfoApp(@HeaderMap Map<String, Object> headers, @Body ExpertInfoAppReq expertInfoAppReq); Call<ExpertInfoAppResp> updateZhuanJiaInfoApp(@HeaderMap Map<String, Object> headers, @Body ExpertInfoAppReq expertInfoAppReq);
<<<<<<< HEAD
=======
>>>>>>> 3bc7fe8847766d39bf419c764c9495af3590dfa2
} }
package com.cnooc.expert.system.entity.pojo;
import lombok.Data;
@Data
public class CaptchaData {
private Integer x;
private Integer y;
private Long timestamp;
public CaptchaData(Integer x, Integer y, Long timestamp) {
this.x = x;
this.y = y;
this.timestamp = timestamp;
}
}
\ No newline at end of file
package com.cnooc.expert.system.entity.pojo;
import lombok.Data;
@Data
public class SlideImageResult {
private String backgroundBase64;
private String slideBase64;
private Integer sliderX;
private Integer sliderY;
}
...@@ -35,6 +35,8 @@ public class ZhuanJiaUser { ...@@ -35,6 +35,8 @@ public class ZhuanJiaUser {
private String suoShuBuMeng;//所在部门 private String suoShuBuMeng;//所在部门
private String adAccount;
} }
package com.cnooc.expert.system.operatelog.aspect; package com.cnooc.expert.system.operatelog.aspect;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.cnooc.expert.common.utils.KafkaProducerUtil;
import com.cnooc.expert.common.utils.UserUtils;
import com.cnooc.expert.system.entity.pojo.ZhuanJiaUser;
import com.cnooc.expert.system.operatelog.dto.LogBody; import com.cnooc.expert.system.operatelog.dto.LogBody;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
...@@ -8,6 +11,8 @@ import org.aspectj.lang.annotation.AfterReturning; ...@@ -8,6 +11,8 @@ import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
...@@ -25,6 +30,14 @@ public class LogAspectj { ...@@ -25,6 +30,14 @@ public class LogAspectj {
private static final String EXCEPTION_CODE = "500"; private static final String EXCEPTION_CODE = "500";
private String host=""; private String host="";
@Value("${app.info.appId}")
private String appId;
@Value("${app.info.appName}")
private String appName;
@Autowired
private KafkaProducerUtil kafkaProducerUtil;
@Pointcut("execution(* com.cnooc.expert.controller.auth.*.*(..)) ||"+ @Pointcut("execution(* com.cnooc.expert.controller.auth.*.*(..)) ||"+
"execution(* com.cnooc.expert.controller.expert.*.*(..)) || " + "execution(* com.cnooc.expert.controller.expert.*.*(..)) || " +
"execution(* com.cnooc.expert.controller.portal.*.*(..)) || " + "execution(* com.cnooc.expert.controller.portal.*.*(..)) || " +
...@@ -90,9 +103,11 @@ public class LogAspectj { ...@@ -90,9 +103,11 @@ public class LogAspectj {
HttpServletRequest request = sra.getRequest(); HttpServletRequest request = sra.getRequest();
return request; return request;
} }
private String getCurrentUser() { private ZhuanJiaUser getCurrentUser() {
//这里需要添加代码 //这里需要添加代码
return ""; ZhuanJiaUser zhuanJiaUser = UserUtils.getUserId();
System.out.println(zhuanJiaUser);
return zhuanJiaUser;
} }
/** /**
* mysql * mysql
...@@ -105,7 +120,13 @@ public class LogAspectj { ...@@ -105,7 +120,13 @@ public class LogAspectj {
System.out.println("进入sendLog方法========"); System.out.println("进入sendLog方法========");
HttpServletRequest httpServletRequest = getHttpServletRequest(); HttpServletRequest httpServletRequest = getHttpServletRequest();
String method = httpServletRequest.getMethod(); String method = httpServletRequest.getMethod();
String account = getCurrentUser(); ZhuanJiaUser zhuanJiaUser = getCurrentUser();
if( zhuanJiaUser == null ){
log.info("用户为空,不记录日志");
return;
}
LogBody logBody = new LogBody(); LogBody logBody = new LogBody();
Enumeration<String> headers = httpServletRequest.getHeaderNames(); Enumeration<String> headers = httpServletRequest.getHeaderNames();
...@@ -125,9 +146,28 @@ public class LogAspectj { ...@@ -125,9 +146,28 @@ public class LogAspectj {
}else { }else {
logBody.setResponseContent(JSON.toJSONString(returnValue)); logBody.setResponseContent(JSON.toJSONString(returnValue));
} }
logBody.setAppId(appId);
logBody.setAppName(appName);
// 用户id
logBody.setUserId(zhuanJiaUser.getZhuanJiaGuid());
// 用户account
logBody.setAccount(zhuanJiaUser.getAdAccount());
// 部门id
// logBody.setDomainId();
// 部门名称
logBody.setDomainName(zhuanJiaUser.getSuoShuBuMeng());
// logBody.setRole();
logBody.setRequestPath(httpServletRequest.getRequestURI());
String uuidKey = UUID.randomUUID().toString();
logBody.setOperateRecordId(uuidKey);
System.out.println("logBody对象信息如下========="); System.out.println("logBody对象信息如下=========");
System.out.println(logBody); System.out.println(logBody);
//将数据发送到kafka,这里需要加代码逻辑 //将数据发送到kafka,这里需要加代码逻辑
// Kafka默认异步发送(Topic、key需要提供,如果有key的话这样写:kafkaProducerUtil.sendMessage("test-topic(topic值)", "key的值",logBody); )
kafkaProducerUtil.sendMessage("test-topic", logBody);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
......
...@@ -38,10 +38,10 @@ spring: ...@@ -38,10 +38,10 @@ spring:
linger-ms: 1 linger-ms: 1
buffer-memory: 33554432 buffer-memory: 33554432
app:
info:
appId: 10000
appName: 海油小程序
server: server:
port: 9090 port: 9090
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