WeChatController.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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.extra.qrcode.QrCodeUtil;
  5. import cn.hutool.extra.qrcode.QrConfig;
  6. import cn.hutool.json.JSONObject;
  7. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  8. import com.ylx.common.annotation.Log;
  9. import com.ylx.common.config.RuoYiConfig;
  10. import com.ylx.common.constant.Constants;
  11. import com.ylx.common.core.controller.BaseController;
  12. import com.ylx.common.core.domain.AjaxResult;
  13. import com.ylx.common.core.domain.R;
  14. import com.ylx.common.core.domain.model.WxLoginUser;
  15. import com.ylx.common.core.redis.RedisCache;
  16. import com.ylx.common.enums.BusinessType;
  17. import com.ylx.common.utils.MessageUtils;
  18. import com.ylx.common.utils.StringUtils;
  19. import com.ylx.common.utils.file.FileUploadUtils;
  20. import com.ylx.common.utils.file.FileUtils;
  21. import com.ylx.framework.config.ServerConfig;
  22. import com.ylx.framework.manager.AsyncManager;
  23. import com.ylx.framework.manager.factory.AsyncFactory;
  24. import com.ylx.framework.web.service.WxTokenService;
  25. import com.ylx.massage.domain.CouponReceive;
  26. import com.ylx.massage.domain.TJs;
  27. import com.ylx.massage.domain.TWxUser;
  28. import com.ylx.massage.domain.TbFile;
  29. import com.ylx.massage.service.CouponReceiveService;
  30. import com.ylx.massage.service.TJsService;
  31. import com.ylx.massage.service.TWxUserService;
  32. import com.ylx.massage.service.TbFileService;
  33. import com.ylx.massage.utils.DateTimeUtils;
  34. import com.ylx.massage.utils.JsSignUtil;
  35. import com.ylx.massage.utils.StringUtilsMassage;
  36. import com.ylx.massage.utils.WeChatUtil;
  37. import io.swagger.annotations.Api;
  38. import io.swagger.annotations.ApiOperation;
  39. import lombok.extern.slf4j.Slf4j;
  40. import org.springframework.beans.BeanUtils;
  41. import org.springframework.beans.factory.annotation.Autowired;
  42. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  43. import org.springframework.util.CollectionUtils;
  44. import org.springframework.web.bind.annotation.*;
  45. import org.springframework.web.multipart.MultipartFile;
  46. import javax.annotation.Resource;
  47. import javax.servlet.http.HttpServletRequest;
  48. import javax.servlet.http.HttpServletResponse;
  49. import java.io.*;
  50. import java.net.MalformedURLException;
  51. import java.net.URL;
  52. import java.net.URLEncoder;
  53. import java.nio.file.Files;
  54. import java.nio.file.Paths;
  55. import java.util.*;
  56. import java.util.concurrent.TimeUnit;
  57. import static com.ylx.massage.utils.OtherUtil.verification;
  58. /**
  59. * @author b16mt
  60. */
  61. @Slf4j
  62. @RestController
  63. @Api(tags = {"微信服务号"})
  64. @RequestMapping("/weChat")
  65. public class WeChatController extends BaseController {
  66. private final static String TOKEN = "abcd1234";
  67. private final static String ENCODING = "UTF-8";
  68. private final static String ACCESS_TOKEN = "access_token";
  69. private final static String REFRESH_TOKEN = "refresh_token";
  70. private final static String OPEN_ID = "openid";
  71. /**
  72. * 二维码保存路径
  73. */
  74. private String IMG_PATH = "D:\\Users\\";
  75. @Resource
  76. private WeChatUtil weChatUtil;
  77. @Resource
  78. private TWxUserService wxUserService;
  79. @Resource(name = "commonAsyncExecutor")
  80. private ThreadPoolTaskExecutor threadPoolTaskExecutor;
  81. @Autowired
  82. private CouponReceiveService couponReceiveService;
  83. @Resource
  84. private WxTokenService wxTokenService;
  85. @Resource
  86. private JsSignUtil jsSignUtil;
  87. @Autowired
  88. private TbFileService tbFileService;
  89. @Resource
  90. private TJsService jsService;
  91. @Autowired
  92. private RedisCache redisCache;
  93. /**
  94. * 微信Token验证
  95. *
  96. * @param signature 微信加密签名
  97. * @param timestamp 时间戳
  98. * @param nonce 随机数
  99. * @param echostr 随机字符串
  100. * @param response HTTP响应对象
  101. * @throws Exception 如果处理过程中出现错误
  102. */
  103. @GetMapping("/verifyToken")
  104. @Log(title = "公众号pverifyToken", businessType = BusinessType.OTHER)
  105. public void verifyToken(@RequestParam(value = "signature") String signature,
  106. @RequestParam(value = "timestamp") String timestamp,
  107. @RequestParam(value = "nonce") String nonce,
  108. @RequestParam(value = "echostr") String echostr, HttpServletResponse response) throws Exception {
  109. log.info("微信Token验证 入参: signature:{},timestamp:{},nonce:{},echostr:{},", signature, timestamp, nonce, echostr);
  110. // 参数排序
  111. String[] params = new String[]{timestamp, nonce, TOKEN};
  112. Arrays.sort(params);
  113. // 校验成功则响应 echostr,失败则不响应
  114. if (verification(params, signature) && echostr != null) {
  115. response.setCharacterEncoding(ENCODING);
  116. response.getWriter().write(echostr);
  117. response.getWriter().flush();
  118. response.getWriter().close();
  119. }
  120. }
  121. @GetMapping("/setMenu")
  122. @ApiOperation("设置菜单")
  123. @Log(title = "设置菜单", businessType = BusinessType.OTHER)
  124. public Map<?,?> setMenu() {
  125. //获取access_token
  126. String token = weChatUtil.getToken();
  127. //获取的二维码ticket
  128. return weChatUtil.menuUtil(token);
  129. }
  130. @GetMapping("/getSignature")
  131. @ApiOperation("前端获取jssdk签名")
  132. @Log(title = "前端获取jssdk签名", businessType = BusinessType.OTHER)
  133. public Map<?,?> getSignature(String url) {
  134. //获取access_token
  135. String token = weChatUtil.getToken();
  136. //获取jsapi_ticket
  137. String jsapiTicket = weChatUtil.getJsapiTicket(token);
  138. //生成签名
  139. return jsSignUtil.sign(url,jsapiTicket);
  140. }
  141. /**
  142. * 处理微信公众号请求信息
  143. *
  144. * @param request
  145. * @return
  146. */
  147. @RequestMapping("/verifyToken")
  148. @ResponseBody
  149. @Log(title = "处理微信公众号请求信息", businessType = BusinessType.OTHER)
  150. public String handlePublicMsg(HttpServletRequest request) throws Exception {
  151. log.info("处理微信公众号请求信息:{}", request.toString());
  152. // 获得微信端返回的xml数据
  153. InputStream is = null;
  154. InputStreamReader isr = null;
  155. BufferedReader br = null;
  156. try {
  157. is = request.getInputStream();
  158. isr = new InputStreamReader(is, "utf-8");
  159. br = new BufferedReader(isr);
  160. String str = null;
  161. StringBuffer returnXml = new StringBuffer();
  162. while ((str = br.readLine()) != null) {
  163. //返回的是xml数据
  164. returnXml.append(str);
  165. }
  166. log.info("微信端返回的xml数据:{}", returnXml);
  167. Map<String, String> encryptMap = WeChatUtil.xmlToMap(returnXml.toString());
  168. // 得到公众号传来的加密信息并解密,得到的是明文xml数据
  169. // String decryptXml = WXPublicUtils.decrypt(encryptMap.get("Encrypt"));
  170. // 将xml数据转换为map
  171. // Map<String, String> decryptMap = WeChatUtil.xmlToMap(decryptXml);
  172. // 区分消息类型
  173. String msgType = encryptMap.get("MsgType");
  174. // 普通消息
  175. if ("text".equals(msgType)) { // 文本消息
  176. // todo 处理文本消息
  177. } else if ("image".equals(msgType)) { // 图片消息
  178. // todo 处理图片消息
  179. } else if ("voice".equals(msgType)) { //语音消息
  180. // todo 处理语音消息
  181. } else if ("video".equals(msgType)) { // 视频消息
  182. // todo 处理视频消息
  183. } else if ("shortvideo".equals(msgType)) { // 小视频消息
  184. // todo 处理小视频消息
  185. } else if ("location".equals(msgType)) { // 地理位置消息
  186. // todo 处理地理位置消息
  187. } else if ("link".equals(msgType)) { // 链接消息
  188. // todo 处理链接消息
  189. }
  190. // 事件推送
  191. else if ("event".equals(msgType)) { // 事件消息
  192. // 区分事件推送
  193. String event = encryptMap.get("Event");
  194. if ("subscribe".equals(event)) { // 订阅事件 或 未关注扫描二维码事件
  195. return getString(encryptMap);
  196. } else if ("unsubscribe".equals(event)) { // 取消订阅事件
  197. // todo 处理取消订阅事件
  198. } else if ("SCAN".equals(event)) { // 已关注扫描二维码事件
  199. return getString(encryptMap);
  200. } else if ("LOCATION".equals(event)) { // 上报地理位置事件
  201. // todo 处理上报地理位置事件
  202. } else if ("CLICK".equals(event)) { // 点击菜单拉取消息时的事件推送事件
  203. // todo 处理点击菜单拉取消息时的事件推送事件
  204. } else if ("VIEW".equals(event)) { // 点击菜单跳转链接时的事件推送
  205. // todo 处理点击菜单跳转链接时的事件推送
  206. }
  207. }
  208. } catch (Exception e) {
  209. logger.error("处理微信公众号请求信息,失败", e);
  210. } finally {
  211. if (null != is) {
  212. is.close();
  213. }
  214. if (null != isr) {
  215. isr.close();
  216. }
  217. if (null != br) {
  218. br.close();
  219. }
  220. }
  221. return null;
  222. }
  223. private String getString(Map<String, String> encryptMap) throws Exception {
  224. // 返回消息时ToUserName的值与FromUserName的互换
  225. Map<String, String> returnMap = new HashMap<>();
  226. String content ="欢迎来到广誉源"+"\n" +
  227. "\n" +
  228. "广誉源是一家快速上门服务预约平台,提供正规 绿色 快捷上门服务,提供按摩、推拿、养生、SPA等服务,专业针对居家、差旅、酒店等顾客提供便捷健康养生服务";
  229. //添加新用户
  230. TWxUser fromUserName = wxUserService.getByOpenId(encryptMap.get("FromUserName"));
  231. if (fromUserName == null) {
  232. fromUserName = new TWxUser();
  233. }
  234. fromUserName.setcOpenid(encryptMap.get("FromUserName"));
  235. //fromUserName.setcUpUser(StringUtilsMassage.afterString(encryptMap.get("EventKey"), "qrscene_"));
  236. fromUserName.setRole(1);
  237. wxUserService.saveOrUpdate(fromUserName);
  238. //绑定技师
  239. TJs byId = jsService.getById(StringUtilsMassage.afterString(encryptMap.get("EventKey"), "qrscene_"));
  240. List<TJs> byOpenid = jsService.list(new LambdaQueryWrapper<TJs>().eq(TJs::getcOpenId, encryptMap.get("FromUserName")));
  241. if (CollectionUtil.isNotEmpty(byOpenid)) {
  242. content = "你已有绑定的账号如需换绑请先解绑";
  243. }
  244. if (StringUtils.isNotEmpty(byId.getcOpenId())) {
  245. content = "该账号已被绑定,请选择其他账号";
  246. } else if (CollectionUtil.isEmpty(byOpenid)) {
  247. TJs tJs = new TJs();
  248. tJs.setId(StringUtilsMassage.afterString(encryptMap.get("EventKey"), "qrscene_"));
  249. tJs.setcOpenId(encryptMap.get("FromUserName"));
  250. jsService.updateById(tJs);
  251. }
  252. returnMap.put("ToUserName", encryptMap.get("FromUserName"));
  253. returnMap.put("FromUserName", encryptMap.get("ToUserName"));
  254. returnMap.put("CreateTime", new Date().getTime() + "");
  255. returnMap.put("MsgType", "text");
  256. returnMap.put("Content", content);
  257. String encryptMsg = weChatUtil.mapToXml(returnMap).toString();
  258. return encryptMsg;
  259. }
  260. /**
  261. * 获取微信code
  262. *
  263. * @param state 状态参数
  264. * @return String
  265. */
  266. @ApiOperation("获取微信code")
  267. @Log(title = "获取微信code", businessType = BusinessType.OTHER)
  268. @GetMapping("/getCode")
  269. public String weiXinLogin(String state) {
  270. // QrConfig config = new QrConfig(300, 300);
  271. // 设置边距,即二维码和背景之间的边距
  272. // config.setMargin(1);
  273. // 生成二维码到文件,也可以到流
  274. String code = weChatUtil.getCode(state);
  275. log.info("code的值:{}", code);
  276. redisCache.setCacheObject("code", state, 10, TimeUnit.MINUTES);
  277. // QrCodeUtil.generate(code, config,
  278. // FileUtil.file(IMG_PATH));
  279. return code;
  280. }
  281. /**
  282. * 获取token和userInfo
  283. *
  284. * @param code 微信授权码
  285. * @return 访问令牌
  286. */
  287. @GetMapping("/getAccessToken")
  288. @ApiOperation("公众号网页登录")
  289. @Log(title = "公众号网页登录", businessType = BusinessType.OTHER)
  290. public R<WxLoginUser> getAccessToken(@RequestParam String code) {
  291. // 发送get请求获取 AccessToken
  292. Map<?, ?> result = weChatUtil.getAccessToken(code);
  293. String accessToken = result.get(ACCESS_TOKEN).toString();
  294. String refreshToken = result.get(REFRESH_TOKEN).toString();
  295. String openid = result.get(OPEN_ID).toString();
  296. // 如果用户是第一次进行微信公众号授权
  297. // 进行这一步时用户应点击了同意授权按钮
  298. String userInfoJsom = weChatUtil.getUserInfo(accessToken, openid);
  299. // 解析JSON数据
  300. JSONObject jsonObject = new JSONObject(userInfoJsom);
  301. log.info("公众号网页登录,{}",jsonObject);
  302. // 将用户信息保存到数据库中
  303. LambdaQueryWrapper<TWxUser> objectLambdaQueryWrapper = new LambdaQueryWrapper<>();
  304. objectLambdaQueryWrapper.eq(TWxUser::getcOpenid, openid);
  305. TWxUser user = wxUserService.getOne(objectLambdaQueryWrapper);
  306. if (user == null || StringUtils.isEmpty(user.getcNickName())) {
  307. if(user == null){
  308. user = new TWxUser();
  309. user.setcOpenid(openid);
  310. TWxUser finalUser = user;
  311. //异步 添加新人优惠卷
  312. // threadPoolTaskExecutor.submit(() -> couponReceiveService.submit(new CouponReceive().setOpenid(finalUser.getcOpenid()).setCouponId("1")));
  313. }
  314. user.setcOpenid(openid);
  315. user.setcNickName(jsonObject.get("nickname").toString());
  316. user.setcIcon(jsonObject.get("headimgurl").toString());
  317. user.setcSessionKey(refreshToken);
  318. // user.setcPhone(phoneNumber);
  319. wxUserService.saveOrUpdate(user);
  320. user.setId(user.getId());
  321. }
  322. WxLoginUser wxUser = new WxLoginUser();
  323. BeanUtils.copyProperties(user, wxUser);
  324. // 生成并返回令牌
  325. String token = wxTokenService.createToken(wxUser);
  326. if (token == null || token.isEmpty()) {
  327. return R.fail("生成令牌失败");
  328. }
  329. wxUser.setToken(token);
  330. // 返回用户信息
  331. // 记录登录信息
  332. AsyncManager.me().execute(AsyncFactory.recordLogininfor(wxUser.getCOpenid(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  333. return R.ok(wxUser);
  334. }
  335. /**
  336. * 刷新token,微信提供的token是有限时间的,但是对于财务报销系统仅需授权一次的情况下一般不需要进行更新
  337. *
  338. * @return accessToken
  339. */
  340. @GetMapping("/refreshToken")
  341. public String refreshToken() {
  342. WxLoginUser wxLoginUser = this.getWxLoginUser();
  343. TWxUser user = wxUserService.getByOpenId(wxLoginUser.getCOpenid());
  344. if (user == null) {
  345. throw new RuntimeException("用户不存在");
  346. }
  347. // 发送get请求获取 RefreshToken
  348. Map<?, ?> result = weChatUtil.refreshToken(user.getcSessionKey());
  349. String accessToken = result.get(ACCESS_TOKEN).toString();
  350. String refreshToken = result.get(REFRESH_TOKEN).toString();
  351. // 更新用户信息
  352. user.setcSessionKey(refreshToken);
  353. // 存储数据库
  354. wxUserService.updateById(user);
  355. return accessToken;
  356. }
  357. @ApiOperation("获取公众号二维码")
  358. @RequestMapping(value = "getwxQrCode", method = RequestMethod.GET)
  359. public Map<?, ?> getWxQrCodeUtil(@RequestParam String openId) {
  360. //获取access_token
  361. String token = weChatUtil.getToken();
  362. //获取的二维码ticket
  363. return weChatUtil.getTicket(token, openId);
  364. }
  365. @ApiOperation("获取JS公众号二维码ticket")
  366. @RequestMapping(value = "getJSwxQrCode", method = RequestMethod.GET)
  367. public void getJSwxQrCode(@RequestParam String jsId) {
  368. //获取access_token
  369. String token = weChatUtil.getToken();
  370. //获取的二维码ticket
  371. Map<?, ?> jsTicket = weChatUtil.getJsTicket(token, jsId);
  372. //获取的二维码ticket
  373. //获取二维码图片
  374. //String qrCodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
  375. //获取二维码图片
  376. //File file = QrCodeUtil.generate(qrCodeUrl, config, FileUtil.file(RuoYiConfig.getUploadPath() + "/code.png"));
  377. TJs tjs = new TJs();
  378. tjs.setId(jsId);
  379. tjs.setTicket(jsTicket.get("ticket").toString());
  380. jsService.updateById(tjs);
  381. }
  382. /**
  383. * 兑换技师公众号二维码
  384. *
  385. * @param ticket
  386. * @param id
  387. * @return String
  388. * @throws UnsupportedEncodingException
  389. */
  390. @ApiOperation("兑换技师公众号二维码")
  391. @RequestMapping(value = "getJSwxQrCodeDh", method = RequestMethod.GET)
  392. public String getJSwxQrCode1(@RequestParam String ticket,@RequestParam String id) throws UnsupportedEncodingException {
  393. String qrCodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + URLEncoder.encode(ticket, "UTF-8");
  394. String filePath = RuoYiConfig.getUploadEwmPath() + id +".png";
  395. log.info("二维码地址:{}", filePath);
  396. try (InputStream in = new URL(qrCodeUrl).openStream();
  397. OutputStream out = Files.newOutputStream(Paths.get(filePath))) {
  398. byte[] buffer = new byte[1024];
  399. int bytesRead;
  400. while ((bytesRead = in.read(buffer)) != -1) {
  401. out.write(buffer, 0, bytesRead);
  402. }
  403. } catch (Exception e) {
  404. e.printStackTrace();
  405. }
  406. int lastSlashIndex = filePath.lastIndexOf("/");
  407. String fileName = filePath.substring(lastSlashIndex + 1);
  408. return Constants.RESOURCE_PREFIX + "/ewm/" + fileName;
  409. }
  410. @ApiOperation("获取公众号网页二维码")
  411. @GetMapping("/getweQrCode")
  412. public AjaxResult weiXinLogin1(String openId) {
  413. QrConfig config = new QrConfig(300, 300);
  414. // 设置边距,即二维码和背景之间的边距
  415. config.setMargin(1);
  416. // 生成二维码到文件,也可以到流
  417. String code = "https://www.baidu.com?openId=" + openId;
  418. log.info("code:{}", code);
  419. String str = IMG_PATH;
  420. File generate = QrCodeUtil.generate(code, config, FileUtil.file(RuoYiConfig.getUploadPath() + "/code.png"));
  421. MultipartFile multipartFile = FileUploadUtils.getMultipartFile(generate);
  422. return tbFileService.uploadFile(multipartFile);
  423. }
  424. }