package com.example.xiaoshiweixinback.service; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.CircleCaptcha; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.IdUtil; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.xiaoshiweixinback.business.common.log.LogHelper; import com.example.xiaoshiweixinback.business.exception.BusinessException; import com.example.xiaoshiweixinback.business.exception.ExceptionEnum; import com.example.xiaoshiweixinback.business.jwt.JwtTokenUtil; import com.example.xiaoshiweixinback.business.redis.CacheTTLEnum; import com.example.xiaoshiweixinback.business.redis.RedisService; import com.example.xiaoshiweixinback.business.utils.*; import com.example.xiaoshiweixinback.domain.Person; import com.example.xiaoshiweixinback.entity.dto.person.*; import com.example.xiaoshiweixinback.entity.vo.PersonnelVO; import com.example.xiaoshiweixinback.entity.vo.person.Jscode2SessionWo; import com.example.xiaoshiweixinback.entity.vo.person.LoginByWxVO; import com.example.xiaoshiweixinback.entity.vo.person.LoginVO; import com.example.xiaoshiweixinback.entity.vo.person.PersonVO; import com.example.xiaoshiweixinback.mapper.PersonMapper; import com.example.xiaoshiweixinback.okhttp.ResponseManager; import com.example.xiaoshiweixinback.service.common.SmsService; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.util.*; @Service public class LoginService { @Value("${WeChat.appId}") private String appId; @Value("${WeChat.appSecret}") private String appSecret; @Autowired private CacheUtil cacheUtil; @Autowired private LoginUtils loginUtils; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private RedisService redisService; @Autowired private SmsService smsService; @Autowired private PersonMapper personMapper; /** * 手机号/账号登录 * @param dto * @return */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public LoginVO loginByPhone(LoginDTO dto) throws Exception { LogHelper.log("登录开始"); //获取缓存中验证码 Object codeObj = redisService.get(AppCacheKeyUtil.getLoginMessageCode(dto.getPhoneNum())); if (ToolUtil.isEmpty(codeObj)) { throw new BusinessException(ExceptionEnum.CODE_WRONG); } //校验验证码 if (ToolUtil.isEmpty(dto.getPhoneCode())) { throw new BusinessException(ExceptionEnum.THE_CODE_IS_NOT_NULL); } if (!ToolUtil.equals(codeObj.toString(), dto.getPhoneCode())) { throw new BusinessException(ExceptionEnum.CODE_WRONG); } //校验验证码成功后使其失效 redisService.delete(AppCacheKeyUtil.getLoginMessageCode(dto.getPhoneNum())); //查询用户 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (ToolUtil.isNotEmpty(dto.getPhoneNum())) { queryWrapper.eq(Person::getPhoneNum, dto.getPhoneNum()); } Person person = personMapper.selectOne(queryWrapper); LoginVO loginVO = new LoginVO(); if (ToolUtil.isEmpty(person)) { person = new Person(); person.setPhoneNum(dto.getPhoneNum()); String uid = IdUtil.simpleUUID(); person.setUuid(uid); PersonnelVO personnelVO = cacheUtil.getLoginUser(loginUtils.getId()); if (ToolUtil.isNotEmpty(personnelVO)) { Integer personId = personnelVO.getId(); person.setCreateId(personId.toString()); } person.setCreateTime(new Date()); personMapper.insert(person); loginVO.setId(person.getId()); loginVO.setPhone(person.getPhoneNum()); loginVO.setIfFirst(true); } else { BeanUtil.copy(person, loginVO); } loginVO.setToken(jwtTokenUtil.createToken()); redisService.set(AppCacheKeyUtil.getUserIdToken(loginVO.getId()),loginVO.getToken()); redisService.set(AppCacheKeyUtil.getTokenUserInfo(loginVO.getToken()), loginVO); LogHelper.log("登陆结束"); return loginVO; } /** * 微信小程序登录 * * @param wxDTO * @throws Exception * @title: loginByWeChat * @author: zero * @date: 2024/04/01 * @return: LoginByWxVO */ public LoginByWxVO loginByWeChat(LoginByWxDTO wxDTO) throws Exception { String code = wxDTO.getCode(); String encryptedData = wxDTO.getEncryptedData(); String iv = wxDTO.getIv(); //返回数据 LoginByWxVO wxVO = new LoginByWxVO(); // //1.根据code 获取微信小程序的openid和session_key JSONObject result = this.getSessionKeyAndOpenId(code); Jscode2SessionWo jscode2SessionWo = ResponseManager.parseObject(result.toString(), Jscode2SessionWo.class); if (ToolUtil.isNotEmpty(jscode2SessionWo)) { // 2. 解密用户数据 String decryptedData = decrypt(encryptedData, jscode2SessionWo.getSession_key(), iv); // 3. 获取用户手机号(需要用户授权) String phoneNumber = ""; JSONObject userData = JSONObject.parseObject(decryptedData); if (ToolUtil.isNotEmpty(userData) && userData.containsKey("purePhoneNumber")) { phoneNumber = userData.getString("purePhoneNumber"); } // 4.查询数据表 Person person = personMapper.selectOne(new LambdaQueryWrapper() .eq(Person::getOpenId, jscode2SessionWo.getOpenid())); if (ToolUtil.isNotEmpty(person)) { BeanUtil.copy(person, wxVO); } else { //添加用户表中 person = personMapper.selectOne(new LambdaQueryWrapper() .eq(Person::getPhoneNum, phoneNumber)); if (ToolUtil.isNotEmpty(person)) { person.setOpenId(jscode2SessionWo.getOpenid()); person.updateById(); } else { person = new Person(); person.setPhoneNum(phoneNumber); person.setOpenId(jscode2SessionWo.getOpenid()); String uid = IdUtil.simpleUUID(); person.setUuid(uid); PersonnelVO personnelVO = cacheUtil.getLoginUser(loginUtils.getId()); if (ToolUtil.isNotEmpty(personnelVO)) { Integer personId = personnelVO.getId(); person.setCreateId(personId.toString()); } person.setCreateTime(new Date()); person.insert(); wxVO.setId(person.getId()); wxVO.setIfFirst(true); } wxVO.setPhoneNum(phoneNumber); } wxVO.setToken(jwtTokenUtil.createToken()); wxVO.setOpenId(jscode2SessionWo.getOpenid()); redisService.set(AppCacheKeyUtil.getUserIdToken(wxVO.getId()), wxVO); redisService.set(AppCacheKeyUtil.getTokenUserInfo(wxVO.getToken()), wxVO); } return wxVO; } /** * 发送验证码 * * @param vo * @return */ public boolean sendCode(SendCodeDTO vo) { boolean flag = false; if (!RegexUtil.isPhoneLegal(vo.getPhoneNum())) { throw new BusinessException(ExceptionEnum.PHONE_FORMAT_ERROR); } Object checkCode = redisService.get(AppCacheKeyUtil.getCheckCode(vo.getPhoneNum())); if (ToolUtil.isEmpty(checkCode)) { throw new BusinessException(ExceptionEnum.VERIFY_CODE); } if (ToolUtil.isNotEmpty(checkCode.toString()) && ToolUtil.equals(checkCode.toString(), vo.getCheckCode())) { //删除校验码 redisService.delete(AppCacheKeyUtil.getCheckCode(vo.getPhoneNum())); //生成验证码 String random = RandomUtil.getSixRandom(); //手机号和验证码放进缓存 设置过期时间5min redisService.set(AppCacheKeyUtil.getLoginMessageCode(vo.getPhoneNum()), random); redisService.expire(AppCacheKeyUtil.getLoginMessageCode(vo.getPhoneNum()), CacheTTLEnum.FIVE_MINUTE); //发送短信 smsService.sendMessage(vo.getPhoneNum(), random); flag = true; } return flag; } /** * 生成验证码 * * @return 1.生成验证码的base64转码 2.生成的UUID 与Redis里面的验证码KEY值一致 * @date: 20240401 */ public Map verifyCode(PersonPhoneDTO vo) { if (!RegexUtil.isPhoneLegal(vo.getPhoneNum())) { throw new BusinessException(ExceptionEnum.PHONE_FORMAT_ERROR); } //1.定义图形验证码的长、宽、验证码字符数、干扰元素个数 CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20); //2.生成UUID String uuid = cn.hutool.core.lang.UUID.fastUUID().toString(); //3.创建返回参数 并用base64将图片转码 与UUID一起填充 Map result = new HashMap<>(); result.put("captcha", ImgUtil.toBase64DataUri(captcha.getImage(), "png")); result.put("uuid", uuid); //4.将验证码存放到Redis里面 redisService.set(AppCacheKeyUtil.getCheckCode(vo.getPhoneNum()), captcha.getCode()); return result; } /** * 查询个人信息 * * @param vo * @return */ public PersonVO selectPerson(PersonIdDTO vo) { String token = LoginUtils.getToken(); if (ToolUtil.isEmpty(token)) { throw new BusinessException(ExceptionEnum.THE_LOG_OUT); } Object obj = redisService.get(token); if (ToolUtil.isEmpty(obj)) { throw new BusinessException(ExceptionEnum.THE_LOG_OUT); } LoginVO loginVO = JSONObject.parseObject(obj.toString(), LoginVO.class); Person person = personMapper.selectById(loginVO.getId()); PersonVO personVO = new PersonVO(); BeanUtil.copy(person, personVO); return personVO; } /** * 修改个人信息 * * @param vo * @return */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class) public boolean editPerson(EditPersonDTO vo) { if (!RegexUtil.isPhoneLegal(vo.getPhoneNum())) { throw new BusinessException(ExceptionEnum.PHONE_FORMAT_ERROR); } long count = personMapper.selectCount(new LambdaQueryWrapper() .eq(Person::getPhoneNum, vo.getPhoneNum())); if (count > 0) { throw new BusinessException(ExceptionEnum.THE_PHONE_CANNOT_BE_EXIST); } Person person = personMapper.selectById(vo.getId()); BeanUtil.copy(vo, person); person.updateById(); return true; } /** * 退出登录 * * @param dto * @title: sendCode * @author: gck */ public boolean logout(PersonIdDTO dto) { String token = LoginUtils.getToken(); Object obj = redisService.get(AppCacheKeyUtil.getTokenUserInfo(token)); if (ToolUtil.isNotEmpty(obj)) { Object object = redisService.get(AppCacheKeyUtil.getTokenUserInfo(token)); LoginVO loginVO = JSONObject.parseObject(object.toString(), LoginVO.class); redisService.delete(AppCacheKeyUtil.getUserIdToken(loginVO.getId())); redisService.delete(AppCacheKeyUtil.getTokenUserInfo(token)); } return true; } public String getToken() { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); com.bjbz.common.jwt.JwtUserInfo jwtUserInfo = new com.bjbz.common.jwt.JwtUserInfo(); jwtUserInfo.setToken(uuid); return jwtTokenUtil.generateToken(jwtUserInfo.toJsonString(), jwtTokenUtil.getRandomKey()); } /** * 根据 code 获取 session_key 和 openId * * @param code 小程序登录时获取的 code * @return session_key 和 openId */ public JSONObject getSessionKeyAndOpenId(String code) { String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code"; JSONObject jsonObject = null; try { // 发送请求 URL requestUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection(); connection.setRequestMethod("GET"); connection.setDoOutput(true); connection.setDoInput(true); connection.connect(); // 读取响应 InputStream inputStream = connection.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); StringBuffer buffer = new StringBuffer(); String temp = null; while ((temp = bufferedReader.readLine()) != null) { buffer.append(temp); } // 解析 JSON 数据 jsonObject = JSONObject.parseObject(buffer.toString()); } catch (Exception e) { e.printStackTrace(); } return jsonObject; } /** * 解密用户数据 * * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据 * @param sessionKey 会话密钥 * @param iv 加密算法的初始向量 * @return 解密后的用户数据 */ private static String decrypt(String encryptedData, String sessionKey, String iv) { byte[] encryptedDataByte = Base64.decodeBase64(encryptedData); byte[] sessionKeyByte = Base64.decodeBase64(sessionKey); byte[] ivByte = Base64.decodeBase64(iv); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKeySpec = new SecretKeySpec(sessionKeyByte, "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(ivByte); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] decryptedDataByte = cipher.doFinal(encryptedDataByte); return new String(decryptedDataByte); } catch (Exception e) { e.printStackTrace(); } return null; } }