Commit a945cf0b by zhangxingmin

push

parent 0d5ba9d2
...@@ -29,8 +29,6 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF ...@@ -29,8 +29,6 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF
/** /**
* 生成登录二维码 * 生成登录二维码
* @param request 请求参数
* @return 二维码信息
*/ */
@Override @Override
public Result<ApiGenerateQrCodeResponse> generateLoginQrCode(ApiGenerateLoginQrCodeRequest request) { public Result<ApiGenerateQrCodeResponse> generateLoginQrCode(ApiGenerateLoginQrCodeRequest request) {
...@@ -38,20 +36,23 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF ...@@ -38,20 +36,23 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF
} }
/** /**
* 扫码回调(微信服务器调用,xml解析出来二维码业务类型,根据业务类型处理不同的业务逻辑,目前两种类型:分享绑定和扫码登录) * 微信服务器配置验证(GET)
* @param appId 公众号appId
* @param xml 微信XML
* @return success
*/ */
@Override @Override
public String loginWechatCallback(String appId, String xml) { public String verifyWechatUrl(String signature, String timestamp, String nonce, String echostr) {
return apiThirdPartyScanLoginService.handleLoginWechatCallback(appId, xml); return apiThirdPartyScanLoginService.verifyWechatUrl(signature, timestamp, nonce, echostr);
}
/**
* 微信事件推送回调(POST)
*/
@Override
public String loginWechatCallback(String xml) {
return apiThirdPartyScanLoginService.handleLoginWechatCallback(xml);
} }
/** /**
* 查询扫码状态 * 查询扫码状态
* @param qrId 二维码参数
* @return 扫码状态
*/ */
@Override @Override
public Result<ApiScanStatusResponse> queryScanStatus(String qrId) { public Result<ApiScanStatusResponse> queryScanStatus(String qrId) {
...@@ -60,8 +61,6 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF ...@@ -60,8 +61,6 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF
/** /**
* 校验 loginTicket * 校验 loginTicket
* @param request 校验参数
* @return 校验结果
*/ */
@Override @Override
public Result<ApiVerifyTicketResponse> verifyTicket(ApiVerifyTicketRequest request) { public Result<ApiVerifyTicketResponse> verifyTicket(ApiVerifyTicketRequest request) {
...@@ -70,8 +69,6 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF ...@@ -70,8 +69,6 @@ public class ApiThirdPartyScanLoginController implements ApiThirdPartyScanLoginF
/** /**
* 消费 loginTicket * 消费 loginTicket
* @param request 消费参数
* @return 是否成功
*/ */
@Override @Override
public Result<Boolean> consumeTicket(ApiVerifyTicketRequest request) { public Result<Boolean> consumeTicket(ApiVerifyTicketRequest request) {
......
...@@ -14,37 +14,40 @@ public interface ApiThirdPartyScanLoginService { ...@@ -14,37 +14,40 @@ public interface ApiThirdPartyScanLoginService {
/** /**
* 生成登录二维码 * 生成登录二维码
* @param request 请求参数
* @return 二维码信息
*/ */
Result<ApiGenerateQrCodeResponse> generateLoginQrCode(ApiGenerateLoginQrCodeRequest request); Result<ApiGenerateQrCodeResponse> generateLoginQrCode(ApiGenerateLoginQrCodeRequest request);
/** /**
* 扫码回调(微信服务器调用,xml解析出来二维码业务类型,根据业务类型处理不同的业务逻辑,目前两种类型:分享绑定和扫码登录) * 微信服务器配置验证(GET)
* @param appId 公众号appId *
* @param xml 微信XML * @param signature 微信加密签名
* @return success * @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return echostr 或 "invalid signature"
*/ */
String handleLoginWechatCallback(String appId, String xml); String verifyWechatUrl(String signature, String timestamp, String nonce, String echostr);
/**
* 微信事件推送回调(POST)
*
* @param xml 微信XML事件内容
* @return "success"
*/
String handleLoginWechatCallback(String xml);
/** /**
* 查询扫码状态 * 查询扫码状态
* @param qrId 二维码参数
* @return 扫码状态
*/ */
Result<ApiScanStatusResponse> queryScanStatus(String qrId); Result<ApiScanStatusResponse> queryScanStatus(String qrId);
/** /**
* 校验 loginTicket * 校验 loginTicket
* @param request 校验参数
* @return 校验结果
*/ */
Result<ApiVerifyTicketResponse> verifyTicket(ApiVerifyTicketRequest request); Result<ApiVerifyTicketResponse> verifyTicket(ApiVerifyTicketRequest request);
/** /**
* 消费 loginTicket * 消费 loginTicket
* @param request 消费参数
* @return 是否成功
*/ */
Result<Boolean> consumeTicket(ApiVerifyTicketRequest request); Result<Boolean> consumeTicket(ApiVerifyTicketRequest request);
} }
...@@ -10,11 +10,15 @@ import com.yd.user.feign.request.thirdscanlogin.ApiVerifyTicketRequest; ...@@ -10,11 +10,15 @@ import com.yd.user.feign.request.thirdscanlogin.ApiVerifyTicketRequest;
import com.yd.user.feign.response.thirdscanlogin.ApiGenerateQrCodeResponse; import com.yd.user.feign.response.thirdscanlogin.ApiGenerateQrCodeResponse;
import com.yd.user.feign.response.thirdscanlogin.ApiScanStatusResponse; import com.yd.user.feign.response.thirdscanlogin.ApiScanStatusResponse;
import com.yd.user.feign.response.thirdscanlogin.ApiVerifyTicketResponse; import com.yd.user.feign.response.thirdscanlogin.ApiVerifyTicketResponse;
import com.yd.user.service.model.ThirdPartyPlatform;
import com.yd.user.service.service.IThirdPartyPlatformService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
/** /**
* 第三方扫码登录 API Service 实现 * 第三方扫码登录 API Service 实现
*/ */
...@@ -28,10 +32,11 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin ...@@ -28,10 +32,11 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin
@Autowired @Autowired
private WeChatApiUtil weChatApiUtil; private WeChatApiUtil weChatApiUtil;
@Autowired
private IThirdPartyPlatformService thirdPartyPlatformService;
/** /**
* 生成登录二维码 * 生成登录二维码
* @param request 请求参数
* @return 二维码信息
*/ */
@Override @Override
public Result<ApiGenerateQrCodeResponse> generateLoginQrCode(ApiGenerateLoginQrCodeRequest request) { public Result<ApiGenerateQrCodeResponse> generateLoginQrCode(ApiGenerateLoginQrCodeRequest request) {
...@@ -40,41 +45,91 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin ...@@ -40,41 +45,91 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin
} }
/** /**
* 扫码回调(微信服务器调用,xml解析出来二维码业务类型,根据业务类型处理不同的业务逻辑,目前两种类型:分享绑定和扫码登录) * 微信服务器配置验证(GET)
* @param appId 公众号appId * 不再依赖路径中的 appId,遍历所有已启用的公众号,用各自 token 计算签名,
* @param xml 微信XML * 匹配成功即返回 echostr。
* @return success *
* @param signature 微信加密签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return echostr 或 "invalid signature"
*/
@Override
public String verifyWechatUrl(String signature, String timestamp, String nonce, String echostr) {
// 1. 查询所有已启用的公众号
List<ThirdPartyPlatform> platformList = thirdPartyPlatformService.listEnabledGzh();
if (platformList == null || platformList.isEmpty()) {
log.warn("未找到任何已启用的公众号,无法进行URL验证");
return "invalid signature";
}
// 2. 遍历公众号,用各自 token 计算签名匹配
for (ThirdPartyPlatform platform : platformList) {
if (StringUtils.isBlank(platform.getToken())) {
continue;
}
boolean valid = weChatApiUtil.checkSignature(platform.getToken(), signature, timestamp, nonce);
if (valid) {
log.info("微信URL验证通过,appId={}, platformCode={}", platform.getAppId(), platform.getPlatformCode());
return echostr;
}
}
log.warn("微信URL验证失败,signature={}, timestamp={}, nonce={}", signature, timestamp, nonce);
return "invalid signature";
}
/**
* 微信事件推送回调(POST)
* 从 XML 的 ToUserName 反查公众号配置,拿到 appId 后按 EventKey 中的 bizType 分流。
*
* @param xml 微信XML事件内容
* @return "success"
*/ */
@Override @Override
public String handleLoginWechatCallback(String appId, String xml) { public String handleLoginWechatCallback(String xml) {
//解析微信XML // 1. 解析 XML 的 ToUserName(公众号原始ID)
String toUserName = weChatApiUtil.getToUserName(xml);
if (StringUtils.isBlank(toUserName)) {
log.warn("微信回调 ToUserName 为空,xml={}", xml);
return "fail";
}
// 2. 用 ToUserName 反查公众号配置
ThirdPartyPlatform platform = thirdPartyPlatformService.getByToUserName(toUserName);
if (platform == null) {
log.warn("未找到 ToUserName 对应的公众号,toUserName={}", toUserName);
return "fail";
}
String appId = platform.getAppId();
log.info("微信回调识别公众号成功,toUserName={}, appId={}", toUserName, appId);
// 3. 解析 EventKey 并分流处理
String eventKey = weChatApiUtil.getEventKey(xml); String eventKey = weChatApiUtil.getEventKey(xml);
if (StringUtils.isBlank(eventKey)) { if (StringUtils.isBlank(eventKey)) {
log.warn("扫码回调参数缺失,eventKey={}", eventKey); log.warn("扫码回调参数缺失,eventKey={}", eventKey);
return "fail"; return "fail";
} }
//解析eventKey // 未关注用户扫码关注时,EventKey 带 qrscene_ 前缀,这里去掉
if (eventKey.startsWith("qrscene_")) {
eventKey = eventKey.substring("qrscene_".length());
}
String[] parts = eventKey.split(":"); String[] parts = eventKey.split(":");
if (parts.length < 1) { if (parts.length < 1) {
log.warn("登录扫码 EventKey 格式错误:{}", eventKey); log.warn("登录扫码 EventKey 格式错误:{}", eventKey);
return "fail"; return "fail";
} }
//二维码业务类型
String bizType = parts[0]; String bizType = parts[0];
if (ScanLoginConstants.BIZ_TYPE_LOGIN.equals(bizType)) { if (ScanLoginConstants.BIZ_TYPE_LOGIN.equals(bizType)) {
//二维码业务类型:扫码登录
scanLoginService.handleLoginWechatCallback(appId, xml); scanLoginService.handleLoginWechatCallback(appId, xml);
}else if (ScanLoginConstants.BIZ_TYPE_SHARE_BIND.equals(bizType)) { } else if (ScanLoginConstants.BIZ_TYPE_SHARE_BIND.equals(bizType)) {
//二维码业务类型:分享绑定
scanLoginService.handleShareWechatCallback(appId, xml); scanLoginService.handleShareWechatCallback(appId, xml);
} else {
log.warn("未知的二维码业务类型:{}", bizType);
} }
return "success"; return "success";
} }
/** /**
* 查询扫码状态 * 查询扫码状态
* @param qrId 二维码参数
* @return 扫码状态
*/ */
@Override @Override
public Result<ApiScanStatusResponse> queryScanStatus(String qrId) { public Result<ApiScanStatusResponse> queryScanStatus(String qrId) {
...@@ -84,8 +139,6 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin ...@@ -84,8 +139,6 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin
/** /**
* 校验 loginTicket * 校验 loginTicket
* @param request 校验参数
* @return 校验结果
*/ */
@Override @Override
public Result<ApiVerifyTicketResponse> verifyTicket(ApiVerifyTicketRequest request) { public Result<ApiVerifyTicketResponse> verifyTicket(ApiVerifyTicketRequest request) {
...@@ -95,8 +148,6 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin ...@@ -95,8 +148,6 @@ public class ApiThirdPartyScanLoginServiceImpl implements ApiThirdPartyScanLogin
/** /**
* 消费 loginTicket * 消费 loginTicket
* @param request 消费参数
* @return 是否成功
*/ */
@Override @Override
public Result<Boolean> consumeTicket(ApiVerifyTicketRequest request) { public Result<Boolean> consumeTicket(ApiVerifyTicketRequest request) {
......
...@@ -6,6 +6,8 @@ import com.yd.common.utils.RedisUtil; ...@@ -6,6 +6,8 @@ import com.yd.common.utils.RedisUtil;
import com.yd.user.service.model.ThirdPartyPlatform; import com.yd.user.service.model.ThirdPartyPlatform;
import com.yd.user.service.service.IThirdPartyPlatformService; import com.yd.user.service.service.IThirdPartyPlatformService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -18,6 +20,7 @@ import javax.xml.parsers.DocumentBuilder; ...@@ -18,6 +20,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
...@@ -220,4 +223,34 @@ public class WeChatApiUtil { ...@@ -220,4 +223,34 @@ public class WeChatApiUtil {
} }
return null; return null;
} }
/**
* 解析微信 XML 获取 ToUserName(公众号原始ID)
*
* @param xml 微信回调 XML
* @return ToUserName 字段值
*/
public String getToUserName(String xml) {
return getXmlField(xml, "ToUserName");
}
/**
* 校验微信签名
*
* @param token 公众号配置的 Token
* @param signature 微信传来的 signature
* @param timestamp 微信传来的 timestamp
* @param nonce 微信传来的 nonce
* @return 校验是否通过
*/
public boolean checkSignature(String token, String signature, String timestamp, String nonce) {
if (StringUtils.isAnyBlank(token, signature, timestamp, nonce)) {
return false;
}
String[] arr = {token, timestamp, nonce};
Arrays.sort(arr);
String str = arr[0] + arr[1] + arr[2];
String sha1 = DigestUtils.sha1Hex(str);
return sha1.equals(signature);
}
} }
...@@ -14,7 +14,8 @@ import org.springframework.web.bind.annotation.*; ...@@ -14,7 +14,8 @@ import org.springframework.web.bind.annotation.*;
/** /**
* 第三方扫码登录 Feign 客户端 * 第三方扫码登录 Feign 客户端
*/ */
@FeignClient(name = "yd-user-api", path = "/user/api/thirdPartyScanLogin", fallbackFactory = ApiThirdPartyScanLoginFeignFallbackFactory.class) @FeignClient(name = "yd-user-api", path = "/user/api/thirdPartyScanLogin",
fallbackFactory = ApiThirdPartyScanLoginFeignFallbackFactory.class)
public interface ApiThirdPartyScanLoginFeignClient { public interface ApiThirdPartyScanLoginFeignClient {
/** /**
...@@ -27,13 +28,32 @@ public interface ApiThirdPartyScanLoginFeignClient { ...@@ -27,13 +28,32 @@ public interface ApiThirdPartyScanLoginFeignClient {
@Validated @RequestBody ApiGenerateLoginQrCodeRequest request); @Validated @RequestBody ApiGenerateLoginQrCodeRequest request);
/** /**
* 扫码回调(微信服务器调用,xml解析出来二维码业务类型,根据业务类型处理不同的业务逻辑,目前两种类型:分享绑定和扫码登录) * 微信服务器配置验证(GET)
* @param appId 公众号appId * 微信后台提交 URL 时,微信发 GET 请求验证,本方法原样返回 echostr。
* @param xml 微信XML * 由于不再携带 appId,需要遍历所有已启用公众号匹配 signature。
* @return success *
* @param signature 微信加密签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return echostr 或 "invalid signature"
*/ */
@PostMapping("/wechat/callback/{appId}") @GetMapping("/wechat/callback")
String loginWechatCallback(@PathVariable("appId") String appId, @RequestBody String xml); String verifyWechatUrl(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr);
/**
* 微信事件推送回调(POST)
* 微信服务器推送 XML 事件,从 XML 的 ToUserName 反查公众号,
* 再根据 EventKey 中的 bizType 分流到分享绑定或扫码登录。
*
* @param xml 微信XML事件内容
* @return "success"
*/
@PostMapping("/wechat/callback")
String loginWechatCallback(@RequestBody String xml);
/** /**
* 查询扫码状态 * 查询扫码状态
......
...@@ -28,8 +28,13 @@ public class ApiThirdPartyScanLoginFeignFallbackFactory implements FallbackFacto ...@@ -28,8 +28,13 @@ public class ApiThirdPartyScanLoginFeignFallbackFactory implements FallbackFacto
} }
@Override @Override
public String loginWechatCallback(String appId, String xml) { public String verifyWechatUrl(String signature, String timestamp, String nonce, String echostr) {
return "success"; return null;
}
@Override
public String loginWechatCallback(String xml) {
return null;
} }
@Override @Override
......
...@@ -61,6 +61,12 @@ public class ThirdPartyPlatform implements Serializable { ...@@ -61,6 +61,12 @@ public class ThirdPartyPlatform implements Serializable {
private String appSecret; private String appSecret;
/** /**
* 公众号原始ID,例如 gh_b7c7abeb143a
*/
@TableField("to_user_name")
private String toUserName;
/**
* 微信服务器配置 Token * 微信服务器配置 Token
*/ */
@TableField("token") @TableField("token")
......
...@@ -3,6 +3,8 @@ package com.yd.user.service.service; ...@@ -3,6 +3,8 @@ package com.yd.user.service.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.yd.user.service.model.ThirdPartyPlatform; import com.yd.user.service.model.ThirdPartyPlatform;
import java.util.List;
/** /**
* 第三方平台基础配置表 服务类 * 第三方平台基础配置表 服务类
*/ */
...@@ -21,4 +23,17 @@ public interface IThirdPartyPlatformService extends IService<ThirdPartyPlatform> ...@@ -21,4 +23,17 @@ public interface IThirdPartyPlatformService extends IService<ThirdPartyPlatform>
* @return 平台配置 * @return 平台配置
*/ */
ThirdPartyPlatform getByPlatformCode(String platformCode); ThirdPartyPlatform getByPlatformCode(String platformCode);
/**
* 查询所有已启用的公众号(platformType = 1)
* @return 已启用公众号列表
*/
List<ThirdPartyPlatform> listEnabledGzh();
/**
* 根据公众号原始ID查询平台配置
* @param toUserName 公众号原始ID,例如 gh_b7c7abeb143a
* @return 平台配置
*/
ThirdPartyPlatform getByToUserName(String toUserName);
} }
...@@ -2,11 +2,14 @@ package com.yd.user.service.service.impl; ...@@ -2,11 +2,14 @@ package com.yd.user.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yd.user.feign.constants.ScanLoginConstants;
import com.yd.user.service.dao.ThirdPartyPlatformMapper; import com.yd.user.service.dao.ThirdPartyPlatformMapper;
import com.yd.user.service.model.ThirdPartyPlatform; import com.yd.user.service.model.ThirdPartyPlatform;
import com.yd.user.service.service.IThirdPartyPlatformService; import com.yd.user.service.service.IThirdPartyPlatformService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
/** /**
* 第三方平台基础配置表 服务实现类 * 第三方平台基础配置表 服务实现类
*/ */
...@@ -29,4 +32,20 @@ public class ThirdPartyPlatformServiceImpl extends ServiceImpl<ThirdPartyPlatfor ...@@ -29,4 +32,20 @@ public class ThirdPartyPlatformServiceImpl extends ServiceImpl<ThirdPartyPlatfor
.eq(ThirdPartyPlatform::getStatus, 1) .eq(ThirdPartyPlatform::getStatus, 1)
.eq(ThirdPartyPlatform::getIsDeleted, 0)); .eq(ThirdPartyPlatform::getIsDeleted, 0));
} }
@Override
public List<ThirdPartyPlatform> listEnabledGzh() {
return list(new LambdaQueryWrapper<ThirdPartyPlatform>()
.eq(ThirdPartyPlatform::getPlatformType, ScanLoginConstants.PLATFORM_TYPE_GZH)
.eq(ThirdPartyPlatform::getStatus, 1)
.eq(ThirdPartyPlatform::getIsDeleted, 0));
}
@Override
public ThirdPartyPlatform getByToUserName(String toUserName) {
return getOne(new LambdaQueryWrapper<ThirdPartyPlatform>()
.eq(ThirdPartyPlatform::getToUserName, toUserName)
.eq(ThirdPartyPlatform::getStatus, 1)
.eq(ThirdPartyPlatform::getIsDeleted, 0));
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment