build: trident-sync prepare

This commit is contained in:
xiaojunnuo
2023-01-29 13:44:19 +08:00
parent dcd1023a39
commit 07a45b4530
589 changed files with 36886 additions and 2 deletions
@@ -0,0 +1,33 @@
import { Inject } from '@midwayjs/decorator';
import { Context } from '@midwayjs/koa';
import { Constants } from './constants';
export abstract class BaseController {
@Inject()
ctx: Context;
/**
* 成功返回
* @param data 返回数据
*/
ok(data) {
const res = {
...Constants.res.success,
data: undefined,
};
if (data) {
res.data = data;
}
return res;
}
/**
* 失败返回
* @param message
*/
fail(msg, code) {
return {
code: code ? code : Constants.res.error.code,
msg: msg ? msg : Constants.res.error.code,
};
}
}
@@ -0,0 +1,207 @@
import { Inject } from '@midwayjs/decorator';
import { ValidateException } from './exception/validation-exception';
import * as _ from 'lodash';
import { Context } from '@midwayjs/koa';
import { PermissionException } from './exception/permission-exception';
import { Repository } from 'typeorm';
/**
* 服务基类
*/
export abstract class BaseService<T> {
@Inject()
ctx: Context;
abstract getRepository(): Repository<T>;
/**
* 获得单个ID
* @param id ID
* @param infoIgnoreProperty 忽略返回属性
*/
async info(id, infoIgnoreProperty?): Promise<T | null> {
if (!id) {
throw new ValidateException('id不能为空');
}
// @ts-ignore
const info = await this.getRepository().findOne({ where: { id } });
if (info && infoIgnoreProperty) {
for (const property of infoIgnoreProperty) {
delete info[property];
}
}
return info;
}
/**
* 非分页查询
* @param option 查询配置
*/
async find(options) {
return await this.getRepository().find(options);
}
/**
* 删除
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
*/
async delete(ids) {
if (ids instanceof Array) {
await this.getRepository().delete(ids);
} else if (typeof ids === 'string') {
await this.getRepository().delete(ids.split(','));
} else {
//ids是一个condition
await this.getRepository().delete(ids);
}
await this.modifyAfter(ids);
}
/**
* 新增|修改
* @param param 数据
*/
async addOrUpdate(param) {
await this.getRepository().save(param);
}
/**
* 新增
* @param param 数据
*/
async add(param) {
const now = new Date().getTime();
param.createTime = now;
param.updateTime = now;
await this.addOrUpdate(param);
await this.modifyAfter(param);
return {
id: param.id,
};
}
/**
* 修改
* @param param 数据
*/
async update(param) {
if (!param.id) throw new ValidateException('no id');
param.updateTime = new Date().getTime();
await this.addOrUpdate(param);
await this.modifyAfter(param);
}
/**
* 新增|修改|删除 之后的操作
* @param data 对应数据
*/
async modifyAfter(data) {}
/**
* 分页查询
* @param query 查询条件 bean
* @param page
* @param order
* @param buildQuery
*/
async page(query, page = { offset: 0, limit: 20 }, order, buildQuery) {
if (page.offset == null) {
page.offset = 0;
}
if (page.limit == null) {
page.limit = 20;
}
const qb = this.getRepository().createQueryBuilder('main');
if (order && order.prop) {
qb.orderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC');
} else {
qb.orderBy('id', 'DESC');
}
qb.offset(page.offset).limit(page.limit);
//根据bean query
if (query) {
let whereSql = '';
let index = 0;
_.forEach(query, (value, key) => {
if (!value) {
return;
}
if (index !== 0) {
whereSql += ' and ';
}
whereSql += ` main.${key} = :${key} `;
index++;
});
if (index > 0) {
qb.where(whereSql, query);
}
}
//自定义query
if (buildQuery) {
buildQuery(qb);
}
const list = await qb.getMany();
const total = await qb.getCount();
return {
records: list,
total,
offset: page.offset,
limit: page.limit,
};
}
/**
* 分页查询
* @param query 查询条件 bean
* @param order
* @param buildQuery
*/
async list(query, order, buildQuery) {
const qb = this.getRepository().createQueryBuilder('main');
if (order && order.prop) {
qb.orderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC');
} else {
qb.orderBy('id', 'DESC');
}
//根据bean query
if (query) {
let whereSql = '';
let index = 0;
_.forEach(query, (value, key) => {
if (!value) {
return;
}
if (index !== 0) {
whereSql += ' and ';
}
whereSql += ` main.${key} = :${key} `;
index++;
});
if (index > 0) {
qb.where(whereSql, query);
}
}
//自定义query
if (buildQuery) {
buildQuery(qb);
}
return await qb.getMany();
}
async checkUserId(id = 0, userId, userKey = 'userId') {
// @ts-ignore
const res = await this.getRepository().findOne({
// @ts-ignore
select: { [userKey]: true },
where: {
// @ts-ignore
id,
},
});
// @ts-ignore
if (!res || res.userId === userId) {
return;
}
throw new PermissionException('权限不足');
}
}
@@ -0,0 +1,28 @@
export const Constants = {
res: {
error: {
code: 1,
message: 'error',
},
success: {
code: 0,
message: 'success',
},
validation: {
code: 10,
message: '参数错误',
},
auth: {
code: 401,
message: '您还未登录或token已过期',
},
permission: {
code: 402,
message: '您没有权限',
},
preview: {
code: 10001,
message: '对不起,预览环境不允许修改此数据',
},
},
};
@@ -0,0 +1,64 @@
import { ALL, Body, Post, Query } from '@midwayjs/decorator';
import { BaseController } from './base-controller';
export abstract class CrudController extends BaseController {
abstract getService();
@Post('/page')
async page(
@Body(ALL)
body
) {
const pageRet = await this.getService().page(
body?.query,
body?.page,
body?.sort,
null
);
return this.ok(pageRet);
}
@Post('/list')
async list(
@Body(ALL)
body
) {
const listRet = await this.getService().list(body, null, null);
return this.ok(listRet);
}
@Post('/add')
async add(
@Body(ALL)
bean
) {
const id = await this.getService().add(bean);
return this.ok(id);
}
@Post('/info')
async info(
@Query('id')
id
) {
const bean = await this.getService().info(id);
return this.ok(bean);
}
@Post('/update')
async update(
@Body(ALL)
bean
) {
await this.getService().update(bean);
return this.ok(null);
}
@Post('/delete')
async delete(
@Query('id')
id
) {
await this.getService().delete([id]);
return this.ok(null);
}
}
@@ -0,0 +1,11 @@
export class EnumItem {
value: string;
label: string;
color: string;
constructor(value, label, color) {
this.value = value;
this.label = label;
this.color = color;
}
}
@@ -0,0 +1,14 @@
import { Constants } from '../constants';
import { BaseException } from './base-exception';
/**
* 授权异常
*/
export class AuthException extends BaseException {
constructor(message) {
super(
'AuthException',
Constants.res.auth.code,
message ? message : Constants.res.auth.message
);
}
}
@@ -0,0 +1,11 @@
/**
* 异常基类
*/
export class BaseException extends Error {
status: number;
constructor(name, code, message) {
super(message);
this.name = name;
this.status = code;
}
}
@@ -0,0 +1,14 @@
import { Constants } from '../constants';
import { BaseException } from './base-exception';
/**
* 通用异常
*/
export class CommonException extends BaseException {
constructor(message) {
super(
'CommonException',
Constants.res.error.code,
message ? message : Constants.res.error.message
);
}
}
@@ -0,0 +1,14 @@
import { Constants } from '../constants';
import { BaseException } from './base-exception';
/**
* 授权异常
*/
export class PermissionException extends BaseException {
constructor(message) {
super(
'PermissionException',
Constants.res.permission.code,
message ? message : Constants.res.permission.message
);
}
}
@@ -0,0 +1,14 @@
import { Constants } from '../constants';
import { BaseException } from './base-exception';
/**
* 预览模式
*/
export class PreviewException extends BaseException {
constructor(message) {
super(
'PreviewException',
Constants.res.preview.code,
message ? message : Constants.res.preview.message
);
}
}
@@ -0,0 +1,14 @@
import { Constants } from '../constants';
import { BaseException } from './base-exception';
/**
* 校验异常
*/
export class ValidateException extends BaseException {
constructor(message) {
super(
'ValidateException',
Constants.res.validation.code,
message ? message : Constants.res.validation.message
);
}
}
@@ -0,0 +1,18 @@
export class Result<T> {
code: number;
msg: string;
data: T;
constructor(code, msg, data?) {
this.code = code;
this.msg = msg;
this.data = data;
}
static error(code = 1, msg) {
return new Result(code, msg, null);
}
static success(msg, data?) {
return new Result(0, msg, data);
}
}
@@ -0,0 +1,60 @@
import { join } from 'path';
import { FlywayHistory } from 'midway-flyway-js/dist/entity';
import { MidwayConfig } from '@midwayjs/core';
import { UserEntity } from '../modules/authority/entity/user';
export default {
// use for cookie sign key, should change to your own and keep security
keys: 'certd666',
koa: {
port: 7001,
},
cron: {},
/**
* 演示环境
*/
preview: {
enabled: false,
},
/**
* 数据库
*/
typeorm: {
dataSource: {
default: {
/**
* 单数据库实例
*/
type: 'sqlite',
database: join(__dirname, '../../data/db.sqlite'),
synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true
logging: true,
// 配置实体模型 或者 entities: '/entity',
entities: [
'**/modules/*/entity/*.ts',
FlywayHistory,
UserEntity,
],
},
},
},
/**
* 自动升级数据库脚本
*/
flyway: {
scriptDir: join(__dirname, '../../db/migration'),
},
biz: {
jwt: {
secret: 'greper-is-666',
expire: 7 * 24 * 60, //单位秒
},
auth: {
ignoreUrls: ['/', '/api/login', '/api/register'],
},
},
} as MidwayConfig;
@@ -0,0 +1,10 @@
import { MidwayConfig } from '@midwayjs/core';
export default {
/**
* 演示环境
*/
preview: {
enabled: true,
}
} as MidwayConfig;
@@ -0,0 +1,10 @@
import { MidwayConfig } from '@midwayjs/core';
export default {
/**
* 演示环境
*/
preview: {
enabled: true,
}
} as MidwayConfig;
@@ -0,0 +1,11 @@
import { MidwayConfig } from '@midwayjs/core';
export default {
typeorm: {
dataSource: {
default: {
synchronize: true, // 如果第一次使用,不存在表,有同步的需求可以写 true
},
},
},
} as MidwayConfig;
@@ -0,0 +1,60 @@
import * as validateComp from '@midwayjs/validate';
import * as productionConfig from './config/config.production';
import * as previewConfig from './config/config.preview';
import * as defaultConfig from './config/config.default';
import { Configuration, App } from '@midwayjs/decorator';
import * as koa from '@midwayjs/koa';
import * as orm from '@midwayjs/typeorm';
import * as cache from '@midwayjs/cache';
import cors from '@koa/cors';
import { join } from 'path';
import * as flyway from 'midway-flyway-js';
import { ReportMiddleware } from './middleware/report';
import { GlobalExceptionMiddleware } from './middleware/global-exception';
import { PreviewMiddleware } from './middleware/preview';
import { AuthorityMiddleware } from './middleware/authority';
import * as pipeline from './plugins/pipeline';
import * as cron from './plugins/cron';
@Configuration({
imports: [koa, orm, cache, flyway, validateComp,pipeline, cron],
importConfigs: [
{
default: defaultConfig,
preview: previewConfig,
production: productionConfig,
},
],
})
export class ContainerConfiguration {}
@Configuration({
conflictCheck: true,
importConfigs: [join(__dirname, './config')],
})
export class ContainerLifeCycle {
@App()
app: koa.Application;
async onReady() {
//跨域
this.app.use(
cors({
origin: '*',
})
);
// bodyparser options see https://github.com/koajs/bodyparser
//this.app.use(bodyParser());
//请求日志打印
this.app.useMiddleware([
ReportMiddleware,
//统一异常处理
GlobalExceptionMiddleware,
//预览模式限制修改id<1000的数据
PreviewMiddleware,
//授权处理
AuthorityMiddleware,
]);
//加载插件
}
}
@@ -0,0 +1,10 @@
import { Controller, Get, Provide } from '@midwayjs/decorator';
@Provide()
@Controller('/')
export class HomeController {
@Get('/')
async home(): Promise<string> {
return 'Hello Midwayjs!';
}
}
@@ -0,0 +1,48 @@
import { Config, Provide } from '@midwayjs/decorator';
import {
IWebMiddleware,
IMidwayKoaContext,
IMidwayKoaNext,
} from '@midwayjs/koa';
import * as _ from 'lodash';
import * as jwt from 'jsonwebtoken';
import { Constants } from '../basic/constants';
/**
* 权限校验
*/
@Provide()
export class AuthorityMiddleware implements IWebMiddleware {
@Config('biz.jwt.secret')
private secret: string;
@Config('biz.auth.ignoreUrls')
private ignoreUrls: string[];
resolve() {
return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
const { url } = ctx;
const token = ctx.get('Authorization');
// 路由地址为 admin前缀的 需要权限校验
// console.log('ctx', ctx);
const queryIndex = url.indexOf('?');
let uri = url;
if (queryIndex >= 0) {
uri = url.substring(0, queryIndex);
}
const yes = this.ignoreUrls.includes(uri);
if (yes) {
await next();
return;
}
try {
ctx.user = jwt.verify(token, this.secret);
} catch (err) {
ctx.status = 401;
ctx.body = Constants.res.auth;
return;
}
await next();
};
}
}
@@ -0,0 +1,27 @@
import { Provide } from '@midwayjs/decorator';
import {
IWebMiddleware,
IMidwayKoaContext,
IMidwayKoaNext,
} from '@midwayjs/koa';
import { logger } from '../utils/logger';
import { Result } from '../basic/result';
@Provide()
export class GlobalExceptionMiddleware implements IWebMiddleware {
resolve() {
return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
const { url } = ctx;
const startTime = Date.now();
logger.info('请求开始:', url);
try {
await next();
logger.info('请求完成', url, Date.now() - startTime + 'ms');
} catch (err) {
logger.error('请求异常:', url, Date.now() - startTime + 'ms', err);
ctx.status = 200;
ctx.body = Result.error(err.code != null ? err.code : 1, err.message);
}
};
}
}
@@ -0,0 +1,50 @@
import { Config, Provide } from '@midwayjs/decorator';
import {
IMidwayKoaContext,
IMidwayKoaNext,
IWebMiddleware,
} from '@midwayjs/koa';
import { PreviewException } from '../basic/exception/preview-exception';
/**
* 预览模式
*/
@Provide()
export class PreviewMiddleware implements IWebMiddleware {
@Config('preview.enabled')
private preview: boolean;
resolve() {
return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
if (!this.preview) {
await next();
return;
}
let { url, request } = ctx;
const body: any = request.body;
let id = body.id || request.query.id;
const roleId = body.roleId;
if (id == null && roleId != null) {
id = roleId;
}
if (id != null && typeof id === 'string') {
id = parseInt(id);
}
if (url.indexOf('?') !== -1) {
url = url.substring(0, url.indexOf('?'));
}
const isModify =
url.endsWith('update') ||
url.endsWith('delete') ||
url.endsWith('authz');
const isPreviewId = id < 1000;
if (this.preview && isModify && isPreviewId) {
throw new PreviewException(
'对不起,预览环境不允许修改此数据,如需体验请添加新数据'
);
}
await next();
return;
};
}
}
@@ -0,0 +1,28 @@
import { Provide } from '@midwayjs/decorator';
import {
IWebMiddleware,
IMidwayKoaContext,
IMidwayKoaNext,
} from '@midwayjs/koa';
import { logger } from '../utils/logger';
@Provide()
export class ReportMiddleware implements IWebMiddleware {
resolve() {
return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
const { url } = ctx;
logger.info('请求开始:', url);
const startTime = Date.now();
await next();
if (ctx.status !== 200) {
logger.error(
'请求失败:',
url,
ctx.status,
Date.now() - startTime + 'ms'
);
}
logger.info('请求完成:', url, ctx.status, Date.now() - startTime + 'ms');
};
}
}
@@ -0,0 +1,62 @@
import {
ALL,
Body,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { CrudController } from '../../../basic/crud-controller';
import { PermissionService } from '../service/permission-service';
/**
* 权限资源
*/
@Provide()
@Controller('/api/sys/authority/permission')
export class PermissionController extends CrudController {
@Inject()
service: PermissionService;
getService() {
return this.service;
}
@Post('/page')
async page(
@Body(ALL)
body
) {
return await super.page(body);
}
@Post('/add')
async add(
@Body(ALL)
bean
) {
return await super.add(bean);
}
@Post('/update')
async update(
@Body(ALL)
bean
) {
return await super.update(bean);
}
@Post('/delete')
async delete(
@Query('id')
id
) {
return await super.delete(id);
}
@Post('/tree')
async tree() {
const tree = await this.service.tree({});
return this.ok(tree);
}
}
@@ -0,0 +1,95 @@
import {
ALL,
Body,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { CrudController } from '../../../basic/crud-controller';
import { RoleService } from '../service/role-service';
/**
* 系统用户
*/
@Provide()
@Controller('/api/sys/authority/role')
export class RoleController extends CrudController {
@Inject()
service: RoleService;
getService() {
return this.service;
}
@Post('/page')
async page(
@Body(ALL)
body
) {
return await super.page(body);
}
@Post('/list')
async list() {
const ret = await this.service.find({});
return this.ok(ret);
}
@Post('/add')
async add(
@Body(ALL)
bean
) {
return await super.add(bean);
}
@Post('/update')
async update(
@Body(ALL)
bean
) {
return await super.update(bean);
}
@Post('/delete')
async delete(
@Query('id')
id
) {
return await super.delete(id);
}
@Post('/getPermissionTree')
async getPermissionTree(
@Query('id')
id
) {
const ret = await this.service.getPermissionTreeByRoleId(id);
return this.ok(ret);
}
@Post('/getPermissionIds')
async getPermissionIds(
@Query('id')
id
) {
const ret = await this.service.getPermissionIdsByRoleId(id);
return this.ok(ret);
}
/**
* 给角色授予权限
* @param id
*/
@Post('/authz')
async authz(
@Body('roleId')
roleId,
@Body('permissionIds')
permissionIds
) {
await this.service.authz(roleId, permissionIds);
return this.ok(null);
}
}
@@ -0,0 +1,118 @@
import {
Provide,
Controller,
Post,
Inject,
Body,
Query,
ALL,
} from '@midwayjs/decorator';
import { UserService } from '../service/user-service';
import { CrudController } from '../../../basic/crud-controller';
import { RoleService } from '../service/role-service';
import { PermissionService } from '../service/permission-service';
/**
* 系统用户
*/
@Provide()
@Controller('/api/sys/authority/user')
export class UserController extends CrudController {
@Inject()
service: UserService;
@Inject()
roleService: RoleService;
@Inject()
permissionService: PermissionService;
getService() {
return this.service;
}
@Post('/page')
async page(
@Body(ALL)
body
) {
const ret = await super.page(body);
const users = ret.data.records;
//获取roles
const userIds = users.map(item => item.id);
const userRoles = await this.roleService.getByUserIds(userIds);
const userRolesMap = new Map();
for (const ur of userRoles) {
let roles = userRolesMap.get(ur.userId);
if (roles == null) {
roles = [];
userRolesMap.set(ur.userId, roles);
}
roles.push(ur.roleId);
}
for (const record of users) {
//withRoles
record.roles = userRolesMap.get(record.id);
//删除密码字段
delete record.password;
}
return ret;
}
@Post('/add')
async add(
@Body(ALL)
bean
) {
return await super.add(bean);
}
@Post('/update')
async update(
@Body(ALL)
bean
) {
return await super.update(bean);
}
@Post('/delete')
async delete(
@Query('id')
id
) {
return await super.delete(id);
}
/**
* 当前登录用户的个人信息
*/
@Post('/mine')
public async mine() {
const id = this.ctx.user.id;
const info = await this.service.info(id, ['password']);
return this.ok(info);
}
/**
* 当前登录用户的权限列表
*/
@Post('/permissions')
public async permissions() {
const id = this.ctx.user.id;
const permissions = await this.service.getUserPermissions(id);
return this.ok(permissions);
}
/**
* 当前登录用户的权限树形列表
*/
@Post('/permissionTree')
public async permissionTree() {
const id = this.ctx.user.id;
const permissions = await this.service.getUserPermissions(id);
const tree = this.permissionService.buildTree(permissions);
return this.ok(tree);
}
}
@@ -0,0 +1,40 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 权限
*/
@Entity('sys_permission')
export class PermissionEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '标题', length: 100 })
title: string;
/**
* 权限代码
* 示例:sys:user:read
*/
@Column({ comment: '权限代码', length: 100, nullable: true })
permission: string;
@Column({ name: 'parent_id', comment: '父节点ID', default: -1 })
parentId: number;
@Column({ comment: '排序号' })
sort: number;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
// @ManyToMany(type => RoleEntity, res => res.permissions)
// roles: RoleEntity[];
}
@@ -0,0 +1,12 @@
import { Entity, PrimaryColumn } from 'typeorm';
/**
* 角色权限多对多
*/
@Entity('sys_role_permission')
export class RolePermissionEntity {
@PrimaryColumn({ name: 'role_id' })
roleId: number;
@PrimaryColumn({ name: 'permission_id' })
permissionId: number;
}
@@ -0,0 +1,43 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
/**
* 角色
*/
@Entity('sys_role')
export class RoleEntity {
@PrimaryGeneratedColumn()
id: number;
@Index({ unique: true })
@Column({ comment: '角色名称', length: 100 })
name: string;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
// @ManyToMany(type => PermissionEntity, res => res.roles)
// @JoinTable({
// name: 'sys_role_resources',
// joinColumn: {
// name: 'roleId',
// referencedColumnName: 'id',
// },
// inverseJoinColumn: {
// name: 'resourceId',
// referencedColumnName: 'id',
// },
// })
// resources: PermissionEntity[];
// @ManyToMany(type => UserEntity, res => res.roles)
// users: UserEntity[];
}
@@ -0,0 +1,12 @@
import { Entity, PrimaryColumn } from 'typeorm';
/**
* 用户角色多对多
*/
@Entity('sys_user_role')
export class UserRoleEntity {
@PrimaryColumn({ name: 'role_id' })
roleId: number;
@PrimaryColumn({ name: 'user_id' })
userId: number;
}
@@ -0,0 +1,63 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
/**
* 系统用户
*/
@Entity('sys_user')
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Index({ unique: true })
@Column({ comment: '用户名', length: 100 })
username: string;
@Column({ comment: '密码', length: 100 })
password: string;
@Column({ name: 'nick_name', comment: '昵称', length: 100, nullable: true })
nickName: string;
@Column({ comment: '头像', length: 255, nullable: true })
avatar: string;
@Column({ name: 'phone_code', comment: '区号', length: 20, nullable: true })
phoneCode: string;
@Column({ comment: '手机', length: 20, nullable: true })
mobile: string;
@Column({ comment: '邮箱', length: 50, nullable: true })
email: string;
@Column({ comment: '备注', length: 100, nullable: true })
remark: string;
@Column({ comment: '状态 0:禁用 1:启用', default: 1, type: 'int' })
status: number;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
// @ManyToMany(type => RoleEntity, res => res.users)
// @JoinTable({
// name: 'sys_user_roles',
// joinColumn: {
// name: 'userId',
// referencedColumnName: 'id',
// },
// inverseJoinColumn: {
// name: 'roleId',
// referencedColumnName: 'id',
// },
// })
// roles: RoleEntity[];
}
@@ -0,0 +1,17 @@
import { EnumItem } from '../../../basic/enum-item';
import * as _ from 'lodash';
class ResourceTypes {
MENU = new EnumItem('menu', '菜单', 'blue');
BTN = new EnumItem('btn', '按钮', 'green');
ROUTE = new EnumItem('route', '路由', 'red');
names() {
const list = [];
_.forEach(this, (item, key) => {
list.push(item);
});
return list;
}
}
export const ResourceTypeEnum = new ResourceTypes();
@@ -0,0 +1,52 @@
import { Provide } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { PermissionEntity } from '../entity/permission';
/**
* 权限资源
*/
@Provide()
export class PermissionService extends BaseService<PermissionEntity> {
@InjectEntityModel(PermissionEntity)
repository: Repository<PermissionEntity>;
getRepository() {
return this.repository;
}
async tree(options: any = {}) {
if (options.order == null) {
options.order = {
sort: 'ASC',
};
}
const list = await this.find(options);
return this.buildTree(list);
}
buildTree(list: any) {
const idMap = {};
const root = [];
for (const item of list) {
idMap[item.id] = item;
if (item.parentId == null || item.parentId <= 0) {
root.push(item);
}
}
for (const item of list) {
if (item.parentId > 0) {
const parent = idMap[item.parentId];
if (parent) {
if (parent.children == null) {
parent.children = [];
}
parent.children.push(item);
}
}
}
return root;
}
}
@@ -0,0 +1,18 @@
import { Provide } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { RolePermissionEntity } from '../entity/role-permission';
/**
* 角色->权限
*/
@Provide()
export class RolePermissionService extends BaseService<RolePermissionEntity> {
@InjectEntityModel(RolePermissionEntity)
repository: Repository<RolePermissionEntity>;
getRepository() {
return this.repository;
}
}
@@ -0,0 +1,101 @@
import { Inject, Provide } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { RoleEntity } from '../entity/role';
import { UserRoleService } from './user-role-service';
import { RolePermissionEntity } from '../entity/role-permission';
import { PermissionService } from './permission-service';
import * as _ from 'lodash';
import { RolePermissionService } from './role-permission-service';
/**
* 角色
*/
@Provide()
export class RoleService extends BaseService<RoleEntity> {
@InjectEntityModel(RoleEntity)
repository: Repository<RoleEntity>;
@Inject()
userRoleService: UserRoleService;
@Inject()
permissionService: PermissionService;
@Inject()
rolePermissionService: RolePermissionService;
getRepository() {
return this.repository;
}
async getRoleIdsByUserId(id: any) {
const userRoles = await this.userRoleService.find({
where: { userId: id },
});
return userRoles.map(item => item.roleId);
}
async getByUserIds(ids: any) {
return await this.userRoleService.find({
where: {
userId: In(ids),
},
});
}
async getPermissionByRoleIds(roleIds: any) {
return await this.permissionService.repository
.createQueryBuilder('permission')
.innerJoinAndSelect(
RolePermissionEntity,
'rp',
'rp.permissionId = permission.id and rp.roleId in (:...roleIds)',
{ roleIds }
)
.getMany();
}
async addRoles(userId: number, roles) {
if (roles == null || roles.length === 0) {
return;
}
for (const roleId of roles) {
await this.userRoleService.add({
userId,
roleId,
});
}
}
async updateRoles(userId, roles) {
if (roles == null) {
return;
}
const oldRoleIds = await this.getRoleIdsByUserId(userId);
if (_.xor(roles, oldRoleIds).length === 0) {
//如果两个数组相等,则不修改
return;
}
//先删除所有
await this.userRoleService.delete({ userId });
//再添加
await this.addRoles(userId, roles);
}
async getPermissionTreeByRoleId(id: any) {
const list = await this.getPermissionByRoleIds([id]);
return this.permissionService.buildTree(list);
}
async getPermissionIdsByRoleId(id: any) {
const list = await this.getPermissionByRoleIds([id]);
return list.map(item => item.id);
}
async authz(roleId: any, permissionIds: any) {
await this.rolePermissionService.delete({ roleId });
for (const permissionId of permissionIds) {
await this.rolePermissionService.add({
roleId,
permissionId,
});
}
}
}
@@ -0,0 +1,18 @@
import { Provide } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { UserRoleEntity } from '../entity/user-role';
/**
* 用户->角色
*/
@Provide()
export class UserRoleService extends BaseService<UserRoleEntity> {
@InjectEntityModel(UserRoleEntity)
repository: Repository<UserRoleEntity>;
getRepository() {
return this.repository;
}
}
@@ -0,0 +1,113 @@
import { Inject, Provide } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from '../entity/user';
import _ from 'lodash';
import md5 from 'md5';
import { CommonException } from '../../../basic/exception/common-exception';
import { BaseService } from '../../../basic/base-service';
import { logger } from '../../../utils/logger';
import { RoleService } from './role-service';
import { PermissionService } from './permission-service';
import { UserRoleService } from './user-role-service';
/**
* 系统用户
*/
@Provide()
export class UserService extends BaseService<UserEntity> {
@InjectEntityModel(UserEntity)
repository: Repository<UserEntity>;
@Inject()
roleService: RoleService;
@Inject()
permissionService: PermissionService;
@Inject()
userRoleService: UserRoleService;
getRepository() {
return this.repository;
}
/**
* 获得个人信息
*/
async mine() {
const info = await this.repository.findOne({
where: {
id: this.ctx.user.id,
},
});
delete info.password;
return info;
}
/**
* 新增
* @param param
*/
async add(param) {
const exists = await this.repository.findOne({
where: {
username: param.username,
},
});
if (!_.isEmpty(exists)) {
throw new CommonException('用户名已经存在');
}
const password = param.password ?? '123456';
param.password = md5(password); // 默认密码 建议未改密码不能登陆
await super.add(param);
//添加角色
if (param.roles && param.roles.length > 0) {
await this.roleService.addRoles(param.id, param.roles);
}
return param.id;
}
/**
* 修改
* @param param 数据
*/
async update(param) {
if (param.id == null) {
throw new CommonException('id不能为空');
}
const userInfo = await this.repository.findOne({
where: { id: param.id },
});
if (!userInfo) {
throw new CommonException('用户不存在');
}
delete param.username;
if (!_.isEmpty(param.password)) {
param.password = md5(param.password);
} else {
delete param.password;
}
await super.update(param);
await this.roleService.updateRoles(param.id, param.roles);
}
async findOne(param) {
return this.repository.findOne({
where: param,
});
}
checkPassword(rawPassword: any, md5Password: any) {
logger.info('md5', md5('123456'));
return md5(rawPassword) === md5Password;
}
/**
* 获取用户的菜单资源列表
* @param id
*/
async getUserPermissions(id: any) {
const roleIds = await this.roleService.getRoleIdsByUserId(id);
return await this.roleService.getPermissionByRoleIds(roleIds);
}
}
@@ -0,0 +1,55 @@
import { Rule, RuleType } from '@midwayjs/validate';
import { ALL, Inject } from '@midwayjs/decorator';
import { Body } from '@midwayjs/decorator';
import { Controller, Post, Provide } from '@midwayjs/decorator';
import { BaseController } from '../../../basic/base-controller';
import { CodeService } from '../service/code-service';
export class SmsCodeReq {
@Rule(RuleType.number().required())
phoneCode: number;
@Rule(RuleType.string().required())
mobile: string;
@Rule(RuleType.string().required().max(10))
randomStr: string;
@Rule(RuleType.number().required().max(4))
imgCode: string;
}
// const enumsMap = {};
// glob('src/modules/**/enums/*.ts', {}, (err, matches) => {
// console.log('matched', matches);
// for (const filePath of matches) {
// const module = require('/' + filePath);
// console.log('modules', module);
// }
// });
/**
*/
@Provide()
@Controller('/api/basic')
export class BasicController extends BaseController {
@Inject()
codeService: CodeService;
@Post('/sendSmsCode')
public sendSmsCode(
@Body(ALL)
body: SmsCodeReq
) {
// 设置缓存内容
return this.ok(null);
}
@Post('/captcha')
public async getCaptcha(
@Body()
randomStr
) {
console.assert(randomStr < 10, 'randomStr 过长');
const captcha = await this.codeService.generateCaptcha(randomStr);
return this.ok(captcha.data);
}
}
@@ -0,0 +1,57 @@
import { Inject, Provide } from '@midwayjs/decorator';
import { CacheManager } from '@midwayjs/cache';
const svgCaptcha = require('svg-captcha');
// {data: '<svg.../svg>', text: 'abcd'}
/**
*/
@Provide()
export class CodeService {
@Inject()
cache: CacheManager; // 依赖注入CacheManager
/**
*/
async generateCaptcha(randomStr) {
console.assert(randomStr < 10, 'randomStr 过长');
const c = svgCaptcha.create();
//{data: '<svg.../svg>', text: 'abcd'}
const imgCode = c.text; // = RandomUtil.randomStr(4, true);
await this.cache.set('imgCode:' + randomStr, imgCode, {
ttl: 2 * 60 * 1000, //过期时间 2分钟
});
return c;
}
async getCaptchaText(randomStr) {
return await this.cache.get('imgCode:' + randomStr);
}
async removeCaptcha(randomStr) {
await this.cache.del('imgCode:' + randomStr);
}
async checkCaptcha(randomStr, userCaptcha) {
const code = await this.getCaptchaText(randomStr);
if (code == null) {
throw new Error('验证码已过期');
}
if (code !== userCaptcha) {
throw new Error('验证码不正确');
}
return true;
}
/**
*/
async sendSms(phoneCode, mobile, smsCode) {
console.assert(phoneCode != null && mobile != null, '手机号不能为空');
console.assert(smsCode != null, '验证码不能为空');
}
/**
* loginBySmsCode
*/
async loginBySmsCode(user, smsCode) {
console.assert(user.mobile != null, '手机号不能为空');
}
}
@@ -0,0 +1,30 @@
import {
Body,
Controller,
Inject,
Post,
Provide,
ALL,
} from '@midwayjs/decorator';
import { LoginService } from '../service/login-service';
import { BaseController } from '../../../basic/base-controller';
/**
*/
@Provide()
@Controller('/api/')
export class LoginController extends BaseController {
@Inject()
loginService: LoginService;
@Post('/login')
public async login(
@Body(ALL)
user
) {
const token = await this.loginService.login(user);
return this.ok(token);
}
@Post('/logout')
public logout() {}
}
@@ -0,0 +1,52 @@
import { Config, Inject, Provide } from '@midwayjs/decorator';
import { UserService } from '../../authority/service/user-service';
import * as jwt from 'jsonwebtoken';
import { CommonException } from '../../../basic/exception/common-exception';
/**
* 系统用户
*/
@Provide()
export class LoginService {
@Inject()
userService: UserService;
@Config('biz.jwt')
private jwt: any;
/**
* login
*/
async login(user) {
console.assert(user.username != null, '用户名不能为空');
const info = await this.userService.findOne({ username: user.username });
if (info == null) {
throw new CommonException('用户名或密码错误');
}
const right = this.userService.checkPassword(user.password, info.password);
if (!right) {
throw new CommonException('用户名或密码错误');
}
return this.generateToken(info);
}
/**
* 生成token
* @param user 用户对象
*/
async generateToken(user) {
const tokenInfo = {
username: user.username,
id: user.id,
};
const expire = this.jwt.expire;
const token = jwt.sign(tokenInfo, this.jwt.secret, {
expiresIn: expire,
});
return {
token,
expire,
};
}
}
@@ -0,0 +1,26 @@
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/decorator";
import { PipelineService } from '../service/pipeline-service';
import { logger } from '../../../utils/logger';
@Autoload()
@Scope(ScopeEnum.Singleton)
export class AutoRegisterCron {
@Inject()
pipelineService: PipelineService;
// @Inject()
// echoPlugin: EchoPlugin;
@Init()
async init() {
logger.info('加载定时trigger开始');
await this.pipelineService.onStartup();
// logger.info(this.echoPlugin, this.echoPlugin.test);
// logger.info('加载定时trigger完成');
//
// const meta = getClassMetadata(CLASS_KEY, this.echoPlugin);
// console.log('meta', meta);
// const metas = listPropertyDataFromClass(CLASS_KEY, this.echoPlugin);
// console.log('metas', metas);
}
}
@@ -0,0 +1,80 @@
import {
ALL,
Body,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { CrudController } from '../../../basic/crud-controller';
import { AccessService } from '../service/access-service';
/**
* 授权
*/
@Provide()
@Controller('/api/pi/access')
export class AccessController extends CrudController {
@Inject()
service: AccessService;
getService() {
return this.service;
}
@Post('/page')
async page(@Body(ALL) body) {
body.query = body.query ?? {};
body.query.userId = this.ctx.user.id;
return super.page(body);
}
@Post('/list')
async list(@Body(ALL) body) {
body.userId = this.ctx.user.id;
return super.list(body);
}
@Post('/add')
async add(@Body(ALL) bean) {
bean.userId = this.ctx.user.id;
return super.add(bean);
}
@Post('/update')
async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
return super.update(bean);
}
@Post('/info')
async info(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
return super.info(id);
}
@Post('/delete')
async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
return super.delete(id);
}
@Post('/define')
async define(@Query('type') type) {
const provider = this.service.getDefineByType(type);
return this.ok(provider);
}
@Post('/accessTypeDict')
async getAccessTypeDict() {
const list = this.service.getDefineList();
const dict = [];
for (const item of list) {
dict.push({
value: item.name,
label: item.title,
});
}
return this.ok(dict);
}
}
@@ -0,0 +1,40 @@
import {
ALL,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { DnsProviderService } from '../service/dns-provider-service';
import { BaseController } from '../../../basic/base-controller';
/**
* 插件
*/
@Provide()
@Controller('/api/pi/dnsProvider')
export class DnsProviderController extends BaseController {
@Inject()
service: DnsProviderService;
@Post('/list')
async list(@Query(ALL) query) {
query.userId = this.ctx.user.id;
const list = this.service.getList();
return this.ok(list);
}
@Post('/dnsProviderTypeDict')
async getDnsProviderTypeDict() {
const list = this.service.getList();
const dict = [];
for (const item of list) {
dict.push({
value: item.name,
label: item.title,
});
}
return this.ok(dict);
}
}
@@ -0,0 +1,106 @@
import {
ALL,
Body,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { CrudController } from '../../../basic/crud-controller';
import { PipelineEntity } from '../entity/pipeline';
import { HistoryService } from '../service/history-service';
import { HistoryLogService } from '../service/history-log-service';
import { HistoryEntity } from '../entity/history';
import { HistoryLogEntity } from '../entity/history-log';
/**
* 证书
*/
@Provide()
@Controller('/api/pi/history')
export class HistoryController extends CrudController {
@Inject()
service: HistoryService;
@Inject()
logService: HistoryLogService;
getService() {
return this.service;
}
@Post('/page')
async page(@Body(ALL) body) {
body.query.userId = this.ctx.user.id;
return super.page(body);
}
@Post('/list')
async list(@Body(ALL) body) {
body.userId = this.ctx.user.id;
if (body.pipelineId == null) {
return this.ok([]);
}
const buildQuery = qb => {
qb.limit(10);
};
const listRet = await this.getService().list(
body,
{ prop: 'id', asc: false },
buildQuery
);
return this.ok(listRet);
}
@Post('/add')
async add(@Body(ALL) bean: PipelineEntity) {
bean.userId = this.ctx.user.id;
return super.add(bean);
}
@Post('/update')
async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
return super.update(bean);
}
@Post('/save')
async save(@Body(ALL) bean: HistoryEntity) {
bean.userId = this.ctx.user.id;
if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
}
await this.service.save(bean);
return this.ok(bean.id);
}
@Post('/saveLog')
async saveLog(@Body(ALL) bean: HistoryLogEntity) {
bean.userId = this.ctx.user.id;
if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
}
await this.logService.save(bean);
return this.ok(bean.id);
}
@Post('/delete')
async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
return super.delete(id);
}
@Post('/detail')
async detail(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
const detail = await this.service.detail(id);
return this.ok(detail);
}
@Post('/logs')
async logs(@Query('id') id) {
await this.logService.checkUserId(id, this.ctx.user.id);
const logInfo = await this.logService.info(id);
return this.ok(logInfo);
}
}
@@ -0,0 +1,77 @@
import {
ALL,
Body,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { CrudController } from '../../../basic/crud-controller';
import { PipelineService } from '../service/pipeline-service';
import { PipelineEntity } from '../entity/pipeline';
/**
* 证书
*/
@Provide()
@Controller('/api/pi/pipeline')
export class PipelineController extends CrudController {
@Inject()
service: PipelineService;
getService() {
return this.service;
}
@Post('/page')
async page(@Body(ALL) body) {
body.query.userId = this.ctx.user.id;
const buildQuery = qb => {
qb.where({});
};
return super.page({ ...body, buildQuery });
}
@Post('/add')
async add(@Body(ALL) bean: PipelineEntity) {
bean.userId = this.ctx.user.id;
return super.add(bean);
}
@Post('/update')
async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
return super.update(bean);
}
@Post('/save')
async save(@Body(ALL) bean: PipelineEntity) {
bean.userId = this.ctx.user.id;
if (bean.id > 0) {
await this.service.checkUserId(bean.id, this.ctx.user.id);
}
await this.service.save(bean);
return this.ok(bean.id);
}
@Post('/delete')
async delete(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
return super.delete(id);
}
@Post('/detail')
async detail(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
const detail = await this.service.detail(id);
return this.ok(detail);
}
@Post('/trigger')
async trigger(@Query('id') id) {
await this.service.checkUserId(id, this.ctx.user.id);
await this.service.trigger(id);
return this.ok({});
}
}
@@ -0,0 +1,27 @@
import {
ALL,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/decorator';
import { BaseController } from '../../../basic/base-controller';
import { PluginService } from '../service/plugin-service';
/**
* 插件
*/
@Provide()
@Controller('/api/pi/plugin')
export class PluginController extends BaseController {
@Inject()
service: PluginService;
@Post('/list')
async list(@Query(ALL) query) {
query.userId = this.ctx.user.id;
const list = this.service.getList();
return this.ok(list);
}
}
@@ -0,0 +1,33 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 授权配置
*/
@Entity('cd_access')
export class AccessEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ comment: '名称', length: 100 })
name: string;
@Column({ comment: '类型', length: 100 })
type: string;
@Column({ name: 'setting', comment: '设置', length: 1024, nullable: true })
setting: string;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}
@@ -0,0 +1,41 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('pi_history_log')
export class HistoryLogEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ name: 'pipeline_id', comment: '流水线' })
pipelineId: number;
@Column({ name: 'history_id', comment: '历史id' })
historyId: number;
@Column({
name: 'node_id',
comment: '任务节点id',
length: 100,
nullable: true,
})
nodeId: string;
@Column({ comment: '日志内容', length: 40960, nullable: true })
logs: string;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}
@@ -0,0 +1,38 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('pi_history')
export class HistoryEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ name: 'pipeline_id', comment: '流水线' })
pipelineId: number;
@Column({ comment: '运行状态', length: 40960, nullable: true })
pipeline: string;
@Column({ comment: '结果状态', length: 20, nullable: true })
status: string;
@Column({
name: 'end_time',
comment: '结束时间',
nullable: true,
})
endTime: Date;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}
@@ -0,0 +1,52 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('pi_pipeline')
export class PipelineEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ name: 'title', comment: '标题' })
title: number;
@Column({ comment: '配置', length: 40960 })
content: string;
@Column({
name: 'keep_history_count',
comment: '历史记录保持数量',
nullable: true,
})
keepHistoryCount: number;
@Column({ comment: '备注', length: 100, nullable: true })
remark: string;
@Column({ comment: '状态', length: 100, nullable: true })
status: string;
@Column({ comment: '启用/禁用', nullable: true, default: false })
disabled: boolean;
@Column({
name: 'last_history_time',
comment: '最后一次执行时间',
nullable: true,
})
lastHistoryTime: number;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}
@@ -0,0 +1,35 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('pi_storage')
export class StorageEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ name: 'scope', comment: '范围' })
scope: string;
@Column({ name: 'namespace', comment: '命名空间' })
namespace: string;
@Column({ comment: 'key', length: 100, nullable: true })
key: string;
@Column({ comment: 'value', length: 40960, nullable: true })
value: string;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}
@@ -0,0 +1,12 @@
import { HistoryEntity } from '../history';
import { HistoryLogEntity } from '../history-log';
export class HistoryDetail {
history: HistoryEntity;
log: HistoryLogEntity;
constructor(history: HistoryEntity, log: HistoryLogEntity) {
this.history = history;
this.log = log;
}
}
@@ -0,0 +1,13 @@
import { PipelineEntity } from '../pipeline';
import { HistoryEntity } from '../history';
import { HistoryLogEntity } from '../history-log';
export class PipelineDetail {
pipeline: PipelineEntity;
constructor(pipeline: PipelineEntity) {
this.pipeline = pipeline;
}
last: HistoryEntity;
logs: HistoryLogEntity[];
}
@@ -0,0 +1,44 @@
import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { AccessEntity } from '../entity/access';
import {
accessRegistry,
IAccessService,
} from '@certd/pipeline';
/**
* 授权
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class AccessService
extends BaseService<AccessEntity>
implements IAccessService
{
@InjectEntityModel(AccessEntity)
repository: Repository<AccessEntity>;
getRepository() {
return this.repository;
}
async getById(id: any): Promise<any> {
const entity = await this.info(id);
// const access = accessRegistry.get(entity.type);
const setting = JSON.parse(entity.setting);
return {
id: entity.id,
...setting,
};
}
getDefineList() {
return accessRegistry.getDefineList();
}
getDefineByType(type) {
return accessRegistry.getDefine(type);
}
}
@@ -0,0 +1,47 @@
import { IStorage } from '@certd/pipeline/src/core/storage';
import { StorageService } from './storage-service';
export class DbStorage implements IStorage {
/**
* 范围: user / pipeline / runtime / task
*/
storageService: StorageService;
userId: number;
constructor(userId: number, storageService: StorageService) {
this.userId = userId;
this.storageService = storageService;
}
async get(
scope: string,
namespace: string,
key: string
): Promise<string | null> {
const storageEntity = await this.storageService.get({
userId: this.userId,
scope: scope,
namespace: namespace,
key,
});
if (storageEntity != null) {
return storageEntity.value;
}
return null;
}
async set(
scope: string,
namespace: string,
key: string,
value: string
): Promise<void> {
await this.storageService.set({
userId: this.userId,
scope: scope,
namespace: namespace,
key,
value,
});
}
}
@@ -0,0 +1,9 @@
import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
import { dnsProviderRegistry } from '@certd/plugin-cert';
@Provide()
@Scope(ScopeEnum.Singleton)
export class DnsProviderService {
getList() {
return dnsProviderRegistry.getDefineList();
}
}
@@ -0,0 +1,27 @@
import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { HistoryLogEntity } from '../entity/history-log';
/**
* 证书申请
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class HistoryLogService extends BaseService<HistoryLogEntity> {
@InjectEntityModel(HistoryLogEntity)
repository: Repository<HistoryLogEntity>;
getRepository() {
return this.repository;
}
async save(bean: HistoryLogEntity) {
if (bean.id > 0) {
await this.update(bean);
} else {
await this.add(bean);
}
}
}
@@ -0,0 +1,81 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { HistoryEntity } from '../entity/history';
import { PipelineEntity } from '../entity/pipeline';
import { HistoryDetail } from '../entity/vo/history-detail';
import { HistoryLogService } from './history-log-service';
/**
* 证书申请
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class HistoryService extends BaseService<HistoryEntity> {
@InjectEntityModel(HistoryEntity)
repository: Repository<HistoryEntity>;
@Inject()
logService: HistoryLogService;
getRepository() {
return this.repository;
}
async save(bean: HistoryEntity) {
if (bean.id > 0) {
await this.update(bean);
} else {
await this.add(bean);
}
}
async detail(historyId: string) {
const entity = await this.info(historyId);
const log = await this.logService.info(historyId);
return new HistoryDetail(entity, log);
}
async start(pipeline: PipelineEntity) {
const bean = {
userId: pipeline.userId,
pipelineId: pipeline.id,
title: pipeline.title,
status: 'start',
};
const { id } = await this.add(bean);
//清除大于pipeline.keepHistoryCount的历史记录
this.clear(pipeline.id, pipeline.keepHistoryCount);
return id;
}
private async clear(pipelineId: number, keepCount = 30) {
const count = await this.repository.count({
where: {
pipelineId,
},
});
if (count <= keepCount) {
return;
}
let shouldDeleteCount = count - keepCount;
const deleteCountBatch = 100;
while (shouldDeleteCount > 0) {
const list = await this.repository.find({
select: {
id: true,
},
where: {
pipelineId,
},
order: {
id: 'ASC',
},
skip: 0,
take: deleteCountBatch,
});
await this.repository.remove(list);
shouldDeleteCount -= deleteCountBatch;
}
}
}
@@ -0,0 +1,241 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/decorator';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { PipelineEntity } from '../entity/pipeline';
import { PipelineDetail } from '../entity/vo/pipeline-detail';
import { Executor, Pipeline, RunHistory } from '@certd/pipeline';
import { AccessService } from './access-service';
import { DbStorage } from './db-storage';
import { StorageService } from './storage-service';
import { Cron } from '../../../plugins/cron/cron';
import { HistoryService } from './history-service';
import { HistoryEntity } from '../entity/history';
import { HistoryLogEntity } from '../entity/history-log';
import { HistoryLogService } from './history-log-service';
import { logger } from '../../../utils/logger';
/**
* 证书申请
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class PipelineService extends BaseService<PipelineEntity> {
@InjectEntityModel(PipelineEntity)
repository: Repository<PipelineEntity>;
@Inject()
accessService: AccessService;
@Inject()
storageService: StorageService;
@Inject()
historyService: HistoryService;
@Inject()
historyLogService: HistoryLogService;
@Inject()
cron: Cron;
getRepository() {
return this.repository;
}
async update(entity) {
await super.update(entity);
await this.registerTriggerById(entity.id);
}
private async registerTriggerById(pipelineId) {
if (pipelineId == null) {
return;
}
const info = await this.info(pipelineId);
if (info && !info.disabled) {
const pipeline = JSON.parse(info.content);
this.registerTriggers(pipeline);
}
}
/**
* 获取详情
* @param id
*/
async detail(id) {
const pipeline = await this.info(id);
return new PipelineDetail(pipeline);
}
async save(bean: PipelineEntity) {
const pipeline = JSON.parse(bean.content);
bean.title = pipeline.title;
await this.addOrUpdate(bean);
await this.registerTriggerById(bean.id);
}
/**
* 应用启动后初始加载记录
*/
async onStartup() {
const idEntityList = await this.repository.find({
select: {
id: true,
},
where: {
disabled: false,
},
});
const ids = idEntityList.map(item => {
return item.id;
});
//id 分段
const idsSpan = [];
let arr = [];
for (let i = 0; i < ids.length; i++) {
if (i % 20 === 0) {
arr = [];
idsSpan.push(arr);
}
arr.push(ids[i]);
}
//分段加载记录
for (const idArr of idsSpan) {
const list = await this.repository.findBy({
id: In(idArr),
});
for (const entity of list) {
const pipeline = JSON.parse(entity.content ?? '{}');
this.registerTriggers(pipeline);
}
}
logger.info('定时器数量:', this.cron.getList());
}
registerTriggers(pipeline?: Pipeline) {
if (pipeline?.triggers == null) {
return;
}
for (const trigger of pipeline.triggers) {
this.registerCron(pipeline.id, trigger);
}
}
async trigger(id) {
this.cron.register({
name: `pipeline.${id}.trigger.once`,
cron: null,
job: async () => {
await this.run(id, null);
},
});
logger.info('定时器数量:', this.cron.getList());
}
registerCron(pipelineId, trigger) {
let cron = trigger.props?.cron;
if (cron == null) {
return;
}
if(cron.startsWith("*")){
cron = "0"+ cron.substring(1,cron.length)
return
}
this.cron.register({
name: this.buildCronKey(pipelineId, trigger.id),
cron: cron,
job: async () => {
logger.info('定时任务触发:', pipelineId, trigger.id);
await this.run(pipelineId, trigger.id);
},
});
}
async run(id, triggerId) {
const entity: PipelineEntity = await this.info(id);
const pipeline = JSON.parse(entity.content);
if (!pipeline.stages || pipeline.stages.length === 0) {
return;
}
const triggerType = this.getTriggerType(triggerId, pipeline);
if (triggerType == null) {
return;
}
const onChanged = async (history: RunHistory) => {
//保存执行历史
await this.saveHistory(history);
};
const userId = entity.userId;
const historyId = await this.historyService.start(entity);
const executor = new Executor({
userId,
pipeline,
onChanged,
accessService: this.accessService,
storage: new DbStorage(userId, this.storageService),
});
await executor.run(historyId, triggerType);
}
private getTriggerType(triggerId, pipeline) {
let triggerType = 'user';
if (triggerId != null) {
//如果不是手动触发
//查找trigger
const found = this.findTrigger(pipeline, triggerId);
if (!found) {
//如果没有找到triggerId,说明被用户删掉了,这里再删除一次
this.cron.remove(this.buildCronKey(pipeline.id, triggerId));
triggerType = null;
} else {
logger.info('timer trigger:' + found.id, found.title, found.cron);
triggerType = 'timer';
}
}
return triggerType;
}
private buildCronKey(pipelineId, triggerId) {
return `pipeline.${pipelineId}.trigger.${triggerId}`;
}
private findTrigger(pipeline, triggerId) {
for (const trigger of pipeline.triggers) {
if (trigger.id === triggerId) {
return trigger;
}
}
return;
}
private async saveHistory(history: RunHistory) {
//修改pipeline状态
const pipelineEntity = new PipelineEntity();
pipelineEntity.id = parseInt(history.pipeline.id);
pipelineEntity.status = history.pipeline.status.status;
pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
await this.update(pipelineEntity);
const entity: HistoryEntity = new HistoryEntity();
entity.id = parseInt(history.id);
entity.userId = history.pipeline.userId;
entity.pipeline = JSON.stringify(history.pipeline);
await this.historyService.save(entity);
const logEntity: HistoryLogEntity = new HistoryLogEntity();
logEntity.id = entity.id;
logEntity.userId = entity.userId;
logEntity.pipelineId = entity.pipelineId;
logEntity.historyId = entity.id;
logEntity.logs = JSON.stringify(history.logs);
await this.historyLogService.addOrUpdate(logEntity);
}
}
@@ -0,0 +1,15 @@
import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
import { pluginRegistry } from '@certd/pipeline';
@Provide()
@Scope(ScopeEnum.Singleton)
export class PluginService {
getList() {
const collection = pluginRegistry.storage;
const list = [];
for (const key in collection) {
const Plugin = collection[key];
list.push({ ...Plugin.define, key });
}
return list;
}
}
@@ -0,0 +1,56 @@
import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service';
import { StorageEntity } from '../entity/storage';
/**
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class StorageService extends BaseService<StorageEntity> {
@InjectEntityModel(StorageEntity)
repository: Repository<StorageEntity>;
getRepository() {
return this.repository;
}
async get(where: {
scope: any;
namespace: any;
userId: number;
key: string;
}) {
if (where.userId == null) {
throw new Error('userId 不能为空');
}
return await this.repository.findOne({
where,
});
}
async set(entity: {
id?: any;
scope: any;
namespace: any;
userId: number;
value: string;
key: string;
}) {
entity.id = null;
const query = { ...entity };
delete query.value;
const ret = await this.get(query);
if (ret != null) {
entity.id = ret.id;
if (ret.userId !== entity.userId) {
throw new Error('您没有权限修改此数据');
}
await this.repository.save(entity);
} else {
await this.repository.insert(entity);
}
return;
}
}
@@ -0,0 +1,27 @@
import { Config, Configuration, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
import { IMidwayContainer } from '@midwayjs/core';
import { Cron } from './cron';
// ... (see below) ...
@Configuration({
namespace: 'cron',
//importConfigs: [join(__dirname, './config')],
})
export class CronConfiguration {
@Config()
config;
@Logger()
logger: ILogger;
cron: Cron;
async onReady(container: IMidwayContainer) {
this.logger.info('cron start');
this.cron = new Cron({
logger: this.logger,
...this.config,
});
container.registerObject('cron', this.cron);
this.logger.info('cron started');
}
}
@@ -0,0 +1,38 @@
import cron from 'node-cron';
export type CronTask = {
/**
* 为空则为单次执行
*/
cron: string;
job: () => Promise<void>;
name: string;
};
export class Cron {
logger;
constructor(opts) {
this.logger = opts.logger;
}
register(task: CronTask) {
if (!task.cron) {
this.logger.info(`[cron] register once : [${task.name}]`);
task.job();
return;
}
this.logger.info(`[cron] register cron : [${task.name}] ,${task.cron}`);
cron.schedule(task.cron, task.job, {
name: task.name,
});
}
remove(taskName: string) {
this.logger.info(`[cron] remove : [${taskName}]`);
const tasks = cron.getTasks() as Map<any, any>;
tasks.delete(taskName);
}
getList() {
const tasks = cron.getTasks();
return tasks.size;
}
}
@@ -0,0 +1,5 @@
// src/index.ts
export { CronConfiguration as Configuration } from './configuration';
// export * from './controller/user';
// export * from './controller/api';
// export * from './service/user';
@@ -0,0 +1,6 @@
// src/index.ts
import '@certd/plugin-all';
export { PipelineConfiguration as Configuration } from '@certd/pipeline';
// export * from './controller/user';
// export * from './controller/api';
// export * from './service/user';
@@ -0,0 +1,27 @@
import { ILogger } from "@midwayjs/logger";
import { ITaskPlugin,Autowire, IsTaskPlugin, TaskInput } from "@certd/pipeline";
@IsTaskPlugin({
name: "EchoPlugin",
title: "测试插件",
desc: "test",
})
export class EchoPlugin implements ITaskPlugin {
@TaskInput({
title: "测试属性",
component: {
name: "text",
},
})
test?: string;
@Autowire()
// @ts-ignore
logger: ILogger;
async onInit(){}
async execute(): Promise<void> {
return Promise.resolve(undefined);
}
}
@@ -0,0 +1,12 @@
const log4js = require('log4js');
const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
const path = require('path');
const filename = path.join('/logs/server.log');
log4js.configure({
appenders: {
std: { type: 'stdout', level: 'debug' },
file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename },
},
categories: { default: { appenders: ['std'], level } },
});
export const logger = log4js.getLogger('fast');
@@ -0,0 +1,43 @@
const numbers = '0123456789';
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const specials = '~!@#$%^*()_+-=[]{}|;:,./<>?';
/**
* Generate random string
* @param {Number} length
* @param {Object} options
*/
function randomStr(length, options) {
length || (length = 8);
options || (options = {});
let chars = '';
let result = '';
if (options === true) {
chars = numbers + letters;
} else if (typeof options === 'string') {
chars = options;
} else {
if (options.numbers !== false) {
chars += typeof options.numbers === 'string' ? options.numbers : numbers;
}
if (options.letters !== false) {
chars += typeof options.letters === 'string' ? options.letters : letters;
}
if (options.specials) {
chars +=
typeof options.specials === 'string' ? options.specials : specials;
}
}
while (length > 0) {
length--;
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
export const RandomUtil = { randomStr };