微信公众号开发

一、准备

1.1、内网穿透NATAPP

下载链接: https://natapp.cn/
购买隧道
在这里插入图片描述
配置隧道
在这里插入图片描述

根据系统版本下载对应的客户端
在这里插入图片描述
解压,在同级目录下创建config.ini文件,并添加相应的配置
authtoken更改为自己的authtoken

在这里插入图片描述
在这里插入图片描述

#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken=2427122cd5c09be9      #对应一条隧道的authtoken
clienttoken=                    #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none                        #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR                  #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy=                     #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

启动,双击natapp.exe
在这里插入图片描述

1.2、微信公众号测试号

地址: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

1.2.1、微信扫码登录

可以得到appID和appsecret,后面会用到
在这里插入图片描述

1.2.2、接口配置信息

URL为NATAPP生成的域名+/wechat/checkToken
在这里插入图片描述

1.2.3、JS接口安全域名

域名是NATAPP生成的域名
在这里插入图片描述

1.2.4、网页账号

在这里插入图片描述
在这里插入图片描述

二、开发

2.1、配置

2.1.1、添加依赖

<dependencies>
  <!-- springboot整合freemarker -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
        <version>2.3.1.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-http</artifactId>
        <version>4.5.3</version>
    </dependency>
    
    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.1.2、yml配置

server:
  port: 9090
spring:
  freemarker:
    suffix: .html                                # 设置模板后缀名
    content-type: text/html                      # 设置文档类型
    charset: UTF-8                               # 设置页面编码格式
    cache: false                                 # 设置页面缓存
    template-loader-path: classpath:/templates   # 设置页面文件路径
    settings:
      numberFormat: 0.##                        #数字格式
      datetimeFormat: yyyy-MM-dd HH:mm:ss       #日期时间格式
      dateFormat: yyyy-MM-dd                    #日期格式
      timeFormat: HH:mm:ss                      #时间格式
      tagSyntax: autoDetect                     #自动检测标签语法
      urlEscapingCharset: UTF-8                 #URL转码字符集
      classicCompatible: true                   #解决前台使用${}赋值值为空的情况
      localizedLookup: false
  redis:
    host: localhost
    port: 6379
    password:
    database: 1
# 微信
wechat:
  # APPNAT生成的域名
  server: http://xndj83.natappfree.cc
  appid: 换成你自己的appid
  appsecret: 换成你自己的appsecret

2.2、前端配置

在templates目录下新建success.html和subscribe.html文件
subscribe.html

<!doctype html>
<html class="height1">
<head>
    <title>关注公众号</title>
</head>
<body>
<div>
    <div>
        <div>
            <img src="${static}/images/img.png" alt="公众号二维码">
            <div>请先关注【用生命研发技术】公众号 再进行扫描 (长按图片识别)</div>
        </div>
    </div>
</div>
</body>
</html>

success.html

<!doctype html>
<html class="height1">
<head>
    <title>关注公众号</title>
</head>
<body>
<div>
    <div>
        <div>
            <img src="${static}/images/img.png" alt="公众号二维码">
            <div>请先关注【用生命研发技术】公众号 再进行扫描 (长按图片识别)</div>
        </div>
    </div>
</div>
</body>
</html>

在static目录下新建images目录,将微信公众号的二维码复制到images目录下,并重命名为img.png

注意:测试号不关注公众号无法进行操作,并且测试号最多100人关注

2.3、Java开发

新建WechatController类

@RestController
@RequestMapping("/wechat")
public class WechatController {
    //openId key
    private static final String WECHAT_OPEN_ID_SESSION_KEY = "WECHAT_OPEN_ID_SESSION_KEY";

    @Value("${wechat.server}")
    private String SERVER;

    @Value("${wechat.appid}")
    private String appid;

    @Value("${wechat.appsecret}")
    private String appsecret;

    @Autowired
    private RedisService redisService;
    
    /**
     * @param request
     * @param response
     * @throws IOException
     * @Author: xuwendong
     * Token验证 发者接入验证 确认请求来自微信服务器
     */
    @GetMapping("/checkToken")
    public void checkToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //消息来源可靠性验证
        String signature = request.getParameter("signature");// 微信加密签名
        String timestamp = request.getParameter("timestamp");// 时间戳
        String nonce = request.getParameter("nonce");       // 随机数
        String echostr = request.getParameter("echostr");//成为开发者验证
        //确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败
        PrintWriter out = response.getWriter();
        if (WxUtil.checkToken(signature, timestamp, nonce)) {
            System.out.println("=======请求校验成功======" + echostr);
            out.print(echostr);
        }
        out.close();
        out = null;
    }

    /**
     * @Author: xuwendong
     * Access token
     * access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token
     * access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
     */
    @GetMapping("/getAccessToken")
    public synchronized String getAccessToken() {
        String accessToken = (String) redisService.get("ACCESS_TOKEN");

        if (accessToken == null) {
            String result = WxUtil.getAccessToken(appid, appsecret);
            JSONObject jsonObject = JSONUtil.parseObj(result);
            //根据key获取json对象的值
            String access_token = Convert.toStr(jsonObject.get("access_token"));
            //根据key获取json对象的值
            Long expires_in = Convert.toLong(jsonObject.get("expires_in"));
            //将key存储到redis中,并设置过期时间
            redisService.set("ACCESS_TOKEN", access_token, expires_in, TimeUnit.SECONDS);
            System.out.println("往redis中存入 access_token = " + access_token);
            return access_token;
        }
        return accessToken;
    }

    /**
     * @Author: xuwendong
     * 获取微信API接口 IP地址
     * 参数	         是否必须	        说明
     * access_token	   是	    公众号的access_token
     */
    @GetMapping("getWechatAPI")
    public String getWechatAPI() {
        String accessToken = getAccessToken();
        System.out.println("从redis中取access_token: " + accessToken);
        String wechatAPI = WxUtil.getWechatAPI(accessToken);
        return wechatAPI;
    }

    /**
     * @param response
     * @throws IOException
     * @Author: xuwendong
     * @Author: xuwendong
     * 微信授权接口
     */
    @GetMapping("/authorize")
    public void authorize(HttpServletResponse response) throws IOException {
        String returnUrl = "";
        try {
            returnUrl = URLEncoder.encode(SERVER + "/wechat/userInfo", "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 按照文档要求拼接访问地址
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize" +
                "?appid=" + appid
                + "&redirect_uri=" + returnUrl
                + "&response_type=code"
                + "&scope=snsapi_userinfo"
                + "&state=STATE#wechat_redirect";

        System.out.println("redirectUrl=" + url);
        // 跳转到要访问的地址;
        response.sendRedirect(url);
    }
    
    /**
     * @param code
     * @param state
     * @return
     * @Author: xuwendong
     * 获取微信openId接口
     */
    @GetMapping("/userInfo")
    public ModelAndView userInfo(@RequestParam("code") String code, @RequestParam("state") String state) {
        System.out.println("code = " + code);
        ModelAndView view = new ModelAndView();
        //通过code换取网页授权access_token
        String tokenJsonStr = WxUtil.getWechatAccessToken(appid, appsecret, code);
        //String转JSON对象
        JSONObject tokenJsonObj = JSONUtil.parseObj(tokenJsonStr);
        //获取openId
        String openId = tokenJsonObj.getStr("openid");

        //获取Access token
        String accessToken = getAccessToken();

        //微信公众号获取用户信息
        String userInfoJsonStr = WxUtil.getWechatUserInfo(accessToken, openId);
        //String转JSON对象
        JSONObject userInfoJsonObj = JSONUtil.parseObj(userInfoJsonStr);
        System.out.println("userInfoJsonObj = " + userInfoJsonObj);
        // 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息
        int subscribe = userInfoJsonObj.getInt("subscribe");

        if (subscribe == 0) {
            // 跳转未关注公众号界面,让用户去关注公众号
            view.setViewName("subscribe");
            return view;
        }

        HttpServletRequest request = getRequest();
        HttpSession session = request.getSession();
        session.setAttribute(WECHAT_OPEN_ID_SESSION_KEY, openId);
        //获取回调地址
        //String redirect_uri = request.getParameter("redirect_uri");
        //System.out.println("redirect_uri = " + redirect_uri);
        //view.setViewName("redirect:" + SERVER+"/wechat/success");

        //这里是测试使用
        String country = userInfoJsonObj.getStr("country");
        String province = userInfoJsonObj.getStr("province");
        String city = userInfoJsonObj.getStr("city");
        String openid = userInfoJsonObj.getStr("openid");
        String nickname = userInfoJsonObj.getStr("nickname");
        String headimgurl = userInfoJsonObj.getStr("headimgurl");

        view.addObject("country",country);
        view.addObject("province",province);
        view.addObject("city",city);
        view.addObject("openid",openid);
        view.addObject("nickname",nickname);
        view.addObject("headimgurl",headimgurl);

        view.setViewName("success");
        return view;
    }
    
    /**
     * @Author xuwendong
     * 获取request
     */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        return request;
    }
}

新建WxUtil类

public class WxUtil {
    //wxToken验证
    private static String wxToken = "wxToken";

    /**
     * @Author: xuwendong
     * Token验证
     * @param signature
     * @param timestamp
     * @param nonce
     */
    public static boolean checkToken(String signature, String timestamp, String nonce) {
        List<String> params = new ArrayList<String>();
        params.add(wxToken);
        params.add(timestamp);
        params.add(nonce);
        // 1. 将token、timestamp、nonce三个参数进行字典序排序
        Collections.sort(params, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
        // 2.将三个参数字符串拼接成一个字符串进行sha1加密
        String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
        // 3. 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
        return temp.equals(signature);
    }

    /**
     * @Author: xuwendong
     * 通过code换取网页授权access_token
     * @param appid     公众号的唯一标识
     * @param secret    公众号的唯一标识
     * @param code      获取的code参数
     * @return
     */
    public static String getWechatAccessToken(String appid, String secret, String code) {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token";
        Map<String, Object> paramMap = new HashMap<>(3);
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        paramMap.put("code", code);
        paramMap.put("grant_type", "authorization_code");
        return HttpUtil.get(url, paramMap);
    }

    /**
     * @Author: xuwendong
     * 微信公众号获取用户信息
     * @param access_token
     * @param openid
     * 返回参数
     * 参数               描述
     * openid	    用户的唯一标识
     * nickname	    用户昵称
     * sex	        用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
     * province	    用户个人资料填写的省份
     * city	        普通用户个人资料填写的城市
     * country	    国家,如中国为CN
     * headimgurl	用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
     * privilege	用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
     * unionid	    只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
     */
    public static String getWechatUserInfo(String access_token, String openid) {
        String url = "https://api.weixin.qq.com/cgi-bin/user/info";
        Map<String, Object> paramMap = new HashMap<>(3);
        paramMap.put("access_token", access_token);
        paramMap.put("openid", openid);
        paramMap.put("lang", "zh_CN");
        return HttpUtil.get(url, paramMap);
    }

    /**
     * @Author: xuwendong
     * 获取微信API接口 IP地址
     * @param accessToken
     */
    public static String getWechatAPI(String accessToken) {
        String url = "https://api.weixin.qq.com/cgi-bin/get_api_domain_ip";
        Map<String, Object> paramMap = new HashMap<>(1);
        paramMap.put("access_token", accessToken);
        return HttpUtil.get(url, paramMap);
    }

    /**
     * @Author: xuwendong
     * 请求接口, 获取Access token
     * @param appid
     * @param appsecret
     * @return
     */
    public synchronized static String getAccessToken(String appid, String appsecret) {
        String url = "https://api.weixin.qq.com/cgi-bin/token";
        Map<String, Object> paramMap = new HashMap<>(3);
        paramMap.put("appid", appid);
        paramMap.put("secret", appsecret);
        paramMap.put("grant_type", "client_credential");
        return HttpUtil.get(url, paramMap);
    }
}

新建SHA1类

public class SHA1 {
    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    // 把密文转换成十六进制的字符串形式
    private static String getFormattedText(byte[] bytes) {
        int len = bytes.length;
        StringBuilder buf = new StringBuilder(len * 2);
        // 把密文转换成十六进制的字符串形式
        for (int j = 0; j < len; j++) {
            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
        }
        return buf.toString();
    }

    public static String encode(String str) {
        if (str == null) {
            return null;
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.update(str.getBytes());
            return getFormattedText(messageDigest.digest());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

三、微信自定义菜单

链接: https://wei.jiept.com/

查询菜单
在这里插入图片描述
添加菜单并发布
在这里插入图片描述

四、测试

关注公众号,测试

在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值