WeChatController.java 15 KB

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