闽公网安备 35020302035485号

用内网穿透软件生成外网代理(一般 80 端口都需要收费)。

├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── wdbyte
│ └── weixin
│ ├── SpringBootApp.java
│ ├── config
│ │ └── JwtFilter.java # JWT 身份认证拦截器
│ ├── controller
│ │ ├── WeixinServerController.java # 微信服务端调用接口
│ │ └── WeixinUserController.java # 浏览器调用接口
│ ├── model
│ │ ├── ApiResult.java
│ │ ├── ReceiveMessage.java # 微信消息封装类
│ │ └── WeixinQrCode.java # 微信二维码 Ticket 封装类
│ ├── service
│ │ ├── WeixinUserService.java # 微信调用处理类
│ │ └── impl
│ │ └── WeixinUserServiceImpl.java
│ └── util
│ ├── AesUtils.java # AES 加密工具类
│ ├── ApiResultUtil.java
│ ├── HttpUtil.java # HTTP 工具类
│ ├── JwtUtil.java # JWT 工具类
│ ├── KeyUtils.java
│ ├── WeixinApiUtil.java # 微信 API 工具类,如获取 AccessToken
│ ├── WeixinMsgUtil.java # 微信消息工具类
│ ├── WeixinQrCodeCacheUtil.java # 微信二维码Ticket缓存
│ └── XmlUtil.java
└── resources
├── application.properties #配置文件
├── static
└── templates
WeixinServerController 和 WeixinUserController 暴漏了三个 API。server.port= weixin.appid= weixin.appsecret= weixin.token= ase.util.secret= key.jwt.secret=4.验证签名
| 参数 | 描述 |
|---|---|
| signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
| timestamp | 时间戳 |
| nonce | 随机数 |
| echostr | 随机字符串 |
开发者需要对 signature 进行校验,判断是否来自微信服务器,公众号相关的其他事件如消息、关注、扫码等一样会回调配置的 URL ,只不过这时是 POST 请求。
// com.wdbyte.weixin.service.impl.WeixinUserServiceImpl.java
// 堆代码 duidaima.com
@Value("${weixin.token}")
private String token;
@Override
public void checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] {token, timestamp, nonce};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (String str : arr) {
content.append(str);
}
String tmpStr = DigestUtils.sha1Hex(content.toString());
if (tmpStr.equals(signature)) {
log.info("check success");
return;
}
log.error("check fail");
throw new RuntimeException("check fail");
}
获取 Access Token// com.wdbyte.weixin.util.WeixinApiUtil.java
@Value("${weixin.appid}")
public String appId;
@Value("${weixin.appsecret}")
public String appSecret;
private static String ACCESS_TOKEN = null;
private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null;
/**
* 获取 access token
*
* @return
*/
public synchronized String getAccessToken() {
if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) {
return ACCESS_TOKEN;
}
String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret="
+ appSecret;
String result = HttpUtil.get(api);
JSONObject jsonObject = JSON.parseObject(result);
ACCESS_TOKEN = jsonObject.getString("access_token");
ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10);
return ACCESS_TOKEN;
}
生成登录二维码// com.wdbyte.weixin.util.WeixinApiUtil.java
private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
/**
* 二维码 Ticket 过期时间
*/
private static int QR_CODE_TICKET_TIMEOUT = 10 * 60;
/**
* 获取二维码 Ticket
*
* https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
*
* @return
*/
public WeixinQrCode getQrCode() {
String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken();
String jsonBody = String.format("{\n"
+ " \"expire_seconds\": %d,\n"
+ " \"action_name\": \"QR_STR_SCENE\",\n"
+ " \"action_info\": {\n"
+ " \"scene\": {\n"
+ " \"scene_str\": \"%s\"\n"
+ " }\n"
+ " }\n"
+ "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32());
String result = HttpUtil.post(api, jsonBody);
log.info("get qr code params:{}", jsonBody);
log.info("get qr code result:{}", result);
WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class);
weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString());
return weixinQrCode;
}
class WeixinQrCode {
private String ticket;
private Long expireSeconds;
private String url;
private String qrCodeUrl;
}
响应内容格式如下:{
"ticket": "gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm3sUw==",
"expire_seconds": 60,
"url": "http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"
}
其中 Ticket 就是二维码凭证,用户扫码后微信会把此 Ticket 回调给网站服务端。可以在下面的链接后面拼上 Ticket 换取二维码图片。
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[qrscene_123123]]></EventKey> <Ticket><![CDATA[TICKET]]></Ticket> </xml>其中 FromUserName 是用户身份标识,EventKey 值 qrscene_标识扫码,Ticket 则是二维码的 Ticket。至此,服务端就可以识别出二维码是被哪个用户扫码了。绑定 Ticket 和用户身份标识。
{
"code": -1,
"data": "check faild",
"message": "error"
}
微信扫码关注公众号。
{
"code": 200,
"data": "mihzE8Z1Y9t2EoppNSzzytV4TOgn+Nc50ORZjsW/oVkxchL4EzGA6rr1tQ0Q7J24Ipm4otjCYf95Nu8JbV31Q/ImKvlta3f5bgvOdWSlO2tNvOwqgzBSItABohbCLVLxjGCci4VtNaEFgQjoDjc1uhwP/GCSohVFc7csO9SxpOm8HKtlRhATjwPrtiQ9iLErfsUs27I0k5OHp55AzuQOYCvza//i3wk8nlv/MDkk7y1nvsZkllyKQGHPB4Ulcraz",
"message": "success"
}
至此,登录完成。