wechat-offiaccount 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +12 -0
- package/.mailmap +1 -0
- package/CHANGELOG +0 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/run +31 -0
- package/nodejs/index.js +24 -0
- package/nodejs/libs/JsApiSignature.js +47 -0
- package/nodejs/libs/auth.js +44 -0
- package/nodejs/libs/getAccessToken.js +58 -0
- package/nodejs/libs/getJsapiTicket.js +62 -0
- package/package.json +19 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: # Replace with a single Ko-fi username
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
otechie: # Replace with a single Otechie username
|
|
12
|
+
custom: ['https://zxl20070701.github.io/notebook/home.html']
|
package/.mailmap
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zxl20070701 <1904314465@qq.com>
|
package/CHANGELOG
ADDED
|
File without changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) zxl20070701 走一步,再走一步
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# [WeChat-offiaccount](https://github.com/fragement-contrib/WeChat-offiaccount)
|
|
2
|
+
微信公众号辅助调试服务器
|
|
3
|
+
|
|
4
|
+
<p>
|
|
5
|
+
<a href="https://www.npmjs.com/package/wechat-offiaccount?activeTab=versions">
|
|
6
|
+
<img src="https://img.shields.io/npm/dm/wechat-offiaccount.svg" alt="downloads">
|
|
7
|
+
</a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/wechat-offiaccount">
|
|
9
|
+
<img src="https://img.shields.io/npm/v/wechat-offiaccount.svg" alt="Version">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/fragement-contrib/WeChat-offiaccount" target='_blank'>
|
|
12
|
+
<img alt="GitHub repo stars" src="https://img.shields.io/github/stars/fragement-contrib/WeChat-offiaccount?style=social">
|
|
13
|
+
</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
本项目用于搭建一个微信公众号本地测试环境,都是基于[测试号](http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index)来说明。
|
|
17
|
+
|
|
18
|
+
## 如何本地调试?
|
|
19
|
+
|
|
20
|
+
首先需要在```./offiaccount.config.js```中填入自己的appID和appsecret。
|
|
21
|
+
|
|
22
|
+
### 接入微信公众平台开发
|
|
23
|
+
|
|
24
|
+
为了接入你首先需要的是“接口配置信息”,这里需要填入```./offiaccount.config.js```中的token,和“测试号管理”页面的保持一致即可。
|
|
25
|
+
|
|
26
|
+
然后你把这个项目在外网可以访问的服务器上运行后,配置后即可通过验证,然后就可以依据接口文档实现业务逻辑。
|
|
27
|
+
|
|
28
|
+
### JS-SDK使用
|
|
29
|
+
|
|
30
|
+
比如你启动后,手机可以通过```http://192.168.0.6:20000/wxConfig.html```(无需外网可以访问,如果电脑和手机连同一个wifi,像这里的例子,用局域网ip即可)访问,那么,“JS接口安全域名”中的域名配置成```192.168.0.6:20000```即可。
|
|
31
|
+
|
|
32
|
+
然后在微信中打开上面的html页面即可(到此为止,你就可以在你的html页面使用[JS-SDK](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html)能力了)。
|
|
33
|
+
|
|
34
|
+
## 如何在项目中使用?
|
|
35
|
+
为了方便随时随地使用,我们发布了npm包,只需要项目中安装后就可以按照下面说明使用需要的能力了:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
npm install --save wechat-offiaccount
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### JS-SDK使用
|
|
42
|
+
|
|
43
|
+
首先,准备一份配置文件,比如在当前目录下有一个文件```offiaccount.config.js```,内容如下:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// 具体的改为自己的即可
|
|
47
|
+
const config = {
|
|
48
|
+
port:8080,
|
|
49
|
+
appID: "",
|
|
50
|
+
appsecret: ""
|
|
51
|
+
};
|
|
52
|
+
module.exports = config;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
然后,在```package.json```中配置启动命令:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
{
|
|
59
|
+
"scripts": {
|
|
60
|
+
"offiaccount": "offiaccount-cli --config offiaccount.config.js"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
然后执行启动命令:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
npm run offiaccount
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
接着,我们用浏览器最新的fetch举例子:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
fetch("http://" + window.location.hostname + ":8080/JsApiSignature?url=" + window.location.href.split('#')[0], {
|
|
75
|
+
method: "get"
|
|
76
|
+
}).then(function (response) {
|
|
77
|
+
response.json()
|
|
78
|
+
.then(function (res) {
|
|
79
|
+
|
|
80
|
+
wx.config({
|
|
81
|
+
debug: true,
|
|
82
|
+
appId: res.appId,
|
|
83
|
+
timestamp: res.timestamp,
|
|
84
|
+
nonceStr: res.nonceStr,
|
|
85
|
+
signature: res.signature,
|
|
86
|
+
jsApiList: ['chooseImage'] // 必填,需要使用的JS接口列表
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
wx.error(function (res) {
|
|
90
|
+
console.error(res);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
上面已经配置好了,并且申请了```wx.chooseImage```权限,后续用的时候,只需要:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
wx.ready(function () {
|
|
101
|
+
console.log('准备好了!');
|
|
102
|
+
|
|
103
|
+
// todo
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
比如访问的地址是:```http://192.168.0.6:20000/index.html```
|
|
108
|
+
|
|
109
|
+
## 版权
|
|
110
|
+
|
|
111
|
+
MIT License
|
|
112
|
+
|
|
113
|
+
Copyright (c) [zxl20070701](https://zxl20070701.github.io/notebook/home.html) 走一步,再走一步
|
package/bin/run
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
process.title = 'offiaccount-cli'
|
|
6
|
+
const { log } = require('devby')
|
|
7
|
+
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
const path = require('path')
|
|
10
|
+
|
|
11
|
+
const jsonfile = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')))
|
|
12
|
+
|
|
13
|
+
// 命令行传递的参数
|
|
14
|
+
const parsed = require("devby").options({
|
|
15
|
+
"-c": "--config"
|
|
16
|
+
}, process.argv)
|
|
17
|
+
|
|
18
|
+
if (parsed.config && parsed.config.length > 0) {
|
|
19
|
+
|
|
20
|
+
// 读取配置
|
|
21
|
+
let configFile = require(path.join(process.cwd(), parsed.config[0]))
|
|
22
|
+
|
|
23
|
+
// 启动
|
|
24
|
+
require('../nodejs/index')(configFile)
|
|
25
|
+
|
|
26
|
+
} else {
|
|
27
|
+
log(`
|
|
28
|
+
offiaccount-cli@v${jsonfile.version}
|
|
29
|
+
`)
|
|
30
|
+
}
|
|
31
|
+
|
package/nodejs/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const express = require('express')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const auth = require('./libs/auth')
|
|
4
|
+
const JsApiSignature = require('./libs/JsApiSignature')
|
|
5
|
+
|
|
6
|
+
module.exports = (config) => {
|
|
7
|
+
const app = express()
|
|
8
|
+
|
|
9
|
+
// 静态资源
|
|
10
|
+
app.use(express.static(path.resolve(__dirname, '../html')))
|
|
11
|
+
|
|
12
|
+
// 获取微信签名
|
|
13
|
+
app.get("/JsApiSignature", JsApiSignature(config))
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 我们这里使用测试号,通过”接口配置信息“来验证
|
|
17
|
+
* https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
|
|
18
|
+
*/
|
|
19
|
+
app.use(auth(config))
|
|
20
|
+
|
|
21
|
+
app.listen(config.port, () => {
|
|
22
|
+
console.log(">>> 服务器启动成功")
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const sha1 = require('sha1')
|
|
2
|
+
|
|
3
|
+
module.exports = (config) => {
|
|
4
|
+
return (req, res) => {
|
|
5
|
+
|
|
6
|
+
const { url } = req.query
|
|
7
|
+
const { appID } = config
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 生成js-sdk的签名
|
|
11
|
+
* 1、组合参与签名的四个参数:jsapi_ticket(临时票据)、noncestr(随机字符串)、timestamp(时间戳)、url(当前服务器地址)
|
|
12
|
+
* 2、将其进行字典序排序,以‘&’拼接在一起
|
|
13
|
+
* 3、进行sha1加密,最终生成signature
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
require('./getJsapiTicket')(config).then(jsapi_ticket => {
|
|
17
|
+
|
|
18
|
+
// 获取随机字符串
|
|
19
|
+
const noncestr = (Math.random() + "").split('.')[1]
|
|
20
|
+
|
|
21
|
+
// 获取时间戳
|
|
22
|
+
const timestamp = new Date().valueOf()
|
|
23
|
+
|
|
24
|
+
// 排序并拼接
|
|
25
|
+
const str = [
|
|
26
|
+
`jsapi_ticket=${jsapi_ticket}`,
|
|
27
|
+
`noncestr=${noncestr}`,
|
|
28
|
+
`timestamp=${timestamp}`,
|
|
29
|
+
`url=${url}`
|
|
30
|
+
].sort().join("&")
|
|
31
|
+
|
|
32
|
+
// sha1加密
|
|
33
|
+
const sha1Str = sha1(str)
|
|
34
|
+
|
|
35
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
36
|
+
res.send(JSON.stringify({
|
|
37
|
+
appId: appID,
|
|
38
|
+
timestamp,
|
|
39
|
+
nonceStr: noncestr,
|
|
40
|
+
signature: sha1Str
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const sha1 = require('sha1')
|
|
2
|
+
|
|
3
|
+
module.exports = (config) => {
|
|
4
|
+
const { token } = config
|
|
5
|
+
|
|
6
|
+
return (req, res) => {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 如何验证信息来自微信服务器?
|
|
10
|
+
* 1、req.query.timestamp、req.query.nonce、token按照字典顺序排列成一个数组
|
|
11
|
+
* 2、把上述数组拼接成字符串,然后使用sha1加密
|
|
12
|
+
* 3、上面加密完成的结果和req.query.signature进行对比,如果一样,就通过
|
|
13
|
+
*
|
|
14
|
+
* 通过的话,返回 req.query.echostr
|
|
15
|
+
* 不通过的话,返回 'error'
|
|
16
|
+
*/
|
|
17
|
+
const { timestamp, nonce, signature, echostr } = req.query
|
|
18
|
+
|
|
19
|
+
// 排序并拼接
|
|
20
|
+
const str = [nonce, timestamp, token].sort().join("")
|
|
21
|
+
|
|
22
|
+
// sha1加密
|
|
23
|
+
const sha1Str = sha1(str)
|
|
24
|
+
|
|
25
|
+
if (sha1Str == signature) {
|
|
26
|
+
|
|
27
|
+
// 如果是服务器验证
|
|
28
|
+
if (req.method == 'GET') {
|
|
29
|
+
res.send(echostr)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 如果是数据请求
|
|
33
|
+
else if (req.method == 'POST') {
|
|
34
|
+
|
|
35
|
+
// todo
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
} else {
|
|
40
|
+
res.end('error')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { get } = require('devby')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const accessTokenPath = path.resolve(process.cwd(), './access_token.json')
|
|
6
|
+
|
|
7
|
+
// 获取access_token
|
|
8
|
+
function getAccessToken(config) {
|
|
9
|
+
const { appID, appsecret } = config
|
|
10
|
+
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
// 定义请求地址
|
|
13
|
+
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`
|
|
14
|
+
|
|
15
|
+
// 发送请求
|
|
16
|
+
get(url, {
|
|
17
|
+
json: true
|
|
18
|
+
}).then(res => {
|
|
19
|
+
|
|
20
|
+
console.log('>>> 获取新的 access_token')
|
|
21
|
+
|
|
22
|
+
// 添加过期时间
|
|
23
|
+
res.endtime = new Date().valueOf() + (res.expires_in - 300) * 1000
|
|
24
|
+
|
|
25
|
+
// 写入文件
|
|
26
|
+
fs.writeFileSync(accessTokenPath, JSON.stringify(res, null, 4))
|
|
27
|
+
|
|
28
|
+
resolve(res.access_token)
|
|
29
|
+
}).catch(err => {
|
|
30
|
+
|
|
31
|
+
console.log(err)
|
|
32
|
+
|
|
33
|
+
reject(err)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = (config) => {
|
|
39
|
+
|
|
40
|
+
// 如果文件不存在,一定需要重新获取
|
|
41
|
+
if (!fs.existsSync(accessTokenPath)) {
|
|
42
|
+
return getAccessToken(config)
|
|
43
|
+
} else {
|
|
44
|
+
let data = JSON.parse(fs.readFileSync(accessTokenPath, 'utf-8'))
|
|
45
|
+
|
|
46
|
+
// 没有过期,直接返回
|
|
47
|
+
if (data.endtime > new Date().valueOf()) {
|
|
48
|
+
return Promise.resolve(data.access_token)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 过期了,重新获取
|
|
52
|
+
else {
|
|
53
|
+
return getAccessToken(config)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { get } = require('devby')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const jsapiTicketPath = path.resolve(process.cwd(), './jsapi_ticket.json')
|
|
6
|
+
|
|
7
|
+
const getAccessToken = require('./getAccessToken')
|
|
8
|
+
|
|
9
|
+
// 获取access_token
|
|
10
|
+
function getJsapiTicket(config) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
getAccessToken(config).then(accessToken => {
|
|
13
|
+
|
|
14
|
+
// 定义请求地址
|
|
15
|
+
const url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${accessToken}&type=jsapi`
|
|
16
|
+
|
|
17
|
+
// 发送请求
|
|
18
|
+
get(url, {
|
|
19
|
+
json: true
|
|
20
|
+
}).then(res => {
|
|
21
|
+
|
|
22
|
+
console.log('>>> 获取新的 jsapi_ticket')
|
|
23
|
+
|
|
24
|
+
// 添加过期时间
|
|
25
|
+
res.endtime = new Date().valueOf() + (res.expires_in - 300) * 1000
|
|
26
|
+
|
|
27
|
+
// 写入文件
|
|
28
|
+
fs.writeFileSync(jsapiTicketPath, JSON.stringify(res, null, 4))
|
|
29
|
+
|
|
30
|
+
resolve(res.ticket)
|
|
31
|
+
}).catch(err => {
|
|
32
|
+
|
|
33
|
+
console.log(err)
|
|
34
|
+
|
|
35
|
+
reject(err)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = (config) => {
|
|
43
|
+
|
|
44
|
+
// 如果文件不存在,一定需要重新获取
|
|
45
|
+
if (!fs.existsSync(jsapiTicketPath)) {
|
|
46
|
+
return getJsapiTicket(config)
|
|
47
|
+
} else {
|
|
48
|
+
let data = JSON.parse(fs.readFileSync(jsapiTicketPath, 'utf-8'))
|
|
49
|
+
|
|
50
|
+
// 没有过期,直接返回
|
|
51
|
+
if (data.endtime > new Date().valueOf()) {
|
|
52
|
+
return Promise.resolve(data.ticket)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 过期了,重新获取
|
|
56
|
+
else {
|
|
57
|
+
return getJsapiTicket(config)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wechat-offiaccount",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "node ./bin/run --config offiaccount.config.js"
|
|
7
|
+
},
|
|
8
|
+
"bin": {
|
|
9
|
+
"offiaccount-cli": "bin/run"
|
|
10
|
+
},
|
|
11
|
+
"description": "微信公众号辅助调试服务器",
|
|
12
|
+
"author": "zxl20070701",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"devby": "^0.4.4",
|
|
16
|
+
"express": "^4.18.2",
|
|
17
|
+
"sha1": "^1.1.1"
|
|
18
|
+
}
|
|
19
|
+
}
|