123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- package com.backendsys.modules.system.service.impl;
- import cn.hutool.core.convert.Convert;
- import cn.hutool.core.date.DateUnit;
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.core.util.NumberUtil;
- import cn.hutool.json.JSONUtil;
- import com.backendsys.exception.CustException;
- import com.backendsys.modules.common.config.redis.utils.RedisUtil;
- import com.backendsys.modules.common.config.security.entity.SecurityUserInfo;
- import com.backendsys.modules.common.config.security.utils.*;
- import com.backendsys.modules.system.dao.SysMobileAreaDao;
- import com.backendsys.modules.system.dao.SysUserDao;
- import com.backendsys.modules.system.dao.SysUserInfoDao;
- import com.backendsys.modules.system.entity.*;
- import com.backendsys.modules.system.service.SysAuthService;
- import com.backendsys.modules.system.service.SysCommonService;
- import com.backendsys.modules.system.service.SysUserIntegralService;
- import com.backendsys.modules.system.service.SysUserService;
- import com.backendsys.utils.response.ResultEnum;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.google.code.kaptcha.Producer;
- import jakarta.servlet.ServletOutputStream;
- import jakarta.servlet.http.HttpServletResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import javax.imageio.ImageIO;
- import java.awt.image.BufferedImage;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.util.*;
- import java.util.concurrent.TimeUnit;
- @Service
- public class SysAuthServiceImpl implements SysAuthService {
- @Autowired
- private JwtUtil jwtUtil;
- @Autowired
- private RedisUtil redisUtil;
- @Autowired
- private TokenUtil tokenUtil;
- @Autowired
- private HttpRequestUtil httpRequestUtil;
- @Autowired
- private CountUtilV2 countUtilV2;
- @Autowired
- private CaptchaUtil captchaUtil;
- @Autowired
- private Producer captchaProducer;
- @Autowired
- private SysUserDao sysUserDao;
- @Autowired
- private SysUserInfoDao sysUserInfoDao;
- @Autowired
- private SysUserService sysUserService;
- @Autowired
- private SysMobileAreaDao sysMobileAreaDao;
- @Autowired
- private SysUserIntegralService sysUserIntegralService;
- @Autowired
- private SysCommonService sysCommonService;
- @Value("${tencent.sms.debug}")
- private String SMS_DEBUG;
- @Value("${CAPTCHA_DURATION}")
- private Integer CAPTCHA_DURATION;
- @Value("${REDIS_LOGIN_TOKEN_PREFIX}")
- private String REDIS_LOGIN_TOKEN_PREFIX;
- @Value("${spring.application.name}")
- private String APPLICATION_NAME;
- private String redisKeyOfLogin = APPLICATION_NAME + "-sms-login";
- private String redisKeyOfRegister = APPLICATION_NAME + "-sms-register";
- private String redisKeyOfLoginFail = APPLICATION_NAME + "-login-error";
- private String redisKeyOfRegisterFail = APPLICATION_NAME + "-register-error";
-
-
- @Override
- public void renderCaptcha(HttpServletResponse response) throws IOException {
- byte[] captchaChallengeAsJpeg;
- ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
- try {
- String createText = captchaProducer.createText();
- // 获得当前 (UA + IP) 生成的 Key
- String captchaRedisKey = httpRequestUtil.getKaptchaKey();
- // 保存 验证码字符串 到 redis 中
- redisUtil.setCacheObject(captchaRedisKey, createText, this.CAPTCHA_DURATION, TimeUnit.MILLISECONDS);
- // 返回 BufferedImage 对象并转为 byte 写入到 byte 数组中
- BufferedImage challenge = captchaProducer.createImage(createText);
- ImageIO.write(challenge, "jpg", jpegOutputStream);
- } catch (Exception e) {
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
- }
- // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
- captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
- response.setHeader("Cache-Control", "no-store");
- response.setHeader("Pragma", "no-cache");
- response.setDateHeader("Expires", 0);
- response.setContentType("image/jpeg");
- ServletOutputStream responseOutputStream = response.getOutputStream();
- responseOutputStream.write(captchaChallengeAsJpeg);
- responseOutputStream.flush();
- responseOutputStream.close();
- }
- @Override
- public List<SysMobileArea> getMobileAreaList(SysMobileArea sysMobileArea) {
- return sysMobileAreaDao.selectMobileAreaList(sysMobileArea);
- }
- // [方法] 登录失败 (errMsg: 错误提示文本, username: 用户名, intercept: 是否拦截)
- private void loginFail(String errMsg, String username, Boolean isIntercept) {
- // 删除图形验证码
- redisUtil.delete(httpRequestUtil.getKaptchaKey());
- // 添加登录错误的冻结标记
- if (isIntercept) countUtilV2.setErrorCount(redisKeyOfLoginFail, username);
- throw new CustException(errMsg, ResultEnum.INVALID_CREDENTIALS.getCode());
- }
- // [方法] 登录成功
- private SysUserInfo loginSuccess(Long user_id, Integer is_remember) {
- // [查询] 登录的用户信息
- SysUserInfo sysUserInfo = sysUserService.selectUserInfo(user_id);
- // 删除图形验证码缓存
- redisUtil.delete(httpRequestUtil.getKaptchaKey());
- // 删除旧的登录缓存
- tokenUtil.deleteRedisLoginToken(sysUserInfo.getLast_login_uuid());
- // 判断用户是否审核
- Integer audit_status = sysUserInfo.getAudit_status();
- if (audit_status != null && audit_status.equals(1)) throw new CustException("用户审核中");
- if (audit_status != null && audit_status.equals(-1)) throw new CustException("用户审核未通过,请与客服联系");
- // 判断用户是否启用
- Integer status = sysUserInfo.getStatus();
- if (status != null && status.equals(-1)) throw new CustException("该用户已被禁用,请与客服联系");
- // 判断用户是否已删除
- Integer del_flag = sysUserInfo.getDel_flag();
- if (del_flag != null && del_flag.equals(1)) throw new CustException("用户处于预删除状态,请与客服联系");
- // 设置 最后一次的登录信息 (uuid, ip, 登录时间)
- String uuid = String.valueOf(UUID.randomUUID());
- sysUserInfo.setLast_login_uuid(uuid);
- sysUserInfo.setLast_login_ip(httpRequestUtil.getIpAddr());
- sysUserInfo.setLast_login_time(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
- sysUserInfoDao.updateById(sysUserInfo);
- // [系统配置] 系统用户默认登录过期时间(小时)
- Integer SYSTEM_USER_LOGIN_DURATION_DEFAULT = Convert.toInt(sysCommonService.getCommonByTag("SYSTEM_USER_LOGIN_DURATION_DEFAULT"));
- // 将小时转换为毫秒
- Long DEFAULT_MILLISECONDS = SYSTEM_USER_LOGIN_DURATION_DEFAULT * DateUnit.HOUR.getMillis();
- // 7天 (转毫秒)
- Long SEVEN_DAY_MILLISECONDS = 7L * 24 * 60 * 60 * 1000;
- Long token_duration_milliseconds = (is_remember != null && is_remember.equals(1)) ? SEVEN_DAY_MILLISECONDS : DEFAULT_MILLISECONDS;
- Integer token_duration_hours = Convert.toInt(token_duration_milliseconds / 3600000L);
- Date token_expiration = new Date((new Date()).getTime() + token_duration_milliseconds);
- sysUserInfo.setToken_expiration(DateUtil.format(token_expiration, "yyyy-MM-dd HH:mm:ss"));
- // 生成 Token
- SecurityUserInfo securityUserInfo = JSONUtil.toBean(JSONUtil.parseObj(sysUserInfo), SecurityUserInfo.class);
- String token = jwtUtil.createSystemJwtToken(securityUserInfo);
- String token_redis_key = REDIS_LOGIN_TOKEN_PREFIX + uuid;
- sysUserInfo.setToken(token);
- // 生成 PerMissionIds
- List<String> permission_ids_list = sysUserInfo.getPermission_ids();
- // [Redis] 将 Token 与 Permission 存入缓存
- TokenCatch tokenCatch = new TokenCatch(token, permission_ids_list);
- redisUtil.setCacheObject(token_redis_key, JSONUtil.toJsonStr(tokenCatch), token_duration_hours, TimeUnit.HOURS);
- return sysUserInfo;
- }
- /**
- * 登录 (用户名)
- */
- @Override
- @Transactional
- public SysUserInfo login(SysAuth sysAuth) {
- String username = sysAuth.getUsername();
- String password = sysAuth.getPassword();
- String captcha = sysAuth.getCaptcha();
- // 判断是否处于登录错误的冻结状态 (2分钟内错误5次,则出现冻结提示)
- countUtilV2.checkErrorStatus(redisKeyOfLoginFail, username);
- // 判断图形验证码是否正确
- if (!captchaUtil.isCaptchaValid(captcha, httpRequestUtil.getKaptchaKey())) {
- loginFail("验证码错误", username, false);
- return null;
- }
- // [Method] 判断 用户 是否存在 && 密码是否正确
- SysUser sysUser = sysUserDao.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
- if (sysUser == null) {
- // [登录失败] 用户不存在
- loginFail("用户名或密码错误", username, true);
- return null;
- } else {
- // [登录失败] 密码不正确
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- if (!encoder.matches(password, sysUser.getPassword())) {
- loginFail("用户名或密码错误", username, true);
- }
- // [登录成功]
- return loginSuccess(sysUser.getId(), sysAuth.getIs_remember());
- }
- }
- /**
- * 登录 (手机号码)
- */
- @Override
- @Transactional
- public SysUserInfo loginWithPhone(SysAuthPhone sysAuthPhone) {
- String phone = sysAuthPhone.getPhone();
- Integer phoneAreaCode = sysAuthPhone.getPhone_area_code();
- Integer phoneValidCode = sysAuthPhone.getPhone_valid_code();
- // 判断是否处于登录错误的冻结状态 (2分钟内错误5次,则出现冻结提示)
- countUtilV2.checkErrorStatus(redisKeyOfLoginFail, phone);
- // 判断短信验证码是否正确
- String redisKey = redisKeyOfLogin + "-" + phone;
- Integer smsCode = redisUtil.getCacheObject(redisKey);
- // 判断是否发送验证码
- if ("false".equals(SMS_DEBUG) && smsCode == null) throw new CustException("请先发送验证码");
- // 判断短信验证码是否错误
- if ("false".equals(SMS_DEBUG) && !smsCode.equals(phoneValidCode)) loginFail("短信验证码错误", phone, true);
- // 判断手机号是否存在
- LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
- queryWrapper.eq(SysUser::getPhone, phone).eq(SysUser::getPhone_area_code, phoneAreaCode);
- SysUser sysUser = sysUserDao.selectOne(queryWrapper);
- if (sysUser == null) {
- // [登录失败] 用户不存在 (并不会销毁短信验证码)
- loginFail("手机号码不存在", phone, true);
- return null;
- } else {
- // 登录成功,销毁短信验证码
- redisUtil.delete(redisKey);
- // [登录成功]
- return loginSuccess(sysUser.getId(), sysAuthPhone.getIs_remember());
- }
- }
- @Override
- @Transactional
- public Map<String, Object> register(SysUserDTO sysUserDTO) {
- // 判断是否允许注册
- // [系统配置] 是否允许系统用户注册
- Boolean SYSTEM_USER_ALLOW_REGISTER = Convert.toBool(sysCommonService.getCommonByTag("SYSTEM_USER_ALLOW_REGISTER"));
- if (!SYSTEM_USER_ALLOW_REGISTER) throw new CustException("系统已禁止注册");
- // -- 参数校验 --------------------------------------------------------------
- String username = sysUserDTO.getUsername();
- String password = sysUserDTO.getPassword();
- String captcha = sysUserDTO.getCaptcha();
- String phone = sysUserDTO.getPhone();
- Integer phoneAreaCode = sysUserDTO.getPhone_area_code();
- Integer phoneValidCode = sysUserDTO.getPhone_valid_code();
- // 判断是否处于登录错误的冻结状态 (2分钟内错误5次,则出现冻结提示)
- countUtilV2.checkErrorStatus(redisKeyOfRegisterFail, username);
- countUtilV2.checkErrorStatus(redisKeyOfRegisterFail, phone);
- // 判断图形验证码是否正确
- if (!captchaUtil.isCaptchaValid(captcha, httpRequestUtil.getKaptchaKey())) {
- loginFail("验证码错误", username, false);
- return null;
- }
- // [查询] 判断用户名是否存在
- SysUser sysUser1 = sysUserDao.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
- if (sysUser1 != null) throw new CustException("用户名 (" + username + ") 已被注册");
-
- // 判断短信验证码是否正确
- String redisKey = redisKeyOfLogin + "-" + phone;
- Integer smsCode = redisUtil.getCacheObject(redisKey);
- // 判断是否发送验证码
- if ("false".equals(SMS_DEBUG) && smsCode == null) throw new CustException("请先发送验证码");
- // 判断短信验证码是否错误
- if ("false".equals(SMS_DEBUG) && !smsCode.equals(phoneValidCode)) loginFail("短信验证码错误", phone, true);
- // [查询] 判断手机号是否存在
- LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
- queryWrapper.eq(SysUser::getPhone, phone).eq(SysUser::getPhone_area_code, phoneAreaCode);
- SysUser sysUser2 = sysUserDao.selectOne(queryWrapper);
- if (sysUser2 != null) throw new CustException("手机号码 (+" + phoneAreaCode + " " + phone + ") 已被注册");
-
- // -- 通过校验 --------------------------------------------------------------
- // 密码二次加密
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- String encodedPassword = encoder.encode(password);
- sysUserDTO.setPassword(encodedPassword);
- // 注册
- SysUserDTO registerEntity = new SysUserDTO();
- registerEntity.setUsername(sysUserDTO.getUsername());
- registerEntity.setPhone(sysUserDTO.getPhone());
- registerEntity.setPhone_area_code(sysUserDTO.getPhone_area_code());
- registerEntity.setPassword(sysUserDTO.getPassword());
- // 注册时,默认使用 游客 2L 权限
- registerEntity.setRole_id(Arrays.asList(2L));
- registerEntity.setInvite_code(sysUserDTO.getInvite_code());
- // 注册时,状态为禁用
- registerEntity.setStatus(-1);
- // 创建用户
- sysUserDao.insertUser(registerEntity);
- // 初始化用户积分
- sysUserIntegralService.init(registerEntity.getId());
- return Map.of("user_id", registerEntity.getId());
- }
- /**
- * 忘记密码/重置密码
- */
- @Override
- public Map<String, Object> forgotPassword(SysUserDTO sysUserDTO) {
- String phone = sysUserDTO.getPhone();
- Integer phoneAreaCode = sysUserDTO.getPhone_area_code();
- Integer phoneValidCode = sysUserDTO.getPhone_valid_code();
- // 判断短信验证码是否正确
- String redisKey = "sms-forgotPassword-" + sysUserDTO.getPhone();
- Integer smsCode = redisUtil.getCacheObject(redisKey);
- if ("false".equals(SMS_DEBUG) && (smsCode == null || !smsCode.equals(phoneValidCode))) {
- throw new CustException("短信验证码错误");
- }
- // [查询] 判断手机号是否存在
- LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
- queryWrapper.eq(SysUser::getPhone, phone).eq(SysUser::getPhone_area_code, phoneAreaCode);
- SysUser sysUser = sysUserDao.selectOne(queryWrapper);
- if (sysUser == null) throw new CustException("手机号码不存在");
- // 密码二次加密
- String password = sysUserDTO.getPassword();
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- String encodedPassword = encoder.encode(password);
- sysUser.setPassword(encodedPassword);
- // 编辑密码
- sysUserDao.updateById(sysUser);
- // 更改成功,销毁短信验证码
- redisUtil.delete(redisKey);
- return Map.of("user_id", sysUser.getId());
- }
- /**
- * 退出登录
- */
- public Map<String, Object> logout() {
- Long user_id = httpRequestUtil.getUserId();
- if (user_id != null) {
- tokenUtil.deleteRedisLoginToken(null);
- }
- return Map.of("user_id", user_id);
- }
- }
|