This commit is contained in:
robin
2022-07-07 22:16:39 +08:00
parent 264ab8d979
commit a9a4bd0981
86 changed files with 1198 additions and 310 deletions
+55
View File
@@ -0,0 +1,55 @@
// https://molunerfinn.com/Use-Jest-To-Test-Vue-Koa/#Koa%E5%90%8E%E7%AB%AFApi%E6%B5%8B%E8%AF%95
//https://jestjs.io/docs/api#testeachtablename-fn-timeout
//Jest CLI选项 : https://runebook.dev/zh-CN/docs/jest/cli#--forceexit
//匹配器:https://www.jianshu.com/p/c1b5676c1edd
const bamboo = require('./lib/main')
const glob = require("glob")
const path = require('path')
import request from 'supertest';
let server = ""
let list = []
/*当前根目录*/
function isPath(args) {
return path.resolve('./' + (args || ""))
}
function load(directory) {
const path_root = isPath()
let files = glob.sync(directory)
files = files.map(item => {
const parse = path.parse(item);
parse.father = path.basename(path.resolve(parse.dir, '..'))
parse.file_father = path.basename(path.resolve(parse.dir + '/' + parse.name, '..'))
return {
parse,
res: require(path.resolve(path_root + "/" + item))
}
})
return files
}
list = load("app/*/test/*.js")
beforeAll(() => {
return new Promise((resolve, reject) => {
bamboo.fullList.push(async (app) => {
server = app.server
//回复测试数据镜像sql文件
await app.db.tool.reduction()
resolve()
})
})
});
afterAll(() => {
server.close();
console.log('服务器关闭!');
});
describe.each(list)('测试清单名称:$res.describe_name', (testDescribe) => {
test.each(testDescribe.res.testList)('测试名称:$test_name', async (o) => await o.fun(request, server));
});
-4
View File
@@ -1,4 +0,0 @@
/*系统启动后要做初始化*/
module.exports = (app) => {
console.log("启动完成");
}
-11
View File
@@ -1,11 +0,0 @@
module.exports = {
doc: "用户",
api: true,//是否需要生成api接口
model: {
uid: {type: "STRING", comment: '用户id'},
age: {type: "INTEGER", comment: '年龄', defaultValue: 0},
age2: {type: "INTEGER", comment: '年龄', defaultValue: 0},
},
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
doc : "用户信息表",
model: {
uid : {type: "STRING", comment: '用户id'},
phone: {type: "STRING", comment: '手机号', defaultValue: null,},
}
}
+20
View File
@@ -0,0 +1,20 @@
'use strict';
module.exports = {
// path : "cms/api/index",//可以覆盖自动生成的路由地址
params: {
"age": "int?"
},
fun : async (ctx, app) => {
const body = ctx.params
const User = await app.db.table("User").find()
//成功返回的2种方式
// ctx.body = app.res.success(User,"success",200)//"success",200可以省略
return User
//异常返回的2种方式
// ctx.body = app.res.error(User, "error", 400)//"error",400可以省略
// return false
}
}
+22
View File
@@ -0,0 +1,22 @@
'use strict';
module.exports = {
name:"加入房间",
doc:``,
params: {
"type": ["accountPassword"],//登录类型
"data": "object",//数据
},
fun : async (ctx, app) => {
const {type, data} = ctx.params
if (await $components.login[type](data)) {
return $res.success({
token: await $components.jwt.sign({})
}, "登录成功")
}
return $res.error({}, "登录失败")
}
}
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
name : "离开房间",
doc : ``,
params: {
// "room_name": 'string',//房间
// "uid" : 'string',//用户id
},
fun : (socket, {msg, onname, callback}) => {
console.log('断线:', socket.id);
}
}
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
name : "加入房间",
doc : ``,
params: {
"room_name": 'string',//房间
"uid" : 'string',//用户id
},
fun : (socket, {msg, onname, callback}) => {
console.log('加入房间:', msg);
socket.join(msg.room_name)//加入房间
socket.emit('success', {
event: onname,
res : app.res.success(""),
})
socket.to(msg.room_name).emit('success', {
event: onname,
res : app.res.success(msg, "有人加入房间"),
})
}
}
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
name : "离开房间",
doc : ``,
params: {
// "room_name": 'string',//房间
// "uid" : 'string',//用户id
},
fun : (socket, {msg, onname, callback}) => {
console.log('离开房间:', socket.id);
}
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = (app, msg, callback) => {
console.log('接收到的消息:', msg);
}
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
doc: "联系人表",
api: true,//是否需要生成api接口
model: {
uid: {type: "STRING", comment: '用户id/群id'},
to_uid: {type: "STRING", comment: '接收人用户id'},
type: {type: "STRING", comment: '类型,单人/群:one/group'},
status: {type: "STRING", comment: '状态:已结束/未结束:y/n'},
},
}
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
doc: "消息表",
api: true,//是否需要生成api接口
model: {
uid: {type: "STRING", comment: '发送人用户id'},
name: {type: "STRING", comment: '发送人名称'},
avatar: {type: "STRING", comment: '发送人头像'},
to_uid: {type: "STRING", comment: '接收人用户id'},
to_name: {type: "STRING", comment: '接收人名称'},
to_avatar: {type: "STRING", comment: '接收人头像'},
status: {type: "STRING", comment: '状态:已读/未读:y/n'},
type: {type: "STRING", comment: '消息类型:file / image / text / event'},
sendTime: {type: "STRING", comment: '消息发送时间'},
content: {type: "STRING", comment: '消息内容,如果type=file,此属性表示文件的URL地址'},
fileSize: {type: "STRING", comment: '文件大小'},
fileName: {type: "STRING", comment: '文件名称'},
},
}
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
doc: "自动回复表",
api: true,//是否需要生成api接口
model: {
type: {type: "STRING", comment: '类型,自动回复/问候语:auto/hello'},
status: {type: "STRING", comment: '状态:显示/隐藏:y/n'},
},
}
+39
View File
@@ -0,0 +1,39 @@
module.exports = {
describe_name: "permission/权限管理",
testList : [
{
test_name: "账号密码注册",
fun : async (request, server) => {
const response = await request(server)
.post('/permission/api/registered')
.send({
type: "accountPassword",
data: {
account : "test",
password: "123456",
},
})
// expect(response.status).toEqual(200)
expect(response.body.status).toEqual(200)
expect(response.body.data).toHaveProperty('token')
}
},
{
test_name: "账号密码登录",
fun : async (request, server) => {
const response = await request(server)
.post('/permission/api/login')
.send({
type: "accountPassword",
data: {
account : "test",
password: "123456",
},
})
// expect(response.status).toEqual(200)
expect(response.body.status).toEqual(200)
expect(response.body.data).toHaveProperty('token')
}
},
]
}
+20
View File
@@ -0,0 +1,20 @@
'use strict';
module.exports = {
params: {
"type": ["accountPassword"],//登录类型
"data": "object",//数据
},
fun : async (ctx, app) => {
const {type, data} = ctx.params
if (await $components.login[type](data)) {
return $res.success({
token: await $components.jwt.sign({})
}, "登录成功")
}
return $res.error({}, "登录失败")
}
}
+19
View File
@@ -0,0 +1,19 @@
'use strict';
module.exports = {
params: {
"type": ["accountPassword"],//注册类型
"data": "object",//数据
},
fun : async (ctx, app) => {
const {type, data} = ctx.params
if (await $components.registered[type](data)) {
return $res.success({
token: await $components.jwt.sign({})
}, "注册成功")
}
return $res.error({}, "登录失败")
}
}
@@ -0,0 +1,26 @@
module.exports = {
//获取用户所属组织信息
async getUserToOrg(uid) {
},
//获取用户所属分组信息
async getUserToGroup(uid) {
},
//获取用户权限
async getUserToPermission(uid) {
},
//获取用户角色信息
async getUserToRole(uid) {
},
//获取用户组织/分组/角色/权限
async getUserAll(uid) {
},
//获取组织下全部分组
async getOrgToGroupAll(uid) {
},
}
@@ -0,0 +1,23 @@
const crypto = require('crypto');
module.exports = {
// 随机数(盐值)
getRandomSalt() {
return Math.random().toString().slice(2, 5);
},
// 加密用户密码(原始密码,盐值)
// 密码同样是123456,由于采用了随机盐值,前后运算得出的结果是不同的。
// 这样带来的好处是,多个用户,同样的密码,攻击者需要进行多次运算才能够完全破解。
// 同样是纯数字3位短盐值,随机盐值破解所需的运算量,是固定盐值的1000倍。
cryptPwd(password, salt) {
// 密码“加盐”
const saltPassword = password + ':' + salt;
// 加盐密码的md5值
const md5 = crypto.createHash('md5');
return md5.update(saltPassword).digest('hex');
},
// 密码验证,如果验证通过 返回 true
cryptPwdVerification(password, salt, user_password_md5) {
return this.cryptPwd(password, salt) === user_password_md5;
},
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
secret : 'sQ6CIfqS4SqF1zZqRZbCDAT5@T]X4fCD',//秘钥
algorithm: 'HS256',//数字签名或 MAC 算法
expiresIn: "7d",//有效期:例如:1000, "2 days", "10h", "7d". 数值被解释为秒数。如果使用字符串,请确保提供时间单位(天、小时等),否则默认使用毫秒单位("120"等于"120ms")。
}
+17
View File
@@ -0,0 +1,17 @@
const jwt = require("jsonwebtoken");
const config = require("./config.js");
module.exports = {
/**
* 签发token
* @param {object} data 加入到签名里的数据.
* @return {string} token 令牌.
*/
sign: (data) => jwt.sign(data, config.secret, {algorithm: config.algorithm, expiresIn: config.expiresIn}),
/**
* 验证token
* @param {string} token 令牌.
* @return {boolean} 验证通过返回true.
*/
verify: (token) => jwt.verify(token, config.secret, {algorithm: config.algorithm, expiresIn: config.expiresIn}),
}
@@ -0,0 +1,15 @@
/**
* app.components.login.accountPassword
* 账号密码登陆
* @param {string} data.account 账号.
* @param {string} data.password 密码.
* @return {object} res 验证是否通过.
*/
module.exports = async (data) => {
const {account, password} = data
const User = await app.db.table("User").where({account}).find()
if (!User) return false
const {salt} = User
const ver = app.components.encrypt.cryptPwdVerification(password, salt, User.password)
return ver
}
@@ -0,0 +1,14 @@
/**
* app.components.registered.accountPassword
* 账号密码注册
* @param {string} data.account 账号.
* @param {string} data.password 密码.
* @return {boolean} 注册 是否 成功.
*/
module.exports = async (data) => {
const {account, password} = data
const salt = app.components.encrypt.getRandomSalt()
const md5 = app.components.encrypt.cryptPwd(password, salt)
await app.db.table("User").data({account, password: md5, salt}).save()
return true
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
path : "app/*/api/*.js",
prefix : "",//接口前缀
statusTobody: true,//是否跟随body结果(如需接口报错也返回200,那么设置为false)
}
+18
View File
@@ -0,0 +1,18 @@
'use strict';
/**
* RBAC用户、角色、权限、组设
* saas的权限验证
*/
const config = require("./config")
// 错误处理
module.exports = {
sort : 1, //排序
use : true, // 是否使用
fun : async (ctx, next, app) => {
await next()
}
}
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
doc: "分组表",
api: true,//是否需要生成api接口
model: {
gid: {type: "STRING", comment: '分组id'},
name: {type: "STRING", comment: '分组名称'},
to_gid: {type: "STRING", comment: '上级分组id'},
to_oid: {type: "STRING", comment: '所属组织id'},
},
}
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
doc: "组织表",
api: true,//是否需要生成api接口
model: {
oid: {type: "STRING", comment: '组织id'},
name: {type: "STRING", comment: '组织名称'},
general_management: {type: "STRING", comment: '是否为总管理平台(可以管理所有组织)'},
},
}
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
doc: "权限列表",
api: true,//是否需要生成api接口
model: {
pid: {type: "STRING", comment: '权限id'},
to_pccode: {type: "STRING", comment: '所属权限分类码'},
name: {type: "STRING", comment: '权限名称'},
code: {type: "STRING", comment: '权限识别码'},
value: {type: "STRING", comment: '权限值'},
},
}
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
doc: "权限分类表",
api: true,//是否需要生成api接口
model: {
name: {type: "STRING", comment: '权限分类名称'},
pccode: {type: "STRING", comment: '权限分类识别码'},
},
}
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
doc: "角色表",
api: true,//是否需要生成api接口
model: {
rid: {type: "STRING", comment: '角色id'},
name: {type: "STRING", comment: '角色名称'},
to_rid: {type: "STRING", comment: '上级角色id'},
to_oid: {type: "STRING", comment: '所属组织id'},
to_gid: {type: "STRING", comment: '所属分组id'},
},
}
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
doc: "角色拥有的权限",
api: true,//是否需要生成api接口
model: {
rpid: {type: "STRING", comment: '角色权限id'},
to_rid: {type: "STRING", comment: '角色id'},
to_pid: {type: "STRING", comment: '权限id'},
},
}
+16
View File
@@ -0,0 +1,16 @@
module.exports = {
doc: "用户表",
api: true,//是否需要生成api接口
model: {
uid: {type: "STRING", comment: '用户id'},
account: {type: "STRING", comment: '用户账号'},
password: {type: "STRING", comment: '用户加密密码'},
salt: {type: "STRING", comment: '用户加密随机数'},
to_oid: {type: "STRING", comment: '所属组织id'},
to_gid: {type: "STRING", comment: '所属分组id'},
to_rid: {type: "STRING", comment: '所属角色id'},
to_uid: {type: "STRING", comment: '上级用户id'},
},
}
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
doc: "用户资料表",
api: true,//是否需要生成api接口
model: {
uid: {type: "STRING", comment: '用户id'},
account: {type: "STRING", comment: '用户账号'},
name: {type: "STRING", comment: '名称'},
avatar: {type: "STRING", comment: '头像'},
wechat_openid: {type: "STRING", comment: '微信openid'},
phone_number: {type: "STRING", comment: '手机号'},
age: {type: "STRING", comment: '年龄'},
sex: {type: "STRING", comment: '性别'},
province: {type: "STRING", comment: '省份'},
city: {type: "STRING", comment: '城市'},
area: {type: "STRING", comment: '地区'},
address: {type: "STRING", comment: '详细地址'},
},
}
+39
View File
@@ -0,0 +1,39 @@
module.exports = {
describe_name: "permission/权限管理",
testList : [
{
test_name: "账号密码注册",
fun : async (request, server) => {
const response = await request(server)
.post('/permission/api/registered')
.send({
type: "accountPassword",
data: {
account : "test",
password: "123456",
},
})
// expect(response.status).toEqual(200)
expect(response.body.status).toEqual(200)
expect(response.body.data).toHaveProperty('token')
}
},
{
test_name: "账号密码登录",
fun : async (request, server) => {
const response = await request(server)
.post('/permission/api/login')
.send({
type: "accountPassword",
data: {
account : "test",
password: "123456",
},
})
// expect(response.status).toEqual(200)
expect(response.body.status).toEqual(200)
expect(response.body.data).toHaveProperty('token')
}
},
]
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
}
-4
View File
@@ -1,4 +0,0 @@
/*应用配置*/
module.exports = {
port: 3000,
}
-1
View File
@@ -1 +0,0 @@
/*缓存配置*/
-1
View File
@@ -1 +0,0 @@
/*控制台配置*/
-77
View File
@@ -1,77 +0,0 @@
/*数据库配置*/
module.exports = {
mysql : {
database: "bamboo",
username: "bamboo",
password: "bamboo",
options : {
dialect: 'mysql',
host : "192.168.1.26",
port : 3306,
// 禁用日志记录或提供自定义日志记录功能;默认值:console.log
// logging: false,
// model的全局配置
define: {
// 添加create,update,delete时间戳
timestamps: true,
// 添加软删除
paranoid: false,
// 防止修改表名为复数
freezeTableName: true,
// 防止驼峰式字段被默认转为下划线
underscored: false,
},
// 由于orm用的UTC时间,这里必须加上东八区,否则取出来的时间相差8小时
timezone: '+08:00',
// 连接数 = ((核心数 * 2) + 有效磁盘数)
pool : {// 连接池
max : require('os').cpus().length * 2 + 1,
min : 0,
acquire: 60000,
idle : 100000,
},
dialectOptions: {
charset : "utf8mb4",
collate : "utf8mb4_general_ci",
supportBigNumbers: true,
bigNumberStrings : true,
dateStrings : true,
typeCast(field, next) {// 让读取date类型数据时返回字符串而不是UTC时间
if (field.type === 'DATETIME') {
// console.log(field);
return field.string();
}
return next();
},
},
},
},
redis : {},
sqlite: {
dialect: 'sqlite',
storage: 'app/sqlite/database.sqlite',
logging: false,
// model的全局配置
define : {
// 添加create,update,delete时间戳
timestamps: true,
// 添加软删除
paranoid: false,
// 防止修改表名为复数
freezeTableName: true,
// 防止驼峰式字段被默认转为下划线
underscored: false,
},
dialectOptions: {
typeCast(field, next) {// 让读取date类型数据时返回字符串而不是UTC时间
if (field.type === 'DATETIME') {
// console.log(field);
return field.string();
}
return next();
},
},
},
}
-13
View File
@@ -1,13 +0,0 @@
module.exports = {
dir: {
"event" : "app/*/event/*.js",
"status" : "app/*/status/*.js",
"controller": "app/*/controller/*.js",
"model" : "app/*/model/*.js",
"middleware": "middleware/*.js",
"extend" : "extend/*.js",
"schedule" : "schedule/*.js",
"sqlite" : "sqlite/model/*.js",
"config" : "config/*.js",
}
}
-1
View File
@@ -1 +0,0 @@
/*文件磁盘配置*/
+5
View File
@@ -0,0 +1,5 @@
//全局配置,权重大于中间件配置与插件配置
// 如果设置了全局配置,会覆盖中间件配置与插件配置
module.exports = {
}
-35
View File
@@ -1,35 +0,0 @@
/*日志配置*/
'use strict';
module.exports = {
replaceConsole: true,
pm2 : true,
appenders : {
stdout: {//控制台输出
type: 'console'
},
req : { //请求转发日志
type : 'dateFile', //指定日志文件按时间打印
filename : 'logs/req/req', //指定输出文件路径
pattern : 'yyyy-MM-dd.log',
alwaysIncludePattern: true
},
err : { //错误日志
type : 'dateFile',
filename : 'logs/err/err',
pattern : 'yyyy-MM-dd.log',
alwaysIncludePattern: true
},
oth : { //其他日志
type : 'dateFile',
filename : 'logs/oth/oth',
pattern : 'yyyy-MM-dd.log',
alwaysIncludePattern: true
}
},
categories : {
//appenders:采用的appender,取appenders项,level:设置级别
default: {appenders: ['stdout', 'req'], level: 'debug'},
err : {appenders: ['stdout', 'err'], level: 'error'},
}
}
-29
View File
@@ -1,29 +0,0 @@
'use strict';
export default {
routerPlus: {
enable: true,
package: 'egg-router-plus',
},
jwt: {
enable: true,
package: "egg-jwt"
},
cors: {
enable: true,
package: 'egg-cors',
},
validate: {
enable: true,
package: 'egg-validate',
},
sequelize:{
enable: true,
package: 'egg-sequelize',
},
axiosPlus: {
enable: true,
package: 'egg-axios-plus',
},
};
-1
View File
@@ -1 +0,0 @@
/*URL和路由配置*/
View File
View File
View File
+47
View File
@@ -0,0 +1,47 @@
//更多配置:http://axios-js.com/zh-cn/docs/index.html#%E8%AF%B7%E6%B1%82%E9%85%8D%E7%BD%AE
module.exports = {
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `headers` 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 30 * 1000,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `maxContentLength` 定义允许的响应内容的最大尺寸
// maxContentLength: 2000,
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0,将不会 follow 任何重定向
// maxRedirects: 5, // default
// 'proxy' 定义代理服务器的主机名称和端口
// `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
// 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
// proxy: {
// host: '127.0.0.1',
// port: 9000,
// auth: {
// username: 'mikeymike',
// password: 'rapunz3l'
// }
// },
}
+16
View File
@@ -0,0 +1,16 @@
/**
* Axios 是一个基于 promise 的 HTTP 库
*/
const config = require("./config")
const axios = require('axios');
const request = require('./request');
const requestError = require('./request.error');
const response = require('./response');
const responseError = require('./response.error');
module.exports = async (app) => {
Object.assign(axios.defaults, config)
axios.interceptors.request.use(request,requestError)
axios.interceptors.response.use(response,responseError)
app.axios = axios
app.alias["$axios"] = app.axios
}
+6
View File
@@ -0,0 +1,6 @@
//请求拦截器错误时
module.exports = (error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
+5
View File
@@ -0,0 +1,5 @@
//请求拦截器
module.exports = (config) => {
// 在发送请求之前做些什么
return config;
}
+6
View File
@@ -0,0 +1,6 @@
//响应拦截器错误时
module.exports = (error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
+5
View File
@@ -0,0 +1,5 @@
//响应拦截器
module.exports = (response) => {
// 对响应数据做点什么
return response;
}
+2 -2
View File
@@ -1,14 +1,14 @@
module.exports = {
success: (body, msg, status) => {
return {
res : body || null,
data : body || null,
status: status || 200,
message : msg || "success"
}
},
error : (body, msg, status) => {
return {
res : body || null,
data : body || null,
status: status || 400,
message : msg || "error"
}
+1
View File
@@ -6,4 +6,5 @@ const body = require("./body")
module.exports = async (app) => {
app.res = body
app.alias["$res"] = app.res
}
+1 -1
View File
@@ -10,7 +10,7 @@ const config = require("./config")
const mq = require("./queue")
module.exports = async (app) => {
app.mq = mq
app.alias["$mq"] = app.mq
const queueName = "boobam_schedule"
const queue = mq.Queue(queueName);
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
path : "components/*/*.js",
app_path: "app/*/components/*/*.js",
}
+21
View File
@@ -0,0 +1,21 @@
/**
* 通用组件
* 加载components文件夹下的函数
*/
const config = require("./config")
module.exports = async (app) => {
const list = await app.load(config.path)
Object.assign(list, await app.load(config.app_path))
app.components = {}
list.forEach(item => {
if (!app.components[item.parse.file_father]) {
app.components[item.parse.file_father] = {}
}
if (item.parse.name==='index') {
Object.assign(app.components[item.parse.file_father],item.res)
}else {
app.components[item.parse.file_father][item.parse.name] = item.res
}
})
app.alias["$components"] = app.components
}
+1
View File
@@ -6,6 +6,7 @@ const config = require("./config")
const EventEmitter = require('events');
module.exports = async (app) => {
app.event = new EventEmitter()
app.alias["$event"] = app.event
const list = await app.load(config.path)
list.forEach(item => {
Object.keys(item.res).forEach(key => {
+2 -1
View File
@@ -1,3 +1,4 @@
module.exports = {
path:"middleware/*/index.js"
path:"middleware/*/index.js",
app_path: "app/*/middleware/*/index.js",
}
+6
View File
@@ -2,17 +2,23 @@
* 加载中间件
* 加载app/middleware文件夹下的中间件
*/
const config = require("./config")
module.exports = async (app) => {
let list = await app.load(config.path)
let list2 = await app.load(config.app_path)
list=[...list,...list2]
// Object.assign(list, list2)
list = app.xe.orderBy(list, "res.sort")
list = list.filter(item => item.res.use)
list.forEach(async item => {
console.log('加载的中间件:',item.parse.dir);
if (item.res.loadFun) {
//如果中间件定义了特殊加载方法
await item.res.loadFun(app, item.res.fun)
} else {
app.use(async (ctx, next) => {
return await item.res.fun(ctx, next, app)
})
}
+9 -9
View File
@@ -9,15 +9,15 @@ module.exports = class Api {
}
init() {
this.tableName = ""
this.tableWhere = ""
this.tableData = ""
this.tablePage = ""
this.tableLimit = ""
this.tableOrder = ""
this.tableGroup = ""
this.tableAttributes = ""
this.t = ""
this.tableName = null
this.tableWhere = null
this.tableData = null
this.tablePage = null
this.tableLimit = null
this.tableOrder = null
this.tableGroup = null
this.tableAttributes = null
this.t = null
}
/**
+16 -4
View File
@@ -1,11 +1,11 @@
module.exports = {
database: "bamboo",
username: "bamboo",
password: "bamboo",
username: "root",
password: "123456",
options: {
dialect: 'mysql',
host: "192.168.1.26",
port: 3306,
host: "127.0.0.1",
port: 3357,
// 禁用日志记录或提供自定义日志记录功能;默认值:console.log
// logging: false,
// model的全局配置
@@ -44,4 +44,16 @@ module.exports = {
},
},
path:"app/*/model/*.js",
sqlite:{
host: 'localhost',
dialect: 'sqlite',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
storage: './database.sqlite',
operatorsAliases: false
}
}
+7
View File
@@ -2,6 +2,7 @@
* mysql/sequelize
*
* 参考文档:
* https://demopark.github.io/sequelize-docs-Zh-CN/
* https://sequelize.org/
* https://sequelize.org/api/v6/identifiers
* https://www.sequelize.com.cn/
@@ -10,6 +11,7 @@ const config = require("./config")
const Sequelize = require("sequelize");
const operatorsAliases = require("./operatorsAliases");
const Api = require("./api");
const Tool = require("./tool.js");
module.exports = async (app) => {
const {database, username, password, options,} = config
const sequelize = new Sequelize(database, username, password, {
@@ -41,6 +43,10 @@ module.exports = async (app) => {
app.models = sequelize.models
const api = new Api(sequelize, sequelize.models)
app.db = api
app.db.tool = new Tool(app, sequelize)
//全局变量
app.alias["$db"] = app.db
// console.log(api);
// console.log(await app.db.table("User").find());
@@ -60,6 +66,7 @@ module.exports = async (app) => {
}
})
// await app.db.tool.reduction()
})
+50
View File
@@ -0,0 +1,50 @@
const config = require("./config")
import mysqldump from 'mysqldump';
const Importer = require('mysql-import');
module.exports = class {
constructor(app, sequelize) {
this.app = app
this.sequelize = sequelize
this.file = this.app.root + '/' + config.database + '.sql'
}
//备份数据库所有表
async backup(file) {
this.file = file || this.app.root + '/' + config.database + '.sql'
console.log('备份数据库中...');
await mysqldump({
connection: {
host : config.options.host,
port : config.options.port,
user : config.username,
password: config.password,
database: config.database,
},
dumpToFile: this.file,
});
console.log('备份数据库完成:', this.file);
}
//还原备份sql文件
async reduction() {
console.time("reduction")
//先备份旧数据
await this.backup(this.app.root + '/' + 'extend/mysql/backup/' + Date.now() + '.sql')
//清空数据库
await this.sequelize.drop()
//导入sql文件
const importer = new Importer({
host : config.options.host,
port : config.options.port,
user : config.username,
password: config.password,
database: config.database,
});
this.file = this.app.root + '/' + config.database + '.sql'
await importer.import(this.file)
console.timeEnd("reduction")
}
}
@@ -1,4 +1,2 @@
'use strict';
module.exports = {
}
+10
View File
@@ -0,0 +1,10 @@
/**
* 参数验证插件
*/
const config = require("./config")
const Parameter = require('parameter');
const parameter = new Parameter();
module.exports = async (app) => {
app.parameter = parameter
app.alias["$parameter"] = app.parameter
}
+3 -1
View File
@@ -11,5 +11,7 @@ module.exports = async (app) => {
const redis = new Redis(config);
app.redis = redis
app.Redis = Redis
//全局变量
app.alias["$redis"] = app.redis
app.alias["$Redis"] = app.Redis
}
+2
View File
@@ -1,3 +1,5 @@
module.exports = {
path: "app/*/io/*.js",
port: 3210,
test_port: 3211,
}
+37 -8
View File
@@ -7,11 +7,16 @@
*/
const config = require("./config")
const path = require('path')
const uuid = require("uuid");
module.exports = async (app) => {
const parameter = app.parameter
const server = require('http').createServer(app.callback());
const io = require('socket.io')(server);
const io = require('socket.io')(server, {cors: true});
// let leaveRoom = () => {
// }
let list = await app.load(config.path)
app.io = io
app.alias["$io"] = app.io
//等待所有插件载入完成后
app.willReadyList.push(async () => {
//默认命名空间
@@ -24,17 +29,35 @@ module.exports = async (app) => {
}
});
console.log('连接=>', "id:" + socket.id);
//监听disconnect事件
socket.on('disconnect', (eventName, callback) => {
console.log('断开=X', "id:" + socket.id)
})
// socket.on('disconnect', (eventName, callback) => {
// console.log('断开=X', "id:" + socket.id)
// leaveRoom(socket, {msg: '', onname: 'leaveRoom', callback})
// })
for (let el of list) {
//空间名称
// const namespace = path.basename(path.resolve(el.parse.dir, '..'))
//事件名称
const onname = el.parse.name
socket.on(onname, (msg, callback) => {
el.res(app, msg, callback)
// if (onname === 'leaveRoom') {
// //离开房间处理
// leaveRoom = (socket, {msg, onname, callback}) => el.res.fun(socket, {msg, onname, callback})
// }
socket.on(onname, (msg, callback, anotherSocketId) => {
console.log('anotherSocketId', socket.id);
const validate = parameter.validate(el.res.params, msg);
if (validate) {
socket.emit('error', {
event: onname,
res : app.res.error(validate, "参数验证不通过", 204)
})
console.error('socket', socket.id, msg, onname, app.res.error(validate, "参数验证不通过", 204));
// ctx.body = app.res.error(validate, "参数验证不通过", 204)
} else {
el.res.fun(socket, {msg, onname, callback})
}
})
}
});
@@ -45,9 +68,15 @@ module.exports = async (app) => {
//覆盖启动方法
app.startServer = () => {
// 监听端口
app.startServer = server.listen(3000, () => {
console.log('listening on *:3000');
const port = (process.env.NODE_ENV === 'test' && config.test_port) || config.port || 3000
const baseURL = "http://127.0.0.1"
app.config.port = port
app.config.baseURL = baseURL
app.server = server.listen(port, () => {
console.log(`http 服务: ${baseURL}:${port}`);
console.log(`socket 服务: ${baseURL}:${port}`);
});
}
}
+195
View File
@@ -0,0 +1,195 @@
/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
module.exports = {
// 测试中所有导入的模块都应该自动模拟
// automock: false,
// `n` 次失败后停止运行测试
// bail: 0,
// TJest 应该存储其缓存依赖信息的目录
// 缓存目录:“privatevarfolderscdm9j91n110831mbzcxnqtlz700000gnTjest_dx”,
// 每次测试前自动清除模拟调用、实例、上下文和结果
clearMocks: true,
// 指示是否应在执行测试时收集覆盖率信息
collectCoverage: false,
// 一组 glob 模式,指示应为其收集覆盖信息的一组文件
// collectCoverageFrom: undefined,
// Jest 应该输出其覆盖文件的目录
coverageDirectory: "coverage",
// 用于跳过覆盖收集的正则表达式模式字符串数组
coveragePathIgnorePatterns: [
"/node_modules/"
],
// 指示应使用哪个提供程序来检测代码以进行覆盖
coverageProvider: "v8",
// Jest 在编写报道报告时使用的记者姓名列表
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// 为覆盖结果配置最小阈值强制执行的对象
// coverageThreshold: undefined,
// 自定义依赖项提取器的路径
// dependencyExtractor: undefined,
// 调用已弃用的 API 会引发有用的错误消息
// errorOnDeprecated: false,
// 假定时器的默认配置
// fakeTimers: {
// "enableGlobally": false
// },
// 使用 glob 模式数组强制从被忽略的文件中收集覆盖率
// forceCoverageMatch: [],
// 导出异步函数的模块的路径,该函数在所有测试套件之前触发一次
// globalSetup: undefined,
// 导出所有测试套件后触发一次的异步函数的模块的路径
// globalTeardown: undefined,
// 一组需要在所有测试环境中可用的全局变量
// globals: {},
// 用于运行测试的最大工作人员数量。可以指定为 % 或数字。例如。 maxWorkers: 10% 将使用 CPU 数量的 10% + 1 作为最大工作线程数。 maxWorkers: 2 将使用最多 2 个工人。
// maxWorkers: "50%",
// 从需要模块的位置递归搜索的目录名称数组
// moduleDirectories: [
// "node_modules"
// ],
// 您的模块使用的一系列文件扩展名
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// 从正则表达式到模块名称或模块名称数组的映射,允许使用单个模块存根资源
// moduleNameMapper: {},
// 一个正则表达式模式字符串数组,在被认为对模块加载器“可见”之前与所有模块路径匹配
// modulePathIgnorePatterns: [],
// 激活测试结果通知
// notify: false,
// 一个指定通知模式的枚举。需要 { 通知:真 }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
//Jest 用于检测测试文件的 glob 模式
testMatch: ["**/+(*.)+(spec|test).+(ts|js)?(x)"],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// 从正则表达式到转换器路径的映射
// transform: undefined,
// 与所有源文件路径匹配的正则表达式模式字符串数组,匹配的文件将跳过转换
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
"testTimeout": 1000000
};
+38 -8
View File
@@ -11,12 +11,25 @@ module.exports = class Bamboo extends Koa {
/*初始化业务目录*/
constructor(extend_directory, root) {
super();
//全局配置
this.config = {}
//xe-utils工具
this.xe = xe
//根目录
this.root = this.isPath() || root;
//插件路径
this.extend_directory = extend_directory || `extend/*/index.js`
//等待扩展已经加载完成后立即执行的任务列表
this.willReadyList = []
console.log(`当前根目录${this.root}`);
console.log(`当前插件目录${this.extend_directory}`);
//全局别名,推荐命名规则,$+全局变量名,如$db
this.alias = {
"app": this,
"$": this,
}
//等待启动完成后立即执行的任务列表
this.fullList = []
// console.log(`当前根目录${this.root}`);
// console.log(`当前插件目录${this.extend_directory}`);
this.loadEvent()
}
@@ -26,17 +39,33 @@ module.exports = class Bamboo extends Koa {
for (let listElement of list) {
await listElement.res(this)
}
this.willReadyList.push(() => {
this.startServer()
})
await this.loadAlias()
await this.willReady()
}
/*加载别名到全局变量,注意只有完成加载扩展后才能使用*/
async loadAlias() {
for (let key of Object.keys(this.alias)) {
global[key] = this.alias[key]
}
}
/*扩展已经加载完成触发*/
async willReady() {
this.willReadyList.push(async () => {
await this.startServer()
await this.full()
})
for (let WRL of this.willReadyList) {
await WRL()
await WRL(this)
}
}
/*启动完成*/
async full() {
for (let ful of this.fullList) {
await ful(this)
}
}
@@ -60,6 +89,7 @@ module.exports = class Bamboo extends Koa {
files = files.map(item => {
const parse = path.parse(item);
parse.father = path.basename(path.resolve(parse.dir, '..'))
parse.file_father = path.basename(path.resolve(parse.dir + '/' + parse.name, '..'))
return {
parse,
res: require(path.resolve(path_root + "/" + item))
@@ -73,7 +103,7 @@ module.exports = class Bamboo extends Koa {
/*启动服务*/
startServer(prod) {
this.listen(prod || 3000)
console.log('启动服务:3000');
app.server = this.listen(prod || 3000)
console.log(`http服务:http://127.0.0.1:${prod || 3000}`);
}
}
+2 -1
View File
@@ -12,7 +12,8 @@ require('@babel/register')({
]
})
require('@babel/polyfill')
// require('@babel/polyfill')
const bamboo = require('./bamboo/index')
const app = new bamboo();
// app.listen(3000);
module.exports = app
+2 -2
View File
@@ -1,8 +1,8 @@
'use strict';
const bodyParser = require('koa-bodyparser')
// 错误处理
// 解析json的入参
module.exports = {
sort: 4, //排序
sort: 2, //排序
use : true, // 是否使用
fun : bodyParser()
}
+8
View File
@@ -0,0 +1,8 @@
'use strict';
const cors = require('@koa/cors')
// 跨域
module.exports = {
sort: 0, //排序
use : true, // 是否使用
fun : cors()
}
+1 -1
View File
@@ -3,7 +3,7 @@ const error = require('koa-json-error')
// 错误处理
module.exports = {
sort: 3, //排序
use : true, // 是否使用
use : false, // 是否使用
fun : error((err) => {
return {
status : err.status,
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
path : "app/*/model/*.js",
prefix : "auto",//接口前缀
}
+27
View File
@@ -0,0 +1,27 @@
'use strict';
/**
* 加载模型到路由
* 加载app/api目录下的文件到路由
*/
const Router = require('koa-router')
const config = require("./config")
// 错误处理
module.exports = {
sort : 999, //排序
use : false, // 是否使用
loadFun: async (app, fun) => { // 自行定义中间件的加载方式,将覆盖默认加载方法
const router = await fun(app)
app.use(router.routes()).use(router.allowedMethods())
},
fun : async (app) => {
const router = new Router({ //设置前缀
prefix: config.prefix
});
let list = await app.load(config.path)
list.forEach(item => {
})
return router
}
}
+2 -2
View File
@@ -1,10 +1,10 @@
'use strict';
/*封装入参*/
module.exports = {
sort: 3, //排序
sort: 5, //排序
use : true, // 是否使用
fun : async (ctx, next, app) => {
ctx.params = ctx.request.body || ctx.query
await next()
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
path : "app/*/api/*.js",
prefix : "",//接口前缀
statusTobody: true,//是否跟随body结果(如需接口报错也返回200,那么设置为false)
statusTobody: false,//是否跟随body结果(如需接口报错也返回200,那么设置为false)
}
+28 -8
View File
@@ -5,7 +5,7 @@
*/
const Router = require('koa-router')
const config = require("./config")
// const parameter = require('./parameter');
// 错误处理
module.exports = {
sort : 999, //排序
@@ -15,36 +15,56 @@ module.exports = {
app.use(router.routes()).use(router.allowedMethods())
},
fun : async (app) => {
const parameter = app.parameter
const router = new Router({ //设置前缀
prefix: config.prefix
});
let list = await app.load(config.path)
list.forEach(item => {
const url = item.res.path ? item.res.path : '/' + item.parse.father + '/api/' + item.parse.name
console.log(url);
router.all(url, async (ctx, next) => {
await next();
const validate = parameter.validate(item.res.params, ctx.params);
let res = null
if (validate) {
ctx.body = app.res.error(validate, "参数验证不通过", 204)
} else {
/*处理body结果*/
try {
const res = await item.res.fun(ctx, app)
res = await item.res.fun(ctx, app)
} catch (err) {
console.log(err);
ctx.body = app.res.error(null, "系统错误", 500)
}
}
if (!ctx.body) {
if (res) {
if (res.status&&res.message) {
ctx.body = res
}else{
ctx.body = app.res.success(res)
}
} else if (res === false) {
ctx.body = app.res.error()
} else {
ctx.body = app.res.success()
}
}
} catch (err) {
console.log(err);
ctx.body = app.res.error(null, "系统错误", 500)
ctx.status = 500
}
//是否跟随body结果
ctx.status = config.statusTobody ? ctx.body.status || 400 : 200
await next();
})
})
router.all('/', async (ctx, next) => {
ctx.body = app.res.success("ok")
ctx.status = 200
await next();
})
return router
}
}
+11
View File
@@ -0,0 +1,11 @@
const Parameter = require('parameter');
const parameter = new Parameter();
//添加规则
// parameter.addRule('123', (rule, value) => {
// if (value !== '123') {
// return 'must be 123';
// }
// });
module.exports = parameter
+1 -1
View File
@@ -5,5 +5,5 @@
".idea",
"node_modules/**/node_modules"
],
"exec": "node lib/main.js"
"exec": "npm run start"
}
+24 -11
View File
@@ -6,24 +6,30 @@
"scripts": {
"dev": "nodemon",
"start": "node lib/main.js",
"server": "cross-env NODE_ENV=development nodemon bin/main.js"
"server": "cross-env NODE_ENV=development nodemon bin/main.js",
"test": "jest --forceExit"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/cli": "^7.18.6",
"@babel/core": "^7.18.6",
"@babel/node": "^7.18.6",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-decorators": "^7.17.9",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/register": "^7.5.5",
"@babel/preset-env": "^7.18.6",
"@babel/register": "^7.18.6",
"@babel/runtime": "^7.18.6",
"art-template": "^4.13.2",
"babel-plugin-webpack-alias": "^2.1.2",
"babel-polyfill": "^6.26.0",
"babel-register": "^6.26.0",
"cross-env": "^5.2.0",
"chai": "^4.3.6",
"cross-env": "^7.0.3",
"ejs": "^2.6.2",
"jest": "^28.1.1",
"koa": "^2.7.0",
"koa-art-template": "^1.1.1",
"koa-bodyparser": "^4.2.1",
@@ -33,24 +39,28 @@
"koa-views": "^6.2.0",
"koa-webpack": "^6.0.0",
"koa-webpack-middleware": "^1.0.7",
"mocha": "^10.0.0",
"mysql-import": "^5.0.21",
"nodeenv": "^1.0.0",
"nodemon": "^1.19.1"
"nodemon": "^1.19.4",
"sequelize-mock": "^0.10.2",
"supertest": "^6.2.3"
},
"dependencies": {
"@babel/core": "^7.16.7",
"@babel/node": "^7.16.8",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.16.8",
"@babel/runtime": "^7.5.5",
"@koa/cors": "^3.3.0",
"ajv": "^8.11.0",
"axios": "^0.27.2",
"babel-loader": "^8.2.3",
"bamboo_smf": "^1.0.5",
"bullmq": "^1.85.1",
"clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0.3",
"crypto": "^1.0.1",
"glob": "^8.0.1",
"ioredis": "^5.0.5",
"jest": "^28.1.1",
"jsonwebtoken": "^8.5.1",
"koa-bodyparser": "^4.3.0",
"koa-json-error": "^3.1.2",
"koa-logger": "^2.0.1",
@@ -58,6 +68,7 @@
"koa-router": "^7.4.0",
"log4js": "^6.4.4",
"mysql2": "^2.3.3",
"mysqldump": "^3.2.0",
"node-schedule": "^2.1.0",
"object-path": "^0.11.8",
"parameter": "^3.6.0",
@@ -68,6 +79,8 @@
"shelljs": "^0.8.5",
"socket.io": "^4.5.1",
"sqlite3": "^5.0.2",
"supertest": "^6.2.3",
"uuid": "^8.3.2",
"webpack": "^4.41.5",
"webpack-cli": "3.3.10",
"webpack-node-externals": "^3.0.0",