微信开发 未关注公众号可以拿到_微信公众号开发和微信公众平台 - CSDN
  • 引导用户点击设计好的链接,形如:...

    引导用户点击设计好的链接,形如:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9a3d0c9c3170978c&redirect_uri=http%3a%2f%2fwx.dizaozhe.cc%2fwechatconfig%2fdesc&response_type=code&scope=snsapi_userinfo&state=ssaweqeqew#wechat_redirect

    appid:为开发者微信公众号的appid。
    redirect_uri:微信验证成功后跳转到的重定向链接。编码后的地址。
    response_type:code
    scope:静默方式还是授权方式,一个只获取openid后者获取详细的基本信息
    state:双重认证的状态码 随意填写不会影响到程序。

    redirect_uri/?code=CODE&state=STATE

    授权完成之后,微信会将生成的code和传入的state作为参数挂在链接的后面。code使用之后就不能再次使用,保质期为5分钟。

    关注公众号的用户,获取基本信息的链接为:

    https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

    需要的参数为:
    access_token | 是 调用接口凭证
    openid | 是 普通用户的标识,对当前公众号唯一
    lang |否 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

    注:此处的access_token是通过基本方式获取的access_token

    https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

    返回用户的基本信息,其中包括nickname【用户的昵称】、头像,城市等其中subscribe【用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。】、subscribe_time【用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间】、unionid【只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。】、remark【公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注】、groupid【用户所在的分组ID(兼容旧的用户分组接口)】、tagid_list【用户被打上的标签ID列表】,未关注的用户获取不到上述数据

    未关注的用户 获取基本信息的链接为

    https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

    需要注意的是此处的access_token不是上面的access_token,而是通过生成的code通过微信来换取的。

    链接为:

    https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

    返回值为:
    { 
    "access_token":"ACCESS_TOKEN", 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同   
     "expires_in":7200,  access_token接口调用凭证超时时间,单位(秒)  
     "refresh_token":"REFRESH_TOKEN",    用户刷新access_token
     "openid":"OPENID",    用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
     "scope":"SCOPE" 用户授权的作用域,使用逗号(,)分隔
     } 

    拿到这个access_token和openid,请求获取该用户的基本信息。

    展开全文
  • 1、根据公众号的appid获取code ...//公众号微信的appid $REDIRECT_URI='http://www.ific.cc/check.php';//回调页面 // $scope='snsapi_base'; $scope='snsapi_userinfo';//需要授权 $url="https://open.


    1、根据公众号的appid获取code
        $APPID=APPID;//公众号在微信的appid
        $REDIRECT_URI='http://www.ific.cc/check.php';//回调页面    
        // $scope='snsapi_base';
        $scope='snsapi_userinfo';//需要授权
        $url="https://open.weixin.qq.com/connect/oauth2/authorize?appid=".$APPID."&redirect_uri=".urlencode($REDIRECT_URI)."&response_type=code&scope=".$scope."&state=STATE#wechat_redirect";
        header("Location:".$url);
    特:
     -->此处必须修改公众号的获取用户信息的回调url
    
    2.check.php页面
    $code = $_GET['code'];
    $state = $_GET['state'];
    
    /*根据code获取用户openid*/
    $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx72e1ef917e46fc68&secret=eb209bfaa8effa31f4508cea9788f5d0&code=".$code."&grant_type=authorization_code";
    
    $abs = file_get_contents($url);
    $obj=json_decode($abs);
    $access_token = $obj->access_token;
    $openid = $obj->openid;
    /*根据code获取用户openid end*/
    
    
    /*根据用户openid获取用户基本信息*/
    $abs_url = "https://api.weixin.qq.com/sns/userinfo?access_token=".$access_token."&openid=".$openid."&lang=zh_CN";
    $abs_url_data = file_get_contents($abs_url);
    $obj_data=json_decode($abs_url_data);
    echo $OpenId = $obj_data->openid;
    
    echo $NickName = $obj_data->nickname;
    /*根据用户openid获取用户基本信息*/ 
    

    批注://这种方法适用于服务号或者其余的申请到接口的,但是不适用于测试号。。。
    展开全文
  •   上周二开晨会分配任务的时候,分配了一个微信扫码关注公众号的需求。刚开始以为只要截个公众号二维码的图,然后按照UI出的设计稿把二维码放指定位置,再加上一波加边框加阴影的操作提交就完事了。所以当部门...


      上周二开晨会分配任务的时候,分配到了一个微信扫码关注公众号的需求。刚开始以为只要截个公众号二维码的图,然后按照UI出的设计稿把二维码放到指定位置,再加上一波加边框加阴影的操作提交就完事了。所以当部门大佬问多久能做完的时候,我毫不犹豫地说:小Case啦,两天妥妥的!

      回到座位上本想着时间还早,先刷会微博。刷着刷着看到广州东站的宜家要搬迁的消息,活动大减价全场5折起!!忍不住点进去宜家的网上商城,看了两圈好想剁手买买买。这时,部门大佬站了起来,好像正往我这边看。算了算了,先关注宜家公众号干活去吧,拿出手机准备扫码的时候整个人都懵了…

      诶?诶诶?!早上的需求好像是要实现扫码关注公众号并登陆的,但浏览器怎么会知道我扫码了,而且扫的还是登陆用的二维码…这怎么跟想象的不一样,我还打包票说两天内做完,真的完了完了。

      当然,两天后这扫码的功能还是经过测试按时提交,并在线上稳定运行了一周。如何才能快速实现,并尽量少踩坑,我想,有些思路和和代码写下来,以后可能会用得着。

      项目主要以Nodejs进行开发,优先选用Koa2 + ioredis等一些比较轻量级的模块实现,配合Alpine Linux制作Docker Image,最后得到的一个开箱即用的Docker镜像也仅仅只有33M

      浏览项目的完整代码可以点击这里github,如果对你有帮助欢迎Star。

    先整理一下需求:

    • 登录入口实现扫描二维码关注公众号并登录网站。已关注的直接跳转登陆,未关注的等待用户关注再跳转;
    • 新的公众平台扫码登录机制代替原有的微信开放平台的扫码登录;
    • 扫码关注后需要根据情况返回不同的提示(欢迎)信息。

      但目前已上线的网站同时提供微信扫码和手机/邮箱注册登录,新需求实际是想让更多的用户关注公众号。完全按照需求上做的话就会变成强制用户必须关注公众号,否则无法完成登录。考虑到市场上有不少同类型产品,这种强制的行为可能会导致用户反感,从而选择其他产品。

    经讨论需求改为:

    • 保留现有的微信扫码和手机/邮箱注册登录,完成注册/登录流程后,新增一个扫码关注公众号的页面;
    • 用户扫码关注,关注后利用**unionid机制**绑定账户,让手机/邮箱注册的用户以后可以直接微信扫码登录;
    • 点击关注后,网站自动跳转进入控制台,或点击暂不关注直接跳转;
    • 扫码关注后需要根据情况返回不同的提示(欢迎)信息。

    实现思路和步骤:

    1. 实现一个与微信公众号平台交互的API,接收并处理公众号推送的事件(关注、扫码和文字消息等);
    2. 实现一个生成二维码的API供浏览器调用,API可通过参数声明需要返回的格式;
    3. 请求公众平台 →【生成带参数的二维码】接口生成带有场景值的二维码,生成成功后记录到数据库并返回;
    4. 浏览器获取二维码信息后轮询二维码的扫描状态,扫描成功后自动跳转;
    5. 用户扫码后,公众平台会向1实现的API推送事件,如果是关注就获取用户信息,然后记录到数据库。

    第一步,搭建Koa的环境并接入微信公众平台

      提供的源码里包含删减过的 Koa2和 koa-router的代码,也可以使用原版的代码。建议使用Nodejs10以上版本,特别是Nodejs12,换了新的HTTP解析器(llhttp)性能直接提高了一倍。

    安装依赖

    package.json

    "dependencies": {
        "debug": "^4.1.1",
        "got": "^9.6.0",
        "ioredis": "^4.10.0",
        "mime-types": "^2.1.24",
        "negotiator": "^0.6.2",
        "xml2js": "^0.4.19",
        "ylru": "^1.2.1"
     }
    

    如果是直接用官方的 Koa,mime-types,negotiator,ylru都不用安装

    目录结构

    dir.png

    Koa APP的代码结构跟官方的栗子差不多,就直接看吧
    app.js

    const http = require('http')
    const Koa = require('./vendor/koa2/application')
    const XMLParser = require('./middlewares/XMLParser')
    const router = require('./routes/wechat')
    const app = new Koa()
    ​
    app.use(XMLParser) // 解析xml的中间件,用于预处理微信公众号推送的事件
    ​
    app.use(router.routes())
    app.use(router.allowedMethods())
    ​
    http.createServer(app.callback()).listen(3000)
    

    middlewares/XMLParser.js

    const parseXML = require('xml2js').parseString
    const debug = require('debug')('xml-parse')const parse = (req, options = {}) => {
        return new Promise((resolve, reject) => {
            let xml = ''
            req.on('data', chunk => { xml += chunk.toString('utf-8') })
               .on('error', reject)
               .on('end', () => parseXML(xml, options, (err, res) => {
                    if (err) reject(err)
                    resolve(res)
            }))
        })
    }
    ​
    module.exports = async (ctx, next) => {
        // 这里先尝试直接匹配,匹配失败再到mime库里查询
        if (ctx.request.type === 'text/xml' || ctx.is('xml')) {
            try {
                ctx.request.body = await parse(ctx.req)
            } catch (e) {
                debug(e.message)
            }
        }
        await next()
    }
    

    routes/wechat.js

    const Router = require('../vendor/koa-router')
    const wechatController = require('../controllers/wechat')
    ​
    ​
    const router = new Router({
        prefix: '/wechat'
    })// 测试号配置接口信息时需要校验,但传输的数据跟推送消息一样,所以放在同一个controller里处理
    // conntroller的完整path是/wechat/event,这个后面配置测试号URL的时候会用到
    router.get('/', ctx => ctx.body = 'hello wechat')
          .get('/event', wechatController)
          .post('/event', wechatController)
    ​
    module.exports = router
    

    先新建一个配置文件,与app.js同目录
    config.js
    WXMP的信息暂时留空,到配置微信公众平台的时候再填写

    const CACHE = {
        host: 'localhost',
        port: 6379
    }// WeChat Media Platform
    const WXMP = {
        appID: '',
        appSecret: '',
        token: ''
    }
    ​
    module.exports = {
        CACHE,
        WXMP
    }
    

    /controllers/wechat.js

    const { WXMP } = require('../config');
    const { SHA1 } = require('../utils/mUtils')
    ​
    module.exports = async (ctx, next) => {
        const token = WXMP.token
        const { signature, nonce, timestamp, echostr } = ctx.query
    ​
        /**
        * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319
        * 1)将token、timestamp、nonce三个参数进行字典序排序
        * 2)将三个参数字符串拼接成一个字符串进行sha1加密
        * 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        */
        const str = [token, timestamp, nonce].sort().join('')const signVerified = SHA1(str) === signature
    ​
        if (!signVerified) {
            ctx.status = 404 // 可以不设为404,koa默认的状态值就是404
            return
        }if (ctx.method === 'GET') ctx.body = echostr
        else if (ctx.method === 'POST') {
            // 实现思路里的第一步
            // 推送的消息会以POST的方式进到这里,暂时用不着,先放着
        }
    }
    

      来到这里,先测试一下Web服务是否能正常跑起来。这里使用Postman直接发请求,也可以用浏览器访问http://localhost:3000/wechat/

    hello_wechat.png

    接下来配置一下微信公众平台。

      线上的环境经不起折腾,还是用公众号测试号进行调试吧,如果你是刚开始接触微信公众号开发,推荐使用测试号。

    扫码登陆后会看到这样一个界面:

    mp-sandbox.png

      appID和appsecret是系统生成的,只需要填写URL和Token。把appID和appsecret贴到之前创建的config.js中,token自己随便输入,保证两个token一致即可。配置URL之前,我们需要一个域名和一个内网穿透的环境。旧版本的微信web开发工具提供一个类似的方式,让微信服务器可以向我们在内网的机器推送消息,新版没有这功能我们就自己百度一个吧。

      粗略比较了一下,发现续断内网穿透很大方,只要9.9就有两条8M永久使用的隧道(只比KFC会员价的原味鸡贵4毛,尊贵的VIP吃鸡怎么还这么贵(╯‵□′)╯︵┻━┻)。注册交了9.9入会费,装上客户端后进行简单配置就可以了,体验挺棒的。设置过程中发现他家支持好多系统,像群晖、OpenWRT那些都有,还有树莓派,看来吃尘多年的B+可以拿出来发挥余热了。好像还有一些有趣的功能,可惜体验隧道不支持。不过只要9.9,还要啥自行车呢,果断开干吧!

    xd.png

      点击保存稍等一会会得到一个外网访问地址,类似http://sd8xxxxxxxxs.gzhttp.cn

    tunnel.png

      接着把外网访问地址+之前定义的path(http://sd8xxxxxxxxs.gzhttp.cn/wechat/event)填写到测试号接口配置的URL中,然后点击提交。这时我们已经成功接入到微信公众平台了。

    第二步,实现一个生成二维码的API并完善与微信公众号平台交互的API

      开始第二步之前,先来了解一下创建二维码及用户扫码后公众号给web服务推送消息的流程:

    sequence.png

      首先在utils/wechat/文件夹中新建一个helper.js,负责提供公众号配置(用于下面创建wechat对象)和get/set access_token的两个方法。

    utils/wechat/helper.js

    const { WXMP } = require('../../config')
    const { redis } = require('../dbHelper')const config = {
        MP: {
            appID: WXMP.appID,
            appSecret: WXMP.appSecret,
            token: WXMP.token,
            getAccessToken: async () => {
                let token = await redis.get('access_token')
                return token
            },
            saveAccessToken: async (data = {}) => {
                await redis.set('access_token', data.access_token ,'EX', data.expires_in)
            }
        }
    }
    ​
    module.exports = {
        ...config
    }
    

      在utils/wechat/文件夹中新建一个wxmp.js,定义一个Wechat类

    ...
    const api = {
        accessToken: 'token?grant_type=client_credential',
        user: {
            info: 'user/info?',
        },
        QRCodeTicket: 'qrcode/create?',
        QRCode: 'showqrcode?'
    }class Wechat {
        constructor (opts) {
            // 这里的opts传入的是上面定义的config
            ...
            this.fetchAccessToken(true)
        }
        // 获取access_token
        async fetchAccessToken (init = false) {
            let token = await this.getAccessToken()if (!token) {
                token = await this.updateAccessToken()
                await this.saveAccessToken(token)
                token = token.access_token
            }
            return token
        }async updateAccessToken () {
            const url = api.accessToken + '&appid=' + this.appID + '&secret=' + this.appSecret
            return await got(url)
        }// 提供一个统一操作的入口,第一个参数传入操作函数名就可以拿到对应的配置
        async handle (operation, ...args) {
            const token = await this.fetchAccessToken()
            if (!token) return nullconst options = this[operation](token, ...args)
            let res = await wxGot(options)
    
            return res
        }// 获取用户信息
        getUserInfo (token, openID, lang) {
            const url = `${api.user.info}access_token=${token}&openid=${openID}&lang=${lang || 'zh_CN'}`return { url: url }
        }// 申请二维码Ticket
        getQRCodeTicket (token, sceneStr, timeout) {
            return {
                url: `${api.QRCodeTicket}access_token=${token}`,
                method: 'post',
                body: {
                    "expire_seconds": timeout || 60,
                    "action_name": "QR_STR_SCENE", // 临时二维码
                    "action_info": {
                    "scene": {
                        "scene_str": sceneStr
                    }
                }
            }
        }
    }
    ​
    module.exports = Wechat
    

      我们继续回到刚才的/routes/wechat.js,增加 “+” 标识的代码(新增获取二维码的路由)

    const Router = require('../vendor/koa-router')
    const wechatController = require('../controllers/wechat')
    + const { createQRCodeMB } = require('../controllers/wechat')const router = new Router({
        prefix: '/wechat'
    })// 测试号配置接口信息时需要校验,但传输的数据跟推送消息一样,所以放在同一个controller里处理
    // conntroller的完整path是/wechat/event,这个后面配置测试号URL的时候会用到
    router.get('/', ctx => ctx.body = 'hello wechat')
          .get('/event', wechatController)
          .post('/event', wechatController)
    +     .get('/qrcode', createQRCodeMB)
    ​
    module.exports = router
    

    然后打开/controllers/wechat.js,公众号推送事件类型可以参考这里

    const { WXMP } = require('../config');
    const { SHA1, fmtNormalXML, streamToBuffer, createTimestamp } = require('../utils/mUtils')
    const { redis } = require('../utils/dbHelper')
    const Wechat = require('../utils/wechat/wxmp')
    const MPConfig = require('../utils/wechat/helper').MP
    const got = require('got')
    const qr = require('../vendor/qr')
    const fs = require('fs')
    const pathResolve = require('path').resolve
    ​
    const MP = new Wechat(MPConfig)
    ​
    module.exports = async (ctx, next) => {
        ...
        if (ctx.method === 'GET') ctx.body = echostr
        else if (ctx.method === 'POST') {
            // 把数组形态的xmlObject转换可读性更高的结构
            const message = fmtNormalXML(ctx.request.body.xml)const msgType = message.MsgType
            const msgEvent = message.Event
            const userID = message.FromUserName
            let eventKey = message.EventKey
            let body = nullif (msgType === 'event') {
                switch (msgEvent) {
                    // 关注&取关
                    case 'subscribe':
                    case 'unsubscribe':
                        body = await subscribe(message)
                        break
                    // 关注后扫码
                    case 'SCAN':
                        body = '扫码成功'
                        break
                }
    
                if (!!eventKey) {
                    // 有场景值(扫了我们生成的二维码)
                    let user = await MP.handle('getUserInfo', userID)
                    let userInfo = `${user.nickname}${user.sex ? '男' : '女'}, ${user.province}${user.city})`
                    if (eventKey.slice(0, 8) === 'qrscene_') {
                        // 扫码并关注
                        // 关注就创建帐号的话可以在这里把用户信息写入数据库完成用户注册
                        eventKey = eventKey.slice(8)
                        console.log(userInfo + '扫码并关注了公众号')
                    } else {
                        // 已关注
                        console.log(userInfo + '扫码进入了公众号')
                    }// 更新扫码记录,供浏览器扫码状态轮询
                    await redis.pipeline()
                               .hset(eventKey, 'unionID', user.unionid || '') // 仅unionid机制下有效
                               .hset(eventKey, 'openID', user.openid)
                               .exec()
                }
            }
        }
    }async function subscribe (message) {
        let userID = message.FromUserName
        if (message.Event === 'subscribe') {
            return '感谢您的关注'
        } else {
            // 用户取消关注后我们不能再通过微信的接口拿到用户信息,
            // 如果要记录用户信息,需要从我们自己的用户记录里获取该信息。
            // 所以建议创建用户时除了unionid,最好把openid也保存起来。
            console.log(userID + '取关了')
        }
    }const templetData = fs.readFileSync(pathResolve(__dirname, '../vendor/qrcode-templet.html'))// 创建二维码
    async function createQRCodeMB (ctx, next) {
        let userID = ctx.query.userID
        let type = +ctx.query.type
        let errno = 0
        let responseDate = {}
        let id = createTimestamp()let res = await MP.handle('getQRCodeTicket', id)if (res === null) errno = 1
        else {
            responseDate = {
                expiresIn: res.expire_seconds,
                id
            }let imgBuffer = await streamToBuffer(qr.image(res.url))
            let imgSrc = imgBuffer.toString('base64')if (type === 1) {
                // 返回图片
                ctx.body = `<img src="data:image/png;base64,${imgSrc}" />`
            } else if (type === 2) {
                // 返回一个自带查询状态和跳转的网页
                let templetValue = `
                    <script>var imgSrc='${imgSrc}',id='${responseDate.id}',
                    timeout=${responseDate.expiresIn},width=100,height=100</script>`
    ​
                ctx.body = templetValue + templetData.toString('utf-8')
            } else {
                // 返回图片内容
                responseDate.imgSrc = imgSrc
            }
         }if (!ctx.body) {
            ctx.body = {
                errno,
                ...responseDate
            }
        }
    }
    ​
    module.exports.createQRCodeMB = createQRCodeMB
    

      到这里应该是可以接收到公众号推送的扫码事件和生成二维码。

      保存后我们先测试一下,首先不带参数访问http://localhost:3000/wechat/qrcode

    normal.png

      接着尝试获取二维码图片(使用参数type=1)并使用微信扫描二维码:

    scan.png

      首次扫描二维码会提示关注,点击关注后数据库就会更新,控制台也会打印出类似 “XXX扫码并关注了公众号“ 的日志。但这时候公众号里应该会提示 ”该公众号提供的服务出现故障,请稍后再试“ 的提示,因为程序并没有把提示信息正确得返回。下一步我们需要格式化返回的信息(即ctx.body的内容)。

    新增一个生成模板的文件/utils/tmpl.js
    格式化给公众号返回的消息,这里只简单使用util.format来格式化消息。

    const util = require('util')const msgTemplet = `
    <xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%d</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        $msgBody$
    </xml>
    `const textMsg = `<Content><![CDATA[%s]]></Content>`
    const imageMsg = `<Image><MediaId><![CDATA[%s]]></MediaId></Image>`
    ​
    module.exports = (ctx, originMsg) => {
        let type = (ctx && ctx.type) || 'text'
        let msgTmpl = util.format(msgTemplet,
            originMsg.FromUserName,
            originMsg.ToUserName,
            Math.floor(new Date().getTime() / 1000),
            type
        )let body = ''switch (type) {
            case 'text':
                body = util.format(textMsg, ctx)
                break
        case 'image':
            break
        default:
            body = util.format(textMsg, '操作无效')
        }return msgTmpl.replace(/\$msgBody\$/, body)
    }
    

    接着我们在controllers/wechat.js增加一下 ”+“ 标记的代码

    const { WXMP } = require('../config');
    const { SHA1, fmtNormalXML, streamToBuffer, createTimestamp } = require('../utils/mUtils')
    + const { tmpl } = require('../utils/wechat')
    const { redis } = require('../utils/dbHelper')
    const Wechat = require('../utils/wechat/wxmp')
    ...
    module.exports = async (ctx, next) => {
        const token = WXMP.token
        const { signature, nonce, timestamp, echostr } = ctx.query
    ​
        const str = [token, timestamp, nonce].sort().join('')
        ...
                    // 更新扫码记录,供浏览器扫码状态轮询
                    await redis.pipeline()
                               .hset(eventKey, 'unionID', user.unionid || '') // 仅unionid机制下有效
                               .hset(eventKey, 'openID', user.openid)
                               .exec()
                }
            }+       ctx.type = 'application/xml'
    +       ctx.body = tmpl(body || ctx.body, message)
        }
    }async function subscribe (message) {
        let userID = message.FromUserName
        if (message.Event === 'subscribe') {
            return '感谢您的关注'
        } else {
            console.log(userID + '取关了')
        }
    }
    

      保存后再获取一次二维码并扫描,微信上就能正确显示提示信息了:

    message.png

    第三步,浏览器增加扫码状态轮询

      这块跟业务代码关系比较密切,所以不做详细介绍。共通点就是通过二维码返回id获取unionid(openid)的记录,然后按需处理,最后以cookies或其他方式更新登录状态。

    轮询的代码可以参考vendor/qrcode-templet.html

    async function waitToSubscribe(id, timeout) {
        let countdown = Math.ceil(timeout / 3);
        return new Promise((resolve, reject) => {
            const loop = async function() {
                let res = await ky.default.get("/wechat/check", {
                    searchParams: { id }
                }).json();
    
                if (!res) return;
                if (res.errno === 0) resolve("subscribe");
                else if (res.errno === 2) reject("timeout");
                else if (countdown-- > 0) self.QRCodeTimer = setTimeout(loop, 3000);
            };
            loop();
        });
    };
    
    (async () => {
        try {
            await waitToSubscribe(id, timeout);
            window.location.href = "/wechat/";
        } catch (e) {
            history.go(0);
        }
    })();
    

    我们可以尝试获取集成好获取状态的二维码网页(使用参数type=2,实际使用时可以用iframe嵌套):

    auto.png

    总结:

      到这里,我们已经实现了:

    1.  与微信公众号平台交互的API,能够接收并处理公众号推送的事件;
    2.  生成二维码的API,并能分别以三种常用方式返回二维码;
    3.  扫描二维码后,微信上能正常显示服务返回的提示信息,并成功记录在数据库中;
    4.  当浏览器轮询二维码的扫描状态并获取到扫描结果后,自动跳转。
    

      以上几乎包含了公众号开发的完整流程,其他的功能可以参照公众号开发文档上的说明按需增加。这里有一点需要注意的,文中提到的unionid机制需要以公司身份申请正式的公众号和微信开放平台,并在开放平台上完成公众号绑定。同一个用户在已绑定公众号、小程序、网站应用等程序里会使用同一个unionid来确定用户的唯一性。

      像公众号网页授权、开放平台的网站应用授权(类似京东的扫码登录)和小程序的开发,等有空的时候再更新。码了这么多字,差点忘了要去宜家扫货,广告上说促销商品数量有限,万一卖完了岂不是错过了几个亿::>_<::,周末要找个时间过去看看才行。

    最后附上Dockerfile 和源码地址

      预先拷贝文件到/build目录,便于生成更小的Docker Image

    cp -rf vendor docker/build/vendor
    cp -rf utils docker/build/utils
    cp -rf routes docker/build/routes
    cp -rf middlewares docker/build/middlewares
    cp -rf controllers docker/build/controllers
    cp app.js docker/build/app.js
    cp config.js docker/build/config.js
    
    FROM alpine
    COPY package.json /var/www/wechat-mp/
    WORKDIR /var/www/wechat-mp
    RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
     && apk add nodejs npm \
     && npm install --production --registry=https://registry.npm.taobao.org \
     && npm cache clean -f \
     && rm package-lock.json \
     && apk del npm \
     && rm -rf ~/.npm \
     && rm -rf /var/cache/apk/* \
     && rm -rf /root/.cache \
     && rm -rf /tmp/*
    COPY build/ /var/www/wechat-mp/
    CMD node app.js</pre>
    

    github: https://github.com/lym0r9/wechat-mp

    文章转载自快速实现微信扫码关注公众号/用户注册并登陆

    展开全文
  • 最近接到新需求,为将用户引流到公众号,官网实现...1、微信扫码进入微信关注页面,若关注微信向之服务器配置中的接口发送一条信息,说明用户已经关注,这时就可以根据拿到到openid去获取用户信息实现登陆。 2、结果...

    最近接到新需求,为将用户引流到公众号,官网实现微信扫码关注公众号并登陆官网。由于第一次需求没有说清楚,只是说了微信登陆,所以想到微信开放平台实现微信登陆,后来做完后发现是为了向公众号引流,需要关注公众号。
    经过查看文档想到了两种实现方式:
    1、微信扫码进入微信关注页面,若关注微信向之服务器配置中的接口发送一条信息,说明用户已经关注,这时就可以根据拿到到openid去获取用户信息实现登陆。
    2、结果想法是美好的,然而现实是残酷到,登陆公众号后台后发现公众号服务器配置已经被别的系统占用,第一种方法不可用。后来查看文档发现获取用户信息的时候微信会同时告诉系统用户是否关注公众号。由此衍生了第二种方案,首先获取用户信息,根据获取用户是否关注公众号判断,若未关注跳转让用户关注,若已经关注则直接登陆。

    开发前配置:

    在公众号后台->接口权限->网页授权中填写网页接口。
    在公众号设置->功能设置中填写你的域名(注意,此处是域名,而不是http链接,所以不用添加http或者https前缀)。

    第一种方法实现

    1、获取关注二维码,此二维码用户扫码后若没关注会跳转到公众号详情页,若已经关注则会跳转到公众号聊天页。以下写的小demo:

    //获取access_token
    $file = file_get_contents("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=AppSecret");
    $acc_token = json_decode($a)->access_token;
    $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=$acc_token";
    
    $result = array(
        'expire_seconds' => 1800,
        'action_name' => 'QR_SCENE',
        'action_info' => ["scene" => ["scene_id" => 123]]
    );
    $result = json_encode($result);
    
    $ch = curl_init();//初始化curl
    curl_setopt($ch, CURLOPT_URL,$url);//抓取指定网页
    curl_setopt($ch, CURLOPT_HEADER, 0);//设置header
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
    curl_setopt($ch, CURLOPT_POSTFIELDS, $result);
    $data = curl_exec($ch);//运行curl
    curl_close($ch);
    $data = json_decode($data);
    $tick = $data->ticket; //拿到tick
    $tick = urlencode($tick);
    //换取二维码
    $img = file_get_contents("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=$tick");
    
    file_put_contents("./test.jpg",$img);
    

    之后用户关注后会向你之前配置的接口中发送用户关注公众号信息。接下来各位可以根据自己的业务进行开发了。

    第二种实现

    注:此方法是在无法配置网页授权接口时候使用
    首先说明我的思路,自己本地生成一个二维码,二维码中应该包含一个网址,一个自定义的code,一个二维码用户过期的时间(这个时间根据各位业务,也可以不配置)。当用户扫描二维码的时候会打开我们我们之前在二维码配置的网址,而这个网址是在微信中打开,这样我们就可以根据微信网页授权接口去调用微信接口。详情请查看:
    微信网页授权
    此方法不需要获取网页授权中的用户信息,只需要获取到用户openid即可。用户授权需要到获取用户信息(注意:此接口中的access_token和之前的网页授权的access_token不是同一个,而应该到以下网址获取:获取access_token,关于两个access_token下方会进行讲解。)获取。此时就可以根据返回的用户信息中subscribe字段查看用户是否关注公众号,若关注则保存信息登陆,否则则跳转到关注页。
    此时大家都会有一个问题,网页是在微信中打开的,那我怎么第三方网站该怎么确定用户是否登陆呢?其实我们前面扫码的时候传过来一个code,此时我们就可以将获取到到用户信息和code绑定,您可以将这部分信息放入数据或者redis中,然后在我们的网站中根据这code循环获取这部分信息,若查询到了网站则登陆。
    以下是具体步骤
    1、生成二维码,二维码中包含信息为http://域名/接口?code=CODE&time=TIME。同时将code放入session,方便循环接口到时候使用。当然也可以根据用户ip放入redis,设置过期时间。
    2、用户微信扫码,打开上步骤到链接,此时会获取到code和过期时间,code应该保存进session,下方会用到。
    3、此时页面是在微信中打开,我们就可以可以根据网页授权接口进行接下来步骤了。
    3.1、用户同意授权,获取code
    3.2、通过code换取网页授权access_token和用户openid
    此时通过以上两步我们已经获取到了用户openid,此时有人问了,文档接下来就是就是获取用户信息了,为什么我们不获取了呢?因为接下来获取到用户信息没有subscribe(用户是否关注)字段,也就是我们无法判断用户是否关注公众号。
    3.3、访问获取用户信息接口。此时我们就可以获取到了用户是否关注公众号信息。
    3.4、根据获取到的用户信息判断用户是否关注公众号,若关注则将获取到的用户信息和之前的code绑定,然后保存,否则则跳转到获取公众号关注页链接。这个链接有一个问题,就是跳转到的公众号关注页面关注按钮会一闪而逝,我这里并没有解决办法,此时用户需要点击页面上公众号名称才可以关注。
    3.5、此时回到咱们的第三方网站,此时用户还没有登陆,这时候我们应该写一个接口,根据之前生成二维码时候的code循环获取用户信息,若信息存在则登陆,此时code相当于用户openid的作用。

    关于网页授权中的access_token和获取用户基本信息中的access_token不同之处

    网页授权中的access_token是一次性的,只在当前网页授权中使用,并且是可以无限制获取的,每次code都可以获取一次。而获取用户基本信息中的access_token是全局的,有获取次数限制,两小时后过期,微信开发中使用的access_token基本都是指这个。

    展开全文
  • 1、了解一下微信官方开发公众号文档 微信官方文档-公众号 对文档介绍有一个大致的印象,然后开始动手配置 2、申请微信号,官方提供的测试号平台 微信平台-测试号管理 申请测试号以及配置ok了,...
  • 业务需求就是生成微信二维码 微信扫维码关注公众号,已关注则跳转业务页面,没关注则跳转关注页面 1 生成需要扫的二维码 https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=“这...
  • 2.如果用户未关注,弹窗提示,是否关注公众号,不关注则取消弹窗,关注的话跳转到微信关注页面 3.一天内未关注用户只会接收一次弹窗提示 4.更新用户的关注状态入库 一,用户在微信公众号端进入h5页面时,前端从后端...
  • 微信扫码 - 关注公众号后网站自动注册并登录的实现 需求描述 在自己网站上点击微信登录,网站自己弹出一个二维码、扫描二维码后...微信开发文档 实现原理 公众平台提供了生成带参数二维码的接口。使用该接口可...
  • 1.UnionId和OpenId 微信登录最重要的两个返回信息...开放平台下面可以申请多个应用或绑定微信公众号(必须为服务号)。如下图所示 APP登录就得申请移动应用(做了微信支付的都知道怎么回事了哈);电脑端登录就...
  • 本人最近要做微信公众号网页开发的项目,其中有个需求是判断用户是否关注公众号,由于之前没有接触过微信授权的东西,所以提前开始做调研。在度娘上看了好多博客、百度知道、百度经验、知乎问答等,还仔细阅读了微信...
  • 微信企业号与微信公众号的配置流程大同小异(殊途同归)。 2,准备工作 2.1,企业号的申请与域名的申请及备案 企业号(公众号)申请需要提前申请,因为有些东西需要备案,审核啥的; 测试环境/生产环境的域名...
  • 微信H5授权用户和公众号关注用户区别 概念H5页面访问用户是通过微信jscode获取token授权普通访问用户 公众号关注用户是关注微信公众号后的用户 概念上一个是普通访问用户(包括了但不限于关注用户) 用户的信息...
  • 利用H5开发微信公众号一、 首先授权配置 公众号设置 --》功能设置 设置业务域名! 123 这里的MP_verify_w7tdZrafqhkK9Mcj.txt文件,需要放你项目的根目录下,例子:你的项目war包叫 test.war ,里面包含了src,WEB...
  • 我们开发微信公众号微信官方规定,公众号请求必须配置已备案的域名,并且只支持80和443端口。以往的做法是每次写完代码发布映射了域名的服务器上,通过打印日志来调试,这种做法很麻烦,也不利于调试,还有就是...
  • 的确,如果是让用户直接关注微信公众号,扫公众号的二维码,或者在微信里面长按识别二维码即可。但是有时候的业务需求可能是:在自己写的一个页面中,需要引导用户去关注公众号。 我们知道,二维码在网页里或者小...
  • 实现html5微信支付以及微信公众号微信支付 微信公众号微信支付 ... /*获取授权拿到code*/ getCodeApi(state){//获取code let urlNow=encodeURIComponent(window.location.href);//授权之后重定向到当前页面 ...
  • 微信公众号开发技术要点 微信公众号开发技术要点 微信公众号及其接口功能介绍 基本概念 公众号开发者模式 代码验证及图示 Open ID与Union ID 基本概念 使用说明 Access_token 基本介绍 注意事项 获取流程 ...
  • 扫二维码关注,获取更多技术分享 ...如果有任何问题大家可以关注以上公众号,在公众号中给我留言,一般我会在晚上统一查看所有邮件及留言逐一回复。我的邮箱地址weiyongqiang@weiyongqiang.com希望和大家
  • 微信的官网上查询,绑定在同一个微信开放者平台上,则会生成一个unionId,这个unionId在小程序端和在微信公众号端都是一样的,所以我们这个来实现向微信公众号推送模板消息的功能。 前期准备: 微信公众号:...
  • 微信支付是集成在微信客户端的支付功能,用户可以通过手机完成快速的支付流程。微信支付以绑定银行卡的快捷支付为基础,向用户提供安全、快捷、高效的支付服务。 公众号支付 APP支付 扫码支付 刷卡支付 H5支付 ...
1 2 3 4 5 ... 20
收藏数 24,026
精华内容 9,610
关键字:

微信开发 未关注公众号可以拿到