分享前后端实现唤起微信扫一扫小demo
一、前置准备工作
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、前端页面
Title 微信扫一扫
//$(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"} }});})//})
创建一个.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 { includemime.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 { listen80; 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 { # listen8000; # listensomename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen443 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中引入相关依赖:
redis.clientsjedis3.3.0org.redissonredisson3.11.1
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 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 */@Componentpublic 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 result = this.redissonClient.getBucket(key); if (!result.isExists()) { result.set(value, leaseTime, unit); } } public void setString(String key, Object value) { RBucket result = this.redissonClient.getBucket(key); if (!result.isExists()) { result.set(value); } } public boolean hasString(String key) { RBucket 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 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 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 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 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 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 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 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 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 rmap = this.redissonClient.getMap(key); rmap.putAll(map); rmap.expire(time, timeUnit); } public void haddAllAsyncEx(String key, Map map, long time, TimeUnit timeUnit) { RMap rmap = this.redissonClient.getMap(key); rmap.putAllAsync(map); rmap.expire(time, timeUnit); } public Map 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 */@Servicepublic 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 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 params) { Iterator<Map.Entry> iter = params.entrySet().iterator(); StringBuffer urlParamsBuffer = new StringBuffer(); while (iter.hasNext()) { Map.Entry 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 params) { List nameValuePairs = new ArrayList(); Iterator<Map.Entry> iter = params.entrySet().iterator(); while (iter.hasNext()) { Map.Entry 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 params) { String result = ""; BufferedReader in = null; StringBuffer strb = new StringBuffer(result); Iterator<Map.Entry> iter = params.entrySet().iterator(); String getUrl = url; StringBuffer urlParamsBuffer = new StringBuffer(); while (iter.hasNext()) { Map.Entry 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 */@Componentpublic class WechatSignUtils { private static Logger logger = LoggerFactory.getLogger(WechatSignUtils.class); / * 获取签名 md5加密(微信支付必须用MD5加密) 获取支付签名 */ public static String getSign(SortedMap params) { List 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 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时,通过代码调用微信接口,接口响应里会有响应的提示,如下图所示: