首次完整推送,
V:1.20240808.006
This commit is contained in:
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
class BridgeError extends Error {
|
||||
|
||||
constructor(code, message) {
|
||||
super(message)
|
||||
|
||||
this._code = code
|
||||
}
|
||||
|
||||
get code() {
|
||||
return this._code
|
||||
}
|
||||
|
||||
get errCode() {
|
||||
return this._code
|
||||
}
|
||||
|
||||
get errMsg() {
|
||||
return this.message
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BridgeError
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
ProviderType
|
||||
} = require('./consts.js')
|
||||
|
||||
const configCenter = require('uni-config-center')
|
||||
|
||||
// 多维数据为兼容uni-id以前版本配置
|
||||
const OauthConfig = {
|
||||
'weixin-app': [
|
||||
['app', 'oauth', 'weixin'],
|
||||
['app-plus', 'oauth', 'weixin']
|
||||
],
|
||||
'weixin-mp': [
|
||||
['mp-weixin', 'oauth', 'weixin']
|
||||
],
|
||||
'weixin-h5': [
|
||||
['web', 'oauth', 'weixin-h5'],
|
||||
['h5-weixin', 'oauth', 'weixin'],
|
||||
['h5', 'oauth', 'weixin']
|
||||
],
|
||||
'weixin-web': [
|
||||
['web', 'oauth', 'weixin-web']
|
||||
],
|
||||
'qq-app': [
|
||||
['app', 'oauth', 'qq'],
|
||||
['app-plus', 'oauth', 'qq']
|
||||
],
|
||||
'qq-mp': [
|
||||
['mp-qq', 'oauth', 'qq']
|
||||
]
|
||||
}
|
||||
|
||||
const Support_Platforms = [
|
||||
ProviderType.WEIXIN_MP,
|
||||
ProviderType.WEIXIN_H5,
|
||||
ProviderType.WEIXIN_APP,
|
||||
ProviderType.WEIXIN_WEB,
|
||||
ProviderType.QQ_MP,
|
||||
ProviderType.QQ_APP
|
||||
]
|
||||
|
||||
class ConfigBase {
|
||||
|
||||
constructor() {
|
||||
const uniIdConfigCenter = configCenter({
|
||||
pluginId: 'uni-id'
|
||||
})
|
||||
|
||||
this._uniIdConfig = uniIdConfigCenter.config()
|
||||
}
|
||||
|
||||
getAppConfig(appid) {
|
||||
if (Array.isArray(this._uniIdConfig)) {
|
||||
return this._uniIdConfig.find((item) => {
|
||||
return (item.dcloudAppid === appid)
|
||||
})
|
||||
}
|
||||
return this._uniIdConfig
|
||||
}
|
||||
}
|
||||
|
||||
class AppConfig extends ConfigBase {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
get(appid, platform) {
|
||||
if (!this.isSupport(platform)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let appConfig = this.getAppConfig(appid)
|
||||
if (!appConfig) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.getOauthConfig(appConfig, platform)
|
||||
}
|
||||
|
||||
isSupport(platformName) {
|
||||
return (Support_Platforms.indexOf(platformName) >= 0)
|
||||
}
|
||||
|
||||
getOauthConfig(appConfig, platformName) {
|
||||
let treePath = OauthConfig[platformName]
|
||||
let node = this.findNode(appConfig, treePath)
|
||||
if (node && node.appid && node.appsecret) {
|
||||
return {
|
||||
appid: node.appid,
|
||||
secret: node.appsecret
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
findNode(treeNode, arrayPath) {
|
||||
let node = treeNode
|
||||
for (let treePath of arrayPath) {
|
||||
for (let name of treePath) {
|
||||
const currentNode = node[name]
|
||||
if (currentNode) {
|
||||
node = currentNode
|
||||
} else {
|
||||
node = null
|
||||
break
|
||||
}
|
||||
}
|
||||
if (node === null) {
|
||||
node = treeNode
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
AppConfig
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
const TAG = "UNI_OPEN_BRIDGE"
|
||||
|
||||
const HTTP_STATUS = {
|
||||
SUCCESS: 200
|
||||
}
|
||||
|
||||
const ProviderType = {
|
||||
WEIXIN_MP: 'weixin-mp',
|
||||
WEIXIN_H5: 'weixin-h5',
|
||||
WEIXIN_APP: 'weixin-app',
|
||||
WEIXIN_WEB: 'weixin-web',
|
||||
QQ_MP: 'qq-mp',
|
||||
QQ_APP: 'qq-app'
|
||||
}
|
||||
|
||||
// old
|
||||
const PlatformType = ProviderType
|
||||
|
||||
const ErrorCodeType = {
|
||||
SYSTEM_ERROR: TAG + "_SYSTEM_ERROR"
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HTTP_STATUS,
|
||||
ProviderType,
|
||||
PlatformType,
|
||||
ErrorCodeType
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
PlatformType,
|
||||
ProviderType,
|
||||
ErrorCodeType
|
||||
} = require('./consts.js')
|
||||
|
||||
const {
|
||||
AppConfig
|
||||
} = require('./config.js')
|
||||
|
||||
const {
|
||||
Storage
|
||||
} = require('./storage.js')
|
||||
|
||||
const {
|
||||
BridgeError
|
||||
} = require('./bridge-error.js')
|
||||
|
||||
const {
|
||||
WeixinServer
|
||||
} = require('./weixin-server.js')
|
||||
|
||||
const appConfig = new AppConfig()
|
||||
|
||||
class AccessToken extends Storage {
|
||||
|
||||
constructor() {
|
||||
super('access-token', ['provider', 'appid'])
|
||||
}
|
||||
|
||||
async update(key) {
|
||||
super.update(key)
|
||||
|
||||
const result = await this.getByWeixinServer(key)
|
||||
|
||||
return this.set(key, result.value, result.duration)
|
||||
}
|
||||
|
||||
async fallback(key) {
|
||||
return this.getByWeixinServer(key)
|
||||
}
|
||||
|
||||
async getByWeixinServer(key) {
|
||||
const oauthConfig = appConfig.get(key.dcloudAppid, key.provider)
|
||||
let methodName
|
||||
if (key.provider === ProviderType.WEIXIN_MP) {
|
||||
methodName = 'GetMPAccessTokenData'
|
||||
} else if (key.provider === ProviderType.WEIXIN_H5) {
|
||||
methodName = 'GetH5AccessTokenData'
|
||||
} else {
|
||||
throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, "provider invalid")
|
||||
}
|
||||
|
||||
const responseData = await WeixinServer[methodName](oauthConfig)
|
||||
|
||||
const duration = responseData.expires_in || (60 * 60 * 2)
|
||||
delete responseData.expires_in
|
||||
|
||||
return {
|
||||
value: responseData,
|
||||
duration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UserAccessToken extends Storage {
|
||||
|
||||
constructor() {
|
||||
super('user-access-token', ['provider', 'appid', 'openid'])
|
||||
}
|
||||
}
|
||||
|
||||
class SessionKey extends Storage {
|
||||
|
||||
constructor() {
|
||||
super('session-key', ['provider', 'appid', 'openid'])
|
||||
}
|
||||
}
|
||||
|
||||
class Encryptkey extends Storage {
|
||||
|
||||
constructor() {
|
||||
super('encrypt-key', ['provider', 'appid', 'openid'])
|
||||
}
|
||||
|
||||
async update(key) {
|
||||
super.update(key)
|
||||
|
||||
const result = await this.getByWeixinServer(key)
|
||||
|
||||
return this.set(key, result.value, result.duration)
|
||||
}
|
||||
|
||||
getKeyString(key) {
|
||||
return `${super.getKeyString(key)}-${key.version}`
|
||||
}
|
||||
|
||||
getExpiresIn(value) {
|
||||
if (value <= 0) {
|
||||
return 60
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
async fallback(key) {
|
||||
return this.getByWeixinServer(key)
|
||||
}
|
||||
|
||||
async getByWeixinServer(key) {
|
||||
const accessToken = await Factory.Get(AccessToken, key)
|
||||
const userSession = await Factory.Get(SessionKey, key)
|
||||
|
||||
const responseData = await WeixinServer.GetUserEncryptKeyData({
|
||||
openid: key.openid,
|
||||
access_token: accessToken.access_token,
|
||||
session_key: userSession.session_key
|
||||
})
|
||||
|
||||
const keyInfo = responseData.key_info_list.find((item) => {
|
||||
return item.version === key.version
|
||||
})
|
||||
|
||||
if (!keyInfo) {
|
||||
throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, 'key version invalid')
|
||||
}
|
||||
|
||||
const value = {
|
||||
encrypt_key: keyInfo.encrypt_key,
|
||||
iv: keyInfo.iv
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
duration: keyInfo.expire_in
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Ticket extends Storage {
|
||||
|
||||
constructor() {
|
||||
super('ticket', ['provider', 'appid'])
|
||||
}
|
||||
|
||||
async update(key) {
|
||||
super.update(key)
|
||||
|
||||
const result = await this.getByWeixinServer(key)
|
||||
|
||||
return this.set(key, result.value, result.duration)
|
||||
}
|
||||
|
||||
async fallback(key) {
|
||||
return this.getByWeixinServer(key)
|
||||
}
|
||||
|
||||
async getByWeixinServer(key) {
|
||||
const accessToken = await Factory.Get(AccessToken, {
|
||||
dcloudAppid: key.dcloudAppid,
|
||||
provider: ProviderType.WEIXIN_H5
|
||||
})
|
||||
|
||||
const responseData = await WeixinServer.GetH5TicketData(accessToken)
|
||||
|
||||
const duration = responseData.expires_in || (60 * 60 * 2)
|
||||
delete responseData.expires_in
|
||||
delete responseData.errcode
|
||||
delete responseData.errmsg
|
||||
|
||||
return {
|
||||
value: responseData,
|
||||
duration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const Factory = {
|
||||
|
||||
async Get(T, key, fallback) {
|
||||
Factory.FixOldKey(key)
|
||||
return Factory.MakeUnique(T).get(key, fallback)
|
||||
},
|
||||
|
||||
async Set(T, key, value, expiresIn) {
|
||||
Factory.FixOldKey(key)
|
||||
return Factory.MakeUnique(T).set(key, value, expiresIn)
|
||||
},
|
||||
|
||||
async Remove(T, key) {
|
||||
Factory.FixOldKey(key)
|
||||
return Factory.MakeUnique(T).remove(key)
|
||||
},
|
||||
|
||||
async Update(T, key) {
|
||||
Factory.FixOldKey(key)
|
||||
return Factory.MakeUnique(T).update(key)
|
||||
},
|
||||
|
||||
FixOldKey(key) {
|
||||
if (!key.provider) {
|
||||
key.provider = key.platform
|
||||
}
|
||||
|
||||
const configData = appConfig.get(key.dcloudAppid, key.provider)
|
||||
if (!configData) {
|
||||
throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, 'appid or provider invalid')
|
||||
}
|
||||
key.appid = configData.appid
|
||||
},
|
||||
|
||||
MakeUnique(T) {
|
||||
return new T()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// exports
|
||||
|
||||
async function getAccessToken(key, fallback) {
|
||||
return Factory.Get(AccessToken, key, fallback)
|
||||
}
|
||||
|
||||
async function setAccessToken(key, value, expiresIn) {
|
||||
return Factory.Set(AccessToken, key, value, expiresIn)
|
||||
}
|
||||
|
||||
async function removeAccessToken(key) {
|
||||
return Factory.Remove(AccessToken, key)
|
||||
}
|
||||
|
||||
async function updateAccessToken(key) {
|
||||
return Factory.Update(AccessToken, key)
|
||||
}
|
||||
|
||||
async function getUserAccessToken(key, fallback) {
|
||||
return Factory.Get(UserAccessToken, key, fallback)
|
||||
}
|
||||
|
||||
async function setUserAccessToken(key, value, expiresIn) {
|
||||
return Factory.Set(UserAccessToken, key, value, expiresIn)
|
||||
}
|
||||
|
||||
async function removeUserAccessToken(key) {
|
||||
return Factory.Remove(UserAccessToken, key)
|
||||
}
|
||||
|
||||
async function getSessionKey(key, fallback) {
|
||||
return Factory.Get(SessionKey, key, fallback)
|
||||
}
|
||||
|
||||
async function setSessionKey(key, value, expiresIn) {
|
||||
return Factory.Set(SessionKey, key, value, expiresIn)
|
||||
}
|
||||
|
||||
async function removeSessionKey(key) {
|
||||
return Factory.Remove(SessionKey, key)
|
||||
}
|
||||
|
||||
async function getEncryptKey(key, fallback) {
|
||||
return Factory.Get(Encryptkey, key, fallback)
|
||||
}
|
||||
|
||||
async function setEncryptKey(key, value, expiresIn) {
|
||||
return Factory.Set(Encryptkey, key, value, expiresIn)
|
||||
}
|
||||
|
||||
async function removeEncryptKey(key) {
|
||||
return Factory.Remove(Encryptkey, key)
|
||||
}
|
||||
|
||||
async function updateEncryptKey(key) {
|
||||
return Factory.Update(Encryptkey, key)
|
||||
}
|
||||
|
||||
async function getTicket(key, fallback) {
|
||||
return Factory.Get(Ticket, key, fallback)
|
||||
}
|
||||
|
||||
async function setTicket(key, value, expiresIn) {
|
||||
return Factory.Set(Ticket, key, value, expiresIn)
|
||||
}
|
||||
|
||||
async function removeTicket(key) {
|
||||
return Factory.Remove(Ticket, key)
|
||||
}
|
||||
|
||||
async function updateTicket(key) {
|
||||
return Factory.Update(Ticket, key)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAccessToken,
|
||||
setAccessToken,
|
||||
removeAccessToken,
|
||||
updateAccessToken,
|
||||
getUserAccessToken,
|
||||
setUserAccessToken,
|
||||
removeUserAccessToken,
|
||||
getSessionKey,
|
||||
setSessionKey,
|
||||
removeSessionKey,
|
||||
getEncryptKey,
|
||||
setEncryptKey,
|
||||
removeEncryptKey,
|
||||
updateEncryptKey,
|
||||
getTicket,
|
||||
setTicket,
|
||||
removeTicket,
|
||||
updateTicket,
|
||||
ProviderType,
|
||||
PlatformType,
|
||||
WeixinServer,
|
||||
ErrorCodeType
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "uni-open-bridge-common",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
Validator
|
||||
} = require('./validator.js')
|
||||
|
||||
const {
|
||||
CacheKeyCascade
|
||||
} = require('./uni-cloud-cache.js')
|
||||
|
||||
const {
|
||||
BridgeError
|
||||
} = require('./bridge-error.js')
|
||||
|
||||
class Storage {
|
||||
|
||||
constructor(type, keys) {
|
||||
this._type = type || null
|
||||
this._keys = keys || []
|
||||
}
|
||||
|
||||
async get(key, fallback) {
|
||||
this.validateKey(key)
|
||||
const result = await this.create(key, fallback).get()
|
||||
return result.value
|
||||
}
|
||||
|
||||
async set(key, value, expiresIn) {
|
||||
this.validateKey(key)
|
||||
this.validateValue(value)
|
||||
const expires_in = this.getExpiresIn(expiresIn)
|
||||
if (expires_in !== 0) {
|
||||
await this.create(key).set(this.getValue(value), expires_in)
|
||||
}
|
||||
}
|
||||
|
||||
async remove(key) {
|
||||
this.validateKey(key)
|
||||
await this.create(key).remove()
|
||||
}
|
||||
|
||||
// virtual
|
||||
async update(key) {
|
||||
this.validateKey(key)
|
||||
}
|
||||
|
||||
async ttl(key) {
|
||||
this.validateKey(key)
|
||||
// 后续考虑支持
|
||||
}
|
||||
|
||||
async fallback(key) {}
|
||||
|
||||
getKeyString(key) {
|
||||
const keyArray = [Storage.Prefix]
|
||||
this._keys.forEach((name) => {
|
||||
keyArray.push(key[name])
|
||||
})
|
||||
keyArray.push(this._type)
|
||||
return keyArray.join(':')
|
||||
}
|
||||
|
||||
getValue(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
getExpiresIn(value) {
|
||||
if (value !== undefined) {
|
||||
return value
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
validateKey(key) {
|
||||
Validator.Key(this._keys, key)
|
||||
}
|
||||
|
||||
validateValue(value) {
|
||||
Validator.Value(value)
|
||||
}
|
||||
|
||||
create(key, fallback) {
|
||||
const keyString = this.getKeyString(key)
|
||||
const options = {
|
||||
layers: [{
|
||||
type: 'database',
|
||||
key: keyString
|
||||
}, {
|
||||
type: 'redis',
|
||||
key: keyString
|
||||
}]
|
||||
}
|
||||
|
||||
const _this = this
|
||||
return new CacheKeyCascade({
|
||||
...options,
|
||||
fallback: async function() {
|
||||
if (fallback) {
|
||||
return fallback(key)
|
||||
} else if (_this.fallback) {
|
||||
return _this.fallback(key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Storage.Prefix = "uni-id"
|
||||
|
||||
module.exports = {
|
||||
Storage
|
||||
};
|
@ -0,0 +1,324 @@
|
||||
const db = uniCloud.database()
|
||||
|
||||
function getType(value) {
|
||||
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
|
||||
}
|
||||
|
||||
const validator = {
|
||||
key: function(value) {
|
||||
const err = new Error('Invalid key')
|
||||
if (typeof value !== 'string') {
|
||||
throw err
|
||||
}
|
||||
const valueTrim = value.trim()
|
||||
if (!valueTrim || valueTrim !== value) {
|
||||
throw err
|
||||
}
|
||||
},
|
||||
value: function(value) {
|
||||
// 仅作简单校验
|
||||
const type = getType(value)
|
||||
const validValueType = ['null', 'number', 'string', 'array', 'object']
|
||||
if (validValueType.indexOf(type) === -1) {
|
||||
throw new Error('Invalid value type')
|
||||
}
|
||||
},
|
||||
duration: function(value) {
|
||||
const err = new Error('Invalid duration')
|
||||
if (value === undefined) {
|
||||
return
|
||||
}
|
||||
if (typeof value !== 'number' || value === 0) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示
|
||||
* 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在
|
||||
*/
|
||||
class DatabaseCache {
|
||||
constructor({
|
||||
collection = 'opendb-open-data'
|
||||
} = {}) {
|
||||
this.type = 'db'
|
||||
this.collection = db.collection(collection)
|
||||
}
|
||||
|
||||
_serializeValue(value) {
|
||||
return value === undefined ? null : JSON.stringify(value)
|
||||
}
|
||||
|
||||
_deserializeValue(value) {
|
||||
return value ? JSON.parse(value) : value
|
||||
}
|
||||
|
||||
async set(key, value, duration) {
|
||||
validator.key(key)
|
||||
validator.value(value)
|
||||
validator.duration(duration)
|
||||
value = this._serializeValue(value)
|
||||
await this.collection.doc(key).set({
|
||||
value,
|
||||
expired: duration && duration !== -1 ? Date.now() + (duration * 1000) : -1
|
||||
})
|
||||
}
|
||||
|
||||
async _getWithDuration(key) {
|
||||
const getKeyRes = await this.collection.doc(key).get()
|
||||
const record = getKeyRes.data[0]
|
||||
if (!record) {
|
||||
return {
|
||||
value: null,
|
||||
duration: -2
|
||||
}
|
||||
}
|
||||
const value = this._deserializeValue(record.value)
|
||||
const expired = record.expired
|
||||
if (expired === -1) {
|
||||
return {
|
||||
value,
|
||||
duration: -1
|
||||
}
|
||||
}
|
||||
const duration = expired - Date.now()
|
||||
if (duration <= 0) {
|
||||
await this.remove(key)
|
||||
return {
|
||||
value: null,
|
||||
duration: -2
|
||||
}
|
||||
}
|
||||
return {
|
||||
value,
|
||||
duration: Math.floor(duration / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
async get(key, {
|
||||
withDuration = true
|
||||
} = {}) {
|
||||
const result = await this._getWithDuration(key)
|
||||
if (!withDuration) {
|
||||
delete result.duration
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async remove(key) {
|
||||
await this.collection.doc(key).remove()
|
||||
}
|
||||
}
|
||||
|
||||
class RedisCache {
|
||||
constructor() {
|
||||
this.type = 'redis'
|
||||
this.redis = uniCloud.redis()
|
||||
}
|
||||
|
||||
_serializeValue(value) {
|
||||
return value === undefined ? null : JSON.stringify(value)
|
||||
}
|
||||
|
||||
_deserializeValue(value) {
|
||||
return value ? JSON.parse(value) : value
|
||||
}
|
||||
|
||||
async set(key, value, duration) {
|
||||
validator.key(key)
|
||||
validator.value(value)
|
||||
validator.duration(duration)
|
||||
value = this._serializeValue(value)
|
||||
if (!duration || duration === -1) {
|
||||
await this.redis.set(key, value)
|
||||
} else {
|
||||
await this.redis.set(key, value, 'EX', duration)
|
||||
}
|
||||
}
|
||||
|
||||
async get(key, {
|
||||
withDuration = false
|
||||
} = {}) {
|
||||
let value = await this.redis.get(key)
|
||||
value = this._deserializeValue(value)
|
||||
if (!withDuration) {
|
||||
return {
|
||||
value
|
||||
}
|
||||
}
|
||||
const durationSecond = await this.redis.ttl(key)
|
||||
let duration
|
||||
switch (durationSecond) {
|
||||
case -1:
|
||||
duration = -1
|
||||
break
|
||||
case -2:
|
||||
duration = -2
|
||||
break
|
||||
default:
|
||||
duration = durationSecond
|
||||
break
|
||||
}
|
||||
return {
|
||||
value,
|
||||
duration
|
||||
}
|
||||
}
|
||||
|
||||
async remove(key) {
|
||||
await this.redis.del(key)
|
||||
}
|
||||
}
|
||||
|
||||
class Cache {
|
||||
constructor({
|
||||
type,
|
||||
collection
|
||||
} = {}) {
|
||||
if (type === 'database') {
|
||||
return new DatabaseCache({
|
||||
collection
|
||||
})
|
||||
} else if (type === 'redis') {
|
||||
return new RedisCache()
|
||||
} else {
|
||||
throw new Error('Invalid cache type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CacheKey {
|
||||
constructor({
|
||||
type,
|
||||
collection,
|
||||
cache,
|
||||
key,
|
||||
fallback
|
||||
} = {}) {
|
||||
this.cache = cache || new Cache({
|
||||
type,
|
||||
collection
|
||||
})
|
||||
this.key = key
|
||||
this.fallback = fallback
|
||||
}
|
||||
|
||||
async set(value, duration) {
|
||||
await this.cache.set(this.key, value, duration)
|
||||
}
|
||||
|
||||
async setWithSync(value, duration, syncMethod) {
|
||||
await Promise.all([
|
||||
this.set(this.key, value, duration),
|
||||
syncMethod(value, duration)
|
||||
])
|
||||
}
|
||||
|
||||
async get() {
|
||||
let {
|
||||
value,
|
||||
duration
|
||||
} = await this.cache.get(this.key)
|
||||
if (value !== null && value !== undefined) {
|
||||
return {
|
||||
value,
|
||||
duration
|
||||
}
|
||||
}
|
||||
if (!this.fallback) {
|
||||
return {
|
||||
value: null,
|
||||
duration: -2
|
||||
}
|
||||
}
|
||||
const fallbackResult = await this.fallback()
|
||||
value = fallbackResult.value
|
||||
duration = fallbackResult.duration
|
||||
if (value !== null && duration !== undefined) {
|
||||
await this.cache.set(this.key, value, duration)
|
||||
}
|
||||
return {
|
||||
value,
|
||||
duration
|
||||
}
|
||||
}
|
||||
|
||||
async remove() {
|
||||
await this.cache.remove(this.key)
|
||||
}
|
||||
}
|
||||
|
||||
class CacheKeyCascade {
|
||||
constructor({
|
||||
layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey]
|
||||
fallback
|
||||
} = {}) {
|
||||
this.layers = layers
|
||||
this.cacheLayers = []
|
||||
let lastCacheKey
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const {
|
||||
type,
|
||||
cache,
|
||||
collection,
|
||||
key
|
||||
} = layers[i]
|
||||
const lastCacheKeyTemp = lastCacheKey
|
||||
try {
|
||||
const currentCacheKey = new CacheKey({
|
||||
type,
|
||||
collection,
|
||||
cache,
|
||||
key,
|
||||
fallback: i === 0 ? fallback : function() {
|
||||
return lastCacheKeyTemp.get()
|
||||
}
|
||||
})
|
||||
this.cacheLayers.push(currentCacheKey)
|
||||
lastCacheKey = currentCacheKey
|
||||
} catch (e) {}
|
||||
}
|
||||
this.highLevelCache = lastCacheKey
|
||||
}
|
||||
|
||||
async set(value, duration) {
|
||||
return Promise.all(
|
||||
this.cacheLayers.map(item => {
|
||||
return item.set(value, duration)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async setWithSync(value, duration, syncMethod) {
|
||||
const setPromise = this.cacheLayers.map(item => {
|
||||
return item.set(value, duration)
|
||||
})
|
||||
return Promise.all(
|
||||
[
|
||||
...setPromise,
|
||||
syncMethod(value, duration)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
async get() {
|
||||
return this.highLevelCache.get()
|
||||
}
|
||||
|
||||
async remove() {
|
||||
await Promise.all(
|
||||
this.cacheLayers.map(cacheKeyItem => {
|
||||
return cacheKeyItem.remove()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Cache,
|
||||
DatabaseCache,
|
||||
RedisCache,
|
||||
CacheKey,
|
||||
CacheKeyCascade
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
const Validator = {
|
||||
|
||||
Key(keyArray, parameters) {
|
||||
for (let i = 0; i < keyArray.length; i++) {
|
||||
const keyName = keyArray[i]
|
||||
if (typeof parameters[keyName] !== 'string') {
|
||||
Validator.ThrowNewError(`Invalid ${keyName}`)
|
||||
}
|
||||
if (parameters[keyName].length < 1) {
|
||||
Validator.ThrowNewError(`Invalid ${keyName}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Value(value) {
|
||||
if (value === undefined) {
|
||||
Validator.ThrowNewError('Invalid Value')
|
||||
}
|
||||
if (typeof value !== 'object') {
|
||||
Validator.ThrowNewError('Invalid Value Type')
|
||||
}
|
||||
},
|
||||
|
||||
ThrowNewError(message) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Validator
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
const {
|
||||
HTTP_STATUS
|
||||
} = require('./consts.js')
|
||||
|
||||
const {
|
||||
BridgeError
|
||||
} = require('./bridge-error.js')
|
||||
|
||||
class WeixinServer {
|
||||
|
||||
constructor(options = {}) {
|
||||
this._appid = options.appid
|
||||
this._secret = options.secret
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return uniCloud.httpclient.request(WeixinServer.AccessToken_Url, {
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
contentType: 'json',
|
||||
data: {
|
||||
appid: this._appid,
|
||||
secret: this._secret,
|
||||
grant_type: "client_credential"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 使用客户端获取的 code 从微信服务器换取 openid,code 仅可使用一次
|
||||
codeToSession(code) {
|
||||
return uniCloud.httpclient.request(WeixinServer.Code2Session_Url, {
|
||||
dataType: 'json',
|
||||
data: {
|
||||
appid: this._appid,
|
||||
secret: this._secret,
|
||||
js_code: code,
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getUserEncryptKey({
|
||||
access_token,
|
||||
openid,
|
||||
session_key
|
||||
}) {
|
||||
console.log(access_token, openid, session_key);
|
||||
const signature = crypto.createHmac('sha256', session_key).update('').digest('hex')
|
||||
return uniCloud.httpclient.request(WeixinServer.User_Encrypt_Key_Url, {
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
dataAsQueryString: true,
|
||||
data: {
|
||||
access_token,
|
||||
openid: openid,
|
||||
signature: signature,
|
||||
sig_method: 'hmac_sha256'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getH5AccessToken() {
|
||||
return uniCloud.httpclient.request(WeixinServer.AccessToken_H5_Url, {
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
data: {
|
||||
appid: this._appid,
|
||||
secret: this._secret,
|
||||
grant_type: "client_credential"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getH5Ticket(access_token) {
|
||||
return uniCloud.httpclient.request(WeixinServer.Ticket_Url, {
|
||||
dataType: 'json',
|
||||
dataAsQueryString: true,
|
||||
method: 'POST',
|
||||
data: {
|
||||
access_token
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getH5AccessTokenForEip() {
|
||||
return uniCloud.httpProxyForEip.postForm(WeixinServer.AccessToken_H5_Url, {
|
||||
appid: this._appid,
|
||||
secret: this._secret,
|
||||
grant_type: "client_credential"
|
||||
}, {
|
||||
dataType: 'json'
|
||||
})
|
||||
}
|
||||
|
||||
getH5TicketForEip(access_token) {
|
||||
return uniCloud.httpProxyForEip.postForm(WeixinServer.Ticket_Url, {
|
||||
access_token
|
||||
}, {
|
||||
dataType: 'json',
|
||||
dataAsQueryString: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
WeixinServer.AccessToken_Url = 'https://api.weixin.qq.com/cgi-bin/stable_token'
|
||||
WeixinServer.Code2Session_Url = 'https://api.weixin.qq.com/sns/jscode2session'
|
||||
WeixinServer.User_Encrypt_Key_Url = 'https://api.weixin.qq.com/wxa/business/getuserencryptkey'
|
||||
WeixinServer.AccessToken_H5_Url = 'https://api.weixin.qq.com/cgi-bin/token'
|
||||
WeixinServer.Ticket_Url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi'
|
||||
|
||||
WeixinServer.GetMPAccessToken = function(options) {
|
||||
return new WeixinServer(options).getAccessToken()
|
||||
}
|
||||
|
||||
WeixinServer.GetCodeToSession = function(options) {
|
||||
return new WeixinServer(options).codeToSession(options.code)
|
||||
}
|
||||
|
||||
WeixinServer.GetUserEncryptKey = function(options) {
|
||||
return new WeixinServer(options).getUserEncryptKey(options)
|
||||
}
|
||||
|
||||
WeixinServer.GetH5AccessToken = function(options) {
|
||||
return new WeixinServer(options).getH5AccessToken()
|
||||
}
|
||||
|
||||
WeixinServer.GetH5Ticket = function(options) {
|
||||
return new WeixinServer(options).getH5Ticket(options.access_token)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
function isAliyun() {
|
||||
return (uniCloud.getCloudInfos()[0].provider === 'aliyun')
|
||||
}
|
||||
|
||||
WeixinServer.GetResponseData = function(response) {
|
||||
console.log("WeixinServer::response", response)
|
||||
|
||||
if (!(response.status === HTTP_STATUS.SUCCESS || response.statusCodeValue === HTTP_STATUS.SUCCESS)) {
|
||||
throw new BridgeError(response.status || response.statusCodeValue, response.status || response.statusCodeValue)
|
||||
}
|
||||
|
||||
const responseData = response.data || response.body
|
||||
|
||||
if (responseData.errcode !== undefined && responseData.errcode !== 0) {
|
||||
throw new BridgeError(responseData.errcode, responseData.errmsg)
|
||||
}
|
||||
|
||||
return responseData
|
||||
}
|
||||
|
||||
WeixinServer.GetMPAccessTokenData = async function(options) {
|
||||
const response = await new WeixinServer(options).getAccessToken()
|
||||
return WeixinServer.GetResponseData(response)
|
||||
}
|
||||
|
||||
WeixinServer.GetCodeToSessionData = async function(options) {
|
||||
const response = await new WeixinServer(options).codeToSession(options.code)
|
||||
return WeixinServer.GetResponseData(response)
|
||||
}
|
||||
|
||||
WeixinServer.GetUserEncryptKeyData = async function(options) {
|
||||
const response = await new WeixinServer(options).getUserEncryptKey(options)
|
||||
return WeixinServer.GetResponseData(response)
|
||||
}
|
||||
|
||||
WeixinServer.GetH5AccessTokenData = async function(options) {
|
||||
const ws = new WeixinServer(options)
|
||||
let response
|
||||
if (isAliyun()) {
|
||||
response = await ws.getH5AccessTokenForEip()
|
||||
if (typeof response === 'string') {
|
||||
response = JSON.parse(response)
|
||||
}
|
||||
} else {
|
||||
response = await ws.getH5AccessToken()
|
||||
}
|
||||
return WeixinServer.GetResponseData(response)
|
||||
}
|
||||
|
||||
WeixinServer.GetH5TicketData = async function(options) {
|
||||
const ws = new WeixinServer(options)
|
||||
let response
|
||||
if (isAliyun()) {
|
||||
response = await ws.getH5TicketForEip(options.access_token)
|
||||
if (typeof response === 'string') {
|
||||
response = JSON.parse(response)
|
||||
}
|
||||
} else {
|
||||
response = await ws.getH5Ticket(options.access_token)
|
||||
}
|
||||
return WeixinServer.GetResponseData(response)
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
WeixinServer
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
|
||||
{
|
||||
"bsonType": "object",
|
||||
"required": ["_id", "value"],
|
||||
"properties": {
|
||||
"_id": {
|
||||
"bsonType": "string",
|
||||
"description": "key,格式:uni-id:[provider]:[appid]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]"
|
||||
},
|
||||
"value": {
|
||||
"bsonType": "object",
|
||||
"description": "字段_id对应的值"
|
||||
},
|
||||
"expired": {
|
||||
"bsonType": "date",
|
||||
"description": "过期时间"
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user