Skip to content

Commit

Permalink
feat(init): 支持内置云函数的导入;增加用户登陆注册、小程序授权、阿里云发短信等内置云函数
Browse files Browse the repository at this point in the history
  • Loading branch information
maslow committed Jun 21, 2021
1 parent 0ade47c commit 8c4b0ec
Show file tree
Hide file tree
Showing 15 changed files with 516 additions and 10 deletions.
73 changes: 73 additions & 0 deletions init/func_loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const fs = require('fs/promises')
const path = require('path')

class FunctionLoader {
rootPath = path.join(__dirname, 'functions')

/**
* 获取函数目录列表
* @returns {Promise<string[]>}
*/
async getFunctionDirectoryList() {
const dirs = await fs.readdir(this.rootPath)
return dirs ?? []
}

/**
* 获取函数
* @returns {Promise<any[]>}
*/
async getFunctions() {
const dirs = await this.getFunctionDirectoryList()
const funcPaths = dirs.map(dir => path.join(this.rootPath, dir))
const results = []
for (const fp of funcPaths) {
const r = await this.loadFunction(fp)
results.push(r)
}
return results
}

/**
* 加载函数
* @param {string}} func_path
* @returns
*/
async loadFunction(func_path) {
const codePath = path.join(func_path, 'index.js')
const code = await this.loadFunctionCode(codePath)

const metaPath = path.join(func_path, 'meta.json')
const meta = await this.loadFunctionMeta(metaPath)

return { ...meta, code }
}

/**
* 获取函数代码
* @param {Promise<string>} file_path
*/
async loadFunctionCode(file_path) {
const data = await fs.readFile(file_path, 'utf-8')
return data
}

/**
* 获取函数 meta 信息
* @param {string} file_path
*/
async loadFunctionMeta(file_path) {
const data = await fs.readFile(file_path, 'utf-8')
return JSON.parse(data)
}
}


const fi = new FunctionLoader()
fi.getFunctions()
.then(console.log)


module.exports = {
FunctionLoader
}
37 changes: 37 additions & 0 deletions init/functions/send-login-smscode/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

/**
* @body phone string 手机号
*/
async function main (ctx) {
const db = less.database()
const phone = ctx.body?.phone
if (!phone) {
return 'Error: invalid phone'
}

const code = Math.min(Math.floor(1000 + Math.random() * 9000), 9999)

const r = await sendSMSCode(phone, code)
if (r.data === 'ok') {
await db.collection('verify_code').add({
type: 'sms',
phone,
code,
event: 'login',
created_at: Date.now()
})
}
return r
}

/**
* 发送验证码
* @return {Promise<string>}
* @see cloud function: aliyun-sms-service
*/
async function sendSMSCode(phone, code) {
const body = { phone, code }
const r = await less.invoke('aliyun-sms-service', { body })
return r
}

7 changes: 7 additions & 0 deletions init/functions/send-login-smscode/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "发送登陆验证码",
"name": "send-login-smscode",
"description": "发送登陆短信验证码",
"enableHTTP": true,
"debugParams": "{\n \"phone\": \"13184211245\"\n}"
}
108 changes: 108 additions & 0 deletions init/functions/sms-service/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

import { v4 as uuidv4 } from 'uuid'
import * as crypto from 'crypto'
const querystring = require('querystring')

const accessKeyId = 'LTAI5tC3FoH47fQT7oZdcYEw'
const accessKeySecret = 'B7NHLuoFKvrPw8w3QIBZWNn4zrZcpp'
const api_entrypoint = 'https://dysmsapi.aliyuncs.com'
const signName = '灼灼信息'
const templateCode = 'SMS_217850726'

/**
* @body phone string 手机号
* @body code string | number 验证码
*/
async function main (ctx) {
const phone = ctx.body?.phone
if (!phone) {
return 'error: invalid phone'
}
const code = ctx.body?.code
if (!code) {
return 'error: invalid code'
}

const params = sortObjectKeys({
AccessKeyId: accessKeyId,
Action: 'SendSms',
Format: 'json',
SignatureMethod: 'HMAC-SHA1',
SignatureNonce: uuidv4(),
SignatureVersion: '1.0',
Version: '2017-05-25',
Timestamp: (new Date()).toISOString(),
PhoneNumbers: phone,
SignName: signName,
TemplateCode: templateCode,
TemplateParam: `{"code": ${code}}`
})

params['Signature'] = specialEncode(sign(params))

const query = querystring.stringify(params)
const url = `${api_entrypoint}?${query}`

try {
const r = await less.fetch(url)
console.log(r.data)
if(r.data?.Code === 'OK')
return 'ok'
else
return r.data

} catch (err) {
console.log(err)
return 'error: ' + err
}
}
// 签名
function sign(raw_params) {
const params = encode(raw_params)

//拼接strToSign
let strToSign = '';
for (let i in params) {
strToSign += i + '=' + params[i] + '&';
}
strToSign = strToSign.substr(0, strToSign.length - 1);
strToSign = "GET&" + encodeURIComponent('/') + '&' + encodeURIComponent(strToSign);

// 阿里云签名是要求 基于 hash 的原始二进制值 进行 base64编码
const ret = crypto.createHmac('sha1', accessKeySecret + '&')
.update(strToSign)
.digest('base64')

return ret
}

//对各个参数进行字典序升序排序
function sortObjectKeys(obj) {
const tmp = {};
Object.keys(obj).sort().forEach(k => tmp[k] = obj[k])
return tmp;
}


//对排序之后的参数进行 uriencode + POP 编码
function encode(params) {
const obj = {}
//对urlencode之后的特殊字符进行替换
for (let i in params) {
const str = encodeURIComponent(params[i])
obj[i] = specialEncode(str)
}
return obj
}

// 阿里云的特殊编码(POP编码)
function specialEncode(encoded) {
if (encoded.indexOf('+')) {
encoded.replace("+", "%20");
} else if (str.indexOf('*')) {
encoded.replace("*", "%2A");
} else if (str.indexOf('%7E')) {
encoded.replace("%7E", "~");
}
return encoded
}
7 changes: 7 additions & 0 deletions init/functions/sms-service/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "aliyun-sms-service",
"name": "aliyun-sms-service",
"description": "阿里云通信-发短信内部调用服务,不开启外网访问",
"enableHTTP": false,
"debugParams": "{\n \"phone\": \"13184211245\",\n \"code\": \"1234\"\n}"
}
7 changes: 7 additions & 0 deletions init/functions/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@


exports.main = async function (ctx) {
console.log(ctx)

return 'ok'
}
6 changes: 6 additions & 0 deletions init/functions/test/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"label": "测试",
"name": "test",
"description": "",
"enableHTTP": true
}
64 changes: 64 additions & 0 deletions init/functions/user-passwd-login/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as crypto from 'crypto'

/**
* @body username string 用户名
* @body password string 密码
*/
export async function main(ctx) {
const db = less.database()

// 参数验证
const { username, password } = ctx.body
if (!username || !password) {
return { code: 1, error: 'invalid phone or password' }
}

// 验证用户名是否存在
const { data: user } = await db.collection('users')
.where({ username })
.getOne()

if (!user) {
return {code: 1, error: 'invalid username or password'}
}

// 验证密码是否正确
const ret = await db.collection('password')
.where({ uid: user._id, password: hashPassword(password), type: 'login' })
.count()

if (ret.total > 0) {

// 默认 token 有效期为 7 天
const expire = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7
const payload = {
uid: user._id,
type: 'user',
exp: expire
}
const access_token = less.getToken(payload)
return {
code: 0,
data: {
access_token,
user,
uid: user._id,
expire
}
}
}

return { code: 1, error: 'invalid username or password' }
}

/**
* @param {string} content
* @return {string}
*/
function hashPassword(content) {
return crypto
.createHash('sha256')
.update(content)
.digest('hex')
}

7 changes: 7 additions & 0 deletions init/functions/user-passwd-login/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "用户密码登陆",
"name": "user-passwd-login",
"description": "用户密码登陆",
"enableHTTP": true,
"debugParams": "{\n \"username\": \"less\",\n \"password\": \"less123\"\n}"
}
Loading

0 comments on commit 8c4b0ec

Please sign in to comment.