分享前后端实现唤起微信扫一扫小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、前端页面

<!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时,通过代码调用微信接口,接口响应里会有响应的提示,如下图所示:

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值