diff --git a/src/main/java/com/gxwebsoft/common/system/controller/WxOfficialController.java b/src/main/java/com/gxwebsoft/common/system/controller/WxOfficialController.java index 716ca8e..edf3e3b 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/WxOfficialController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/WxOfficialController.java @@ -10,6 +10,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alipay.api.internal.util.file.IOUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.qq.weixin.mp.aes.WXBizJsonMsgCrypt; import com.gxwebsoft.common.core.utils.CommonUtil; import com.gxwebsoft.common.core.utils.JSONUtil; import com.gxwebsoft.common.core.utils.RedisUtil; @@ -63,6 +64,15 @@ public class WxOfficialController extends BaseController { private static final String miniAppid = "wx541db955e7a62709"; // 创建公众号菜单 private static final String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="; + // 生成二维码接口 + private static final String QRCODE_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="; + // 查看二维码接口 + private static final String QRCODE_SHOW_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="; + + // 微信服务器配置(从配置文件读取或使用默认值) + private static final String TOKEN = "gxwebsoft"; + private static final String ENCODING_AES_KEY = "ARve4au5GF2fE2cT13xpaHhuqS2yjE34gpVe8IZwd4C"; + @Resource private UserService userService; @Resource @@ -84,7 +94,7 @@ public class WxOfficialController extends BaseController { if (tenantId == null) { return null; } - String token = "gxwebsoft"; + String token = TOKEN; String[] array = new String[]{token, timestamp, nonce}; // 将token、timestamp、nonce三个参数进行字典序排序 Arrays.sort(array); @@ -100,92 +110,240 @@ public class WxOfficialController extends BaseController { return null; } - @Operation(summary = "接收微信的xml消息") + @Operation(summary = "接收微信的消息推送") @Transactional(rollbackFor = {Exception.class}) - @RequestMapping("/{id}") + @PostMapping("/{id}") @ResponseBody - public String receiveMessages(HttpServletRequest request, @PathVariable("id") Integer tenantId) throws IOException { + public String receiveMessages(HttpServletRequest request, @PathVariable("id") Integer tenantId, + @RequestParam(required = false) String msg_signature, + @RequestParam(required = false) String timestamp, + @RequestParam(required = false) String nonce) throws Exception { + System.out.println("========== 接收微信消息 =========="); System.out.println("tenantId = " + tenantId); - Integer userId = 0; // 用户ID + System.out.println("msg_signature = " + msg_signature); + // 从请求中获取XML数据 String xmlData = IOUtils.toString(request.getInputStream(), "UTF-8"); - System.out.println("xmlData = " + xmlData); + System.out.println("原始xmlData = " + xmlData); + + // 如果有加密参数,进行解密 + if (StrUtil.isNotBlank(msg_signature) && StrUtil.isNotBlank(xmlData) && xmlData.contains("Encrypt")) { + try { + WXBizJsonMsgCrypt crypt = new WXBizJsonMsgCrypt(TOKEN, ENCODING_AES_KEY, ""); + xmlData = crypt.DecryptMsg(msg_signature, timestamp, nonce, xmlData); + System.out.println("解密后xmlData = " + xmlData); + } catch (Exception e) { + log.error("消息解密失败: {}", e.getMessage()); + return "error"; + } + } + // 解析XML数据 Document document = XmlUtil.parseXml(xmlData); Element rootElement = XmlUtil.getRootElement(document); + + // 获取消息类型 + Element msgTypeElement = XmlUtil.getElement(rootElement, "MsgType"); + String msgType = msgTypeElement != null ? msgTypeElement.getTextContent() : ""; + System.out.println("msgType = " + msgType); + + // 获取事件类型(如果是事件消息) + Element eventElement = XmlUtil.getElement(rootElement, "Event"); + String event = eventElement != null ? eventElement.getTextContent() : ""; + System.out.println("event = " + event); + + // 获取事件KEY(用于判断是否是扫码事件) + Element eventKeyElement = XmlUtil.getElement(rootElement, "EventKey"); + String eventKey = eventKeyElement != null ? eventKeyElement.getTextContent() : ""; + System.out.println("eventKey = " + eventKey); + + // 获取用户openid Element FromUserName = XmlUtil.getElement(rootElement, "FromUserName"); - String openId = FromUserName.getTextContent(); + String openId = FromUserName != null ? FromUserName.getTextContent() : ""; System.out.println("openId = " + openId); - if (StrUtil.isNotBlank(openId)) { - // 获取用户基本信息(UnionID机制) - final String userStr = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + getAccessToken() + "&openid=" + openId + "&lang=zh_CN"); - // 保存第三方用户信息表shop_user_oauth - final JSONObject jsonObject = JSONObject.parseObject(userStr); - final String unionid = jsonObject.getString("unionid"); - final String subscribe = jsonObject.getString("subscribe"); - System.out.println("unionid = " + unionid); - sendTemplateMessage(openId); - // 关注操作 - if (subscribe != null && subscribe.equals("1")) { - final int count = userOauthService.count(new LambdaQueryWrapper().eq(UserOauth::getOauthType, MP_OFFICIAL).eq(UserOauth::getUnionid, unionid).eq(UserOauth::getTenantId, tenantId)); - System.out.println("count = " + count); - if (count == 0) { - // 其他平台是否有注册过 - final List list = userOauthService.list(new LambdaQueryWrapper().eq(UserOauth::getUnionid, unionid).eq(UserOauth::getDeleted, 0)); - final int size = list.size(); - // 新用户注册 - if (size == 0) { - User user = new User(); - user.setStatus(0); - user.setUsername("wxoff_".concat(RandomUtil.randomString(12))); - user.setStatus(0); - user.setNickname("微信公众号用户"); - user.setPlatform(MP_OFFICIAL); - user.setGradeId(1); - user.setPassword(userService.encodePassword(CommonUtil.randomUUID16())); - user.setTenantId(tenantId); - user.setRecommend(0); - final RoleParam roleParam = new RoleParam(); - roleParam.setTenantId(tenantId); - roleParam.setRoleCode("guest"); - Role role = roleService.getByRoleCode(roleParam); - user.setRoleId(role.getRoleId()); - if (userService.saveUser(user)) { - userId = user.getUserId(); - // 添加用户角色 - final UserRole userRole = new UserRole(); - userRole.setUserId(user.getUserId()); - userRole.setTenantId(user.getTenantId()); - userRole.setRoleId(user.getRoleId()); - userRoleService.save(userRole); - // 同步到 websopy - userSyncService.syncUserToWebsopy(user); - } - System.out.println("新微信公众号用户 = " + userId); - } - // 更新 - if (!CollectionUtils.isEmpty(list)) { - for (UserOauth item : list) { - if (item.getUserId() != null) { - userId = item.getUserId(); - } - } - System.out.println("其他平台有注册过 = " + userId); - } - // 保存第三方用户记录 - final UserOauth userOauth = new UserOauth(); - userOauth.setOauthType(MP_OFFICIAL); - userOauth.setUnionid(unionid); - userOauth.setOauthId(openId); - userOauth.setUserId(userId); - userOauth.setTenantId(tenantId); - boolean save = userOauthService.save(userOauth); - System.out.println("关注微信公众号 = " + save); + + // 获取 ticket(扫码事件专用) + Element ticketElement = XmlUtil.getElement(rootElement, "Ticket"); + String ticket = ticketElement != null ? ticketElement.getTextContent() : ""; + System.out.println("ticket = " + ticket); + + // 处理扫码关注事件 + if ("event".equals(msgType) && ("subscribe".equals(event) || "SCAN".equals(event))) { + System.out.println("========== 处理扫码关注事件 =========="); + + // 获取扫码的 token(从 EventKey 中提取,格式:qrscene_xxx 或直接是 xxx) + String token = ""; + if (StrUtil.isNotBlank(eventKey)) { + if (eventKey.startsWith("qrscene_")) { + token = eventKey.substring(8); // 去掉 qrscene_ 前缀 + } else { + token = eventKey; + } + } + System.out.println("扫码登录token = " + token); + + // 获取用户信息 + if (StrUtil.isNotBlank(openId)) { + // 获取用户基本信息(UnionID机制) + final String userStr = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + getAccessToken() + "&openid=" + openId + "&lang=zh_CN"); + final JSONObject jsonObject = JSONObject.parseObject(userStr); + final String unionid = jsonObject.getString("unionid"); + final String subscribe = jsonObject.getString("subscribe"); + System.out.println("unionid = " + unionid); + System.out.println("subscribe = " + subscribe); + + Integer userId = processWxUser(tenantId, openId, unionid, subscribe); + + // 如果有关联的扫码登录token,完成登录 + if (StrUtil.isNotBlank(token) && userId != null && userId > 0) { + completeQrLogin(token, userId, tenantId); } } - } - return null; + + // 返回 success 表示处理成功 + return "success"; + } + + /** + * 处理微信用户(关注/注册/登录) + */ + private Integer processWxUser(Integer tenantId, String openId, String unionid, String subscribe) { + Integer userId = 0; + + // 关注操作 + if (subscribe != null && subscribe.equals("1")) { + final int count = userOauthService.count(new LambdaQueryWrapper() + .eq(UserOauth::getOauthType, MP_OFFICIAL) + .eq(UserOauth::getUnionid, unionid) + .eq(UserOauth::getTenantId, tenantId)); + System.out.println("已绑定用户数量 = " + count); + + if (count == 0) { + // 检查其他平台是否有注册过 + final List list = userOauthService.list( + new LambdaQueryWrapper() + .eq(UserOauth::getUnionid, unionid) + .eq(UserOauth::getDeleted, 0)); + final int size = list.size(); + + // 新用户注册 + if (size == 0) { + User user = new User(); + user.setStatus(0); + user.setUsername("wxoff_".concat(RandomUtil.randomString(12))); + user.setNickname("微信公众号用户"); + user.setPlatform(MP_OFFICIAL); + user.setGradeId(1); + user.setPassword(userService.encodePassword(CommonUtil.randomUUID16())); + user.setTenantId(tenantId); + user.setRecommend(0); + final RoleParam roleParam = new RoleParam(); + roleParam.setTenantId(tenantId); + roleParam.setRoleCode("guest"); + Role role = roleService.getByRoleCode(roleParam); + user.setRoleId(role.getRoleId()); + if (userService.saveUser(user)) { + userId = user.getUserId(); + // 添加用户角色 + final UserRole userRole = new UserRole(); + userRole.setUserId(user.getUserId()); + userRole.setTenantId(user.getTenantId()); + userRole.setRoleId(user.getRoleId()); + userRoleService.save(userRole); + // 同步到 websopy + userSyncService.syncUserToWebsopy(user); + } + System.out.println("新微信公众号用户 userId = " + userId); + } + + // 更新 + if (!CollectionUtils.isEmpty(list)) { + for (UserOauth item : list) { + if (item.getUserId() != null) { + userId = item.getUserId(); + } + } + System.out.println("其他平台有注册过 userId = " + userId); + } + + // 保存第三方用户记录 + final UserOauth userOauth = new UserOauth(); + userOauth.setOauthType(MP_OFFICIAL); + userOauth.setUnionid(unionid); + userOauth.setOauthId(openId); + userOauth.setUserId(userId); + userOauth.setTenantId(tenantId); + boolean save = userOauthService.save(userOauth); + System.out.println("关注微信公众号保存结果 = " + save); + } else { + // 已绑定用户,获取userId + UserOauth existingUser = userOauthService.getOne(new LambdaQueryWrapper() + .eq(UserOauth::getOauthType, MP_OFFICIAL) + .eq(UserOauth::getUnionid, unionid) + .eq(UserOauth::getTenantId, tenantId)); + if (existingUser != null) { + userId = existingUser.getUserId(); + } + } + } + + return userId; + } + + /** + * 完成扫码登录 + */ + private void completeQrLogin(String token, Integer userId, Integer tenantId) { + try { + // 更新扫码登录状态 + String redisKey = "QR_LOGIN_TOKEN:" + token; + JSONObject qrLoginData = new JSONObject(); + qrLoginData.put("status", "confirmed"); + qrLoginData.put("userId", userId); + qrLoginData.put("tenantId", tenantId); + qrLoginData.put("confirmTime", System.currentTimeMillis()); + // 保存1分钟,给前端足够时间获取 + redisUtil.set(redisKey, qrLoginData.toJSONString(), 60L, TimeUnit.SECONDS); + System.out.println("扫码登录完成,token=" + token + ", userId=" + userId); + } catch (Exception e) { + log.error("完成扫码登录失败: {}", e.getMessage()); + } + } + + @Operation(summary = "生成微信扫码登录二维码") + @GetMapping("/qrcode/{token}") + public ApiResult generateQrCode(@PathVariable("token") String token) { + try { + // 生成带参数的二维码,scene 为 token + String url = QRCODE_CREATE_URL + getAccessToken(); + + // 创建临时二维码(有效期7天),scene_str 最大32个可见字符 + JSONObject params = new JSONObject(); + params.put("action_info", new JSONObject().put("scene", new JSONObject().put("scene_str", token))); + params.put("action_name", "QR_STR_SCENE"); + params.put("expire_seconds", 604800); // 7天有效期 + + String result = HttpRequest.post(url) + .body(params.toJSONString()) + .timeout(10000) + .execute().body(); + + System.out.println("生成二维码结果: " + result); + + JSONObject jsonResult = JSONObject.parseObject(result); + if (jsonResult.containsKey("ticket")) { + String ticket = jsonResult.getString("ticket"); + String qrCodeUrl = QRCODE_SHOW_URL + java.net.URLEncoder.encode(ticket, "UTF-8"); + + return success(qrCodeUrl); + } else { + return fail("生成二维码失败: " + result); + } + } catch (Exception e) { + log.error("生成二维码异常: {}", e.getMessage()); + return fail("生成二维码异常: " + e.getMessage()); + } } private void sendTemplateMessage(String openId) {