123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- 'use strict';
- const assert = require('assert');
- const uuid = require('uuid');
- const _ = require('lodash');
- const { BusinessError, ErrorCode } = require('naf-core').Error;
- const jwt = require('jsonwebtoken');
- const { AxiosService } = require('naf-framework-mongoose/lib/service');
- class WeixinAuthService extends AxiosService {
- constructor(ctx) {
- super(ctx, {}, _.get(ctx.app.config, 'wxapi'));
- }
- // 通过认证码获得用户信息
- async fetch(code) {
- // TODO:参数检查和默认参数处理
- assert(code);
- const { wxapi } = this.app.config;
- const res = await this.httpGet('http://api.jilinjobswx.cn/api/fetch', { code });
- console.log(res);
- if (res.errcode && res.errcode !== 0) {
- this.ctx.logger.error(`[WeixinAuthService] fetch open by code fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信认证信息失败');
- }
- this.ctx.logger.info(`auth=>${JSON.stringify(res)}`);
- // const reqUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token';
- // const params = {
- // appid: wxapi.appid,
- // secret: wxapi.appSecret,
- // code,
- // grant_type: 'authorization_code',
- // };
- // const res = await this.httpGet(reqUrl, params);
- // if (res.errcode && res.errcode !== 0) {
- // this.ctx.logger.error(`[WeixinAuthService] fetch open by code fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
- // throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信认证信息失败');
- // }
- // const { openid } = res;
- return res;
- }
- // 通过openid获得用户信息
- async fetchUnionID(openid) {
- // TODO:参数检查和默认参数处理
- assert(openid);
- const appid = 'wxdf3ed83c095be97a';
- const grant_type = 'client_credential';
- const secret = '748df7c2a75077a79ae0c971b1638244';
- // TODO: 获得用户信息
- const url = 'http://wx.cc-lotus.info/api.weixin.qq.com/cgi-bin/token?appid=' + appid + '&grant_type=' + grant_type + '&secret=' + secret;
- const res = await this.ctx.curl(url, {
- method: 'get',
- headers: {
- 'content-type': 'application/json',
- },
- dataType: 'json',
- });
- // console.debug('res: ', res);
- if (res.errcode && res.errcode !== 0) {
- this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
- }
- const token = res.access_token;
- const urlun = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' + token + '&openid=' + openid + '&lang=zh_CN';
- const result = await this.ctx.curl(urlun, {
- method: 'get',
- headers: {
- 'content-type': 'application/json',
- },
- dataType: 'json',
- });
- // console.debug('res: ', res);
- if (result.errcode && result.errcode !== 0) {
- this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
- }
- return result;
- }
- async createJwt({ openid, nickname, subscribe }) {
- const { secret, expiresIn = '1d', issuer = 'weixin' } = this.config.jwt;
- const subject = openid;
- const userinfo = { nickname, subscribe };
- const token = await jwt.sign(userinfo, secret, { expiresIn, issuer, subject });
- return token;
- }
- /**
- * 创建二维码
- * 随机生成二维码,并保存在Redis中,状态初始为pending
- * 状态描述:
- * pending - 等待扫码
- * consumed - 使用二维码登录完成
- * scand:token - Jwt登录凭证
- */
- async createQrcode() {
- const qrcode = uuid();
- const key = `visit:qrcode:group:${qrcode}`;
- await this.app.redis.set(key, 'pending', 'EX', 600);
- return qrcode;
- }
- /**
- * 创建二维码
- * 生成群二维码
- * 状态描述:
- * pending - 等待扫码
- * consumed - 使用二维码登录完成
- * scand:token - Jwt登录凭证
- */
- async createQrcodeGroup({ groupid }) {
- const { authUrl = this.ctx.path } = this.app.config;
- let backUrl;
- if (authUrl.startsWith('http')) {
- backUrl = encodeURI(`${authUrl}?state=${groupid}`);
- } else {
- backUrl = encodeURI(`${this.ctx.protocol}://${this.ctx.host}${authUrl}?state=${groupid}`);
- }
- return backUrl;
- }
- /**
- * 扫码登录确认
- */
- async scanQrcode({ qrcode, token }) {
- assert(qrcode, 'qrcode不能为空');
- assert(token, 'token不能为空');
- const key = `smart:qrcode:login:${qrcode}`;
- const status = await this.app.redis.get(key);
- if (!status) {
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
- }
- if (status !== 'pending') {
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效');
- }
- // 验证Token
- const { secret } = this.config.jwt;
- const decoded = jwt.verify(token, secret, { issuer: 'weixin' });
- this.ctx.logger.debug(`[weixin] qrcode login - ${decoded}`);
- // TODO: 修改二维码状态,登录凭证保存到redis
- await this.app.redis.set(key, `scaned:${token}`, 'EX', 600);
- // TODO: 发布扫码成功消息
- const { mq } = this.ctx;
- const ex = 'qrcode.login';
- if (mq) {
- await mq.topic(ex, qrcode, 'scaned', { durable: true });
- } else {
- this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
- }
- }
- // 使用二维码换取登录凭证
- async qrcodeLogin(qrcode) {
- assert(qrcode, 'qrcode不能为空');
- const key = `smart:qrcode:login:${qrcode}`;
- const val = await this.app.redis.get(key);
- if (!val) {
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
- }
- const [status, token] = val.split(':', 2);
- if (status !== 'scaned' || !token) {
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效');
- }
- // TODO: 修改二维码状态
- await this.app.redis.set(key, 'consumed', 'EX', 600);
- return { token };
- }
- // 检查二维码状态
- async checkQrcode(qrcode) {
- assert(qrcode, 'qrcode不能为空');
- const key = `smart:qrcode:login:${qrcode}`;
- const val = await this.app.redis.get(key);
- if (!val) {
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
- }
- const [status] = val.split(':', 2);
- return { status };
- }
- // 发送微信模板消息
- async sendTemplateMsg(templateid, openid, first, keyword1, keyword2, remark, tourl) {
- const { wxapi } = this.ctx.app.config;
- const url = wxapi.sendDirMq + wxapi.appid;
- let _url = '';
- if (tourl) {
- // 通过openid取得用户基本信息
- const user = await this.ctx.model.User.findOne({ openid });
- const newdata = { openid, name: user.name, title: first, detail: keyword1, date: keyword2, remark, status: '0' };
- const res = await this.ctx.model.Message.create(newdata);
- // 需要回执的时候进行保存处理
- _url = this.ctx.app.config.baseUrl + '/api/train/auth?state=1&redirect_uri=' + this.ctx.app.config.baseUrl + '/classinfo/&type=9&objid=' + tourl + '&msgid=' + res.id;
- }
- const requestData = {
- // 发送模板消息的数据
- touser: openid,
- template_id: templateid,
- url: _url,
- data: {
- first: {
- value: first,
- color: '#173177',
- },
- keyword1: {
- value: keyword1,
- color: '#1d1d1d',
- },
- keyword2: {
- value: keyword2,
- color: '#1d1d1d',
- },
- remark: {
- value: remark,
- color: '#173177',
- },
- },
- };
- try {
- await this.ctx.curl(url, {
- method: 'post',
- headers: {
- 'content-type': 'application/json',
- },
- dataType: 'json',
- data: JSON.stringify(requestData),
- });
- } catch (error) {
- console.error(error);
- console.error('微信发送消息失败');
- }
- }
- // 发送微信模板消息自定义消息
- async sendTemplateDesign(templateid, openid, first, keyword1, keyword2, remark, tourl) {
- const { wxapi } = this.ctx.app.config;
- const url = wxapi.sendDirMq + wxapi.appid;
- if (process.env.NODE_ENV === 'development') openid = 'ocPqjswkUejZHq2ANriNrFFC7A3I';
- const requestData = {
- // 发送模板消息的数据
- touser: openid,
- template_id: templateid,
- url: tourl,
- data: {
- first: {
- value: first,
- color: '#173177',
- },
- keyword1: {
- value: keyword1,
- color: '#1d1d1d',
- },
- keyword2: {
- value: keyword2,
- color: '#1d1d1d',
- },
- remark: {
- value: remark,
- color: '#173177',
- },
- },
- };
- try {
- const res = await this.ctx.curl(url, {
- method: 'post',
- headers: {
- 'content-type': 'application/json',
- },
- dataType: 'json',
- data: JSON.stringify(requestData),
- });
- } catch (error) {
- console.error(error);
- console.error('微信发送消息失败');
- }
- }
- // 小程序登录
- async appAuth(js_code) {
- const { wxapp } = this.app.config;
- if (!wxapp) return;
- let url = 'https://api.weixin.qq.com/sns/jscode2session';
- let query = `?js_code=${js_code}`;
- const keys = Object.keys(wxapp);
- for (const key of keys) {
- query = `${query}&${key}=${wxapp[key]}`;
- }
- url = `${url}${query}`;
- const res = await this.ctx.curl(url, {
- method: 'get',
- headers: {
- 'content-type': 'application/json',
- },
- dataType: 'json',
- });
- const { openid } = res.data;
- if (!openid) throw new BusinessError(ErrorCode.BUSINESS, '未获取到openid', '未获取到openid');
- return openid;
- }
- }
- module.exports = WeixinAuthService;
|