一、前置准备工作
1、注册微信公众平台账户,个人可以注册订阅号就可以。
2、查看接口权限是否已开通调起微信扫一扫权限:
3、获取appId和secret参数信息
注意:初次注册的公众号,secret没有开通,需要开通获取,获取之后微信公众平台是不会保存secret的,所以需要生成之后,找个地方保存起来;如果后期忘记了,也可以选择重置,重新获取secret。
4、域名准备
4.1、首先需要提供一个可供外网访问的已备案的域名,并且需要支持https协议的,有条件的小伙伴可以选择使用花生壳,支持内网穿透,也支持https服务,但是需要花钱开通,自我感觉还是比较方便的。
4.2、页面我是通过nginx进行访问,所以需要在自己的电脑上下载安装个nginx,并启动,后续花生壳的https协议配置需要;
4.3、花生壳注册地址:花生壳-免费内网穿透软件|端口映射工具|DNS免费动态域名解析_花生壳,内网也能用-贝锐花生壳官网
4.4、由于域名需要支持https协议,所以需要开通花生壳的https服务,价格比较划算一年不到100,开通步骤如下:
4.4.1、去官网下载花生壳客户端,并安装,登录之前注册的账号,界面如下:
4.4.2、由于初次注册的账户没有配置过映射,所以通过点击上图所示的添加映射,然后选择https协议,由于没有开通https服务,所以需要升级开通,按照步骤填写信息并支付即可开通https服务,步骤如下:
由于我已经开通了,所以上图所示的是已开通后的页面,如果未开通,点击https协议的时候,就会弹出如下所示的弹窗。
需要填写一些个人的基本信息,联系人可以不写正式姓名,地址随便填写就行。
4.4.3、开通之后就可以配置https协议进行内网穿透了。
5、花生壳域名配置(已有备案域名此步骤可以跳过)
5.1、登录花生壳客户端,添加映射;
端口号选择80,是因为想使用nginx来访问页面,所以域名配置的内网端口号是和nginx一样的,填写好后,保存即可;
注意:域名配置成功之后,如图所示即代表域名可以正常访问了,配置之前如果想用nginx的话,记得先启动nginx,如果没有启动会造成连接失败的情况,如下图所示:
6、在微信公众平台配置安全域名
6.1、获取域名之后,需要将该域名配置到微信公众平台,配置位置如下图所示:
注意:这块儿有个需要注意的地方,配置安全域名的时候,需要将一个文件下载下来,并放到域名所绑定的服务器中,并且支持通过域名可以访问该文件,如:域名/文件名称.txt如果能看到内容,证明可以正常访问;然后就可以将需要使用的域名填写在输入框中,并点击保存,如果没有如上操作的话,是无法保存的。
6.2、因为前面说将页面放到nginx下,通过nginx访问页面,所以我放到了nginx下面,需要启动nginx,并且花生壳的https协议也配置好了,文件也放好了,微信公众平台的安全域名才能配置成功,如图所示:
此时就可以点击保存
7、在公众平台配置IP白名单
7.1、这个由于不知道外网访问的IP是多少,所以需要后续开发的时候去配置,可在文章末尾查看获取外网IP方式;
7.2、如果使用花生壳的小伙伴,可以在这个位置查看外网IP地址,如下图所示:
公众号配置IP白名单:
IP填写完毕之后,点击确认修改即可;
做好上面以上的步骤,就可以进行代码开发了。
二、应用场景
1、可以在开发的微信小程序上进行扫一扫的商品核销、扫码获取物流信息等等应用场景。
三、代码实现
1、前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
<script src="https://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
</head>
<body>
<h3>微信扫一扫</h3>
<button onclick="saoyisao()">获取参数</button>
<button id="checkJsApi">验证</button>
<button id="scanQRCode">扫码</button>
</body>
<script type="text/javascript">
//$(function() {
function saoyisao() {
//需要把当前页面的url地址传到后台,生成签名信息时需要使用到
var tokenUrl= "https://域名/wechatScan.html";
alert(location.href.split('#')[0]);
//获取签名的后台接口
var _getWechatSignUrl = '/wechat/getWechatScan';
var param = {
pagePath:tokenUrl
}
//获取签名
$.ajax({
url:_getWechatSignUrl,
type:"post",
data:JSON.stringify(param),
contentType:"application/json",
dataType:"json",
success:function(res){
console.log(res);
var result = res.data;
//获得签名之后传入配置中进行配置
wxConfig(result.appId,result.timestamp,result.nonceStr,result.signature);
}
})
}
function wxConfig(_appId,_timestamp, _nonceStr, _signature) {
console.log('获取数据:' + _timestamp +'\n' + _nonceStr +'\n' + _signature);
wx.config({
debug:true,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: _appId,// 必填,公众号的唯一标识
timestamp: _timestamp,// 必填,生成签名的时间戳
nonceStr: _nonceStr,// 必填,生成签名的随机串
signature: _signature,// 必填,签名,见附录1
jsApiList: ['checkJsApi','scanQRCode']
// 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
}
$("#scanQRCode").click(function(event){
wx.scanQRCode({
desc: 'scanQRCode desc',
needResult : 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType : [ "qrCode", "barCode" ], // 可以指定扫二维码还是一维码,默认二者都有
success : function(res) {
console.log("调用扫描成功",res);
var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
$("#codeValue").val(result);//显示结果
alert("扫码结果为:" + result);
},
error:function(res){
console.log(res)
}
});
})
$("#checkJsApi").click(function(event) {
wx.checkJsApi({
jsApiList: ['scanQRCode'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success: function(res) {
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
}
});
})
//})
</script>
</html>
创建一个.html文件,将上面内容粘贴到新创建的html文件中,并放到nginx的html目录下:
启动nginx,尝试使用花生壳的域名访问html文件,如果如下图所示,则证明可以正常访问页面:
也可以发送到手机上,在微信中打开,看是否在外网能够访问到,如果能访问,证明花生壳配置成功;
附上nginx的配置:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index wechatScan.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
location ~ /wechat/ {
proxy_pass http://127.0.0.1:8080;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
后端接口是通过nginx代理转发实现请求的:
2、后端代码实现
首先需要用到redisson,在pom中引入相关依赖:
<!-- redission -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
redisson服务类和配置类:
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
/**
* @Author
* @Date 2022/3/4 10:51
* @Description
*/
public interface RedissonManager {
void lock(String lockKey);
void unlock(String lockKey);
void lock(String lockKey, long timeout);
void lock(String lockKey, long timeout, TimeUnit unit);
boolean tryLock(String lockKey);
boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
boolean isLocked(String lockKey);
Object getString(String key);
void setString(String key, Object value, long leaseTime, TimeUnit unit);
void setString(String key, Object value);
boolean hasString(String key);
boolean deleteString(String key);
boolean deleteList(String key);
List getList(String key);
boolean addList(String key, Object value);
boolean addListEx(String key, Object value, long leaseTime, TimeUnit unit);
boolean addAllList(String key, List value);
RFuture addAsyncList(String key, List value);
boolean addAllListEx(String key, List value, long leaseTime, TimeUnit unit);
int addAfterList(String key, Object value);
int addBeforeList(String key, Object value);
boolean removeList(String key, String value);
boolean removeAllList(String key, List value);
boolean hasList(String key);
boolean deleteSet(String key);
Set getSet(String key);
boolean addSet(String key, Object value);
boolean addSetEx(String key, Object value, long leaseTime, TimeUnit unit);
boolean addAllSet(String key, Set value);
RFuture addAsyncSet(String key, Set value);
boolean addAllSetEx(String key, Set value, long leaseTime, TimeUnit unit);
boolean removeSet(String key, String value);
boolean removeAllSet(String key, Set value);
boolean hasSet(String key);
boolean deleteZSet(String key);
Set getZSet(String key);
boolean addZSet(String key, Object value);
boolean addAllZSet(String key, Set value);
boolean removeZSet(String key, String value);
boolean removeAllZSet(String key, Set value);
boolean hasZSet(String key);
void hadd(String key, String f, Object v);
void haddAllEx(String key, Map map);
void haddEx(String key, String f, Object v, long time, TimeUnit timeUnit);
void haddAllEx(String key, Map map, long time, TimeUnit timeUnit);
void haddAllAsyncEx(String key, Map map, long time, TimeUnit timeUnit);
Map<String, Object> getMap(String key);
void hdelete(String key, String f);
boolean checkHKey(String key, String f);
boolean checkKey(String key);
boolean checkSet(String key, int id);
String getHashV(String key, String f);
int getHashVInt(String key, String f);
Object hreplace(String key, String f, Object v);
long incr(String key, long delta);
}
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RBucket;
import org.redisson.api.RFuture;
import org.redisson.api.RList;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RSet;
import org.redisson.api.RSortedSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @Author
* @Date 2022/3/4 10:57
* @Description
*/
@Component
public class RedissonManagerImpl implements RedissonManager {
@Autowired
private RedissonClient redissonClient;
public RedissonManagerImpl() {
}
public void lock(String lockKey) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.lock();
}
public void unlock(String lockKey) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.unlock();
}
public void lock(String lockKey, long leaseTime) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
}
public void lock(String lockKey, long timeout, TimeUnit unit) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
}
public boolean tryLock(String lockKey) {
RLock lock = this.redissonClient.getLock(lockKey);
return lock.tryLock();
}
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
RLock lock = this.redissonClient.getLock(lockKey);
return lock.tryLock(waitTime, leaseTime, unit);
}
public boolean isLocked(String lockKey) {
RLock lock = this.redissonClient.getLock(lockKey);
return lock.isLocked();
}
public Object getString(String key) {
return this.redissonClient.getBucket(key).get();
}
public void setString(String key, Object value, long leaseTime, TimeUnit unit) {
RBucket<Object> result = this.redissonClient.getBucket(key);
if (!result.isExists()) {
result.set(value, leaseTime, unit);
}
}
public void setString(String key, Object value) {
RBucket<Object> result = this.redissonClient.getBucket(key);
if (!result.isExists()) {
result.set(value);
}
}
public boolean hasString(String key) {
RBucket<Object> result = this.redissonClient.getBucket(key);
return result.isExists();
}
public boolean deleteString(String key) {
return this.redissonClient.getBucket(key).delete();
}
public boolean deleteList(String key) {
return this.redissonClient.getList(key).delete();
}
public List getList(String key) {
return this.redissonClient.getList(key);
}
public boolean addList(String key, Object value) {
return this.redissonClient.getList(key).add(value);
}
public boolean addListEx(String key, Object value, long leaseTime, TimeUnit unit) {
RList<Object> list = this.redissonClient.getList(key);
list.add(value);
return list.expire(leaseTime, unit);
}
public boolean addAllList(String key, List value) {
return this.redissonClient.getList(key).addAll(value);
}
public RFuture addAsyncList(String key, List value) {
return this.redissonClient.getList(key).addAllAsync(value);
}
public boolean addAllListEx(String key, List value, long leaseTime, TimeUnit unit) {
RList<Object> list = this.redissonClient.getList(key);
list.addAll(value);
return list.expire(leaseTime, unit);
}
public int addAfterList(String key, Object value) {
return this.redissonClient.getList(key).addAfter(key, value);
}
public int addBeforeList(String key, Object value) {
return this.redissonClient.getList(key).addBefore(key, value);
}
public boolean removeList(String key, String value) {
return this.redissonClient.getList(key).remove(value);
}
public boolean removeAllList(String key, List list) {
return this.redissonClient.getList(key).removeAll(list);
}
public boolean hasList(String key) {
RList<Object> list = this.redissonClient.getList(key);
return list.isExists();
}
public boolean deleteSet(String key) {
return this.redissonClient.getSet(key).delete();
}
public Set getSet(String key) {
return this.redissonClient.getSet(key);
}
public boolean addSet(String key, Object value) {
return this.redissonClient.getSet(key).add(value);
}
public boolean addSetEx(String key, Object value, long leaseTime, TimeUnit unit) {
RSet<Object> set = this.redissonClient.getSet(key);
set.add(value);
return set.expire(leaseTime, unit);
}
public boolean addAllSet(String key, Set value) {
return this.redissonClient.getSet(key).addAll(value);
}
public RFuture addAsyncSet(String key, Set value) {
return this.redissonClient.getSet(key).addAsync(value);
}
public boolean addAllSetEx(String key, Set value, long leaseTime, TimeUnit unit) {
RSet<Object> set = this.redissonClient.getSet(key);
set.addAll(value);
return set.expire(leaseTime, unit);
}
public boolean removeSet(String key, String value) {
return this.redissonClient.getSet(key).remove(value);
}
public boolean removeAllSet(String key, Set value) {
return this.redissonClient.getSet(key).removeAll(value);
}
public boolean hasSet(String key) {
RSet<Object> set = this.redissonClient.getSet(key);
return set.isExists();
}
public boolean deleteZSet(String key) {
return this.redissonClient.getSortedSet(key).delete();
}
public Set getZSet(String key) {
return this.redissonClient.getSortedSet(key);
}
public boolean addZSet(String key, Object value) {
return this.redissonClient.getSortedSet(key).add(value);
}
public boolean addAllZSet(String key, Set value) {
return this.redissonClient.getSortedSet(key).addAll(value);
}
public boolean removeZSet(String key, String value) {
return this.redissonClient.getSortedSet(key).remove(value);
}
public boolean removeAllZSet(String key, Set value) {
return this.redissonClient.getSortedSet(key).removeAll(value);
}
public boolean hasZSet(String key) {
RSortedSet<Object> sortedSet = this.redissonClient.getSortedSet(key);
return sortedSet.isExists();
}
public void hadd(String key, String f, Object v) {
this.redissonClient.getMap(key).put(f, v);
}
public void haddAllEx(String key, Map map) {
this.redissonClient.getMap(key).putAll(map);
}
public void haddEx(String key, String f, Object v, long time, TimeUnit timeUnit) {
RMap<Object, Object> map = this.redissonClient.getMap(key);
map.put(f, v);
map.expire(time, timeUnit);
}
public void haddAllEx(String key, Map map, long time, TimeUnit timeUnit) {
RMap<Object, Object> rmap = this.redissonClient.getMap(key);
rmap.putAll(map);
rmap.expire(time, timeUnit);
}
public void haddAllAsyncEx(String key, Map map, long time, TimeUnit timeUnit) {
RMap<Object, Object> rmap = this.redissonClient.getMap(key);
rmap.putAllAsync(map);
rmap.expire(time, timeUnit);
}
public Map<String, Object> getMap(String key) {
return this.redissonClient.getMap(key);
}
public void hdelete(String key, String f) {
this.redissonClient.getMap(key).delete();
}
public boolean checkHKey(String key, String f) {
return this.redissonClient.getMap(key).containsKey(f);
}
public boolean checkKey(String key) {
return this.redissonClient.getKeys().countExists(new String[]{key}) > 0L;
}
public boolean checkSet(String key, int id) {
return this.redissonClient.getSet(key).contains(id);
}
public String getHashV(String key, String f) {
return (String)this.redissonClient.getMap(key).get(f);
}
public int getHashVInt(String key, String f) {
return (Integer)this.redissonClient.getMap(key).get(f);
}
public Object hreplace(String key, String f, Object v) {
return this.redissonClient.getMap(key).replace(f, v);
}
public long incr(String key, long delta) {
return this.redissonClient.getAtomicLong(key).addAndGet(delta);
}
}
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
/**
* @Author
* @Date 2022/3/4 11:00
* @Description
*/
@ComponentScan(
basePackages = {"io.renren.common.redission"}
)
public class RedissonConfig {
@Value("${redis.redisson.url}")
private String url;
// @Value("${renren.redis.redisson.password}")
// private String password;
public RedissonConfig() {
}
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
String redisUrl = String.format(this.url);
// ((SingleServerConfig)config.useSingleServer().setPingConnectionInterval(1000)).setAddress(redisUrl).setPassword(this.password);
((SingleServerConfig)config.useSingleServer().setPingConnectionInterval(1000)).setAddress(redisUrl);
return Redisson.create(config);
}
}
启动类引入redisson:
注意:此时配置好redisson后启动尝试启动一下代码,看是否正确配置了redisson,保证redisson正常可用;
controller控制层代码:
import io.renren.common.utils.R;
import io.renren.common.constants.ErrorCodeMsg;
import io.renren.modules.app.form.WechatScanQueryForm;
import io.renren.modules.app.service.IWechatManager;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author
* @Date 2022/3/4 10:24
* @Description
*/
@RestController
@RequestMapping("/wechat")
public class WechatController {
@Autowired
private IWechatManager wechatManager;
@ApiOperation(value = "获取微信扫一扫参数")
@PostMapping(value = "/getWechatScan")
public R getWechatScan(@RequestBody WechatScanQueryForm wechatScanQueryForm) {
if (StringUtils.isBlank(wechatScanQueryForm.getPagePath())) {
return R.error(ErrorCodeMsg.PARAMETER_DOES_NOT_EXIST.getMsg());
}
return wechatManager.wechatScan(wechatScanQueryForm);
}
}
接口接收实体类:
import io.swagger.annotations.ApiModelProperty;
/**
* @Author
* @Date 2022/3/4 10:27
* @Description
*/
public class WechatScanQueryForm {
@ApiModelProperty(value = "扫一扫页面路径")
private String pagePath;
public String getPagePath() {
return pagePath;
}
public void setPagePath(String pagePath) {
this.pagePath = pagePath;
}
}
接口返回vo类:
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* @Author
* @Date 2022/3/4 10:40
* @Description
*/
public class WXjsapiConfigVO implements Serializable {
@ApiModelProperty(value = "必填,公众号的唯一标识")
private String appId;
@ApiModelProperty(value = "必填,生成签名的时间戳")
private String timestamp;
@ApiModelProperty(value = "必填,生成签名的随机串")
private String nonceStr;
@ApiModelProperty(value = "必填,签名,见附录1")
private String signature;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
}
service层代码:
import io.renren.common.utils.R;
import io.renren.modules.app.form.WechatScanQueryForm;
/**
* @Author
* @Date 2022/3/4 10:28
* @Description
*/
public interface IWechatManager {
R wechatScan(WechatScanQueryForm wechatScanQueryForm);
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.renren.common.redission.RedissonManager;
import io.renren.common.utils.R;
import io.renren.common.constants.ErrorCodeMsg;
import io.renren.modules.app.form.WechatScanQueryForm;
import io.renren.modules.app.service.IWechatManager;
import io.renren.modules.app.utils.WechatSignUtils;
import io.renren.modules.app.utils.WechatUtil;
import io.renren.modules.app.vo.WXjsapiConfigVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
/**
* @Author
* @Date 2022/3/4 10:40
* @Description
*/
@Service
public class WechatManagerImpl implements IWechatManager {
private static Logger logger = LoggerFactory.getLogger(WechatManagerImpl.class);
/**
* 微信access_token在redis中的key
**/
private static final String ACCESS_TOKEN = "travel-integration_wechat_access_token";
/**
* 微信ticket在redis中的key
**/
private static final String TICKET = "travel-integration_wechat_ticket";
@Value("${wechat.appid}")
private String appid;
@Value("${wechat.secret}")
private String secret;
@Resource
private RedissonManager redissonManager;
/**
* @Description: 获取微信扫一扫参数
* @Author: yuxuan
* @Date: 2022/3/4 10:33
* @Param wechatScanQueryForm:
* @return: io.renren.common.utils.R
**/
@Override
public R wechatScan(WechatScanQueryForm wechatScanQueryForm) {
// 先判断access_token和ticket在redis中是否存在
boolean accessTokenBoo = redissonManager.checkKey(ACCESS_TOKEN);
boolean ticketBoo = redissonManager.checkKey(TICKET);
// 如果不存在则重新获取access_token和ticket并存储到redis中
if (!accessTokenBoo || !ticketBoo) {
// 调用生成access_token的方法
JSONObject accessToken = WechatUtil.getAccessToken(appid, secret);
if (ObjectUtils.isEmpty(accessToken) || !accessToken.containsKey("access_token")) {
logger.error("=========获取token失败,失败信息: {}", JSON.toJSONString(accessToken));
return R.error(ErrorCodeMsg.SYSTEM_ERROR.getMsg());
}
String access_token = accessToken.getString("access_token");
logger.info("=========获取access_token成功: {}", JSON.toJSONString(access_token));
redissonManager.setString(ACCESS_TOKEN, access_token, 7000, TimeUnit.SECONDS);
logger.info("=========access_token保存成功================");
// 调用生成ticket的方法,获取ticket,并保存到redis中,设置ticket的有效时间为7200秒
JSONObject ticketJsonObject = WechatUtil.getJsApiTicket(redissonManager.getString(ACCESS_TOKEN).toString());
if (ObjectUtils.isEmpty(ticketJsonObject) || !ticketJsonObject.containsKey("ticket")) {
logger.error("=========获取ticket失败,失败信息: {}", JSON.toJSONString(ticketJsonObject));
return R.error(ErrorCodeMsg.SYSTEM_ERROR.getMsg());
}
String ticket = ticketJsonObject.getString("ticket");
logger.info("=========获取ticket成功: {}", ticket);
redissonManager.setString(TICKET, ticket, 7000, TimeUnit.SECONDS);
logger.info("=========ticket保存成功================");
}
SortedMap<String, Object> map = new TreeMap<>();
String noncestr = WechatSignUtils.createNonceStr();
String timestamp = WechatSignUtils.createTimestamp();
map.put("jsapi_ticket", redissonManager.getString(TICKET).toString());
map.put("noncestr", noncestr);
map.put("timestamp", timestamp);
map.put("url", wechatScanQueryForm.getPagePath());
// 对参数进行加密操作
String sign = WechatSignUtils.getSign(map);
// 封装返回参数
WXjsapiConfigVO wXjsapiConfigVO = new WXjsapiConfigVO();
wXjsapiConfigVO.setAppId(appid);
wXjsapiConfigVO.setNonceStr(noncestr);
wXjsapiConfigVO.setTimestamp(timestamp);
wXjsapiConfigVO.setSignature(sign);
return R.ok().put("data", wXjsapiConfigVO);
}
}
微信请求接口常量类:
/**
* @Author
* @Date 2022/3/4 10:40
* @Description
*/
public class WechatConstants {
/**
* 换取ticket的url
*/
public static final String JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
/**
* 换取token的url
*/
public static final String JSAPI_TOKEN = " https://api.weixin.qq.com/cgi-bin/token";
}
异常信息枚举:
import java.util.Objects;
/**
* @Author
* @Date 2022/3/4 10:36
* @Description
*/
public interface BaseEnum {
String getCode();
String getMsg();
default boolean equalsValue(Object value) {
return Objects.equals(this.getCode(), value);
}
default boolean equals(BaseEnum baseEnum) {
return Objects.equals(this.getCode(), baseEnum.getCode()) && Objects.equals(this.getMsg(), baseEnum.getMsg());
}
}
/**
* @Author
* @Date 2022/3/4 10:40
*/
public enum ErrorCodeMsg implements BaseEnum {
SUCCESS("0", "成功"),
TOKEN_EXPIRE("101", "登录已过期,请重新登录"),
BAD_REQUEST("401", "非法请求"),
UNKNOWN("500", "系统繁忙,请稍后重试"),
INVALID_PARAM_ERROR("501", "参数错误"),
PARAMETER_DOES_NOT_EXIST("50100", "请求参数有误"),
RPC_CALL_ERROR("50200", "RPC调用返回有误"),
SYSTEM_ERROR("90001", "第三方调用异常")
;
ErrorCodeMsg(String code, String msg) {
this.code = code;
this.msg = msg;
}
private String code;
private String msg;
@Override
public String getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
public static ErrorCodeMsg valueOfByCode(String code) {
for (ErrorCodeMsg item : ErrorCodeMsg.values()) {
if (item != null && item.code.equalsIgnoreCase(code)) {
return item;
}
}
return null;
}
}
工具类:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.LineIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @Description http、https请求工具
* @Author
* @Date 2022/3/4 10:40
*/
public class HttpClientUtils {
private static final Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
//设置超时时间
private int timeout = 30000;
private RequestConfig requestConfig;
private static final HttpClientUtils httpsRequest = new HttpClientUtils();
public HttpClientUtils() {
requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectionRequestTimeout(timeout).build();
}
public static HttpClientUtils getHttpsRequestSingleton() {
return httpsRequest;
}
/**
* 发送get请求
*
* @param url
* @param params
* @return
*/
public JSONObject sendGet(String url, Map<String, String> params) {
Iterator<Map.Entry<String, String>> iter = params.entrySet().iterator();
StringBuffer urlParamsBuffer = new StringBuffer();
while (iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
urlParamsBuffer.append(entry.getKey() + "=" + entry.getValue() + "&");
}
String getUrl = url;
if (urlParamsBuffer.length() > 0) {
urlParamsBuffer.deleteCharAt(urlParamsBuffer.length() - 1);
getUrl += '?' + urlParamsBuffer.toString();
}
CloseableHttpClient httpClient = HttpClients.createDefault();
logger.info(getUrl);
HttpGet httpGet;
httpGet = new HttpGet(getUrl);
httpGet.setConfig(requestConfig);
JSONObject jsonObject = new JSONObject();
try {
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
String responseContent = EntityUtils.toString(entity);
logger.info("&*&*&*&*&*&*&*" + responseContent + "#%^$^&*@$^#%^%$");
jsonObject = JSON.parseObject(responseContent);
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
}
return jsonObject;
}
/**
* 发送post请求
*
* @param url
* @param params
* @return
*/
public JSONObject sendPost(String url, Map<String, String> params) {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
Iterator<Map.Entry<String, String>> iter = params.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpost = new HttpPost(url);
httpost.setConfig(requestConfig);
JSONObject jsonObject = new JSONObject();
try {
httpost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpost);
HttpEntity entity = response.getEntity();
String responseContent = EntityUtils.toString(entity);
jsonObject = JSON.parseObject(responseContent);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
try {
httpClient.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
return jsonObject;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
StringBuffer strb = new StringBuffer(result);
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setConnectTimeout(3000);
// conn.setRequestProperty("accept", "*/*");
// conn.setRequestProperty("connection", "Keep-Alive");
// conn.setRequestProperty("user-agent",
// "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Content-Type", "application/json");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(), "utf-8"));
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
String line = "";
LineIterator lineItr = new LineIterator(in);
while (lineItr.hasNext()) {
line = (String) lineItr.next();
strb.append(line);
}
return strb.toString();
} catch (Exception e) {
logger.error("发送 POST 请求出现异常!", e);
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
logger.error("关闭post请求IO流异常", ex);
}
}
return result;
}
public String sendGetTets(String url, Map<String, String> params) {
String result = "";
BufferedReader in = null;
StringBuffer strb = new StringBuffer(result);
Iterator<Map.Entry<String, String>> iter = params.entrySet().iterator();
String getUrl = url;
StringBuffer urlParamsBuffer = new StringBuffer();
while (iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
urlParamsBuffer.append(entry.getKey() + "=" + entry.getValue() + "&");
}
if (urlParamsBuffer.length() > 0) {
urlParamsBuffer.deleteCharAt(urlParamsBuffer.length() - 1);
getUrl += '?' + urlParamsBuffer.toString();
}
try {
URL realUrl = new URL(getUrl);
URLConnection conn = realUrl.openConnection();
//设置通道的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 设置通用的请求属性
conn.setConnectTimeout(3000);
//建立实际的连接
conn.connect();
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
String line = "";
LineIterator lineItr = new LineIterator(in);
while (lineItr.hasNext()) {
line = (String) lineItr.next();
strb.append(line);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
return strb.toString();
}
}
微信加密工具类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* @Author
* @Date 2022/3/4 10:40
* @Description
*/
@Component
public class WechatSignUtils {
private static Logger logger = LoggerFactory.getLogger(WechatSignUtils.class);
/**
* 获取签名 md5加密(微信支付必须用MD5加密) 获取支付签名
*/
public static String getSign(SortedMap<String, Object> params) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key).toString();
// 拼接时,不包括最后一个&字符
if (i == keys.size() - 1) {
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
String signature = "";
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(prestr.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (NoSuchAlgorithmException e) {
logger.error("微信参数加密异常: {}", e.getCause());
} catch (UnsupportedEncodingException e) {
logger.error("微信参数加密异常: {}", e.getCause());
}
return signature;
}
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
/**
* @param content
* @param charset
* @return
* @throws java.security.SignatureException
*/
public static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
/**
* @Description: 获取随机字符串
* @Author: o_yangruipan
* @Date: 2021/10/29 14:25
* @return: java.lang.String
**/
public static String createNonceStr() {
return UUID.randomUUID().toString();
}
/**
* @Description: 获取随机时间戳
* @Author: o_yangruipan
* @Date: 2021/10/29 14:25
* @return: java.lang.String
**/
public static String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
微信请求接口封装工具类:
import com.alibaba.fastjson.JSONObject;
import io.renren.common.constants.WechatConstants;
import org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @Author
* @Date 2022/3/4 10:40
* @Description
*/
public class WechatUtil {
private static final HttpClientUtils httpClient = HttpClientUtils.getHttpsRequestSingleton();
/**
* 获得jsapi_ticket
*/
public static JSONObject getJsApiTicket(String token) {
String url = WechatConstants.JSAPI_TICKET
+ "?access_token=" + token
+ "&type=jsapi";
String msg = httpClient.sendGetTets(url, new HashMap<>());
if (StringUtils.isBlank(msg)) {
return null;
}
JSONObject jsonObject = JSONObject.parseObject(msg);
return jsonObject;
}
/**
* 获取token
* @return msg
*/
public static JSONObject getAccessToken(String appid, String secret) {
String url = WechatConstants.JSAPI_TOKEN;
Map<String, String> param = new HashMap<>(16);
param.put("grant_type", "client_credential");
param.put("appid", appid);
param.put("secret", secret);
String msg = httpClient.sendGetTets(url, param);
if (StringUtils.isBlank(msg)) {
return null;
}
JSONObject jsonObject = JSONObject.parseObject(msg);
return jsonObject;
}
}
注意:前面说的配置公众号IP白名单,不知道外网IP时,通过代码调用微信接口,接口响应里会有响应的提示,如下图所示: