From d2dbf517c5444485bc768580ae7b30626fc7e33c Mon Sep 17 00:00:00 2001 From: arter Date: Fri, 20 Jul 2018 16:33:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=AE=E4=BF=A1=E6=89=AB?= =?UTF-8?q?=E7=A0=81=E7=99=BB=E5=BD=95=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 16 +- src/controller/ext/changyan/index.js | 1 - src/controller/ext/weixin/admin.js | 74 ++++++ src/controller/ext/weixin/config.js | 41 ++++ src/controller/ext/weixin/hooks.js | 13 ++ src/controller/ext/weixin/index.js | 210 ++++++++++++++++++ src/controller/ext/weixin/service/weixin.js | 57 +++++ .../ext/weixin/view/mobile/hooks_logins.html | 1 + .../ext/weixin/view/mobile/index_index.html | 33 +++ .../ext/weixin/view/pc/admin_index.html | 60 +++++ .../ext/weixin/view/pc/hooks_logins.html | 3 + .../ext/weixin/view/pc/index_index.html | 36 +++ .../ext/weixin/view/pc/index_login.html | 170 ++++++++++++++ src/controller/ext/weixin/weixin.sql | 36 +++ 14 files changed, 742 insertions(+), 9 deletions(-) create mode 100644 src/controller/ext/weixin/admin.js create mode 100644 src/controller/ext/weixin/config.js create mode 100644 src/controller/ext/weixin/hooks.js create mode 100644 src/controller/ext/weixin/index.js create mode 100644 src/controller/ext/weixin/service/weixin.js create mode 100644 src/controller/ext/weixin/view/mobile/hooks_logins.html create mode 100644 src/controller/ext/weixin/view/mobile/index_index.html create mode 100644 src/controller/ext/weixin/view/pc/admin_index.html create mode 100644 src/controller/ext/weixin/view/pc/hooks_logins.html create mode 100644 src/controller/ext/weixin/view/pc/index_index.html create mode 100644 src/controller/ext/weixin/view/pc/index_login.html create mode 100644 src/controller/ext/weixin/weixin.sql diff --git a/package.json b/package.json index f5b9d3d16..686c1993b 100644 --- a/package.json +++ b/package.json @@ -8,22 +8,22 @@ "lint-fix": "eslint --fix src/" }, "dependencies": { - "co-wechat-api": "^3.3.3", + "co-wechat-api": "^3.7.0", "gt3-sdk": "^2.0.0", "jimp": "^0.2.28", - "lodash": "4.x.x", - "moment": "^2.20.1", - "pingpp": "^2.1.0", - "qiniu": "^7.1.1", + "lodash": "^4.17.10", + "moment": "^2.22.2", + "pingpp": "^2.1.3", + "qiniu": "^7.2.1", "segment": "^0.1.3", - "superagent": "^3.8.2", + "superagent": "^3.8.3", "tar.gz": "^1.0.7", "think-cache": "^1.0.7", "think-cache-file": "^1.0.10", "think-email": "^1.1.0", "think-fetch": "^1.1.0", "think-logger3": "^1.1.1", - "think-model": "^1.1.6", + "think-model": "^1.2.2", "think-model-mysql": "^1.0.6", "think-pagination": "^2.0.0", "think-session": "^1.0.3", @@ -31,7 +31,7 @@ "think-view": "^1.0.11", "think-view-nunjucks": "^1.0.7", "think-wechat": "^1.1.0", - "thinkjs": "^3.2.6" + "thinkjs": "^3.2.8" }, "devDependencies": { "eslint": "^4.13.1", diff --git a/src/controller/ext/changyan/index.js b/src/controller/ext/changyan/index.js index 3964fe324..a92327998 100644 --- a/src/controller/ext/changyan/index.js +++ b/src/controller/ext/changyan/index.js @@ -38,7 +38,6 @@ module.exports = class extends think.cmswing.extIndex { 'is_login': 0// 为登录 }; } - console.log(ret); return this.jsonp(ret); } // 用户登录接口 diff --git a/src/controller/ext/weixin/admin.js b/src/controller/ext/weixin/admin.js new file mode 100644 index 000000000..1b339d171 --- /dev/null +++ b/src/controller/ext/weixin/admin.js @@ -0,0 +1,74 @@ +// +---------------------------------------------------------------------- +// | CmsWing [ 网站内容管理框架 ] +// +---------------------------------------------------------------------- +// | Copyright (c) 2015-2115 http://www.cmswing.com All rights reserved. +// +---------------------------------------------------------------------- +// | Author: arterli +// +---------------------------------------------------------------------- +// 本文件不能删除 +module.exports = class extends think.cmswing.extAdmin { + /** + * index action + * 插件管理入口 + * @return {Promise} [] + */ + async indexAction() { + // 分页列表实例 + + // -- 获取插件目录名 + // this.ext.ext; + + // -- 获取当前分类 + // await this.gettype() + + // -- model调用 + // const list = await this.extModel('demo').select(); + // const list2 = await this.extModel('demo').demo(); + // const list3 = await think.extModel('demo','demo').demo(); + + // -- 分页 + // const model = this.extModel('demo'); + // const data = await model.page(this.get('page')).countSelect(); // 获取分页数据 + // console.log(data); + // const html = this.pagination(data); // 调取分页 + // this.assign('pagerData', html); // 分页展示使用 + + // -- ext service + // 无参数类的实例化 + // const Ser1 = this.extService('demo'); + // const Ser1 = think.extService('demo','demo'); + // const ser1 = Ser1.bbb('bbb'); + // console.log(ser1); + // 有参数类的实例化 + // const Ser2 = this.extService('demo','demo','aaa','bbb'); + // const Ser2 = think.extService('demo','demo','aaa','bbb'); + // const ser2 = Ser2.aaa(); + // console.log(ser2); + const weixin = this.model('ext_weixin') + weixin._pk = 'openid' + const userlist = await weixin.page(this.get('page'), 20).countSelect() + const html = this.pagination(userlist) + this.assign('pagerData', html) // 分页展示使用 + this.assign('list', userlist.data) + // 入口模版渲染 + return this.extDisplay() + } + + /** + * 添加 + * @returns {*} + */ + async addAction() { + // d + } + + /** + * 修改 + */ + async editAction() {} + + /** + * 删除 + */ + async delAction() {} +} diff --git a/src/controller/ext/weixin/config.js b/src/controller/ext/weixin/config.js new file mode 100644 index 000000000..a156c96f6 --- /dev/null +++ b/src/controller/ext/weixin/config.js @@ -0,0 +1,41 @@ +module.exports = { + ext: 'weixin', // 插件目录,必须为英文 + name: '微信扫码登录', // 插件名称 + description: '通过接入微信的开放平台来实现网站的扫码登录', // 插件描述 + isadm: 1, // 是否有后台管理,1:有,0:没有,入口地址:'/ext/weixin/admin/index' + isindex: 0, // 是否需要前台访问,1:需要,0:不需要,入口地址:'/ext/weixin/index/index' + version: '1.0', // 版本号 + author: 'NickMa', // 作者 + table: [], // 插件包含的 数据库表,不包含表前缀,如:cmswing_ext_table 就是 table,多个['table','table_2']没有留空数组。 + sql: '', // 插件安装的时候会找个名字的sql文件导入,默认 插件目录名.sql; + hooks: ['logins'], // 挂载的钩子,数组格式,如['hooks1', 'hooks2'],不挂载留空:[] + setting: [ + { + '微信扫码登录设置': [ + { + 'name': 'appid', // 配置在表单中的键名 ,这个会是this.config('appid') + 'label': 'AppID:', // 表单的文字 + 'type': 'text', // 表单的类型:text、radio、select + 'value': 'wx85ee8ad668c3d09b', // 表单的默认值 + 'html': '填写在微信开放平台上获取的网站应用的AppID' + }, + { + 'name': 'secret', // 配置在表单中的键名 ,这个会是this.config('secret') + 'label': 'AppSecret:', // 表单的文字 + 'type': 'text', // 表单的类型:text、radio、select + 'value': '', // 表单的默认值 + 'html': `

申请地址

+
https://open.weixin.qq.com/
+
+

申请完成后会得到 AppID 和 AppSecret,对应填写到插件配置中。

+
+

授权回调URL

+
http(s)://host/ext/weixin/index/index
+
+

例如:你的网站是 http 就填 http://www.cmswing.com/ext/weixin/index/index 否则就是 https://www.cmswing.com/ext/weixin/index/index,www.cmswing.com 替换成你的网站域名,请认真按照实例填写,填错会通信失败!

+
` + } + ] + } + ] +}; \ No newline at end of file diff --git a/src/controller/ext/weixin/hooks.js b/src/controller/ext/weixin/hooks.js new file mode 100644 index 000000000..b651fbc5b --- /dev/null +++ b/src/controller/ext/weixin/hooks.js @@ -0,0 +1,13 @@ +// hooks +module.exports = class extends think.cmswing.extIndex { + /** + * 实现的AdminIndex钩子方法 + * 【视图】 + * @param ...val + */ + async logins(...val) { + // 钩子业务处理 + const html = await this.hookRender('logins', 'weixin'); + return html; + } +} \ No newline at end of file diff --git a/src/controller/ext/weixin/index.js b/src/controller/ext/weixin/index.js new file mode 100644 index 000000000..c89f58c98 --- /dev/null +++ b/src/controller/ext/weixin/index.js @@ -0,0 +1,210 @@ +// +---------------------------------------------------------------------- +// | CmsWing [ 网站内容管理框架 ] +// +---------------------------------------------------------------------- +// | Copyright (c) 2015-2115 http://www.cmswing.com All rights reserved. +// +---------------------------------------------------------------------- +// | Author: arterli +// +---------------------------------------------------------------------- +/** + * 插件前台控制器 + * 如果插件有前台展示业务,写在这个控制器里面 + * 插件如需建立表务必遵循下面格式: + * 单表:表前缀_ext_插件目录名 + * 多表:表前缀_ext_插件目录名,表前缀_ext_插件目录名_分表1,表前缀_ext_插件目录名_分表2... + */ +const fs = require('fs') +module.exports = class extends think.cmswing.extIndex { + /** + * index action + * + * @return {Promise} [] + */ + + async indexAction() { + // 检测是否开启微博登陆 + if (Number(this.config('ext.weixin.is')) === 0) { + const error = this.controller('cmswing/error') + return error.noAction('没有开启微信扫码登陆,请到后台开启!') + } + // 检测是否登陆 + if (this.is_login) { + return this.redirect('/center/index') + } + const host = this.ctx.host + const redirectURI = `https://${host}/ext/weixin/index/index` + // 检查是否回掉code,如果没有跳转授权接口 + if (think.isEmpty(this.get('code'))) { + const appid = this.config('ext.weixin.appid') + const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${encodeURIComponent( + redirectURI + )}&response_type=code&scope=snsapi_login&state=weblogin#wechat_redirect` + return this.redirect(url) + } else { + const code = this.get('code') + const state = this.get('state') + const weixin = this.extService('weixin', 'weixin', code, state) + const token = await weixin.gettoken() + // console.log(token); + // 获取access token + const wxUser = await this.model('ext_weixin').where({ openid: token.openid }).find() + if (think.isEmpty(wxUser)) { + // 创建 + await this.model('ext_weixin').add(token) + } else { + // 更新 + await this.model('ext_weixin') + .where({ + openid: token.openid + }) + .update(token) + } + // 获取用户信息 + const userinfo = await weixin.getuserinfo(token.access_token, token.openid) + await this.model('ext_weixin') + .where({ + openid: userinfo.openid + }) + .update(userinfo) + // 检查微信号是否跟网站会员绑定 + if (think.isEmpty(wxUser.uid) || wxUser.uid == 0) { + // 没绑定跳转绑定页面 + return this.redirect(`/ext/weixin/index/login/?openid=${userinfo.openid}`) + } else { + // 绑定直接登陆 + const last_login_time = await this.model('member').where({ id: wxUser.uid }).getField('last_login_time', true) + const weixinUserInfo = { + uid: wxUser.uid, + username: wxUser.nickname, + last_login_time: last_login_time + } + await this.session('webuser', weixinUserInfo) + await this.model('cmswing/member').autoLogin({ id: wxUser.uid }, this.ip) // 更新用户登录信息,自动登陆 + return this.redirect('/center/index') + } + } + } + + // 用户绑定页面 + async loginAction() { + if (Number(this.config('ext.weixin.is')) === 0) { + const error = this.controller('common/error') + return error.noAction('没有开启微信扫码登陆,请到后台开启!') + } + if (this.is_login) { + return this.redirect('/center/index') + } + const openid = this.get('openid') + const wxUser = await this.model('ext_weixin').where({ openid: openid }).find() + console.log(wxUser) + this.assign('wx_user', wxUser) + this.meta_title = '账号绑定' + return this.isMobile ? this.extDisplay('m') : this.extDisplay() + } + + /** 完善资料绑定 */ + async organizingAction() { + const data = this.post() + // 验证 + let res + if (think.isEmpty(data.username)) { + return this.fail('用户昵称不能为空!') + } else { + res = await this.model('member').where({ username: ltrim(data.username) }).find() + if (!think.isEmpty(res)) { + return this.fail('用户昵称已存在,请重新填写!') + } + } + if (think.isEmpty(data.mobile)) { + return this.fail('手机号码不能为空!') + } else { + res = await this.model('member').where({ mobile: data.mobile }).find() + if (!think.isEmpty(res)) { + return this.fail('手机号码已存在,请重新填写!') + } + } + if (think.isEmpty(data.email)) { + return this.fail('电子邮箱不能为空!') + } else { + res = await this.model('member').where({ email: data.email }).find() + if (!think.isEmpty(res)) { + return this.fail('电子邮箱已存在,请重新填写!') + } + } + if (think.isEmpty(data.password) && think.isEmpty(data.password2)) { + return this.fail('密码不能为空!') + } else { + if (data.password != data.password2) { + return this.fail('两次输入的密码不一致,请重新填写!') + } + } + data.status = 1 + data.reg_time = new Date().valueOf() + data.reg_ip = _ip2int(this.ip) + data.password = encryptPassword(data.password) + const reg = await this.model('member').add(data) + if (!think.isEmpty(reg)) { + // 添加用户副表 + await this.model('ext_weixin').where({ openid: data.openid }).update({ uid: reg }) + // 更新微信头像 + // const filePath = think.resource + '/upload/avatar/' + reg + // think.mkdir(filePath) + // if (!think.isFile(filePath + '/avatar.png')) { + // await this.spiderImage(data.headimgurl, filePath + '/avatar.png') + // } + } + // console.log(data); + await this.model('cmswing/member').autoLogin({ id: reg }, this.ip) // 更新用户登录信息,自动登陆 + const wxUserInfo = { + uid: reg, + username: data.username, + last_login_time: data.reg_time + } + await this.session('webuser', wxUserInfo) + return this.success({ name: '绑定成功', url: '/center/index' }) + } + /** 登录绑定 */ + async logonbindingAction() { + const data = this.post() + // console.log(data); + const username = this.post('username') + let password = this.post('password') + password = encryptPassword(password) + // console.log(data); + + const res = await this.model('cmswing/member').signin(username, password, this.ip, 5, 0) + // console.log(res); + if (res.uid > 0) { + // 记录用户登录行为 + // await this.model("action", {}, "admin").log("user_login", "member", res.uid, res.uid, this.ip(), this.http.url); + // console.log(11111111111111); + const wxInfo = await this.model('ext_weixin').where({ openid: data.openid }).find() + // console.log(wxInfo); + await this.model('ext_weixin').where({ openid: data.openid }).update({ uid: res.uid }) + // 更新微信头像 + // const filePath = think.resource + '/upload/avatar/' + res.uid + // think.mkdir(filePath) + // if (!think.isFile(filePath + '/avatar.png')) { + // await this.spiderImage(data.headimgurl, filePath + '/avatar.png') + // } + res.username = wxInfo.nickname + await this.session('webuser', res) + // TODO 用户密钥 + return this.success({ name: '绑定成功', url: '/center/index' }) + } else { + // 登录失败 + let fail + switch (res) { + case -1: + fail = '用户不存在或被禁用' + break // 系统级别禁用 + case -2: + fail = '密码错误' + break + default: + fail = '未知错误' + break // 0-接口参数错误(调试阶段使用) + } + return this.fail(fail) + } + } +} diff --git a/src/controller/ext/weixin/service/weixin.js b/src/controller/ext/weixin/service/weixin.js new file mode 100644 index 000000000..9104fc1cb --- /dev/null +++ b/src/controller/ext/weixin/service/weixin.js @@ -0,0 +1,57 @@ +const superagent = require('superagent') +module.exports = class extends think.Service { + constructor(code, state, ctx) { + super(ctx) + this.code = code + this.baseUrl = 'https://api.weixin.qq.com/sns' + this.state = state + } + + // 获取 + async gettoken() { + const setup = think.config('ext.weixin') + const APPID = setup.appid + const SECRET = setup.secret + const GRANT_TYPE = 'authorization_code' + const code = this.code + // const REDIRECT_URI = this.redirect_uri + const URL_GET_TOKEN = `${this.baseUrl}/oauth2/access_token` + const gettoken = (URL_GET_TOKEN) => { + const deferred = think.defer() + superagent + .get(`${URL_GET_TOKEN}?appid=${APPID}&secret=${SECRET}&code=${code}&grant_type=${GRANT_TYPE}`) + .end(function(err, res) { + // console.log(res); + if (err) { + deferred.resolve(err.message) + } + if (res.ok) { + if (think.isEmpty(res.body)) { + deferred.resolve(JSON.parse(res.text)) + } else { + deferred.resolve(res.body) + } + } else { + console.log('Oh no! error ' + res.text) + deferred.resolve(res.text) + } + }) + return deferred.promise + } + return await gettoken(URL_GET_TOKEN) + } + async getuserinfo(token, openid) { + const URL_GET_USERINFO = `${this.baseUrl}/userinfo?access_token=${token}&openid=${openid}` + const getuserinfo = () => { + const deferred = think.defer() + superagent.get(URL_GET_USERINFO).end(function(err, res) { + if (err) { + deferred.resolve(err.message) + } + deferred.resolve(JSON.parse(res.text)) + }) + return deferred.promise + } + return await getuserinfo() + } +} diff --git a/src/controller/ext/weixin/view/mobile/hooks_logins.html b/src/controller/ext/weixin/view/mobile/hooks_logins.html new file mode 100644 index 000000000..5561d7fe5 --- /dev/null +++ b/src/controller/ext/weixin/view/mobile/hooks_logins.html @@ -0,0 +1 @@ +weixin \ No newline at end of file diff --git a/src/controller/ext/weixin/view/mobile/index_index.html b/src/controller/ext/weixin/view/mobile/index_index.html new file mode 100644 index 000000000..bf09d5017 --- /dev/null +++ b/src/controller/ext/weixin/view/mobile/index_index.html @@ -0,0 +1,33 @@ + + + + +New ThinkJS Application + + + +
+
+

A New App Created By ThinkJS

+
+
+
+ mobile +
+ + + \ No newline at end of file diff --git a/src/controller/ext/weixin/view/pc/admin_index.html b/src/controller/ext/weixin/view/pc/admin_index.html new file mode 100644 index 000000000..0ef66e16f --- /dev/null +++ b/src/controller/ext/weixin/view/pc/admin_index.html @@ -0,0 +1,60 @@ +{% extends "view/admin/ext_admin.html" %} +{% block extadmin %} +
+
+ + +
+ + +
+ + + + + + + + + + + + + + + {% for item in list %} + + + + + + + + + + + + {% else %} + + + {% endfor %} + + +
uidopenid头像昵称性别UnionId
{{item.uid}}{{item.openid}}{%if item.headimgurl%}{%else%}无{%endif%}{{item.nickname}}{%if item.sex ==1%}男{%else%}女{%endif%}{{item.unionid}}
+ +

暂无数据

+
+
+ + + + +
+{% endblock %} diff --git a/src/controller/ext/weixin/view/pc/hooks_logins.html b/src/controller/ext/weixin/view/pc/hooks_logins.html new file mode 100644 index 000000000..f3ebd48d6 --- /dev/null +++ b/src/controller/ext/weixin/view/pc/hooks_logins.html @@ -0,0 +1,3 @@ + + 使用微信账号登录 + \ No newline at end of file diff --git a/src/controller/ext/weixin/view/pc/index_index.html b/src/controller/ext/weixin/view/pc/index_index.html new file mode 100644 index 000000000..2faac7d19 --- /dev/null +++ b/src/controller/ext/weixin/view/pc/index_index.html @@ -0,0 +1,36 @@ + + + + +ThinkCMS插件-微信扫码登录 + + + +
+
+

微信扫码登陆插件

+
+
+
+
+      基于微信开放平台的扫码登录插件。 
+      作者: NickMa honeyday.mj@gmail.com
+    
+
+ + + \ No newline at end of file diff --git a/src/controller/ext/weixin/view/pc/index_login.html b/src/controller/ext/weixin/view/pc/index_login.html new file mode 100644 index 000000000..5ce7710ac --- /dev/null +++ b/src/controller/ext/weixin/view/pc/index_login.html @@ -0,0 +1,170 @@ +{% extends "view/center/inc/base.html" %} {% block content %} + + + + + + + + +
+
+ +
+ + +
+ + + + + + +
+
+ 完善个人资料 +
+ +

{{wx_user.nickname}}

+
+ +
+ +
+ + + + + + + +
+ +
+
+ + + +
+
+ +
+ + +
+ + + +
+
+ +
+ 绑定到已有账号 +
+
+
+ + + + +
+
+ + + + +
+
+

正在加载验证码......

+

请先拖动验证码到相应位置

+
+
+ +
+ + +
+ +
+ + + + +
+ +
+
+ +
+ +
+ + +
+ + +
+
+ +{% endblock %} +{% block script %} + + + {% endblock %} \ No newline at end of file diff --git a/src/controller/ext/weixin/weixin.sql b/src/controller/ext/weixin/weixin.sql new file mode 100644 index 000000000..8d1d0014b --- /dev/null +++ b/src/controller/ext/weixin/weixin.sql @@ -0,0 +1,36 @@ +/* +Navicat MySQL Data Transfer + +Source Server : 本地 +Source Server Version : 50553 +Source Host : localhost:3306 +Source Database : cmswing + +Target Server Type : MYSQL +Target Server Version : 50553 +File Encoding : 65001 + +Date: 2017-10-06 18:37:24 +*/ + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for cmswing_ext_weixin +-- ---------------------------- +DROP TABLE IF EXISTS `cmswing_ext_weixin`; +CREATE TABLE `cmswing_ext_weixin` ( + `openid` varchar(50) NOT NULL COMMENT 'OopenId', + `unionid` varchar(50) NOT NULL COMMENT 'UnionId', + `nickname` varchar(255) DEFAULT NULL COMMENT '微信昵称', + `sex` int(5) DEFAULT NULL COMMENT '性别', + `language` varchar(20) DEFAULT NULL COMMENT '用户的语言', + `city` varchar(50) DEFAULT NULL COMMENT '城市', + `province` varchar(50) DEFAULT NULL COMMENT '省', + `country` varchar(50) DEFAULT NULL COMMENT '国家', + `headimgurl` varchar(255) DEFAULT NULL COMMENT '头像url', + `access_token` varchar(255) DEFAULT NULL COMMENT 'access_token', + `refresh_token` varchar(255) DEFAULT NULL COMMENT 'refresh_token', + `uid` int(10) NOT NULL DEFAULT '0', + PRIMARY KEY (`openid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;