LoginService.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. package com.example.xiaoshiweixinback.service;
  2. import cn.dev33.satoken.stp.StpUtil;
  3. import cn.hutool.captcha.CaptchaUtil;
  4. import cn.hutool.captcha.CircleCaptcha;
  5. import cn.hutool.core.img.ImgUtil;
  6. import cn.hutool.core.util.IdUtil;
  7. import com.alibaba.fastjson2.JSONObject;
  8. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9. import com.example.xiaoshiweixinback.business.common.log.LogHelper;
  10. import com.example.xiaoshiweixinback.business.exception.BusinessException;
  11. import com.example.xiaoshiweixinback.business.exception.ExceptionEnum;
  12. import com.example.xiaoshiweixinback.business.jwt.JwtTokenUtil;
  13. import com.example.xiaoshiweixinback.business.redis.CacheTTLEnum;
  14. import com.example.xiaoshiweixinback.business.redis.RedisService;
  15. import com.example.xiaoshiweixinback.business.utils.*;
  16. import com.example.xiaoshiweixinback.domain.Person;
  17. import com.example.xiaoshiweixinback.entity.dto.person.*;
  18. import com.example.xiaoshiweixinback.entity.vo.PersonnelVO;
  19. import com.example.xiaoshiweixinback.entity.vo.person.Jscode2SessionWo;
  20. import com.example.xiaoshiweixinback.entity.vo.person.LoginByWxVO;
  21. import com.example.xiaoshiweixinback.entity.vo.person.LoginVO;
  22. import com.example.xiaoshiweixinback.entity.vo.person.PersonVO;
  23. import com.example.xiaoshiweixinback.mapper.PersonMapper;
  24. import com.example.xiaoshiweixinback.okhttp.ResponseManager;
  25. import com.example.xiaoshiweixinback.service.common.SmsService;
  26. import org.apache.commons.codec.binary.Base64;
  27. import org.apache.commons.lang3.StringUtils;
  28. import org.springframework.beans.factory.annotation.Autowired;
  29. import org.springframework.beans.factory.annotation.Value;
  30. import org.springframework.stereotype.Service;
  31. import org.springframework.transaction.annotation.Propagation;
  32. import org.springframework.transaction.annotation.Transactional;
  33. import javax.crypto.Cipher;
  34. import javax.crypto.spec.IvParameterSpec;
  35. import javax.crypto.spec.SecretKeySpec;
  36. import java.io.BufferedReader;
  37. import java.io.InputStream;
  38. import java.io.InputStreamReader;
  39. import java.io.UnsupportedEncodingException;
  40. import java.net.HttpURLConnection;
  41. import java.net.URL;
  42. import java.util.*;
  43. @Service
  44. public class LoginService {
  45. @Value("${WeChat.appId}")
  46. private String appId;
  47. @Value("${WeChat.appSecret}")
  48. private String appSecret;
  49. @Autowired
  50. private CacheUtil cacheUtil;
  51. @Autowired
  52. private LoginUtils loginUtils;
  53. @Autowired
  54. private JwtTokenUtil jwtTokenUtil;
  55. @Autowired
  56. private RedisService redisService;
  57. @Autowired
  58. private SmsService smsService;
  59. @Autowired
  60. private PersonMapper personMapper;
  61. /**
  62. * 手机号/账号登录
  63. * @param dto
  64. * @return
  65. */
  66. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
  67. public LoginVO loginByPhone(LoginDTO dto) throws Exception {
  68. LogHelper.log("登录开始");
  69. //获取缓存中验证码
  70. Object codeObj = redisService.get(AppCacheKeyUtil.getLoginMessageCode(dto.getPhoneNum()));
  71. if (ToolUtil.isEmpty(codeObj)) {
  72. throw new BusinessException(ExceptionEnum.CODE_WRONG);
  73. }
  74. //校验验证码
  75. if (ToolUtil.isEmpty(dto.getPhoneCode())) {
  76. throw new BusinessException(ExceptionEnum.THE_CODE_IS_NOT_NULL);
  77. }
  78. if (!ToolUtil.equals(codeObj.toString(), dto.getPhoneCode())) {
  79. throw new BusinessException(ExceptionEnum.CODE_WRONG);
  80. }
  81. //校验验证码成功后使其失效
  82. redisService.delete(AppCacheKeyUtil.getLoginMessageCode(dto.getPhoneNum()));
  83. //查询用户
  84. LambdaQueryWrapper<Person> queryWrapper = new LambdaQueryWrapper<>();
  85. if (ToolUtil.isNotEmpty(dto.getPhoneNum())) {
  86. queryWrapper.eq(Person::getPhoneNum, dto.getPhoneNum());
  87. }
  88. Person person = personMapper.selectOne(queryWrapper);
  89. LoginVO loginVO = new LoginVO();
  90. if (ToolUtil.isEmpty(person)) {
  91. person = new Person();
  92. person.setPhoneNum(dto.getPhoneNum());
  93. String uid = IdUtil.simpleUUID();
  94. person.setUuid(uid);
  95. PersonnelVO personnelVO = cacheUtil.getLoginUser(loginUtils.getId());
  96. if (ToolUtil.isNotEmpty(personnelVO)) {
  97. Integer personId = personnelVO.getId();
  98. person.setCreateId(personId.toString());
  99. }
  100. person.setCreateTime(new Date());
  101. personMapper.insert(person);
  102. loginVO.setId(person.getId());
  103. loginVO.setPhone(person.getPhoneNum());
  104. loginVO.setIfFirst(true);
  105. } else {
  106. BeanUtil.copy(person, loginVO);
  107. }
  108. loginVO.setToken(jwtTokenUtil.createToken());
  109. redisService.set(AppCacheKeyUtil.getUserIdToken(loginVO.getId()),loginVO.getToken());
  110. redisService.set(AppCacheKeyUtil.getTokenUserInfo(loginVO.getToken()), loginVO);
  111. LogHelper.log("登陆结束");
  112. return loginVO;
  113. }
  114. /**
  115. * 微信小程序登录
  116. *
  117. * @param wxDTO
  118. * @throws Exception
  119. * @title: loginByWeChat
  120. * @author: zero
  121. * @date: 2024/04/01
  122. * @return: LoginByWxVO
  123. */
  124. public LoginByWxVO loginByWeChat(LoginByWxDTO wxDTO) throws Exception {
  125. String code = wxDTO.getCode();
  126. String encryptedData = wxDTO.getEncryptedData();
  127. String iv = wxDTO.getIv();
  128. //返回数据
  129. LoginByWxVO wxVO = new LoginByWxVO();
  130. // //1.根据code 获取微信小程序的openid和session_key
  131. JSONObject result = this.getSessionKeyAndOpenId(code);
  132. Jscode2SessionWo jscode2SessionWo = ResponseManager.parseObject(result.toString(), Jscode2SessionWo.class);
  133. if (ToolUtil.isNotEmpty(jscode2SessionWo)) {
  134. // 2. 解密用户数据
  135. String decryptedData = decrypt(encryptedData, jscode2SessionWo.getSession_key(), iv);
  136. // 3. 获取用户手机号(需要用户授权)
  137. String phoneNumber = "";
  138. JSONObject userData = JSONObject.parseObject(decryptedData);
  139. if (ToolUtil.isNotEmpty(userData) && userData.containsKey("purePhoneNumber")) {
  140. phoneNumber = userData.getString("purePhoneNumber");
  141. }
  142. // 4.查询数据表
  143. Person person = personMapper.selectOne(new LambdaQueryWrapper<Person>()
  144. .eq(Person::getOpenId, jscode2SessionWo.getOpenid()));
  145. if (ToolUtil.isNotEmpty(person)) {
  146. BeanUtil.copy(person, wxVO);
  147. } else {
  148. //添加用户表中
  149. person = personMapper.selectOne(new LambdaQueryWrapper<Person>()
  150. .eq(Person::getPhoneNum, phoneNumber));
  151. if (ToolUtil.isNotEmpty(person)) {
  152. person.setOpenId(jscode2SessionWo.getOpenid());
  153. person.updateById();
  154. } else {
  155. person = new Person();
  156. person.setPhoneNum(phoneNumber);
  157. person.setOpenId(jscode2SessionWo.getOpenid());
  158. String uid = IdUtil.simpleUUID();
  159. person.setUuid(uid);
  160. PersonnelVO personnelVO = cacheUtil.getLoginUser(loginUtils.getId());
  161. if (ToolUtil.isNotEmpty(personnelVO)) {
  162. Integer personId = personnelVO.getId();
  163. person.setCreateId(personId.toString());
  164. }
  165. person.setCreateTime(new Date());
  166. person.insert();
  167. wxVO.setId(person.getId());
  168. wxVO.setIfFirst(true);
  169. }
  170. wxVO.setPhoneNum(phoneNumber);
  171. }
  172. wxVO.setToken(jwtTokenUtil.createToken());
  173. wxVO.setOpenId(jscode2SessionWo.getOpenid());
  174. redisService.set(AppCacheKeyUtil.getUserIdToken(wxVO.getId()), wxVO);
  175. redisService.set(AppCacheKeyUtil.getTokenUserInfo(wxVO.getToken()), wxVO);
  176. }
  177. return wxVO;
  178. }
  179. /**
  180. * 发送验证码
  181. *
  182. * @param vo
  183. * @return
  184. */
  185. public boolean sendCode(SendCodeDTO vo) {
  186. boolean flag = false;
  187. if (!RegexUtil.isPhoneLegal(vo.getPhoneNum())) {
  188. throw new BusinessException(ExceptionEnum.PHONE_FORMAT_ERROR);
  189. }
  190. Object checkCode = redisService.get(AppCacheKeyUtil.getCheckCode(vo.getPhoneNum()));
  191. if (ToolUtil.isEmpty(checkCode)) {
  192. throw new BusinessException(ExceptionEnum.VERIFY_CODE);
  193. }
  194. if (ToolUtil.isNotEmpty(checkCode.toString()) && ToolUtil.equals(checkCode.toString(), vo.getCheckCode())) {
  195. //删除校验码
  196. redisService.delete(AppCacheKeyUtil.getCheckCode(vo.getPhoneNum()));
  197. //生成验证码
  198. String random = RandomUtil.getSixRandom();
  199. //手机号和验证码放进缓存 设置过期时间5min
  200. redisService.set(AppCacheKeyUtil.getLoginMessageCode(vo.getPhoneNum()), random);
  201. redisService.expire(AppCacheKeyUtil.getLoginMessageCode(vo.getPhoneNum()), CacheTTLEnum.FIVE_MINUTE);
  202. //发送短信
  203. smsService.sendMessage(vo.getPhoneNum(), random);
  204. flag = true;
  205. }
  206. return flag;
  207. }
  208. /**
  209. * 生成验证码
  210. *
  211. * @return 1.生成验证码的base64转码 2.生成的UUID 与Redis里面的验证码KEY值一致
  212. * @date: 20240401
  213. */
  214. public Map<String, String> verifyCode(PersonPhoneDTO vo) {
  215. if (!RegexUtil.isPhoneLegal(vo.getPhoneNum())) {
  216. throw new BusinessException(ExceptionEnum.PHONE_FORMAT_ERROR);
  217. }
  218. //1.定义图形验证码的长、宽、验证码字符数、干扰元素个数
  219. CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
  220. //2.生成UUID
  221. String uuid = cn.hutool.core.lang.UUID.fastUUID().toString();
  222. //3.创建返回参数 并用base64将图片转码 与UUID一起填充
  223. Map<String, String> result = new HashMap<>();
  224. result.put("captcha", ImgUtil.toBase64DataUri(captcha.getImage(), "png"));
  225. result.put("uuid", uuid);
  226. //4.将验证码存放到Redis里面
  227. redisService.set(AppCacheKeyUtil.getCheckCode(vo.getPhoneNum()), captcha.getCode());
  228. return result;
  229. }
  230. /**
  231. * 查询个人信息
  232. *
  233. * @param vo
  234. * @return
  235. */
  236. public PersonVO selectPerson(PersonIdDTO vo) {
  237. String token = LoginUtils.getToken();
  238. if (ToolUtil.isEmpty(token)) {
  239. throw new BusinessException(ExceptionEnum.THE_LOG_OUT);
  240. }
  241. Object obj = redisService.get(token);
  242. if (ToolUtil.isEmpty(obj)) {
  243. throw new BusinessException(ExceptionEnum.THE_LOG_OUT);
  244. }
  245. LoginVO loginVO = JSONObject.parseObject(obj.toString(), LoginVO.class);
  246. Person person = personMapper.selectById(loginVO.getId());
  247. PersonVO personVO = new PersonVO();
  248. BeanUtil.copy(person, personVO);
  249. return personVO;
  250. }
  251. /**
  252. * 修改个人信息
  253. *
  254. * @param vo
  255. * @return
  256. */
  257. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
  258. public boolean editPerson(EditPersonDTO vo) {
  259. if (!RegexUtil.isPhoneLegal(vo.getPhoneNum())) {
  260. throw new BusinessException(ExceptionEnum.PHONE_FORMAT_ERROR);
  261. }
  262. long count = personMapper.selectCount(new LambdaQueryWrapper<Person>()
  263. .eq(Person::getPhoneNum, vo.getPhoneNum()));
  264. if (count > 0) {
  265. throw new BusinessException(ExceptionEnum.THE_PHONE_CANNOT_BE_EXIST);
  266. }
  267. Person person = personMapper.selectById(vo.getId());
  268. BeanUtil.copy(vo, person);
  269. person.updateById();
  270. return true;
  271. }
  272. /**
  273. * 退出登录
  274. *
  275. * @param dto
  276. * @title: sendCode
  277. * @author: gck
  278. */
  279. public boolean logout(PersonIdDTO dto) {
  280. String token = LoginUtils.getToken();
  281. Object obj = redisService.get(AppCacheKeyUtil.getTokenUserInfo(token));
  282. if (ToolUtil.isNotEmpty(obj)) {
  283. Object object = redisService.get(AppCacheKeyUtil.getTokenUserInfo(token));
  284. LoginVO loginVO = JSONObject.parseObject(object.toString(), LoginVO.class);
  285. redisService.delete(AppCacheKeyUtil.getUserIdToken(loginVO.getId()));
  286. redisService.delete(AppCacheKeyUtil.getTokenUserInfo(token));
  287. }
  288. return true;
  289. }
  290. public String getToken() {
  291. String uuid = UUID.randomUUID().toString().replaceAll("-", "");
  292. com.bjbz.common.jwt.JwtUserInfo jwtUserInfo = new com.bjbz.common.jwt.JwtUserInfo();
  293. jwtUserInfo.setToken(uuid);
  294. return jwtTokenUtil.generateToken(jwtUserInfo.toJsonString(), jwtTokenUtil.getRandomKey());
  295. }
  296. /**
  297. * 根据 code 获取 session_key 和 openId
  298. *
  299. * @param code 小程序登录时获取的 code
  300. * @return session_key 和 openId
  301. */
  302. public JSONObject getSessionKeyAndOpenId(String code) {
  303. String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";
  304. JSONObject jsonObject = null;
  305. try {
  306. // 发送请求
  307. URL requestUrl = new URL(url);
  308. HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection();
  309. connection.setRequestMethod("GET");
  310. connection.setDoOutput(true);
  311. connection.setDoInput(true);
  312. connection.connect();
  313. // 读取响应
  314. InputStream inputStream = connection.getInputStream();
  315. InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
  316. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  317. StringBuffer buffer = new StringBuffer();
  318. String temp = null;
  319. while ((temp = bufferedReader.readLine()) != null) {
  320. buffer.append(temp);
  321. }
  322. // 解析 JSON 数据
  323. jsonObject = JSONObject.parseObject(buffer.toString());
  324. } catch (Exception e) {
  325. e.printStackTrace();
  326. }
  327. return jsonObject;
  328. }
  329. /**
  330. * 解密用户数据
  331. *
  332. * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
  333. * @param sessionKey 会话密钥
  334. * @param iv 加密算法的初始向量
  335. * @return 解密后的用户数据
  336. */
  337. private static String decrypt(String encryptedData, String sessionKey, String iv) {
  338. byte[] encryptedDataByte = Base64.decodeBase64(encryptedData);
  339. byte[] sessionKeyByte = Base64.decodeBase64(sessionKey);
  340. byte[] ivByte = Base64.decodeBase64(iv);
  341. try {
  342. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  343. SecretKeySpec secretKeySpec = new SecretKeySpec(sessionKeyByte, "AES");
  344. IvParameterSpec ivParameterSpec = new IvParameterSpec(ivByte);
  345. cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
  346. byte[] decryptedDataByte = cipher.doFinal(encryptedDataByte);
  347. return new String(decryptedDataByte);
  348. } catch (Exception e) {
  349. e.printStackTrace();
  350. }
  351. return null;
  352. }
  353. }