精华内容
下载资源
问答
  • 关注公众号下载
    千次阅读
    2021-02-22 11:43:51

    小程序内部引导关注公众号实现方法:

    1、小程序内部嵌入公众号二维码,提示保存到本地然后扫码关注
    2、小程序关注组件,官方已有的开放功能,该方式使用有限制,
    a.公众号和小程序必须为同一主体
    b.特殊场景才能使用,比如扫码进入小程序,具体见官方文档
    3、文字描述,提示用户搜索公众号名字进行关注
    4、客服消息
    5、跳转关联的公众号文章,通过文章头部公众号入口进行关注

    官方提示:

    1、不支持引导打开关注公众号页,支持打开关联公众号文章

    2、只支持打开关联公众号的文章


    引用:博客园

    更多相关内容
  • PHP微信扫码关注公众号并授权登录,网站使用公众号授权登录,上传资料中已经数据库和代码全部生成,未删减
  • 在WordPress后台,我们需要下载插件,而后通过上传的方式启用这款插件。插件安装完成后进入后台文章编辑文本模式下可以看到有一个“插入微信隐藏标签”的按钮,选中需要隐藏的图文内容,然后点击该按钮则会自动为你...
  • PHP原生代码实现微信扫码关注公众号并同时登录,适用于PC段进行扫描登陆。 大家可以下载使用。PHP原生代码实现微信扫码关注公众号并同时登录,适用于PC段进行扫描登陆。 大家可以下载使用。
  • PHP源码楼资源下载站整站打包 关注公众号获取下载密码 引流吸粉 可做成公众号吸粉资源站 赠送修改成购买下载密码文件 空间要求php mysql 伪静态
  • 资源名:asp微信公众号授权登陆程序源码 资源类型:程序源代码 源码说明: 基于asp编写的微信公众号授权登陆程序 包含完整的代码和注释 非常适合借鉴学习 适合人群:新手及有一定经验的开发人员
  • //插入表 } elseif ($Event == 'unsubscribe' or $Event == 'scan') {//已关注 $is_first = 1; } $access_token = $this->getAccessToken(); $userinfo = $this->getUserinfo($openid, $access_token); if($is_...

    class wechatCallbackapiTest {

    public function valid() {

    $echoStr = $_GET["echostr"];

    if ($echoStr) {

    if ($this->checkSignature()) {

    file_put_contents('access_token.txt', "jj." . date("Y-m-d H:i:s"));

    echo $echoStr;

    } else {

    file_put_contents('access_token.txt', "dd." . date("Y-m-d H:i:s"));

    }

    } else {

    $this->responseMsg();

    exit;

    }

    }

    public function responseMsg() {

    file_put_contents('userinfo.txt', date("Y-m-d H:i:s"));

    $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

    if (!empty($postStr)) {

    include_once 'config.php';

    $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);

    $scenes = json_decode($postObj->EventKey, true);

    $openid = $postObj->FromUserName; //openid

    $Event = strtolower($postObj->Event);

    if ($Event == 'subscribe') {//首次关注

    $is_first = 0;

    //插入表

    } elseif ($Event == 'unsubscribe' or $Event == 'scan') {//已关注

    $is_first = 1;

    }

    $access_token = $this->getAccessToken();

    $userinfo = $this->getUserinfo($openid, $access_token);

    if($is_first == 0){ //首次关注

    $qr_scene_str = json_decode($userinfo['qr_scene_str'],true);

    $mtype = $qr_scene_str['mtype'];

    $scene_id = $qr_scene_str['scene_id'];

    file_put_contents('mtype_scene_id.txt', $mtype."***".$scene_id);

    }else{

    $scene_id = $scenes['scene_id'];

    $mtype = $scenes['mtype'];

    }

    $sql = "UPDATE `qrcode` SET `openid` = '" . $openid . "',logintime='" . time() . "',is_first=" . $is_first . ",nickname='" . $userinfo['nickname'] . "'"

    . ",avatar='" . $userinfo['headimgurl'] . "',sex='" . $userinfo['sex'] . "',province='" . $userinfo['province'] . "',city='" . $userinfo['city'] . "',country='" . $userinfo['country'] . "' WHERE `id` =" . $scene_id . "";

    mysql_query($sql);

    file_put_contents('userinfo.txt', 'userinfo: ' . json_encode($userinfo) . $sql . "|" . $postObj->EventKey);

    } else {

    echo '咋不说哈呢';

    exit;

    }

    }

    private function getUserinfo($openid, $access_token) {

    if ($access_token && $openid) {

    $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=$access_token&openid=$openid&lang=zh_CN";

    $userinfo = $this->get_curl($url);

    return $userinfo;

    } else {

    return array("code" => "userinfo_null");

    }

    }

    public function getAccessToken() {

    $appid = 'wx123456b0b6bbfcfc';

    $secret = '12345657a3559cc5a5e606dc4735c47a';

    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" . $appid . "&secret=" . $secret . "";

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_POST, 1);

    curl_setopt($ch, CURLOPT_HEADER, 0);

    curl_setopt($ch, CURLOPT_URL, $url);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    $result = curl_exec($ch);

    curl_close($ch);

    $access_tokens = json_decode($result, true);

    $access_token = $access_tokens['access_token'];

    return $access_token;

    }

    public function get_curl($url) {

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_POST, 1);

    curl_setopt($ch, CURLOPT_HEADER, 0);

    curl_setopt($ch, CURLOPT_URL, $url);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    $result = curl_exec($ch);

    curl_close($ch);

    $rs = $result ? json_decode($result, true) : "";

    return $rs;

    }

    private function checkSignature() {

    $echoStr = $_GET["echostr"];

    $signature = $_GET["signature"];

    $timestamp = $_GET["timestamp"];

    $nonce = $_GET["nonce"];

    $token = TOKEN;

    $tmpArr = array($token, $timestamp, $nonce);

    sort($tmpArr, SORT_STRING);

    $tmpStr = implode($tmpArr);

    $tmpStr = sha1($tmpStr);

    if ($tmpStr == $signature) {

    return true;

    } else {

    return false;

    }

    }

    }

    ?>

    展开全文
  • 微信公众号开发(1)-实现关注公众号自动登录网站微信公众号初识一、[微信官方文档](https://developers.weixin.qq.com/doc/)**二、公众号申请流程三、公众号管理页面四、必备开发者工具的使用五、细读开发者文档六...

    微信公众号初识

    一、微信官方文档**

    在这里插入图片描述

    二、公众号申请流程

    在这里插入图片描述

    首先注册时可以看到公众号有三种类型,个人用户大多数选择订阅号,而企业用户一般选择服务号和企业号。

    我们平常大多数关注的都是订阅号,他们统一都放置在微信应用的订阅号消息列表中,没有微信支付等高级功能,只是用于发布文章等基础功能。
    在这里插入图片描述
    而服务号和企业号都在会话列表,和我们的微信好友是同级别的位置,具备微信支付等高级功能,一般是某个企业品牌的对外操作窗口,如海底捞火锅、顺丰速运等。
    在这里插入图片描述
    我们前期开发测试只需要注册个人订阅号即可,真正开发使用的是开发者工具里的测试号,具体下面会说。

    真正生产的话,使用的都是经过微信认证的订阅号、服务号、企业号。

    三、公众号管理页面

    我们在微信公众平台扫码登录后可以发现管理页面左侧菜单栏有丰富的功能:
    在这里插入图片描述
    大概可以分为这几大模块:
    首页、功能、小程序、管理、推广、统计、设置、开发

    作为开发人员,首先应该关注的是设置、开发模块;而作为产品运营人员,关注的是功能、管理、推广模块;作为数据分析人员,关注的是统计模块。

    首先我们不妨各个功能模块都点击看一看,大概了解下我们能做些什么。可以确认的是,这个微信公众平台当然不只是给开发人员使用的,它提供了很多非技术人员可在UI界面上交互操作的功能模块。

    如配置消息回复、自定义菜单、发布文章等:
    在这里插入图片描述
    这个时候我们可能会想:这些功能好像非技术人员都能随意操作,那么还需要我们技术人员去开发吗?

    答案是: 如果只是日常简单的推送文章,就像我们关注的大多数公众号一样,那确实不需要技术人员去开发;但是,如果你想将你们的网站嵌入进去公众号菜单里(这里指的是把前端项目的首页链接配置在自定义菜单),并且实现微信端的独立登录认证、获取微信用户信息、微信支付等高级功能,或者觉得UI交互的配置方式无法满足你的需求,你需要更加自由、随心所欲的操作,那么我们就必须启用开发者模式了,通过技术人员的手段去灵活控制公众号。

    这里有一点需要注意,如果我们决定技术人员开发公众号,必须启用服务器配置,而这将导致UI界面设置的自动回复和自定义菜单失效!

    我们在 开发 - 基本配置 - 服务器配置 中点击启用:
    在这里插入图片描述
    在这里插入图片描述
    我们团队就遇到过这种情况:两个项目组共用一个公众号,结果一个启用了服务器配置,使另一个项目组手动配置的菜单失效了。所以要注意这点!

    至于服务器配置中的选项代表什么意思、如何填写,我们下面再讲。

    四、必备开发者工具的使用

    在这里插入图片描述
    我们进入 开发 - 开发者工具, 可以发现微信提供了六种开发者工具,其中前四种属于开发必备:开发者文档在线接口调试工具web开发者工具公众平台测试账号

    1、开发者文档
    在这里插入图片描述
    这个不用说!在我们开发中属于最最最基础和重要的东西了,我们要想熟练开发公众号,首先必须熟读开发者文档!有些功能的开发甚至非要反复研读、咬文嚼字一番不可。PS:该文档吐槽的地方也不少,有些地方的确讲的不够明确!

    2、在线接口调试工具

    在这里插入图片描述
    这个工具也算比较实用,包含大多数接口的在线调试,我们可以直接在上面输入参数,获取微信服务端的返回结果。

    3、web开发者工具
    在这里插入图片描述
    这个工具是一款桌面应用,需要下载,它通过模拟微信客户端的UI使得开发者可以使用这个工具方便地在PC或者Mac上进行开发和调试工作,一般是前端使用该工具进行页面、接口调试。

    4、公众平台测试账号

    在这里插入图片描述
    这个测试号工具对我们的重要性可以说是仅次于开发者文档。我们可以创建测试号,无需申请、认证真实的公众帐号、可在测试帐号中体验并测试微信公众平台所有高级接口。并且所有的配置都可在一个页面上编辑,使开发测试变得极其便利。

    五、细读开发者文档

    需要注意的是,细读开发者文档不是让你所有模块都去阅读,而是重点的重复细读,非重点的选择性阅读。
    在这里插入图片描述

    其中前两个模块:开始前必读、开始开发,属于重点关注对象,也是整个微信开发的基石所在,需要多读几遍。其次是微信网页开发模块的微信网页授权,比较难理解,需要特别注意。其他的模块则根据你们的项目功能需求,有选择性的阅读即可。

    这里我就不多罗嗦了,大家看文档去吧!下面我会描述一些重点内容的实际操作情况以及代码,请确保你已经浏览过文档

    六、开发流程重点解析

    1.开发环境准备

    这里所谓的开发环境准备主要指的是我们项目服务端和微信服务端的网络通讯环境准备。

    我们平常开发可能只需要IP端口就能通讯,顶多配置下白名单放行,但微信公众号开发我们需要通过域名通讯(微信会访问我们配置的域名地址:服务器基本配置中的URL,下面会介绍),也就是我们各自开发环境需要拥有独立的域名,微信就能通过这个域名请求到我们的本地开发服务,各自进行开发测试。

    而我们一般都是内网开发,整个内网只有一个对外域名,所以这时就需要 内网穿透 ,为我们每个开发人员配置各自开发机器的域名。

    那如何进行内网穿透呢?你首先可以找下你们的网管,看他能不能帮你解决,如果不能,那就安装内网穿透工具,我们自己动手!

    我选择的内网穿透工具是natapp,这个有免费版、收费版,免费版的域名会随机变化,而收费版可以拥有固定域名,建议选择收费版,9元每月并不贵;大家可以对照natapp的文档安装使用,并不难。
    在这里插入图片描述
    这样我们本地开发环境就拥有自己的域名啦!然后就可以在测试号管理页面配置本地访问地址URL了。

    2.服务器基本配置
    无论是在真实公众号的 开发 - 基本配置 - 服务器配置,还是在 测试号管理 中,我们都可以看到这几个基本参数:
    开发者ID(AppID)开发者密码(AppSecret)服务器地址(URL)令牌(Token)

    AppID 是公众号唯一开发识别码,配合开发者密码可调用公众号的接口能力,大多数微信接口都需要附带该参数。

    AppSecret 是校验公众号开发者身份的密码,具有极高的安全性。切记勿把密码直接交给第三方开发者或直接存储在代码中。如需第三方代开发公众号,请使用授权方式接入。其中获取accessToken就需要同时传入AppID和AppSecret获取。

    URL 是开发者用来接收微信消息和事件的接口URL,也就是我们服务后端的入口地址,需要注意的是该地址必须以域名形式填写,且必须以http 或 https 开头,分别支持80端口和443端口。如:http://yuanj.natapp1.cc/wechat。

    Token 可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性),也就是我们项目和微信服务端进行通信时,必须保证公众平台配置的Token和我们后台代码配置的Token保持一致,这样微信就能验证我们身份。

    注:EncodingAESKey 参数由开发者手动填写或随机生成,将用作消息体加解密密钥,我们前期可以采用明文模式进行开发测试,暂时先不用关注。
    在这里插入图片描述
    我们点击提交时,微信会以GET请求的方式访问我们配置的URL地址,并附加几个参数进行验证,所以你需要在该地址对应的项目后端接口里对这几个参数进行加工处理,返回微信需要的结果,这样就可以验证成功,使微信服务端认可你配置的URL和Token参数,后续就能互相通信了!

    在这里插入图片描述
    具体情况可以阅读微信文档 - 开始前必读 - 接入指南。
    微信服务器将发送GET请求到填写的服务器地址URL上,GET请求参数如下:
    在这里插入图片描述

    验证签名主要流程:
    1)将token、timestamp、nonce三个参数进行字典序排序
    2)将三个参数字符串拼接成一个字符串进行sha1加密
    3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

    这里附上我的签名校验的代码。

        public String wxOfficialTokenCheck(String signature, String timestamp, String nonce, String echostr) {
            log.info("开始校验此次消息是否来自微信服务器,param->signature:{},\ntimestamp:{},\nnonce:{},\nechostr:{}",
                    signature, timestamp, nonce, echostr);
            if (CheckUtils.checkSignature(signature, timestamp, nonce)) {
                return echostr;
            }
            return "";
        }
    
    import lombok.extern.log4j.Log4j2;
    
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    
    /**
     * @author wangshanshan
     * @date 2021/3/25
     */
    @Log4j2
    public class CheckUtils {
        private static final String TOKEN = "xiaobot";
    
        /**
         * 校验微信服务器Token签名
         *
         * @param signature 微信加密签名
         * @param timestamp 时间戳
         * @param nonce     随机数
         * @return boolean
         */
        public static boolean checkSignature(String signature, String timestamp, String nonce) {
            String[] arr = {TOKEN, timestamp, nonce};
            Arrays.sort(arr);
            StringBuilder stringBuilder = new StringBuilder();
            for (String param : arr) {
                stringBuilder.append(param);
            }
    
            String hexString = SHA1(stringBuilder.toString());
            return signature.equals(hexString);
        }
    
        private static String SHA1(String str) {
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("SHA-1");
                byte[] digest = md.digest(str.getBytes());
                return toHexString(digest);
            } catch (NoSuchAlgorithmException e) {
                log.info("校验令牌Token出现错误:{}", e.getMessage());
            }
            return "";
        }
    
        /**
         * 字节数组转化为十六进制
         *
         * @param digest 字节数组
         * @return String
         */
        private static String toHexString(byte[] digest) {
            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                String shaHex = Integer.toHexString(b & 0xff);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();
        }
    }
    

    3.access_token参数

    access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时(7200秒),需定时刷新,重复获取将导致上次获取的access_token失效。

    access_token这个参数非常重要,几乎贯穿整个微信公关号项目开发,我们如何在有效期内定时刷新获取呢?
    如果我们的微信公众号项目是单服务架构,可以直接作为静态变量存储在内存里;如果是多服务,可以用中间件存储,Redis、数据库都可以。SpringBoot项目内部可以通过@Scheduled注解,执行定时任务,既然access_token有效期是2小时,那我们可以一小时刷新获取一次,将其存入Redis,覆盖之前的access_token。

    七.业务逻辑及代码

    大体思路:
    1)生成一个带参数的二维码,用户扫码后会向配置的URL发送事件消息
    2)在配置的URL中可以获取到用户的openId以及二维码的参数等信息,若事件为关注和扫描,则将二维码参数保存到redis中,并规划好过期时间
    3)前端拿着二维码参数轮询redis,如果redis中有此key,则证明用户已关注

    1.生成带参二维码
    当前面的签名验证完成后,此时公众号接收到的消息就会放松到我们配置的URL上,对于扫码关注公众号实现登录的逻辑来讲,需要阅读以下模块文档
    在这里插入图片描述
    首先由于需要用户扫码跳转到公众号关注页面,所以先要产生一个带参数的二维码,用户扫描后,公众号可以接收到事件推送。

    目前有2种类型的二维码:

    临时二维码,是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
    永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。

    获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。

    创建二维码ticket

    每次创建二维码ticket需要提供一个开发者自行设定的参数(scene_id),分别介绍临时二维码和永久二维码的创建二维码ticket过程。

    临时二维码请求说明

    http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN

    POST数据格式
    json POST数据例子:{“expire_seconds”: 604800, “action_name”: “QR_SCENE”, “action_info”: {“scene”: {“scene_id”: 123}}}
    或者也可以使用以下POST数据创建字符串形式的二维码参数:{“expire_seconds”: 604800, “action_name”: “QR_STR_SCENE”, “action_info”: {“scene”: {“scene_str”: “test”}}}

    永久二维码请求说明

    http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
    POST数据格式
    json POST数据例子:{“action_name”: “QR_LIMIT_SCENE”, “action_info”: {“scene”: {“scene_id”: 123}}}
    或者也可以使用以下POST数据创建字符串形式的二维码参数: {“action_name”: “QR_LIMIT_STR_SCENE”, “action_info”: {“scene”: {“scene_str”: “test”}}}

    在我们的业务里由于不需要保存二维码,所以选择了使用临时二维码,代码如下(这里的代码有不同的Class,我放在了一起,复制时可根据跟人需要灵活摘取和删减):

    public ResponseBean<GetQRCodeRes> getQRCode() {
            GetQRCodeRes getQRCodeRes = new GetQRCodeRes();
            Snowflake snowflake = IdUtil.createSnowflake(SnowFlakeUtil.getWorkId(), SnowFlakeUtil.getDataCenterId());
            String scene = String.valueOf(snowflake.nextId());
            String qrUrl = WxOfficalApiClient.getQrCode(APPID, SECRET, scene);
    
            getQRCodeRes.setQrCodeUrl(qrUrl);
            getQRCodeRes.setScene(scene);
            return ResponseBean.create(getQRCodeRes);
        }
    
    @Data
    @ApiModel("获取带参数的二维码返回值")
    public class GetQRCodeRes {
        /**
         * 二维码url
         */
        private String qrCodeUrl;
        /**
         * redis的key值,可通过此key查询用户是否关注
         */
        private String scene;
    }
    
    @Log4j2
    public class WxOfficalApiClient {
        public static String getQrCode(String appId, String appSecret, String scene) {
            WxOfficalAccessToken accessToken = WxOfficalApi.getAccessToken(appId, appSecret);
            if (accessToken == null) {
                return null;
            }
            if (accessToken.getErrCode() != null) {
                log.error("获取access_token错误 {}", accessToken.getErrMsg());
                return null;
            }
            WxOfficalTicket ticket = WxOfficalApi.getTicket(accessToken.getAccessToken(), scene);
            if (ticket == null) {
                return null;
            }
            if (ticket.getErrCode() != null) {
                log.error("获取ticket错误 {}", ticket.getErrMsg());
                return null;
            }
            return ticket.getUrl();
        }
    
        public static WxOfficalUserInfo getUserInfo(String appId, String appSecret, String openId) {
            WxOfficalAccessToken accessToken = WxOfficalApi.getAccessToken(appId, appSecret);
            WxOfficalUserInfo userInfo = WxOfficalApi.getUserInfo(accessToken.getAccessToken(), openId);
            if (userInfo != null) {
                if (userInfo.getErrcode() != null) {
                    log.error("获取微信info错误 {}", userInfo.getErrmsg());
                } else {
                    return userInfo;
                }
            }
            return null;
        }
    
    }
    
    @Slf4j
    public class WxOfficalApi {
        /**
         * 获取access_token
         */
        private static final String GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    
        /**
         * 获取创建二维码ticket
         */
        private static final String GET_TICKET = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s";
    
        /**
         * 创建二维码
         */
        private static final String GET_QR_CODE = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s";
    
        /**
         * 获取用户信息
         */
        private static final String GET_USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s";
    
        public static String getAccessTokenUrl(String appId, String appSecret) {
            return String.format(GET_ACCESS_TOKEN, appId, appSecret);
        }
    
        public static String getTicketUrl(String accessToken) {
            return String.format(GET_TICKET, accessToken);
        }
    
        public static String getQRCodeUrl(String ticket) {
            return String.format(GET_QR_CODE, ticket);
        }
    
        public static String getUserInfoUrl(String accessToken, String openId) {
            return String.format(GET_USER_INFO, accessToken, openId);
        }
    
        @SuppressWarnings("DuplicatedCode")
        public static WxOfficalUserInfo getUserInfo(String accessToken, String openId) {
            WxOfficalUserInfo info = null;
            JSONObject jsonObject = WxApi.httpsRequest(getUserInfoUrl(accessToken, openId), "GET", null);
            if (null != jsonObject && !jsonObject.containsKey("errcode")) {
                try {
                    info = new WxOfficalUserInfo();
                    info.setSubscribe(jsonObject.getInteger("subscribe"));
                    info.setOpenid(jsonObject.getString("openid"));
                    info.setNickname(jsonObject.getString("nickname"));
                    info.setCity(jsonObject.getString("city"));
                    info.setCountry(jsonObject.getString("country"));
                    info.setHeadimgurl(jsonObject.getString("headimgurl"));
                    info.setUnionid(jsonObject.getString("unionid"));
                    info.setProvince(jsonObject.getString("province"));
                    info.setSex(jsonObject.getInteger("sex"));
                } catch (JSONException e) {
                    log.error("用户信息解析异常", e);
                }
            } else if (null != jsonObject) {
                info = new WxOfficalUserInfo();
                info.setErrcode(jsonObject.getInteger("errcode"));
                info.setErrmsg(jsonObject.getString("errmsg"));
            }
            return info;
        }
    
        /**
         * 获取access_token
         */
        public static WxOfficalAccessToken getAccessToken(String appId, String appSecret) {
            WxOfficalAccessToken info = null;
            JSONObject jsonObject = WxApi.httpsRequest(getAccessTokenUrl(appId, appSecret), "GET", null);
            if (null != jsonObject && !jsonObject.containsKey("errcode")) {
                try {
                    info = new WxOfficalAccessToken();
                    info.setAccessToken(jsonObject.getString("access_token"));
                } catch (JSONException e) {
                    log.error("accessToken解析异常", e);
                }
            } else if (null != jsonObject) {
                info = new WxOfficalAccessToken();
                info.setErrCode(jsonObject.getInteger("errcode"));
                info.setErrMsg(jsonObject.getString("errmsg"));
            }
            return info;
        }
    
        /**
         * 根据access_token获取Ticket
         */
        public static WxOfficalTicket getTicket(String accessToken, String scene) {
            WxOfficalTicket info = null;
            WxOfficialTicketRequestParam requestParam = new WxOfficialTicketRequestParam(scene);
            JSONObject jsonObject = WxApi.httpsRequest(getTicketUrl(accessToken), "POST",
                    JSONObject.toJSONString(requestParam));
            if (null != jsonObject && !jsonObject.containsKey("errcode")) {
                try {
                    info = new WxOfficalTicket();
                    info.setTicket(jsonObject.getString("ticket"));
                    info.setUrl(jsonObject.getString("url"));
                    info.setExpireSeconds(jsonObject.getLong("expire_seconds"));
                } catch (JSONException e) {
                    log.error("Ticket解析异常", e);
                }
            } else if (null != jsonObject) {
                info = new WxOfficalTicket();
                info.setErrCode(jsonObject.getInteger("errcode"));
                info.setErrMsg(jsonObject.getString("errmsg"));
            }
            return info;
        }
    
    }
    
    /**
     * 雪花算法获取workId和dataCenterId
     * 
     * @author wangshanshan
     * @date 2021/3/29
     */
    public class SnowFlakeUtil {
    
        public static Long getWorkId() {
            try {
                String hostAddress = Inet4Address.getLocalHost().getHostAddress();
                int[] ints = StringUtils.toCodePoints(hostAddress);
                int sums = 0;
                for (int b : ints) {
                    sums += b;
                }
                return (long) (sums % 32);
            } catch (UnknownHostException e) {
                // 如果获取失败,则使用随机数备用
                return RandomUtils.nextLong(0, 31);
            }
        }
    
        public static Long getDataCenterId() {
            try {
                int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName());
                int sums = 0;
                for (int i : ints) {
                    sums += i;
                }
                return (long) (sums % 32);
            } catch (NullPointerException e) {
                // 如果获取失败,则使用随机数备用
                return RandomUtils.nextLong(0, 31);
            }
        }
    
    }
    

    需要说明的是,项目开始阶段我使用的是scene_id作为二维码携带的参数,文档中的scene_id需要的是32位非0整型,并非是十进制的32位,而是二进制的32位,也就是不超过2^32-1,由于在业务中我选择了使用场景值ID绑定用户,通知前端扫描此二维码的用户已经关注,所以此处的scene_id必须要保证不重复,但是对于32位来讲我并没有想到一个很好的办法保证不重复,因此我选择了使用scene_str,文档介绍是一个字符串型的场景值ID,长度限制为1到64,此处生成的不重复的scene_str我选择了雪花算法(需要导入hutool依赖),用起来很简单,可以适当去了解一下。

    2.事件消息回调
    用户扫描了带参数的二维码,可能推送以下两种事件:

    • 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
    • 如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。

    参数会以xml的数据包格式发到配置的URL,xml格式如下

    <xml>
      <ToUserName><![CDATA[toUser]]></ToUserName>
      <FromUserName><![CDATA[FromUser]]></FromUserName>
      <CreateTime>123456789</CreateTime>
      <MsgType><![CDATA[event]]></MsgType>
      <Event><![CDATA[subscribe]]></Event>
      <EventKey><![CDATA[qrscene_123123]]></EventKey>
      <Ticket><![CDATA[TICKET]]></Ticket>
    </xml>
    

    MsgType为event,证明是事件消息
    Event为subscribe,证明是关注事件
    Event为SCAN证明是已关注后的扫描事件
    EventKey是场景值ID,即二维码携带的参数

    public String wxOfficialCallback(Map<String, String> map) {
            try {
                Map<String, String> map = XmlUtil.xmlToMap(request.getInputStream());
                Assert.notEmpty(map, "微信公众号事件回调xml数据包参数为空");
                if ("event".equals(map.get("MsgType"))) {
                    String jsonString = JSONObject.toJSONString(map);
                    MessageEventInfo messageEventInfo = JSONObject.parseObject(jsonString, MessageEventInfo.class);
                    log.info("接收到来自微信服务器的时间消息:{}", jsonString);
                    String openId = messageEventInfo.getFromUserName();
                    String event = messageEventInfo.getEvent();
                    WxOfficalUserInfo wxOfficalUserInfo = userDomainService.getWxOfficalUserInfo(EnumUtil.fromString(AppEnum.class, AppEnum.XIAOBOT.name()), openId);
                    String sceneId;
                    if (MessageEventEnum.SUBSCRIBE.getName().equals(event)) {
                        //关注
                        log.info("用户{}关注公众号", openId);
                        sceneId = messageEventInfo.getEventKey().replace("qrscene_", "");
                        messageEventInfo.setEventKey(sceneId);
                        //将场景值或者ticket保存到redis
                        RBucket<Object> bucket = redissonClient.getBucket(qrSceneRedisKeyPrefix + ":" + sceneId);
                        bucket.set(wxOfficalUserInfo, 2, TimeUnit.MINUTES);
                    } else if (MessageEventEnum.SCAN.getName().equals(event)) {
                        //扫码
                        log.info("用户{}扫描公众号二维码", openId);
                        sceneId = messageEventInfo.getEventKey();
                        //将场景值或者Ticket保存到redis
                        RBucket<Object> bucket = redissonClient.getBucket(qrSceneRedisKeyPrefix + ":" + sceneId);
                        bucket.set(wxOfficalUserInfo, 2, TimeUnit.MINUTES);
                    } else if (MessageEventEnum.UNSUBSCRIBE.getName().equals(event)) {
                        //取消关注
                        log.info("用户{}取消关注公众号", openId);
                    }
                }
            } catch (Exception e) {
                log.info("微信公众号事件回调接口异常:{}", e.getMessage());
                return "ERROR";
            }
            return "SUCCESS";
        }
    
    @Log4j2
    public class XmlUtil {
        public static Map<String, String> xmlToMap(InputStream inputStream) {
            Map<String, String> map = new HashMap<>();
            try {
                SAXReader reader = new SAXReader();
                org.dom4j.Document document = reader.read(inputStream);
                Element root = document.getRootElement();
                List<Element> elementList = root.elements();
                // 遍历所有子节点
                for (Element e : elementList)
                    map.put(e.getName(), e.getText());
                // 释放资源
                inputStream.close();
            } catch (IOException | DocumentException e) {
                log.info("xml转化为map出现异常:{}", e.getMessage());
            }
            return map;
        }
    }
    
    @Data
    public class MessageEventInfo {
        /**
         * 用户openid
         */
        private String FromUserName;
        /**
         * 消息类型
         */
        private String MsgType;
        /**
         * 事件类型
         */
        private String Event;
        /**
         * 事件KEY值,获取二维码时的scene_id
         */
        private String EventKey;
        /**
         * 二维码的Ticket
         */
        private String Ticket;
    }
    

    这里的返回值必须要返回一个字符串,否则会导致扫描后公众号提示“该公众号提供的服务出现故障,请稍后再试”。

    3.轮询

    轮询接口很简单,就是去查redis中是否有这个场景值ID。
    这里我返回的是openId和unionId,是因为在登录的逻辑上需要使用

    public ResponseBean<Map<String, String>> pollingLoginStatus(String sceneId) {
            RBucket<Object> bucket = redissonClient.getBucket(qrSceneRedisKeyPrefix + ":" + sceneId);
            if (bucket.isExists()) {
                WxOfficalUserInfo wxOfficalUserInfo = (WxOfficalUserInfo) bucket.get();
                HashMap<String, String> map = new HashMap<>();
                map.put("openId", wxOfficalUserInfo.getOpenid());
                map.put("unionId", wxOfficalUserInfo.getUnionid());
                return ResponseBean.create(map);
            }
            return new ResponseBean<>(-1, "用户未关注");
        }
    

    4.登录注册

    前端轮询得知用户已关注时就可以进行登录了,登录注册的逻辑需要根据业务需求去做,简单的流程就是获取到微信用户的信息然后在库里新增一个网站用户,绑定上他的openId、unionId等信息,也可以绑定上手机号等信息,这里我就不贴我的代码了,我们的代码业务很繁琐。

    八、补充-网页授权

    这是我在看到这位博主的博客受到的启发,讲的非常清楚,通俗易懂,并且跟着这位博主的引导,我也测试成功了网页授权的逻辑。

    接下来我就粘贴一下吧。

    注意,这是公众号开发的重难点之一,请把技术文档中的微信网页授权模块多读两遍,然后带着疑问来看我的解析。

    (1)先明确为什么需要网页授权?我们的目的是什么?
    答:用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。也就是通过这种授权机制,我们能获取微信用户信息,比如:头像、昵称、地区、个性签名等。

    (2)既然目的是获取用户基本信息,微信不是提供了专门的接口吗?非要网页授权?
    答:在文档的 用户管理 - 获取用户基本信息(UnionID机制) 模块可以看到的确有获取用户基本信息接口:

    在这里插入图片描述
    可以看到,这个接口只需要提供openid或者unionid,即可直接获取用户基本信息。那么问题来了,openid(unionid)又是如何获取呢?

    微信平台提供了两种方式获取用户的openid
    第一种方式:

    用户与公众号产生消息交互时,会以POST请求的方式向我们配置的服务器URL地址发送XML格式的消息,并附带该用户对应公众号的openid!关于什么是消息交互我们可以查看文档中的消息管理模块,比如我们在公众号输入栏中发送文字图片语音等属于普通消息交互,我们关注、取关、点击自定义菜单等属于事件消息交互,每当前端用户进行这个操作时,微信服务端都会向我们项目后台发送POST请求给我们传达信息:
    在这里插入图片描述
    可以看到,这个推送数据包中就包含了用户的消息交互类型、时间以及我们需要的openid!也就是说,无论用户在公众号里干了啥操作,我们都能知道他这个操作干了啥,以及他是谁(openid),这时就能调用 用户管理 - 获取用户基本信息(UnionID机制) 接口获取用户基本信息了。

    别高兴太早,这种通过消息交互获取用户信息的方式,用户占主动地位,我们项目后端服务被动接受,那么如果我有个基本需求:我想在自定义菜单 - 对应我们网站的前端页面上展示微信用户基本信息,能做到吗?你如何把后台接收到的消息和前端用户关联绑定?
    可见,这种被动的方式并不能实现该功能,我们需要主动出击,在前端就能获取到当前操作用户的openid!

    第二种方式:

    这种方式就是通过网页授权机制主动出击!详情见下文。
    (3)网页授权有哪几种机制?分别是怎样实现?应用于什么场景?
    答:主要有两种机制,对应两种scope:

    以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)。

    以snsapi_userinfo为scope发起的网页授权,是用来获取用户基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

    光看这两句解释你可能有一堆疑问,我们逐一分析:

    两种机制的前面授权步骤相同,大概如下:

    我们先要按照文档要求构造一个链接:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
    其中重点参数是redirect_uri,这个参数填的既可以是前端项目url,也可以是后端接口url,然后点击这个链接后,微信服务端经过重定向到我们填写的redirect_uri,会在此redirect_uri后拼接上一个code参数!然后前端或者后端通过code参数就可以调微信接口https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code获取openid等信息了:
    在这里插入图片描述
    这里讲下 snsapi_base 和 snsapi_userinfo 的不同点:

    首先snsapi_base是静默授权,什么意思呢?就是用户没有感知;与之对应的就是非静默授权的snsapi_userinfo了,这个scope公众号会弹出一个小窗口,需要用户手动点击授权,类似这种:

    在这里插入图片描述
    那么这两种scope授权的优劣势在哪呢?

    snsapi_base 的优势在于用户无感知,体验好,方便快捷;劣势在于获取openid后只能通过用户管理 - 获取用户基本信息(UnionID机制) 接口获取用户基本信息,而这种方式需要确保用户已经关注,不然是没有相关信息的!
    snsapi_userinfo 的优势在于无需用户关注公众号,只要用户点击了授权确认,即可通过access_token和openid调用专门的拉去用户信息接口获取信息,比较暴力。。;劣势在于需要用户手动授权,可能影响用户体验。

    在这里插入图片描述
    在此说下,我们项目是通过snsapi_base静默授权的,其中redirect_uri配置的是前端项目首页地址(前后端分离),并将构造的这个链接封装起来,直接配置在自定义菜单里,那么用户点击菜单,就直接重定向到前端项目,然后前端获取code参数调用后端获取openid接口,将获取的openid缓存到客户端,以便后面使用。

    (4)想要进行网页授权,我们需要在公众平台配置什么吗?
    答:需要!
    如果是测试号,需要在 测试号管理 - 体验接口权限表 - 网页服务 - 网页帐号 点击 修改。
    在这里插入图片描述
    在这里插入图片描述
    在这里配置的是回调页面即redirect_uri的域名!

    如果是正式号(需要微信认证),需要在 开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息 的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;

    而且正式号其他配置的地方也和测试号不一样,比如多了IP白名单、域名根路径下的txt验证文件,这个稍微摸索下应该没啥问题的。

    借鉴于:微信公众号开发基本流程

    展开全文
  • 想要短信唤起微信并引导用户关注公众号,经过调研这样是做不了的,但是我们发现可以通过短信唤起小程序,那么我们只需要在小程序中内嵌一个h5,用 h5 来引导跳转公众号就好了。 那么我们拆解一下要做的事情: 短信...

    背景

    想要短信唤起微信并引导用户关注公众号,经过调研这样是做不了的,但是我们发现可以通过短信唤起小程序,那么我们只需要在小程序中内嵌一个h5,用 h5 来引导跳转公众号就好了。

    那么我们拆解一下要做的事情:

    1. 短信唤起小程序
    2. 小程序webview内嵌h5
    3. h5跳转公众号

    1、短信唤起小程序

    https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/sms.html

    2、小程序webview内嵌h5

    第一步我们唤起小程序后,直接跳转到一个 webview 页面
    这个页面就是小程序的 webview 页面,比如路径为 /pages/web-view/web-view,那么第一步我们跳转的时候填写的小程序路径就为这个,然后携带一个叫 url 的参数就可以了,url 就是我们第 3 步中的 h5

    <template>
      <web-view :src="url" />
    </template>
    <script >
    import wepy from '@wepy/core'
    wepy.page({
      data: {
        url: ''
      },
      onLoad: function (options) {
        console.log('web-view的options', options)
    
        if (options.url) {
          this.url = decodeURIComponent(options.url)
        }
      }
    })
    </script>
    
    

    3、h5跳转公众号

    这个 h5 页面主要功能就是引导用户关注公众号,有三种方式:

    1. 二维码
    2. 链接直接点击
    3. 公众号文章卡片

    3.1 二维码

    登录微信公众号后台,找到“设置”>“公众号设置”>“账号详情” 里面有个下载二维码功能,这样就把公众号名片二维码下载下来放到页面上就好了

    长按二维码效果:
    在这里插入图片描述

    3.2 链接直接点击

    我们先找链接
    在这里插入图片描述
    如图拿到公众号的链接,在页面上用 a 标签跳转一下就好了
    跳转后的效果是这样:
    在这里插入图片描述

    3.3 公众号文章卡片

    文章中插入公众号卡片
    在这里插入图片描述
    效果
    在这里插入图片描述
    点击卡片跳到这里
    在这里插入图片描述

    展开全文
  • PHP判断是否首次关注公众号,扫码关注公众号获取微信用户头像、openid和省市等信息源码。 演示体验地址: https://www.skpan.cn/user/login.html 网盘下载地址: https://www.skpan.cn/EA3tZACFyO7 部分截图如下:...
  • 如何判断用户是否已关注公众号

    万次阅读 2018-09-11 19:10:49
    openid 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID scope 用户授权的作用域,使用逗号(,)分隔 错误返回结果: {"errcode":40029, "errmsg":...
  • 对于用户来说,能够越简单,不用动手做...今天就来介绍一下PC网站如何通过扫描微信二维码关注公众号,直接完成注册登录。 1、思考 最近在刷乐观数据的时候,发现网站注册登录流程有点不一样,都没怎么操作,只是用...
  • 一款关注可复制、可查看隐藏内容的插件,想要为公众号涨粉不可错过哦~ 可以隐藏Wordpress文章中的部分内容,只有访客点击分享到微博后才能查看,该插件很多博主都在使用,引流效果非常明显。 插件使用: 本插件和...
  • 对于很多做公众号的朋友来说,如果能够在粉丝关注公众号的时候收到消息通知,然后在粉丝关注的时候就立即与粉丝取得联系进行互动,这无疑是最佳的营销时机了。 但是由于微信官方是没有这个通知功能的,所以,最佳的...
  • 本文给大家推荐4种需要关注公众号后才能查看网站内容的方法。 方法一:插件版公众号引流 对于普通用户来说,使用插件版是最方便快捷的方法,这里分享的是”微信公众号涨粉“这款插件。 “微信公众号涨粉插件可以...
  • 本人最近在做一个saas模式的产品开发,公众号只有一个,但服务的客户有多种,在各客户下又有各自的用户。现在有这么一个需求,当用户扫描客户提供的公众号二维码时,会出现对应的客户欢迎语,并且显示客户的LOGO界面...
  • 用户下载文件前,必须关注公众号,回复指定关键词获取验证码才能下载文件。 支持所有模块的File文件字段安装。 每天自动更新验证码,持续留住粉丝。 后台插件界面 后台插件设置 前端下载流程 ..
  • 微信浏览器是屏蔽资源文件下载的,但是微信公众号内如何下载文件呢。只能借助于在其他浏览器打开,也就是跳到其他浏览器进行下载(如图)。 具体的逻辑很简单,就是在需要下载的位置比如:是一个单击事件 &lt;...
  • 根据任务提示扫码关注公众号后,给了一个下载码,搞不懂它怎么用,下载的时候还是要用积分下载的啊!!</p>
  • php微信扫码关注并登录,内包含sql,原生php代码,配置简单。最少积分下载
  • 判断该用户是否关注了公众号。访问下面的URL就可以判断 ...openid=".$openid."&lang=zh_CN 1 如果关注了公众号,subscribe为1。如果没有关注公众号subscribe为0。 未关注公众号: ...
  • 在网上查看很多博主的个人博客的文章的时候,要查看全文的时候需要关注博主的微信公众号获取验证码才行,以前自己还是老老实实的关注,但是发现关注了之后几乎没有技术文章,而是一次又一次发软广,不是卖课就是各种...
  • 目前只有认证的服务号有生成带参数的二维码接口权限(注意不要使用测试号,测试号虽然有服务号的接口权限,但是限制关注人数最多100人,超过了你就不能用了),认证服务号的这个接口权限可用于关注公众号登录,且不...
  • 如何获取微信公众号关注页链接

    千次阅读 2022-02-22 16:07:45
    想了想是否可以通过下载公众号的二维码,然后通过解析 出来二维码里面的地址,是否可以进行跳转?,亲测不行。 2.2 正确获取的方式 1.登录公众号 2.点击“账号详情” 3.右键查看网页源代码 4.搜索‘uin_base64...
  • 微信公众号关注/取消关注事件推送
  • 最新有小伙伴问我一个问题,像辉哥博客有些文章是用的关注微信公众号并回复指定内容后获取验证码才能查看文章的部分内容是怎么实现的,今天辉哥就教你纯代码实现WordPress文章部分内容关注微信公众号后可见 ...
  • WeChatDownload是一款用于下载微信公众号文章的神器,面向所有用户免费使,支持批量下载。  使用方法:复制微信链接后点击粘贴下载按钮下载该篇文章。关注作者可以实时获取最新版本。  批量下载公众号所有文章和...
  • 需求 解决方案 步骤 这里我们选择永久二维码 代码见第二章需求 用户通过微信扫码关注公众号获取...扫码是否关注公众号已关注,则微信会将带场景值扫描事件推送给开发者,根据场景判断当前二维码是否失效失效,公众号
  • 那么,我们怎么设置关注公众号的欢迎语呢?熟悉公众号运营的朋友肯定知道,在后台的自动回复那里,选择被关注回复,上传文字、图片、音频或视频,点击保存,你的欢迎语就设置好了。不过,你只能设置文字、图片、音频...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 119,992
精华内容 47,996
热门标签
关键字:

关注公众号下载