WeChatController.java 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. package com.ylx.web.controller.massage;
  2. import cn.hutool.core.collection.CollectionUtil;
  3. import cn.hutool.core.io.FileUtil;
  4. import cn.hutool.core.util.ObjUtil;
  5. import cn.hutool.extra.qrcode.QrCodeUtil;
  6. import cn.hutool.extra.qrcode.QrConfig;
  7. import cn.hutool.json.JSONObject;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  10. import com.ylx.common.annotation.Log;
  11. import com.ylx.common.config.RuoYiConfig;
  12. import com.ylx.common.constant.Constants;
  13. import com.ylx.common.core.controller.BaseController;
  14. import com.ylx.common.core.domain.AjaxResult;
  15. import com.ylx.common.core.domain.R;
  16. import com.ylx.common.core.domain.model.BindPhoneBody;
  17. import com.ylx.common.core.domain.model.PhoneLoginBody;
  18. import com.ylx.common.core.domain.model.WxLoginUser;
  19. import com.ylx.common.core.domain.model.aliyun.SMSVerificationCode;
  20. import com.ylx.common.core.domain.model.aliyun.SendSmsComponents;
  21. import com.ylx.common.core.domain.model.aliyun.SendSmsEnum;
  22. import com.ylx.common.core.redis.RedisCache;
  23. import com.ylx.common.enums.BusinessType;
  24. import com.ylx.common.utils.MessageUtils;
  25. import com.ylx.common.utils.StringUtils;
  26. import com.ylx.common.utils.file.FileUploadUtils;
  27. import com.ylx.framework.manager.AsyncManager;
  28. import com.ylx.framework.manager.factory.AsyncFactory;
  29. import com.ylx.framework.web.service.WxTokenService;
  30. import com.ylx.massage.domain.TJs;
  31. import com.ylx.massage.domain.TWxUser;
  32. import com.ylx.massage.service.CouponReceiveService;
  33. import com.ylx.massage.service.TJsService;
  34. import com.ylx.massage.service.TWxUserService;
  35. import com.ylx.massage.service.TbFileService;
  36. import com.ylx.massage.utils.JsSignUtil;
  37. import com.ylx.massage.utils.StringUtilsMassage;
  38. import com.ylx.massage.utils.WeChatUtil;
  39. import com.ylx.usercenter.domain.vo.OneAccountVO;
  40. import com.ylx.usercenter.service.UnifiedUserCenterService;
  41. import io.swagger.annotations.Api;
  42. import io.swagger.annotations.ApiOperation;
  43. import lombok.extern.slf4j.Slf4j;
  44. import org.springframework.beans.BeanUtils;
  45. import org.springframework.beans.factory.annotation.Autowired;
  46. import org.springframework.beans.factory.annotation.Value;
  47. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  48. import org.springframework.web.bind.annotation.*;
  49. import org.springframework.web.multipart.MultipartFile;
  50. import javax.annotation.Resource;
  51. import javax.servlet.http.HttpServletRequest;
  52. import javax.servlet.http.HttpServletResponse;
  53. import java.io.*;
  54. import java.net.URL;
  55. import java.net.URLEncoder;
  56. import java.nio.file.Files;
  57. import java.nio.file.Paths;
  58. import java.util.*;
  59. import java.util.concurrent.TimeUnit;
  60. import static com.ylx.massage.utils.OtherUtil.verification;
  61. /**
  62. * @author b16mt
  63. */
  64. @Slf4j
  65. @RestController
  66. @Api(tags = {"微信服务号"})
  67. @RequestMapping("/weChat")
  68. public class WeChatController extends BaseController {
  69. private final static String TOKEN = "abcd1234";
  70. private final static String ENCODING = "UTF-8";
  71. private final static String ACCESS_TOKEN = "access_token";
  72. private final static String REFRESH_TOKEN = "refresh_token";
  73. private final static String OPEN_ID = "openid";
  74. public static final String PHONE_VERIFICATION_CODE_KEY = "sys:clientLogin:phone:";
  75. public static final Integer PHONE_VERIFICATION_CODE_KEY_TIME = 5;
  76. /**
  77. * 二维码保存路径
  78. */
  79. private String IMG_PATH = "D:\\Users\\";
  80. @Resource
  81. private WeChatUtil weChatUtil;
  82. @Resource
  83. private TWxUserService wxUserService;
  84. @Resource(name = "commonAsyncExecutor")
  85. private ThreadPoolTaskExecutor threadPoolTaskExecutor;
  86. @Autowired
  87. private CouponReceiveService couponReceiveService;
  88. @Resource
  89. private WxTokenService wxTokenService;
  90. @Resource
  91. private JsSignUtil jsSignUtil;
  92. @Autowired
  93. private TbFileService tbFileService;
  94. @Resource
  95. private TJsService jsService;
  96. @Autowired
  97. private RedisCache redisCache;
  98. @Autowired
  99. private SendSmsComponents sendSms;
  100. /**
  101. * 发送验证码开关(true:调用真实发送接口,false:验证码写死123456)
  102. */
  103. @Value("${ylx.sendSmsEnabled:false}")
  104. private boolean sendSmsEnabled;
  105. /**
  106. * 写死的验证码(sendSmsEnabled为false时使用)
  107. */
  108. @Value("${ylx.fixedVerifyCode:123456}")
  109. private String fixedVerifyCode;
  110. @Resource
  111. private UnifiedUserCenterService unifiedUserCenterService;
  112. /**
  113. * 微信Token验证
  114. *
  115. * @param signature 微信加密签名
  116. * @param timestamp 时间戳
  117. * @param nonce 随机数
  118. * @param echostr 随机字符串
  119. * @param response HTTP响应对象
  120. * @throws Exception 如果处理过程中出现错误
  121. */
  122. @GetMapping("/verifyToken")
  123. @Log(title = "公众号pverifyToken", businessType = BusinessType.OTHER)
  124. public void verifyToken(@RequestParam(value = "signature") String signature,
  125. @RequestParam(value = "timestamp") String timestamp,
  126. @RequestParam(value = "nonce") String nonce,
  127. @RequestParam(value = "echostr") String echostr, HttpServletResponse response) throws Exception {
  128. log.info("微信Token验证 入参: signature:{},timestamp:{},nonce:{},echostr:{},", signature, timestamp, nonce, echostr);
  129. // 参数排序
  130. String[] params = new String[]{timestamp, nonce, TOKEN};
  131. Arrays.sort(params);
  132. // 校验成功则响应 echostr,失败则不响应
  133. if (verification(params, signature) && echostr != null) {
  134. response.setCharacterEncoding(ENCODING);
  135. response.getWriter().write(echostr);
  136. response.getWriter().flush();
  137. response.getWriter().close();
  138. }
  139. }
  140. /**
  141. * 设置服务号的菜单
  142. *
  143. * @return Map<?, ?>
  144. */
  145. @GetMapping("/setMenu")
  146. @ApiOperation("设置菜单")
  147. @Log(title = "设置菜单", businessType = BusinessType.OTHER)
  148. public Map<?, ?> setMenu() {
  149. //获取access_token
  150. String token = weChatUtil.getToken();
  151. //获取的二维码ticket
  152. return weChatUtil.menuUtil(token);
  153. }
  154. /**
  155. * 前端获取jssdk签名
  156. *
  157. * @param url
  158. * @return Map<?, ?>
  159. */
  160. @GetMapping("/getSignature")
  161. @ApiOperation("前端获取jssdk签名")
  162. @Log(title = "前端获取jssdk签名", businessType = BusinessType.OTHER)
  163. public Map<?, ?> getSignature(String url) {
  164. //获取access_token
  165. String token = weChatUtil.getToken();
  166. //获取jsapi_ticket
  167. String jsapiTicket = weChatUtil.getJsapiTicket(token);
  168. //生成签名
  169. return jsSignUtil.sign(url, jsapiTicket);
  170. }
  171. /**
  172. * 处理微信公众号请求信息
  173. *
  174. * @param request
  175. * @return
  176. */
  177. @RequestMapping("/verifyToken")
  178. @ResponseBody
  179. @Log(title = "处理微信公众号请求信息", businessType = BusinessType.OTHER)
  180. public String handlePublicMsg(HttpServletRequest request) throws Exception {
  181. log.info("处理微信公众号请求信息:{}", request.toString());
  182. // 获得微信端返回的xml数据
  183. InputStream is = null;
  184. InputStreamReader isr = null;
  185. BufferedReader br = null;
  186. try {
  187. is = request.getInputStream();
  188. isr = new InputStreamReader(is, "utf-8");
  189. br = new BufferedReader(isr);
  190. String str = null;
  191. StringBuffer returnXml = new StringBuffer();
  192. while ((str = br.readLine()) != null) {
  193. //返回的是xml数据
  194. returnXml.append(str);
  195. }
  196. log.info("微信端返回的xml数据:{}", returnXml);
  197. Map<String, String> encryptMap = WeChatUtil.xmlToMap(returnXml.toString());
  198. // 得到公众号传来的加密信息并解密,得到的是明文xml数据
  199. // String decryptXml = WXPublicUtils.decrypt(encryptMap.get("Encrypt"));
  200. // 将xml数据转换为map
  201. // Map<String, String> decryptMap = WeChatUtil.xmlToMap(decryptXml);
  202. // 区分消息类型
  203. String msgType = encryptMap.get("MsgType");
  204. // 普通消息
  205. if ("text".equals(msgType)) { // 文本消息
  206. // todo 处理文本消息
  207. } else if ("image".equals(msgType)) { // 图片消息
  208. // todo 处理图片消息
  209. } else if ("voice".equals(msgType)) { //语音消息
  210. // todo 处理语音消息
  211. } else if ("video".equals(msgType)) { // 视频消息
  212. // todo 处理视频消息
  213. } else if ("shortvideo".equals(msgType)) { // 小视频消息
  214. // todo 处理小视频消息
  215. } else if ("location".equals(msgType)) { // 地理位置消息
  216. // todo 处理地理位置消息
  217. } else if ("link".equals(msgType)) { // 链接消息
  218. // todo 处理链接消息
  219. }
  220. // 事件推送
  221. else if ("event".equals(msgType)) { // 事件消息
  222. // 区分事件推送
  223. String event = encryptMap.get("Event");
  224. if ("subscribe".equals(event)) { // 订阅事件 或 未关注扫描二维码事件
  225. return getString(encryptMap);
  226. } else if ("unsubscribe".equals(event)) { // 取消订阅事件
  227. // todo 处理取消订阅事件
  228. } else if ("SCAN".equals(event)) { // 已关注扫描二维码事件
  229. return getString(encryptMap);
  230. } else if ("LOCATION".equals(event)) { // 上报地理位置事件
  231. // todo 处理上报地理位置事件
  232. } else if ("CLICK".equals(event)) { // 点击菜单拉取消息时的事件推送事件
  233. // todo 处理点击菜单拉取消息时的事件推送事件
  234. } else if ("VIEW".equals(event)) { // 点击菜单跳转链接时的事件推送
  235. // todo 处理点击菜单跳转链接时的事件推送
  236. }
  237. }
  238. } catch (Exception e) {
  239. logger.error("处理微信公众号请求信息,失败", e);
  240. } finally {
  241. if (null != is) {
  242. is.close();
  243. }
  244. if (null != isr) {
  245. isr.close();
  246. }
  247. if (null != br) {
  248. br.close();
  249. }
  250. }
  251. return null;
  252. }
  253. private String getString(Map<String, String> encryptMap) throws Exception {
  254. // 返回消息时ToUserName的值与FromUserName的互换
  255. Map<String, String> returnMap = new HashMap<>();
  256. String content = "欢迎来到广誉源" + "\n" +
  257. "\n" +
  258. "广誉源是一家快速上门服务预约平台,提供正规 绿色 快捷上门服务,提供按摩、推拿、养生、SPA等服务,专业针对居家、差旅、酒店等顾客提供便捷健康养生服务";
  259. //添加新用户
  260. TWxUser fromUserName = wxUserService.getByOpenId(encryptMap.get("FromUserName"));
  261. if (fromUserName == null) {
  262. fromUserName = new TWxUser();
  263. }
  264. fromUserName.setcOpenid(encryptMap.get("FromUserName"));
  265. //fromUserName.setcUpUser(StringUtilsMassage.afterString(encryptMap.get("EventKey"), "qrscene_"));
  266. fromUserName.setRole(1);
  267. wxUserService.saveOrUpdate(fromUserName);
  268. //绑定技师
  269. TJs byId = jsService.getById(StringUtilsMassage.afterString(encryptMap.get("EventKey"), "qrscene_"));
  270. List<TJs> byOpenid = jsService.list(new LambdaQueryWrapper<TJs>().eq(TJs::getcOpenId, encryptMap.get("FromUserName")));
  271. if (CollectionUtil.isNotEmpty(byOpenid)) {
  272. content = "你已有绑定的账号如需换绑请先解绑";
  273. }
  274. if (StringUtils.isNotEmpty(byId.getcOpenId())) {
  275. content = "该账号已被绑定,请选择其他账号";
  276. } else if (CollectionUtil.isEmpty(byOpenid)) {
  277. TJs tJs = new TJs();
  278. tJs.setId(StringUtilsMassage.afterString(encryptMap.get("EventKey"), "qrscene_"));
  279. tJs.setcOpenId(encryptMap.get("FromUserName"));
  280. jsService.updateById(tJs);
  281. }
  282. returnMap.put("ToUserName", encryptMap.get("FromUserName"));
  283. returnMap.put("FromUserName", encryptMap.get("ToUserName"));
  284. returnMap.put("CreateTime", new Date().getTime() + "");
  285. returnMap.put("MsgType", "text");
  286. returnMap.put("Content", content);
  287. String encryptMsg = weChatUtil.mapToXml(returnMap).toString();
  288. return encryptMsg;
  289. }
  290. /**
  291. * 获取微信code
  292. *
  293. * @param state 状态参数
  294. * @return String
  295. */
  296. @ApiOperation("获取微信code")
  297. @Log(title = "获取微信code", businessType = BusinessType.OTHER)
  298. @GetMapping("/getCode")
  299. public String weiXinLogin(String state) {
  300. String code = weChatUtil.getCode(state);
  301. log.info("code的值:{}", code);
  302. redisCache.setCacheObject("code", state, 10, TimeUnit.MINUTES);
  303. return code;
  304. }
  305. /**
  306. * 获取token和userInfo
  307. *
  308. * @param code 微信授权码
  309. * @return R<WxLoginUser> 访问令牌
  310. */
  311. @GetMapping("/getAccessToken")
  312. @ApiOperation("公众号网页登录")
  313. @Log(title = "公众号网页登录", businessType = BusinessType.OTHER)
  314. public R<WxLoginUser> getAccessToken(@RequestParam String code) {
  315. // 发送get请求获取 AccessToken
  316. Map<?, ?> result = weChatUtil.getAccessToken(code);
  317. String accessToken = result.get(ACCESS_TOKEN).toString();
  318. String refreshToken = result.get(REFRESH_TOKEN).toString();
  319. String openid = result.get(OPEN_ID).toString();
  320. // 如果用户是第一次进行微信公众号授权
  321. // 进行这一步时用户应点击了同意授权按钮
  322. String userInfoJsom = weChatUtil.getUserInfo(accessToken, openid);
  323. // 解析JSON数据
  324. JSONObject jsonObject = new JSONObject(userInfoJsom);
  325. log.info("公众号网页登录:{}", jsonObject);
  326. // 将用户信息保存到数据库中
  327. LambdaQueryWrapper<TWxUser> objectLambdaQueryWrapper = new LambdaQueryWrapper<>();
  328. objectLambdaQueryWrapper.eq(TWxUser::getcOpenid, openid);
  329. TWxUser user = wxUserService.getOne(objectLambdaQueryWrapper);
  330. if (user == null || StringUtils.isEmpty(user.getcNickName())) {
  331. user.setcOpenid(openid);
  332. user.setcNickName(jsonObject.get("nickname").toString());
  333. user.setcIcon(jsonObject.get("headimgurl").toString());
  334. user.setcSessionKey(refreshToken);
  335. wxUserService.saveOrUpdate(user);
  336. user.setId(user.getId());
  337. }
  338. WxLoginUser wxUser = new WxLoginUser();
  339. BeanUtils.copyProperties(user, wxUser);
  340. // 生成并返回令牌
  341. String token = wxTokenService.createToken(wxUser);
  342. log.info("生成的token值:{}", token);
  343. if (token == null || token.isEmpty()) {
  344. return R.fail("生成令牌失败");
  345. }
  346. wxUser.setToken(token);
  347. // 返回用户信息
  348. // 记录登录信息
  349. AsyncManager.me().execute(AsyncFactory.recordLogininfor(wxUser.getCOpenid(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  350. return R.ok(wxUser);
  351. }
  352. /**
  353. * 刷新token,微信提供的token是有限时间的,但是对于财务报销系统仅需授权一次的情况下一般不需要进行更新
  354. *
  355. * @return accessToken
  356. */
  357. @GetMapping("/refreshToken")
  358. public String refreshToken() {
  359. WxLoginUser wxLoginUser = this.getWxLoginUser();
  360. TWxUser user = wxUserService.getByOpenId(wxLoginUser.getCOpenid());
  361. if (user == null) {
  362. throw new RuntimeException("用户不存在");
  363. }
  364. // 发送get请求获取 RefreshToken
  365. Map<?, ?> result = weChatUtil.refreshToken(user.getcSessionKey());
  366. String accessToken = result.get(ACCESS_TOKEN).toString();
  367. String refreshToken = result.get(REFRESH_TOKEN).toString();
  368. // 更新用户信息
  369. user.setcSessionKey(refreshToken);
  370. // 存储数据库
  371. wxUserService.updateById(user);
  372. return accessToken;
  373. }
  374. @ApiOperation("获取公众号二维码")
  375. @RequestMapping(value = "getwxQrCode", method = RequestMethod.GET)
  376. public Map<?, ?> getWxQrCodeUtil(@RequestParam String openId) {
  377. //获取access_token
  378. String token = weChatUtil.getToken();
  379. //获取的二维码ticket
  380. return weChatUtil.getTicket(token, openId);
  381. }
  382. @ApiOperation("获取JS公众号二维码ticket")
  383. @RequestMapping(value = "getJSwxQrCode", method = RequestMethod.GET)
  384. public void getJSwxQrCode(@RequestParam String jsId) {
  385. //获取access_token
  386. String token = weChatUtil.getToken();
  387. //获取的二维码ticket
  388. Map<?, ?> jsTicket = weChatUtil.getJsTicket(token, jsId);
  389. //获取的二维码ticket
  390. //获取二维码图片
  391. //String qrCodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
  392. //获取二维码图片
  393. //File file = QrCodeUtil.generate(qrCodeUrl, config, FileUtil.file(RuoYiConfig.getUploadPath() + "/code.png"));
  394. TJs tjs = new TJs();
  395. tjs.setId(jsId);
  396. tjs.setTicket(jsTicket.get("ticket").toString());
  397. jsService.updateById(tjs);
  398. }
  399. /**
  400. * 兑换技师公众号二维码
  401. *
  402. * @param ticket
  403. * @param id
  404. * @return String
  405. * @throws UnsupportedEncodingException
  406. */
  407. @ApiOperation("兑换技师公众号二维码")
  408. @RequestMapping(value = "getJSwxQrCodeDh", method = RequestMethod.GET)
  409. public String getJSwxQrCode1(@RequestParam String ticket, @RequestParam String id) throws UnsupportedEncodingException {
  410. String qrCodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + URLEncoder.encode(ticket, "UTF-8");
  411. String filePath = RuoYiConfig.getUploadEwmPath() + id + ".png";
  412. log.info("二维码地址:{}", filePath);
  413. try (InputStream in = new URL(qrCodeUrl).openStream();
  414. OutputStream out = Files.newOutputStream(Paths.get(filePath))) {
  415. byte[] buffer = new byte[1024];
  416. int bytesRead;
  417. while ((bytesRead = in.read(buffer)) != -1) {
  418. out.write(buffer, 0, bytesRead);
  419. }
  420. } catch (Exception e) {
  421. e.printStackTrace();
  422. }
  423. int lastSlashIndex = filePath.lastIndexOf("/");
  424. String fileName = filePath.substring(lastSlashIndex + 1);
  425. return Constants.RESOURCE_PREFIX + "/ewm/" + fileName;
  426. }
  427. @ApiOperation("获取公众号网页二维码")
  428. @GetMapping("/getweQrCode")
  429. public AjaxResult weiXinLogin1(String openId) {
  430. QrConfig config = new QrConfig(300, 300);
  431. // 设置边距,即二维码和背景之间的边距
  432. config.setMargin(1);
  433. // 生成二维码到文件,也可以到流
  434. String code = "https://www.baidu.com?openId=" + openId;
  435. log.info("code:{}", code);
  436. String str = IMG_PATH;
  437. File generate = QrCodeUtil.generate(code, config, FileUtil.file(RuoYiConfig.getUploadPath() + "/code.png"));
  438. MultipartFile multipartFile = FileUploadUtils.getMultipartFile(generate);
  439. return tbFileService.uploadFile(multipartFile);
  440. }
  441. /**
  442. * 发送验证码
  443. *
  444. * @param phone 手机号
  445. * @return R<?> 发送结果
  446. */
  447. @ApiOperation("发送绑定手机号验证码")
  448. @PostMapping("/sendBindPhoneCode")
  449. public R<?> sendBindPhoneCode(@RequestParam String phone) {
  450. if (StringUtils.isEmpty(phone)) {
  451. return R.fail("手机号不能为空");
  452. }
  453. if (!phone.matches("^1[3-9]\\d{9}$")) {
  454. return R.fail("手机号格式不正确");
  455. }
  456. // 根据开关判断是否调用真实发送接口
  457. if (sendSmsEnabled) {
  458. // TODO: 调用真实发送验证码接口
  459. log.info("发送验证码到手机号: {}", phone);
  460. } else {
  461. // 使用写死的验证码
  462. log.info("发送验证码开关关闭,使用写死验证码: {}, 手机号: {}", fixedVerifyCode, phone);
  463. }
  464. return R.ok("验证码发送成功");
  465. }
  466. /**
  467. * 绑定手机号
  468. *
  469. * @param bindPhoneBody 绑定手机号请求参数
  470. * @return R<?> 绑定结果
  471. */
  472. @ApiOperation("绑定手机号")
  473. @PostMapping("/bindPhone")
  474. public R<?> bindPhone(@RequestBody BindPhoneBody bindPhoneBody) {
  475. String openId = bindPhoneBody.getOpenId();
  476. String phone = bindPhoneBody.getPhone();
  477. String code = bindPhoneBody.getCode();
  478. if (StringUtils.isEmpty(openId)) {
  479. return R.fail("openId不能为空");
  480. }
  481. if (StringUtils.isEmpty(phone)) {
  482. return R.fail("手机号不能为空");
  483. }
  484. if (StringUtils.isEmpty(code)) {
  485. return R.fail("验证码不能为空");
  486. }
  487. if (!phone.matches("^1[3-9]\\d{9}$")) {
  488. return R.fail("手机号格式不正确");
  489. }
  490. // 校验验证码
  491. if (!validateVerifyCode(phone, code)) {
  492. return R.fail("验证码错误");
  493. }
  494. // 查询当前用户
  495. TWxUser currentUser = wxUserService.getByOpenId(openId);
  496. if (currentUser == null) {
  497. return R.fail("用户不存在");
  498. }
  499. // 检查手机号是否已被其他用户绑定
  500. TWxUser existUser = wxUserService.getByPhone(phone);
  501. if (existUser != null && !existUser.getId().equals(currentUser.getId())) {
  502. return R.fail("手机号已被其他用户绑定");
  503. }
  504. // 如果该用户已经绑定过此手机号,直接返回成功(无变化)
  505. if (StringUtils.isNotEmpty(currentUser.getcPhone()) && currentUser.getcPhone().equals(phone)) {
  506. return R.ok("手机号已绑定,无需重复绑定");
  507. }
  508. // 绑定手机号
  509. boolean success = wxUserService.bindPhone(openId, phone);
  510. if (!success) {
  511. return R.fail("绑定失败");
  512. }
  513. log.info("用户openId: {} 绑定手机号: {} 成功", openId, phone);
  514. return R.ok("绑定成功");
  515. }
  516. /**
  517. * 校验验证码
  518. *
  519. * @param phone 手机号
  520. * @param code 用户输入的验证码
  521. * @return boolean 校验是否通过
  522. */
  523. private boolean validateVerifyCode(String phone, String code) {
  524. if (sendSmsEnabled) {
  525. // TODO: 从Redis或其他存储中获取真实发送的验证码进行校验
  526. // String realCode = redisCache.getCacheObject("sms:bindPhone:" + phone);
  527. // return code.equals(realCode);
  528. return true;
  529. } else {
  530. // 使用写死的验证码进行校验
  531. return fixedVerifyCode.equals(code);
  532. }
  533. }
  534. /**
  535. * 手机号登录
  536. *
  537. * @param phoneLoginBody 手机号登录请求参数
  538. * @return R<WxLoginUser> 登录结果
  539. */
  540. @ApiOperation("手机号登录")
  541. @PostMapping("/phoneLogin")
  542. //@Log(title = "手机号登录", businessType = BusinessType.OTHER)
  543. public R<WxLoginUser> phoneLogin(@RequestBody PhoneLoginBody phoneLoginBody) {
  544. String phone = phoneLoginBody.getPhone();
  545. String code = phoneLoginBody.getCode();
  546. // 校验验证码(验证码不为空且为6位数字在@Valid注解中已校验)
  547. if (StringUtils.isEmpty(code)) {
  548. return R.fail("验证码不能为空");
  549. }
  550. // 手机号登录获取用户
  551. TWxUser user = wxUserService.phoneLogin(phone);
  552. if (user == null) {
  553. return R.fail("登录失败");
  554. }
  555. // 构建WxLoginUser
  556. WxLoginUser wxUser = new WxLoginUser();
  557. BeanUtils.copyProperties(user, wxUser);
  558. // 生成并返回令牌
  559. String token = wxTokenService.createToken(wxUser);
  560. log.info("手机号登录生成的token值:{}", token);
  561. if (StringUtils.isEmpty(token)) {
  562. return R.fail("生成令牌失败");
  563. }
  564. wxUser.setToken(token);
  565. // 记录登录信息
  566. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phone, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  567. log.info("手机号: {} 登录成功", phone);
  568. return R.ok(wxUser);
  569. }
  570. /**
  571. * 发送短信验证码
  572. *
  573. * @param phone 手机号
  574. * @return R<String> 发送结果
  575. */
  576. @GetMapping("/sendMsg")
  577. @ApiOperation(value = "发送短信验证码", notes = "发送短信验证码")
  578. public R<String> sendMsg(@RequestParam String phone, HttpServletRequest request) {
  579. if (org.apache.commons.lang3.StringUtils.isEmpty(phone)) {
  580. return R.fail("手机号不能为空");
  581. }
  582. Random rand = new Random();
  583. // randNumber 将被赋值为一个 MIN 和 MAX 范围内的随机数
  584. int randNumber = rand.nextInt(9999 - 1000 + 1) + 1000;
  585. // 保存验证码到redis
  586. redisCache.setCacheObject(PHONE_VERIFICATION_CODE_KEY + phone, String.valueOf(randNumber), PHONE_VERIFICATION_CODE_KEY_TIME, TimeUnit.MINUTES);
  587. try {
  588. SMSVerificationCode smsVerificationCode = new SMSVerificationCode(String.valueOf(randNumber));
  589. String jsonString = JSON.toJSONString(smsVerificationCode);
  590. sendSms.sendSms(phone, SendSmsEnum.SMS_220650023, jsonString);
  591. return R.ok("发送成功");
  592. } catch (Exception e) {
  593. e.printStackTrace();
  594. }
  595. return R.fail("发送失败");
  596. }
  597. @ApiOperation("uuid登录")
  598. @PostMapping("/uuidLogin")
  599. @Log(title = "uuid登录", businessType = BusinessType.OTHER)
  600. public R<WxLoginUser> uuidLogin(@RequestBody PhoneLoginBody phoneLoginBody) {
  601. String uuid = phoneLoginBody.getUuid();
  602. if (StringUtils.isEmpty(uuid)) {
  603. return R.fail("uuid不能为空");
  604. }
  605. OneAccountVO oneAccountVO = this.unifiedUserCenterService.queryClients(phoneLoginBody.getUuid());
  606. if (ObjUtil.isNull(oneAccountVO)) {
  607. return R.fail("未查询到用户信息");
  608. }
  609. TWxUser user = wxUserService.getById(oneAccountVO.getUserId());
  610. if (ObjUtil.isNull(user)) {
  611. return R.fail("登录失败");
  612. }
  613. // 构建WxLoginUser
  614. WxLoginUser wxUser = new WxLoginUser();
  615. BeanUtils.copyProperties(user, wxUser);
  616. // 生成并返回令牌
  617. String token = wxTokenService.createToken(wxUser);
  618. log.info("uuid登录生成的token值:{}", token);
  619. if (StringUtils.isEmpty(token)) {
  620. return R.fail("生成令牌失败");
  621. }
  622. wxUser.setToken(token);
  623. // 记录登录信息
  624. AsyncManager.me().execute(AsyncFactory.recordLogininfor(uuid, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  625. log.info("uuid: {} 登录成功", uuid);
  626. return R.ok(wxUser);
  627. }
  628. }