清空仓库

This commit is contained in:
COOL 2025-01-09 16:30:35 +08:00
parent 0623782867
commit 48aa401553
237 changed files with 0 additions and 13059 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

4
.gitattributes vendored
View File

@ -1,4 +0,0 @@
*.js text eol=lf
*.json text eol=lf
*.ts text eol=lf
*.code-snippets text eol=lf

19
.gitignore vendored
View File

@ -1,19 +0,0 @@
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
dist/
.idea/
run/
.DS_Store
launch.json
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*
data/*
pnpm-lock.yaml
public/uploads/*

View File

@ -1,6 +0,0 @@
# cool-admin-midway-packages
cool-admin midway 后端核心包
- 为了不和src代码相互影响7.0的核心依赖包单独成一个项目
- 7.0之前的在cool-admin-midway这个项目的packages目录下

View File

@ -1,11 +0,0 @@
# 🎨 editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,20 +0,0 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"env": {
"jest": true
},
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"node/no-extraneous-import": "off",
"no-empty": "off",
"node/no-extraneous-require": "off",
"eqeqeq": "off",
"node/no-unsupported-features/node-builtins": "off",
"@typescript-eslint/ban-types": "off",
"no-control-regex": "off",
"prefer-const": "off"
}
}

15
cloud/.gitignore vendored
View File

@ -1,15 +0,0 @@
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
dist/
.idea/
run/
.DS_Store
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*

View File

@ -1,3 +0,0 @@
module.exports = {
...require('mwts/.prettierrc.json')
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2013 - Now midwayjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
cloud/index.d.ts vendored
View File

@ -1,10 +0,0 @@
export * from './dist/index';
declare module '@midwayjs/core/dist/interface' {
interface MidwayConfig {
book?: PowerPartial<{
a: number;
b: string;
}>;
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
setupFilesAfterEnv: ['./jest.setup.js']
};

View File

@ -1 +0,0 @@
jest.setTimeout(30000);

View File

@ -1,49 +0,0 @@
{
"name": "@cool-midway/cloud",
"version": "7.1.0",
"description": "",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [
"cool",
"cool-admin",
"cooljs"
],
"author": "COOL",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"index.d.ts"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"devDependencies": {
"@cool-midway/core": "^7.1.19",
"@midwayjs/cli": "^2.0.0",
"@midwayjs/core": "^3.16.0",
"@midwayjs/decorator": "^3.9.0",
"@midwayjs/mock": "^3.9.0",
"@midwayjs/typeorm": "^3.9.0",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.15",
"cross-env": "^7.0.3",
"jest": "^29.3.1",
"lodash": "^4.17.21",
"mwts": "^1.3.0",
"ts-jest": "^29.0.3",
"typeorm": "^0.3.11",
"typescript": "^4.9.4"
},
"dependencies": {
"sqlstring": "^2.3.3"
}
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2013 - Now midwayjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,27 +0,0 @@
### COOL-ADMIN
cool-admin一个很酷的后台权限管理系统开源免费模块化、插件化、极速开发CRUD方便快速构建迭代后台管理系统
大数据、微服务、AI编码快速开发
### 技术栈
- 后端node.js midway.js koa.js mysql typescript
- 前端vue.js element-plus jsx pinia vue-router
### 官网
[https://cool-js.com](https://cool-js.com)
### 演示地址
[https://show.cool-admin.com](https://show.cool-admin.com)
- 账户admin
- 密码123456
### 项目地址
[https://github.com/cool-team-official/cool-admin-midway](https://github.com/cool-team-official/cool-admin-midway)

View File

@ -1,4 +0,0 @@
/**
* cool的配置
*/
export default {};

View File

@ -1,22 +0,0 @@
import { ILifeCycle, ILogger, IMidwayContainer, Logger } from '@midwayjs/core';
import { Configuration } from '@midwayjs/decorator';
import * as DefaultConfig from './config/config.default';
import { CoolCloudDb } from './db';
@Configuration({
namespace: 'cloud',
importConfigs: [
{
default: DefaultConfig,
},
],
})
export class CoolCloudConfiguration implements ILifeCycle {
@Logger()
coreLogger: ILogger;
async onReady(container: IMidwayContainer) {
await container.getAsync(CoolCloudDb);
this.coreLogger.info('\x1B[36m [cool:cloud] ready \x1B[0m');
}
}

View File

@ -1,131 +0,0 @@
import { CoolCommException } from '@cool-midway/core';
import { CoolDataSource } from './source';
import {
ALL,
Config,
ILogger,
Init,
Logger,
Provide,
Scope,
ScopeEnum,
} from '@midwayjs/core';
import { Repository } from 'typeorm';
import * as ts from 'typescript';
import * as _ from 'lodash';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolCloudDb {
@Logger()
coreLogger: ILogger;
coolDataSource: CoolDataSource;
@Config(ALL)
config;
@Init()
async init() {
const config = this.config.typeorm.dataSource.default;
if (!config) {
throw new CoolCommException('未配置数据库default信息');
}
this.coolDataSource = new CoolDataSource({
...this.config.typeorm.dataSource.default,
entities: [],
});
// 连接数据库
await this.coolDataSource.initialize();
}
/**
*
* @param tableClass
* @param appId ID
* @returns
*/
getRepository(tableClass: string, appId = 'CLOUD'): Repository<any> {
return this.coolDataSource.getRepository(`${tableClass}${appId}`);
}
/**
*
* @param table
* @param appId ID
* @param synchronize
*/
async createTable(table: string, synchronize = false, appId = 'CLOUD') {
if (!table || !appId) {
throw new CoolCommException('table、appId不能为空');
}
const { newCode, className } = this.parseCode(table, appId);
const entities = this.coolDataSource.options.entities;
// @ts-ignore
this.coolDataSource.options.entities = _.dropWhile(entities, {
name: className,
});
const code = ts.transpile(
`${newCode}
this.coolDataSource.options.entities.push(${className})
this.coolDataSource.buildMetadatas().then(() => {
if(synchronize){
this.coolDataSource.synchronize();
}
});
`,
{
emitDecoratorMetadata: true,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2018,
removeComments: true,
experimentalDecorators: true,
noImplicitThis: true,
noUnusedLocals: true,
stripInternal: true,
skipLibCheck: true,
pretty: true,
declaration: true,
noImplicitAny: false,
}
);
eval(code);
}
/**
* appId相关的类名
* @param code
* @param appId
*/
parseCode(code: string, appId = 'CLOUD') {
try {
const oldClassName = code
.match('class(.*)extends')[1]
.replace(/\s*/g, '');
const oldTableStart = code.indexOf('@Entity(');
const oldTableEnd = code.indexOf(')');
const oldTableName = code
.substring(oldTableStart + 9, oldTableEnd - 1)
.replace(/\s*/g, '')
// eslint-disable-next-line no-useless-escape
.replace(/\"/g, '')
// eslint-disable-next-line no-useless-escape
.replace(/\'/g, '');
const className = `${oldClassName}${appId}`;
return {
newCode: code
.replace(oldClassName, className)
.replace(oldTableName, `func_${oldTableName}`),
className,
tableName: `func_${oldTableName}`,
};
} catch (err) {
this.coreLogger.error(err);
throw new CoolCommException('代码结构不正确,请检查');
}
}
}

View File

@ -1,10 +0,0 @@
import { DataSource } from 'typeorm';
export class CoolDataSource extends DataSource {
/**
*
*/
async buildMetadatas() {
await super.buildMetadatas();
}
}

View File

@ -1,525 +0,0 @@
import { CloudReq } from './../interface';
import { IMidwayApplication } from '@midwayjs/core';
import {
CoolConfig,
CoolEventManager,
CoolValidateException,
CurdOption,
ERRINFO,
EVENT,
} from '@cool-midway/core';
import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm';
import { CoolCloudDb } from '../db';
import * as _ from 'lodash';
import * as SqlString from 'sqlstring';
export abstract class CloudCrud {
ctx;
curdOption: CurdOption;
coolCloudDb: CoolCloudDb;
coolConfig: CoolConfig;
entity: Repository<any>;
app: IMidwayApplication;
req: CloudReq;
coolEventManager: CoolEventManager;
protected sqlParams;
setCurdOption(curdOption: CurdOption) {
this.curdOption = curdOption;
}
/**
*
* @param entityModel
*/
async setEntity() {
this.entity = this.coolCloudDb.getRepository(
this.curdOption.entity,
'CLOUD'
);
}
abstract main(req: CloudReq): Promise<void>;
async init(req: CloudReq) {
this.sqlParams = [];
// 执行主函数
await this.main(req);
// 操作之前
await this.before();
// // 设置实体
await this.setEntity();
}
/**
*
* @param params
*/
async paramSafetyCheck(params) {
const lp = params.toLowerCase();
return !(
lp.indexOf('update ') > -1 ||
lp.indexOf('select ') > -1 ||
lp.indexOf('delete ') > -1 ||
lp.indexOf('insert ') > -1
);
}
/**
*
* @param query
* @param option
*/
async list(query): Promise<any> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, this.curdOption.listQueryOp);
return this.nativeQuery(sql, []);
}
/**
* SQL并获得分页数据
* @param sql sql语句
* @param query
* @param autoSort
*/
async sqlRenderPage(sql, query, autoSort = true) {
const {
size = this.coolConfig.crud.pageSize,
page = 1,
order = 'createTime',
sort = 'desc',
isExport = false,
maxExportLimit,
} = query;
if (order && sort && autoSort) {
if (!(await this.paramSafetyCheck(order + sort))) {
throw new CoolValidateException('非法传参~');
}
sql += ` ORDER BY ${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
}
if (isExport && maxExportLimit > 0) {
this.sqlParams.push(parseInt(maxExportLimit));
sql += ' LIMIT ? ';
}
if (!isExport) {
this.sqlParams.push((page - 1) * size);
this.sqlParams.push(parseInt(size));
sql += ' LIMIT ?,? ';
}
let params = [];
params = params.concat(this.sqlParams);
const result = await this.nativeQuery(sql, params);
const countResult = await this.nativeQuery(this.getCountSql(sql), params);
return {
list: result,
pagination: {
page: parseInt(page),
size: parseInt(size),
total: parseInt(countResult[0] ? countResult[0].count : 0),
},
};
}
/**
*
* @param connectionName
*/
async page(query) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, this.curdOption.pageQueryOp);
return this.sqlRenderPage(sql, query, false);
}
/**
* SQL
* @param sql
*/
getCountSql(sql) {
sql = sql
.replace(new RegExp('LIMIT', 'gm'), 'limit ')
.replace(new RegExp('\n', 'gm'), ' ');
if (sql.includes('limit')) {
const sqlArr = sql.split('limit ');
sqlArr.pop();
sql = sqlArr.join('limit ');
}
return `select count(*) as count from (${sql}) a`;
}
/**
* entity获得分页数据sql
* @param find QueryBuilder
* @param query
* @param autoSort
* @param connectionName
*/
async entityRenderPage(
find: SelectQueryBuilder<any>,
query,
autoSort = true
) {
const {
size = this.coolConfig.crud.pageSize,
page = 1,
order = 'createTime',
sort = 'desc',
isExport = false,
maxExportLimit,
} = query;
const count = await find.getCount();
let dataFind: SelectQueryBuilder<any>;
if (isExport && maxExportLimit > 0) {
dataFind = find.limit(maxExportLimit);
} else {
dataFind = find.offset((page - 1) * size).limit(size);
}
if (autoSort) {
find.addOrderBy(order, sort.toUpperCase());
}
return {
list: await dataFind.getRawMany(),
pagination: {
page: parseInt(page),
size: parseInt(size),
total: count,
},
};
}
/**
*
* @param sort
* @returns
*/
private checkSort(sort) {
if (!['desc', 'asc'].includes(sort.toLowerCase())) {
throw new CoolValidateException('sort 非法传参~');
}
return sort;
}
/**
*
* @param sql
* @param params
*/
async nativeQuery(sql, params?) {
if (_.isEmpty(params)) {
params = this.sqlParams;
}
let newParams = [];
newParams = newParams.concat(params);
this.sqlParams = [];
for (const param of newParams) {
SqlString.escape(param);
}
return await this.getOrmManager().query(sql, newParams || []);
}
/**
* ORM管理
* @param connectionName
*/
getOrmManager() {
return this.coolCloudDb.coolDataSource;
}
private async before() {
if (!this.curdOption?.before) {
return;
}
await this.curdOption.before(this.ctx, this.app);
}
/**
*
* @param curdOption
*/
private async insertParam(param) {
if (!this.curdOption?.insertParam) {
return param;
}
return {
...param,
...(await this.curdOption.insertParam(this.ctx, this.app)),
};
}
/**
* ||
* @param data
*/
async modifyAfter(
data: any,
type: 'delete' | 'update' | 'add'
): Promise<void> {}
/**
* ||
* @param data
*/
async modifyBefore(
data: any,
type: 'delete' | 'update' | 'add'
): Promise<void> {}
/**
*
* @param param
* @returns
*/
async add(param) {
param = await this.insertParam(param);
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
await this.modifyBefore(param, 'add');
await this.addOrUpdate(param);
await this.modifyAfter(param, 'add');
return {
id:
param instanceof Array
? param.map(e => {
return e.id ? e.id : e._id;
})
: param.id
? param.id
: param._id,
};
}
/**
* |
* @param param
*/
async addOrUpdate(param: any | any[]) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
delete param.createTime;
if (param.id) {
param.updateTime = new Date();
await this.entity.update(param.id, param);
} else {
param.createTime = new Date();
param.updateTime = new Date();
await this.entity.insert(param);
}
}
/**
*
* @param ids ID集合 [1,2,3] 1,2,3
*/
async delete(ids: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
await this.modifyBefore(ids, 'delete');
if (ids instanceof String) {
ids = ids.split(',');
}
if (this.coolConfig.crud?.softDelete) {
this.softDelete(ids);
}
await this.entity.delete(ids);
await this.modifyAfter(ids, 'delete');
}
/**
*
* @param ids ID数组
* @param entity
*/
async softDelete(ids: string[], entity?: Repository<any>, userId?: string) {
const data = await this.entity.find({
where: {
id: In(ids),
},
});
if (_.isEmpty(data)) return;
const _entity = entity ? entity : this.entity;
const params = {
data,
ctx: this.ctx,
entity: _entity,
};
if (data.length > 0) {
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
}
}
/**
*
* @param param
*/
async update(param: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
await this.modifyBefore(param, 'update');
if (!param.id && !(param instanceof Array))
throw new CoolValidateException(ERRINFO.NOID);
await this.addOrUpdate(param);
await this.modifyAfter(param, 'update');
}
/**
* ID
* @param id ID
*/
async info(id: any): Promise<any> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (!id) {
throw new CoolValidateException(ERRINFO.NOID);
}
const info = await this.entity.findBy({ id });
if (info && this.curdOption?.infoIgnoreProperty) {
for (const property of this.curdOption?.infoIgnoreProperty) {
delete info[property];
}
}
return info;
}
/**
*
* @param query
* @param option
*/
private async getOptionFind(query, option) {
let { order = 'createTime', sort = 'desc', keyWord = '' } = query;
const sqlArr = ['SELECT'];
const selects = ['a.*'];
const find = this.entity.createQueryBuilder('a');
if (option) {
if (typeof option == 'function') {
// @ts-ignore
option = await option(this.baseCtx, this.baseApp);
}
// 判断是否有关联查询,有的话取个别名
if (!_.isEmpty(option.join)) {
for (const item of option.join) {
selects.push(`${item.alias}.*`);
find[item.type || 'leftJoin'](
item.entity,
item.alias,
item.condition
);
}
}
// 默认条件
if (option.where) {
const wheres =
typeof option.where == 'function'
? await option.where(this.ctx, this.app)
: option.where;
if (!_.isEmpty(wheres)) {
for (const item of wheres) {
if (
item.length == 2 ||
(item.length == 3 &&
(item[2] || (item[2] === 0 && item[2] != '')))
) {
for (const key in item[1]) {
this.sqlParams.push(item[1][key]);
}
find.andWhere(item[0], item[1]);
}
}
}
}
// 附加排序
if (!_.isEmpty(option.addOrderBy)) {
for (const key in option.addOrderBy) {
if (order && order == key) {
sort = option.addOrderBy[key].toUpperCase();
}
find.addOrderBy(
SqlString.escapeId(key),
this.checkSort(option.addOrderBy[key].toUpperCase())
);
}
}
// 关键字模糊搜索
if (keyWord || (keyWord == 0 && keyWord != '')) {
keyWord = `%${keyWord}%`;
find.andWhere(
new Brackets(qb => {
const keyWordLikeFields = option.keyWordLikeFields;
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
keyWord,
});
this.sqlParams.push(keyWord);
}
})
);
}
// 筛选字段
if (!_.isEmpty(option.select)) {
sqlArr.push(option.select.join(','));
find.select(option.select);
} else {
sqlArr.push(selects.join(','));
}
// 字段全匹配
if (!_.isEmpty(option.fieldEq)) {
for (const key of option.fieldEq) {
const c = {};
// 单表字段无别名的情况下操作
if (typeof key === 'string') {
if (query[key] || (query[key] == 0 && query[key] == '')) {
c[key] = query[key];
const eq = query[key] instanceof Array ? 'in' : '=';
if (eq === 'in') {
find.andWhere(`${key} ${eq} (:${key})`, c);
} else {
find.andWhere(`${key} ${eq} :${key}`, c);
}
this.sqlParams.push(query[key]);
}
} else {
if (
query[key.requestParam] ||
(query[key.requestParam] == 0 && query[key.requestParam] !== '')
) {
c[key.column] = query[key.requestParam];
const eq = query[key.requestParam] instanceof Array ? 'in' : '=';
if (eq === 'in') {
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
} else {
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
}
this.sqlParams.push(query[key.requestParam]);
}
}
}
}
} else {
sqlArr.push(selects.join(','));
}
// 接口请求的排序
if (sort && order) {
const sorts = sort.toUpperCase().split(',');
const orders = order.split(',');
if (sorts.length != orders.length) {
throw new CoolValidateException(ERRINFO.SORTFIELD);
}
for (const i in sorts) {
find.addOrderBy(
SqlString.escapeId(orders[i]),
this.checkSort(sorts[i])
);
}
}
if (option?.extend) {
await option?.extend(find, this.ctx, this.app);
}
const sqls = find.getSql().split('FROM');
sqlArr.push('FROM');
sqlArr.push(sqls[1]);
return sqlArr.join(' ');
}
}

View File

@ -1,17 +0,0 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolCloudFunc {
/**
*
* @param code
* @returns
*/
getClassName(code: string) {
return code.match('class(.*)extends')[1].replace(/\s*/g, '');
}
}

View File

@ -1,10 +0,0 @@
export { CoolCloudConfiguration as Configuration } from './configuration';
export * from './interface';
// 云数据库
export * from './db/index';
// 云函数
export * from './func/index';
export * from './func/crud';

View File

@ -1,11 +0,0 @@
/**
*
*/
export interface CloudReq {
// 云函数名称
name: string;
// 请求参数
params: any;
// 调用方法
method: string;
}

View File

@ -1,45 +0,0 @@
{
"name": "@cool-midway/cloud",
"version": "7.1.0",
"description": "",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": ["cool","cool-admin","cooljs"],
"author": "COOL",
"files": [
"**/*.js",
"**/*.d.ts",
"index.d.ts"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"devDependencies": {
"@cool-midway/core": "^7.1.19",
"@midwayjs/cli": "^2.0.0",
"@midwayjs/core": "^3.16.0",
"@midwayjs/decorator": "^3.9.0",
"@midwayjs/mock": "^3.9.0",
"@midwayjs/typeorm": "^3.9.0",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.15",
"cross-env": "^7.0.3",
"jest": "^29.3.1",
"lodash": "^4.17.21",
"mwts": "^1.3.0",
"ts-jest": "^29.0.3",
"typeorm": "^0.3.11",
"typescript": "^4.9.4"
},
"dependencies": {
"sqlstring": "^2.3.3"
}
}

View File

@ -1 +0,0 @@
export class CoolCloudUtil {}

View File

@ -1,25 +0,0 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
"skipLibCheck": true,
"noImplicitReturns": false,
"pretty": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist"
},
"exclude": [
"dist",
"node_modules",
"test"
]
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2013 - Now midwayjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,3 +0,0 @@
# cool-admin
https://cool-js.com

View File

@ -1,11 +0,0 @@
# 🎨 editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,7 +0,0 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"env": {
"jest": true
}
}

View File

@ -1,15 +0,0 @@
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
dist/
.idea/
run/
.DS_Store
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*

View File

@ -1,3 +0,0 @@
module.exports = {
...require('mwts/.prettierrc.json')
}

10
core/index.d.ts vendored
View File

@ -1,10 +0,0 @@
export * from './dist/index';
declare module '@midwayjs/core/dist/interface' {
interface MidwayConfig {
book?: PowerPartial<{
a: number;
b: string;
}>;
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
setupFilesAfterEnv: ['./jest.setup.js']
};

View File

@ -1 +0,0 @@
jest.setTimeout(30000);

View File

@ -1,64 +0,0 @@
{
"name": "@cool-midway/core",
"version": "7.1.25",
"description": "",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [
"cool",
"cool-admin",
"cooljs"
],
"author": "COOL",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"index.d.ts"
],
"readme": "README.md",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"devDependencies": {
"@midwayjs/cli": "2.1.1",
"@midwayjs/core": "^3.15.0",
"@midwayjs/decorator": "^3.15.0",
"@midwayjs/koa": "^3.15.2",
"@midwayjs/mock": "^3.15.2",
"@midwayjs/typeorm": "^3.15.2",
"@types/download": "^8.0.5",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.30",
"aedes": "^0.51.0",
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"mwts": "^1.3.0",
"ts-jest": "^29.1.2",
"typeorm": "^0.3.20",
"typescript": "~5.4.2"
},
"dependencies": {
"@cool-midway/cache-manager-fs-hash": "^7.0.0",
"@midwayjs/cache": "^3.14.0",
"@midwayjs/cache-manager": "^3.15.2",
"axios": "^1.6.8",
"decompress": "^4.2.1",
"download": "^8.0.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"moment": "^2.30.1",
"pm2": "^5.3.1",
"sqlstring": "^2.3.3",
"uuid": "^9.0.1",
"ws": "^8.16.0"
}
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2013 - Now midwayjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,27 +0,0 @@
### COOL-ADMIN
cool-admin一个很酷的后台权限管理系统开源免费模块化、插件化、极速开发CRUD方便快速构建迭代后台管理系统
大数据、微服务、AI编码快速开发
### 技术栈
- 后端node.js midway.js koa.js mysql typescript
- 前端vue.js element-plus jsx pinia vue-router
### 官网
[https://cool-js.com](https://cool-js.com)
### 演示地址
[https://show.cool-admin.com](https://show.cool-admin.com)
- 账户admin
- 密码123456
### 项目地址
[https://github.com/cool-team-official/cool-admin-midway](https://github.com/cool-team-official/cool-admin-midway)

View File

@ -1,64 +0,0 @@
import * as FsStore from "@cool-midway/cache-manager-fs-hash";
/**
* cool
*/
class FsCacheStore {
options: any;
store: FsStore;
constructor(options = {}) {
options = {
...options,
path: "cache",
ttl: -1,
};
this.options = options;
this.store = FsStore.create(options);
}
/**
*
* @param key
* @returns
*/
async get<T>(key: string): Promise<T> {
return await this.store.get(key);
}
/**
*
* @param key
* @param value
* @param ttl
*/
async set<T>(key: string, value: T, ttl: number): Promise<void> {
let t = ttl ? ttl : this.options.ttl;
if (t > 0) {
t = t / 1000;
}
await this.store.set(key, value, {
ttl: t,
});
}
/**
*
* @param key
*/
async del(key: string): Promise<void> {
await this.store.del(key);
}
/**
*
*/
async reset(): Promise<void> {
await this.store.reset();
}
}
export const CoolCacheStore = function (options = {}) {
return new FsCacheStore(options);
};

View File

@ -1,22 +0,0 @@
import { CoolConfig } from "../interface";
/**
* cool的配置
*/
export default {
cool: {
// 是否自动导入数据库
initDB: false,
// 是否自动导入模块菜单
initMenu: true,
// 判断是否初始化的方式
initJudge: "file",
// crud配置
crud: {
// 软删除
softDelete: true,
// 分页查询每页条数
pageSize: 15,
},
} as CoolConfig,
};

View File

@ -1,79 +0,0 @@
import {
App,
Context,
ILifeCycle,
ILogger,
IMidwayBaseApplication,
IMidwayContainer,
Inject,
Logger,
} from "@midwayjs/core";
import { Configuration } from "@midwayjs/decorator";
import * as DefaultConfig from "./config/config.default";
import { CoolExceptionFilter } from "./exception/filter";
import { FuncUtil } from "./util/func";
import * as koa from "@midwayjs/koa";
import { CoolModuleConfig } from "./module/config";
import { CoolModuleImport } from "./module/import";
import { CoolEventManager } from "./event";
import { CoolEps } from "./rest/eps";
import { CoolDecorator } from "./decorator";
import * as cache from "@midwayjs/cache-manager";
import * as _cache from "@midwayjs/cache";
@Configuration({
namespace: "cool",
imports: [_cache, cache],
importConfigs: [
{
default: DefaultConfig,
},
],
})
export class CoolConfiguration implements ILifeCycle {
@Logger()
coreLogger: ILogger;
@App()
app: koa.Application;
@Inject()
coolEventManager: CoolEventManager;
async onReady(container: IMidwayContainer) {
this.coolEventManager.emit("onReady");
// 处理模块配置
await container.getAsync(CoolModuleConfig);
// 常用函数处理
await container.getAsync(FuncUtil);
// 异常处理
this.app.useFilter([CoolExceptionFilter]);
// 装饰器
await container.getAsync(CoolDecorator);
// 缓存设置为全局
// global["COOL-CACHE"] = await container.getAsync(CacheManager);
// // 清除 location
// setTimeout(() => {
// location.clean();
// this.coreLogger.info("\x1B[36m [cool:core] location clean \x1B[0m");
// }, 10000);
}
async onConfigLoad(
container: IMidwayContainer,
mainApp?: IMidwayBaseApplication<Context>
) {}
async onServerReady(container: IMidwayContainer) {
// 事件
await (await container.getAsync(CoolEventManager)).init();
// 导入模块数据
(await container.getAsync(CoolModuleImport)).init();
// 实体与路径
const eps: CoolEps = await container.getAsync(CoolEps);
eps.init();
this.coolEventManager.emit("onServerReady");
// location.clean();
}
}

View File

@ -1,84 +0,0 @@
/**
*
*/
export enum RESCODE {
// 成功
SUCCESS = 1000,
// 失败
COMMFAIL = 1001,
// 参数验证失败
VALIDATEFAIL = 1002,
// 参数验证失败
COREFAIL = 1003,
}
/**
*
*/
export enum RESMESSAGE {
// 成功
SUCCESS = "success",
// 失败
COMMFAIL = "comm fail",
// 参数验证失败
VALIDATEFAIL = "validate fail",
// 核心异常
COREFAIL = "core fail",
}
/**
*
*/
export enum ERRINFO {
NOENTITY = "未设置操作实体",
NOID = "查询参数[id]不存在",
SORTFIELD = "排序参数不正确",
}
/**
*
*/
export enum EVENT {
// 软删除
SOFT_DELETE = "onSoftDelete",
// 服务成功启动
SERVER_READY = "onServerReady",
// 服务就绪
READY = "onReady",
// ES 数据改变
ES_DATA_CHANGE = "esDataChange",
}
export class GlobalConfig {
private static instance: GlobalConfig;
RESCODE = {
SUCCESS: 1000,
COMMFAIL: 1001,
VALIDATEFAIL: 1002,
COREFAIL: 1003,
};
RESMESSAGE = {
SUCCESS: "success",
COMMFAIL: "comm fail",
VALIDATEFAIL: "validate fail",
COREFAIL: "core fail",
};
// ... 其他的配置 ...
private constructor() {}
static getInstance(): GlobalConfig {
if (!GlobalConfig.instance) {
GlobalConfig.instance = new GlobalConfig();
}
return GlobalConfig.instance;
}
}

View File

@ -1,220 +0,0 @@
import {
App,
CONTROLLER_KEY,
getClassMetadata,
Init,
Inject,
Provide,
} from "@midwayjs/decorator";
import { GlobalConfig } from "../constant/global";
import { ControllerOption, CurdOption } from "../decorator/controller";
import { BaseService } from "../service/base";
import { IMidwayApplication } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
/**
*
*/
@Provide()
export abstract class BaseController {
@Inject("ctx")
baseCtx: Context;
@Inject()
service: BaseService;
@App()
baseApp: IMidwayApplication;
curdOption: CurdOption;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
connectionName;
@Init()
async init() {
const option: ControllerOption = getClassMetadata(CONTROLLER_KEY, this);
const curdOption: CurdOption = option.curdOption;
this.curdOption = curdOption;
if (!this.curdOption) {
return;
}
// 操作之前
await this.before(curdOption);
// 设置service
await this.setService(curdOption);
// 设置实体
await this.setEntity(curdOption);
}
private async before(curdOption: CurdOption) {
if (!curdOption?.before) {
return;
}
await curdOption.before(this.baseCtx, this.baseApp);
}
/**
*
* @param curdOption
*/
private async insertParam(curdOption: CurdOption) {
if (!curdOption?.insertParam) {
return;
}
const body = this.baseCtx.request.body;
if (body) {
// 判断body是否是数组
if (Array.isArray(body)) {
for (let i = 0; i < body.length; i++) {
body[i] = {
...body[i],
...(await curdOption.insertParam(this.baseCtx, this.baseApp)),
};
}
this.baseCtx.request.body = body;
return;
}
this.baseCtx.request.body = {
// @ts-ignore
...this.baseCtx.request.body,
...(await curdOption.insertParam(this.baseCtx, this.baseApp)),
};
}
}
/**
*
* @param curdOption
*/
private async setEntity(curdOption: CurdOption) {
const entity = curdOption?.entity;
if (entity) {
const dataSourceName =
this.typeORMDataSourceManager.getDataSourceNameByModel(entity);
this.connectionName = dataSourceName;
let entityModel = this.typeORMDataSourceManager
.getDataSource(dataSourceName)
.getRepository(entity);
this.service.setEntity(entityModel);
}
}
/**
* service
* @param curdOption
*/
private async setService(curdOption: CurdOption) {
if (curdOption.service) {
this.service = await this.baseCtx.requestContext.getAsync(
curdOption.service
);
}
}
/**
*
* @returns
*/
async add() {
// 插入参数
await this.insertParam(this.curdOption);
const { body } = this.baseCtx.request;
return this.ok(await this.service.add(body));
}
/**
*
* @returns
*/
async delete() {
// @ts-ignore
const { ids } = this.baseCtx.request.body;
return this.ok(await this.service.delete(ids));
}
/**
*
* @returns
*/
async update() {
const { body } = this.baseCtx.request;
return this.ok(await this.service.update(body));
}
/**
*
* @returns
*/
async page() {
const { body } = this.baseCtx.request;
return this.ok(
await this.service.page(
body,
this.curdOption.pageQueryOp,
this.connectionName
)
);
}
/**
*
* @returns
*/
async list() {
const { body } = this.baseCtx.request;
return this.ok(
await this.service.list(
body,
this.curdOption.listQueryOp,
this.connectionName
)
);
}
/**
* ID查询信息
* @returns
*/
async info() {
const { id } = this.baseCtx.query;
return this.ok(
await this.service.info(id, this.curdOption.infoIgnoreProperty)
);
}
/**
*
* @param data
*/
ok(data?: any) {
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
const res = {
code: RESCODE.SUCCESS,
message: RESMESSAGE.SUCCESS,
};
if (data || data == 0) {
res["data"] = data;
}
return res;
}
/**
*
* @param message
*/
fail(message?: string, code?: number) {
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
return {
code: code ? code : RESCODE.COMMFAIL,
message: message
? message
: code == RESCODE.VALIDATEFAIL
? RESMESSAGE.VALIDATEFAIL
: RESMESSAGE.COMMFAIL,
};
}
}

View File

@ -1,8 +0,0 @@
import { createCustomMethodDecorator } from "@midwayjs/decorator";
// 装饰器内部的唯一 id
export const COOL_CACHE = "decorator:cool_cache";
export function CoolCache(ttl?: number): MethodDecorator {
return createCustomMethodDecorator(COOL_CACHE, ttl);
}

View File

@ -1,212 +0,0 @@
import { ModuleConfig } from "./../interface";
import {
Scope,
ScopeEnum,
saveClassMetadata,
saveModule,
CONTROLLER_KEY,
MiddlewareParamArray,
WEB_ROUTER_KEY,
attachClassMetadata,
} from "@midwayjs/decorator";
import * as fs from "fs";
import * as _ from "lodash";
import location from "../util/location";
export type ApiTypes = "add" | "delete" | "update" | "page" | "info" | "list";
// Crud配置
export interface CurdOption {
// 路由前缀不配置默认是按Controller下的文件夹路径
prefix?: string;
// curd api接口
api: ApiTypes[];
// 分页查询配置
pageQueryOp?: QueryOp | Function;
// 非分页查询配置
listQueryOp?: QueryOp | Function;
// 插入参数
insertParam?: Function;
// 操作之前
before?: Function;
// info 忽略返回属性
infoIgnoreProperty?: string[];
// 实体
entity: any;
// 服务
service?: any;
// api标签
urlTag?: {
name: "ignoreToken" | string;
url: ApiTypes[];
};
}
export interface JoinOp {
// 实体
entity: any;
// 别名
alias: string;
// 关联条件
condition: string;
// 关联类型
type?: "innerJoin" | "leftJoin";
}
// 字段匹配
export interface FieldEq {
// 字段
column: string;
// 请求参数
requestParam: string;
}
// 查询配置
export interface QueryOp {
// 需要模糊查询的字段
keyWordLikeFields?: string[];
// 查询条件
where?: Function;
// 查询字段
select?: string[];
// 字段相等
fieldEq?: string[] | FieldEq[] | (string | FieldEq)[];
// 添加排序条件
addOrderBy?: {};
// 关联配置
join?: JoinOp[];
// 其他条件
extend?: Function;
}
// Controller 配置
export interface ControllerOption {
// crud配置 如果是字符串则为路由前缀不配置默认是按Controller下的文件夹路径
curdOption?: CurdOption & string;
// 路由配置
routerOptions?: {
// 是否敏感
sensitive?: boolean;
// 路由中间件
middleware?: MiddlewareParamArray;
// 别名
alias?: string[];
// 描述
description?: string;
// 标签名称
tagName?: string;
};
}
// 路由配置
export interface RouterOptions {
sensitive?: boolean;
middleware?: MiddlewareParamArray;
description?: string;
tagName?: string;
ignoreGlobalPrefix?: boolean;
}
// COOL的装饰器
export function CoolController(
curdOption?: CurdOption | string | RouterOptions,
routerOptions: RouterOptions = { middleware: [], sensitive: true }
): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(CONTROLLER_KEY, target);
let prefix;
if (curdOption) {
// 判断 curdOption 的类型
if (typeof curdOption === "string") {
prefix = curdOption;
} else if (curdOption && "api" in curdOption) {
// curdOption 是 CurdOption 类型
prefix = curdOption.prefix || "";
} else {
// curdOption 是 RouterOptions 类型 合并到 routerOptions
routerOptions = { ...curdOption, ...routerOptions };
}
}
// 如果不存在路由前缀,那么自动根据当前文件夹路径
location.scriptPath(target).then(async (res: any) => {
const pathSps = res.path.split(".");
const paths = pathSps[pathSps.length - 2].split(/[/\\]/);
const pathArr = [];
let module = null;
for (const path of paths.reverse()) {
if (path != "controller" && !module) {
pathArr.push(path);
}
if (path == "controller" && !paths.includes("modules")) {
break;
}
if (path == "controller" && paths.includes("modules")) {
module = "ready";
}
if (module && path != "controller") {
module = `${path}`;
break;
}
}
if (module) {
pathArr.reverse();
pathArr.splice(1, 0, module);
// 追加模块中间件
let path = `${res.path.split(new RegExp(`modules[/\\\\]${module}`))[0]}modules/${module}/config.${_.endsWith(res.path, "ts") ? "ts" : "js"}`;
if (fs.existsSync(path)) {
const config: ModuleConfig = require(path).default();
routerOptions.middleware = (config.middlewares || []).concat(
routerOptions.middleware || []
);
}
}
if (!prefix) {
prefix = `/${pathArr.join("/")}`;
}
saveMetadata(prefix, routerOptions, target, curdOption, module);
});
};
}
export const apiDesc = {
add: "新增",
delete: "删除",
update: "修改",
page: "分页查询",
list: "列表查询",
info: "单个信息",
};
// 保存一些元数据信息,任意你希望存的东西
function saveMetadata(prefix, routerOptions, target, curdOption, module) {
if (module && !routerOptions.tagName) {
routerOptions = routerOptions || {};
routerOptions.tagName = module;
}
saveClassMetadata(
CONTROLLER_KEY,
{
prefix,
routerOptions,
curdOption,
module,
} as ControllerOption,
target
);
// 追加CRUD路由
if (!_.isEmpty(curdOption?.api)) {
curdOption?.api.forEach((path) => {
attachClassMetadata(
WEB_ROUTER_KEY,
{
path: `/${path}`,
requestMethod: path == "info" ? "get" : "post",
method: path,
summary: apiDesc[path],
description: "",
},
target
);
});
Scope(ScopeEnum.Request)(target);
}
}

View File

@ -1,54 +0,0 @@
import {
saveClassMetadata,
saveModule,
attachClassMetadata,
} from "@midwayjs/decorator";
import { Scope, ScopeEnum } from "@midwayjs/core";
export const COOL_CLS_EVENT_KEY = "decorator:cool:cls:event";
/**
*
*/
export interface CoolEventOptions {
/** 是否全局 */
isGlobal: boolean;
}
/**
*
* @param options
* @returns
*/
export function CoolEvent(options = {} as CoolEventOptions): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(COOL_CLS_EVENT_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(COOL_CLS_EVENT_KEY, options, target);
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Singleton)(target);
};
}
export const COOL_EVENT_KEY = "decorator:cool:event";
/**
*
* @param eventName
* @returns
*/
export function Event(eventName?: string): MethodDecorator {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
attachClassMetadata(
COOL_EVENT_KEY,
{
eventName,
propertyKey,
descriptor,
},
target
);
};
}

View File

@ -1,112 +0,0 @@
import { COOL_CACHE } from "./cache";
import { CachingFactory, MidwayCache } from "@midwayjs/cache-manager";
import {
Init,
Inject,
InjectClient,
JoinPoint,
MidwayDecoratorService,
Provide,
Scope,
ScopeEnum,
} from "@midwayjs/core";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { CoolCommException } from "../exception/comm";
import { COOL_TRANSACTION, TransactionOptions } from "./transaction";
import * as md5 from "md5";
import { CoolUrlTagData } from "../tag/data";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolDecorator {
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
decoratorService: MidwayDecoratorService;
@InjectClient(CachingFactory, "default")
midwayCache: MidwayCache;
@Inject()
coolUrlTagData: CoolUrlTagData;
@Init()
async init() {
// 事务
await this.transaction();
// 缓存
await this.cache();
// URL标签
await this.coolUrlTagData.init();
}
/**
*
*/
async cache() {
this.decoratorService.registerMethodHandler(COOL_CACHE, (options) => {
return {
around: async (joinPoint: JoinPoint) => {
const key = md5(
joinPoint.target.constructor.name +
joinPoint.methodName +
JSON.stringify(joinPoint.args)
);
// 缓存有数据就返回
let data: any = await this.midwayCache.get(key);
if (data) {
return JSON.parse(data);
} else {
// 执行原始方法
data = await joinPoint.proceed(...joinPoint.args);
await this.midwayCache.set(
key,
JSON.stringify(data),
options.metadata
);
}
return data;
},
};
});
}
/**
*
*/
async transaction() {
this.decoratorService.registerMethodHandler(COOL_TRANSACTION, (options) => {
return {
around: async (joinPoint: JoinPoint) => {
const option: TransactionOptions = options.metadata;
const dataSource = this.typeORMDataSourceManager.getDataSource(
option?.connectionName || "default"
);
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
if (option && option.isolation) {
await queryRunner.startTransaction(option.isolation);
} else {
await queryRunner.startTransaction();
}
let data;
try {
joinPoint.args.push(queryRunner);
data = await joinPoint.proceed(...joinPoint.args);
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw new CoolCommException(error.message);
} finally {
await queryRunner.release();
}
return data;
},
};
});
}
}

View File

@ -1,50 +0,0 @@
import { saveClassMetadata, savePropertyDataToClass, saveModule } from "@midwayjs/decorator";
export const COOL_URL_TAG_KEY = "decorator:cool:url:tag";
export const COOL_METHOD_TAG_KEY = "decorator:cool:method:tag";
export enum TagTypes {
IGNORE_TOKEN = "ignoreToken",
IGNORE_SIGN = "ignoreSign",
}
export interface CoolUrlTagConfig {
key: TagTypes | string;
value?: string[];
}
/**
*
* @param data
* @returns
*/
export function CoolUrlTag(data?: CoolUrlTagConfig): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(COOL_URL_TAG_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(COOL_URL_TAG_KEY, data, target);
};
}
/**
*
* @param data
* @returns
*/
export function CoolTag(tag: TagTypes | string): MethodDecorator {
return (target, key, descriptor: PropertyDescriptor) => {
savePropertyDataToClass(
COOL_METHOD_TAG_KEY,
{
key,
tag
},
target,
key
);
return descriptor;
};
}

View File

@ -1,19 +0,0 @@
import { createCustomMethodDecorator } from "@midwayjs/decorator";
type IsolationLevel =
| "READ UNCOMMITTED"
| "READ COMMITTED"
| "REPEATABLE READ"
| "SERIALIZABLE";
export interface TransactionOptions {
connectionName?: string;
isolation?: IsolationLevel;
}
// 装饰器内部的唯一 id
export const COOL_TRANSACTION = "decorator:cool_transaction";
export function CoolTransaction(option?: TransactionOptions): MethodDecorator {
return createCustomMethodDecorator(COOL_TRANSACTION, option);
}

View File

@ -1,26 +0,0 @@
import {
Index,
UpdateDateColumn,
CreateDateColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { CoolBaseEntity } from "./typeorm";
/**
*
*/
export abstract class BaseEntity extends CoolBaseEntity {
// 默认自增
@PrimaryGeneratedColumn("increment", {
comment: "ID",
})
id: number;
@Index()
@CreateDateColumn({ comment: "创建时间" })
createTime: Date;
@Index()
@UpdateDateColumn({ comment: "更新时间" })
updateTime: Date;
}

View File

@ -1,25 +0,0 @@
import {
Index,
UpdateDateColumn,
CreateDateColumn,
// @ts-ignore
ObjectID,
ObjectIdColumn,
} from "typeorm";
import { CoolBaseEntity } from "./typeorm";
/**
*
*/
export abstract class BaseMongoEntity extends CoolBaseEntity {
@ObjectIdColumn({ comment: "id" })
id: ObjectID;
@Index()
@CreateDateColumn({ comment: "创建时间" })
createTime: Date;
@Index()
@UpdateDateColumn({ comment: "更新时间" })
updateTime: Date;
}

View File

@ -1,3 +0,0 @@
import { BaseEntity } from "typeorm";
export abstract class CoolBaseEntity extends BaseEntity {}

View File

@ -1,190 +0,0 @@
import {
App,
getClassMetadata,
listModule,
Provide,
} from "@midwayjs/decorator";
import * as Events from "events";
import { Scope, ScopeEnum, IMidwayApplication, Config } from "@midwayjs/core";
import { COOL_CLS_EVENT_KEY, COOL_EVENT_KEY } from "../decorator/event";
import * as pm2 from "pm2";
import * as _ from "lodash";
export const COOL_EVENT_MESSAGE = "cool:event:message";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolEventManager extends Events {
@App()
app: IMidwayApplication;
@Config("keys")
keys: string;
// 事件数据 某个事件对应的模块对应的方法
eventData = {} as {
[key: string]: {
module: any;
method: string;
}[];
};
/**
*
*/
async init() {
const eventModules = listModule(COOL_CLS_EVENT_KEY);
for (const module of eventModules) {
await this.handlerEvent(module);
}
await this.commEvent();
await this.globalEvent();
}
/**
*
* @param event
* @param args
* @returns
*/
emit(event: string | symbol, ...args: any[]): boolean {
return super.emit(COOL_EVENT_MESSAGE, {
type: COOL_EVENT_MESSAGE,
data: {
event,
args,
},
});
}
/**
*
* @param event
* @param random
* @param args
* @returns
*/
async globalEmit(event: string, random: boolean = false, ...args) {
// 如果是本地运行还是转普通模式
if (this.app.getEnv() === "local") {
this.emit(event, ...args);
return;
}
pm2.connect(() => {
pm2.list((err, list) => {
const ps = list.map((e) => {
return {
id: e.pm_id,
name: e.name,
};
});
// random 为 true 时随机发给同名称的一个进程
if (random) {
// 按名称分组
const group = _.groupBy(ps, "name");
const names = Object.keys(group);
// 遍历名称
names.forEach((name) => {
const pss = group[name];
// 随机一个
const index = _.random(0, pss.length - 1);
const ps = pss[index];
// 发给这个进程
// @ts-ignore
pm2.sendDataToProcessId(
{
type: "process:msg",
data: {
type: `${COOL_EVENT_MESSAGE}@${this.keys}`,
event,
args,
},
id: ps.id,
topic: "cool:event:topic",
},
(err, res) => {}
);
});
} else {
// 发给所有进程
ps.forEach((e) => {
// @ts-ignore
pm2.sendDataToProcessId(
{
type: "process:msg",
data: {
type: `${COOL_EVENT_MESSAGE}@${this.keys}`,
event,
args,
},
id: e.id,
topic: "cool:event:topic",
},
(err, res) => {}
);
});
}
});
});
}
/**
*
* @param module
*/
async handlerEvent(module) {
const events = getClassMetadata(COOL_EVENT_KEY, module);
for (const event of events) {
const listen = event.eventName ? event.eventName : event.propertyKey;
if (!this.eventData[listen]) {
this.eventData[listen] = [];
}
this.eventData[listen].push({
module,
method: event.propertyKey,
});
}
}
/**
*
*/
async globalEvent() {
process.on("message", async (message: any) => {
const data = message?.data;
if (!data) return;
if (data.type != `${COOL_EVENT_MESSAGE}@${this.keys}`) return;
await this.doAction(message);
});
}
/**
*
*/
async commEvent() {
this.on(COOL_EVENT_MESSAGE, async (message: any) => {
await this.doAction(message);
});
}
/**
*
* @param message
*/
async doAction(message) {
const data = message.data;
const method = data.event;
const args = data.args;
if (this.eventData[method]) {
for (const event of this.eventData[method]) {
const moduleInstance = await this.app
.getApplicationContext()
.getAsync(event.module);
moduleInstance[event.method](...args);
}
}
}
}

View File

@ -1,13 +0,0 @@
/**
*
*/
export class BaseException extends Error {
status: number;
constructor(name: string, code: number, message: string) {
super(message);
this.name = name;
this.status = code;
}
}

View File

@ -1,16 +0,0 @@
import { GlobalConfig } from '../constant/global';
import { BaseException } from './base';
/**
*
*/
export class CoolCommException extends BaseException {
constructor(message: string) {
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
super(
'CoolCommException',
RESCODE.COMMFAIL,
message ? message : RESMESSAGE.COMMFAIL
);
}
}

View File

@ -1,16 +0,0 @@
import { GlobalConfig } from '../constant/global';
import { BaseException } from './base';
/**
*
*/
export class CoolCoreException extends BaseException {
constructor(message: string) {
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
super(
'CoolCoreException',
RESCODE.COREFAIL,
message ? message : RESMESSAGE.COREFAIL
);
}
}

View File

@ -1,21 +0,0 @@
import { ILogger } from '@midwayjs/core';
import { Catch, Logger } from '@midwayjs/decorator';
import { GlobalConfig } from '../constant/global';
/**
*
*/
@Catch()
export class CoolExceptionFilter {
@Logger()
coreLogger: ILogger;
async catch(err) {
const { RESCODE } = GlobalConfig.getInstance();
this.coreLogger.error(err);
return {
code: err.status || RESCODE.COMMFAIL,
message: err.message,
};
}
}

View File

@ -1,16 +0,0 @@
import { GlobalConfig } from '../constant/global';
import { BaseException } from './base';
/**
*
*/
export class CoolValidateException extends BaseException {
constructor(message: string) {
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
super(
'CoolValidateException',
RESCODE.VALIDATEFAIL,
message ? message : RESMESSAGE.VALIDATEFAIL
);
}
}

View File

@ -1,52 +0,0 @@
export { CoolConfiguration as Configuration } from "./configuration";
// 异常处理
export * from "./exception/filter";
export * from "./exception/core";
export * from "./exception/base";
export * from "./exception/comm";
export * from "./exception/validate";
// cache
export * from "./cache/store";
// entity
export * from "./entity/base";
export * from "./entity/typeorm";
export * from "./entity/mongo";
// service
export * from "./service/base";
export * from "./service/mysql";
export * from "./service/postgres";
export * from "./service/sqlite";
// controller
export * from "./controller/base";
// 事件
export * from "./event/index";
// 装饰器
export * from "./decorator/controller";
export * from "./decorator/cache";
export * from "./decorator/event";
export * from "./decorator/transaction";
export * from "./decorator/tag";
export * from "./decorator/index";
// rest
export * from "./rest/eps";
// tag
export * from "./tag/data";
// 模块
export * from "./module/config";
export * from "./module/import";
export * from "./module/menu";
// 其他
export * from "./interface";
export * from "./util/func";
export * from "./constant/global";

View File

@ -1,447 +0,0 @@
import { MiddlewareParamArray } from "@midwayjs/core";
import { AedesOptions } from "aedes";
// @ts-ignore
import { PublishPacket } from "packet";
/**
*
*/
export interface ModuleConfig {
/** 名称 */
name: string;
/** 描述 */
description: string;
/** 模块中间件 */
middlewares?: MiddlewareParamArray;
/** 全局中间件 */
globalMiddlewares?: MiddlewareParamArray;
/** 模块加载顺序默认为0值越大越优先加载 */
order?: number;
}
export interface CoolConfig {
/** 短信 */
sms?: CoolSmsConfig;
/** 是否自动导入数据库 */
initDB?: boolean;
/** Eps */
eps?: boolean;
/** 是否自动导入模块菜单 */
initMenu?: boolean;
/** 判断是否初始化的方式 */
initJudge: "file" | "db";
// 实体配置
// entity?: {
// primaryType: "uuid" | "increment" | "rowid" | "identity";
// };
/** crud配置 */
crud?: {
/** 软删除 */
softDelete: boolean;
/** 分页查询每页条数 */
pageSize: number;
/** 插入方式 */
upsert: "normal" | "save";
// 多租户
// tenant: boolean;
};
/** elasticsearch配置 */
es?: {
nodes: string[];
options?: any;
};
/** pay */
pay?: {
/** 微信支付 */
wx?: CoolWxPayConfig;
/** 支付宝支付 */
ali?: CoolAliPayConfig;
};
/** rpc */
rpc?: CoolRpcConfig;
/** redis */
redis?: RedisConfig | RedisConfig[];
/** 文件上传 */
file?: {
/** 上传模式 */
mode: MODETYPE;
/** 本地上传 文件地址前缀 */
domain?: string;
/** oss */
oss?: OSSConfig;
/** cos */
cos?: COSConfig;
/** qiniu */
qiniu?: QINIUConfig;
/** aws */
aws: AWSConfig;
};
/** IOT 配置 */
iot?: CoolIotConfig;
}
export interface CoolRpcConfig {
/** 服务名称 */
name: string;
/** redis */
redis: RedisConfig & RedisConfig[] & unknown;
}
export interface RedisConfig {
/** host */
host: string;
/** password */
password: string;
/** port */
port: number;
/** db */
db: number;
}
// 模式
export enum MODETYPE {
/** 本地 */
LOCAL = "local",
/** 云存储 */
CLOUD = "cloud",
/** 其他 */
OTHER = "other",
}
export enum CLOUDTYPE {
/** 阿里云存储 */
OSS = "oss",
/** 腾讯云存储 */
COS = "cos",
/** 七牛云存储 */
QINIU = "qiniu",
/** AWS S3 */
AWS = "aws",
}
/**
*
*/
export interface Mode {
/** 模式 */
mode: MODETYPE;
/** 类型 */
type: string;
}
/**
*
*/
export interface CoolFileConfig {
/** 上传模式 */
mode: MODETYPE;
/** 阿里云oss 配置 */
oss: OSSConfig;
/** 腾讯云 cos配置 */
cos: COSConfig;
/** 七牛云 配置 */
qiniu: QINIUConfig;
/** AWS s3 配置 */
aws: AWSConfig;
/** 文件前缀 */
domain: string;
}
/**
* OSS
*/
export interface OSSConfig {
/** 阿里云accessKeyId */
accessKeyId: string;
/** 阿里云accessKeySecret */
accessKeySecret: string;
/** 阿里云oss的bucket */
bucket: string;
/** 阿里云oss的endpoint */
endpoint: string;
/** 阿里云oss的timeout */
timeout: string;
/** 签名失效时间,毫秒 */
expAfter?: number;
/** 文件最大的 size */
maxSize?: number;
// host
host?: string;
// 阿里云oss的公网访问地址
publicDomain?: string;
}
/**
* COS
*/
export interface COSConfig {
/** 腾讯云accessKeyId */
accessKeyId: string;
/** 腾讯云accessKeySecret */
accessKeySecret: string;
/** 腾讯云cos的bucket */
bucket: string;
/** 腾讯云cos的区域 */
region: string;
/** 腾讯云cos的公网访问地址 */
publicDomain: string;
/** 上传持续时间 */
durationSeconds?: number;
/** 允许操作(上传)的对象前缀 */
allowPrefix?: string;
/** 密钥的权限列表 */
allowActions?: string[];
}
export interface QINIUConfig {
/** 七牛云accessKeyId */
accessKeyId: string;
/** 七牛云accessKeySecret */
accessKeySecret: string;
/** 七牛云cos的bucket */
bucket: string;
/** 七牛云cos的区域 */
region: string;
/** 七牛云cos的公网访问地址 */
publicDomain: string;
/** 上传地址 */
uploadUrl?: string;
/** 上传fileKey */
fileKey?: string;
}
export interface AWSConfig {
/** accessKeyId */
accessKeyId: string;
/** secretAccessKey */
secretAccessKey: string;
/** bucket */
bucket: string;
/** region */
region: string;
/** fields */
fields?: any;
/** conditions */
conditions?: any[];
/** expires */
expires?: number;
/** publicDomain */
publicDomain?: string;
/** forcePathStyle */
forcePathStyle?: boolean;
}
/**
*
*/
export interface CoolWxPayConfig {
/** 直连商户申请的公众号或移动应用appid。 */
appid: string;
/** 商户号 */
mchid: string;
/** 可选参数 证书序列号 */
serial_no?: string;
/** 回调链接 */
notify_url: string;
/** 公钥 */
publicKey: Buffer;
/** 私钥 */
privateKey: Buffer;
/** 可选参数 认证类型目前为WECHATPAY2-SHA256-RSA2048 */
authType?: string;
/** 可选参数 User-Agent */
userAgent?: string;
/** 可选参数 APIv3密钥 */
key?: string;
}
/**
*
*/
export interface CoolAliPayConfig {
/** 支付回调地址 */
notifyUrl: string;
/** 应用ID */
appId: string;
/**
*
* RSA签名验签工具https://docs.open.alipay.com/291/106097
* PKCS1(JAVA适用)
*/
privateKey: string;
/** 签名类型 */
signType?: "RSA2" | "RSA";
/** 支付宝公钥(需要对返回值做验签时候必填) */
alipayPublicKey?: string;
/** 网关 */
gateway?: string;
/** 网关超时时间(单位毫秒,默认 5s */
timeout?: number;
/** 是否把网关返回的下划线 key 转换为驼峰写法 */
camelcase?: boolean;
/** 编码(只支持 utf-8 */
charset?: "utf-8";
/** api版本 */
version?: "1.0";
urllib?: any;
/** 指定private key类型, 默认: PKCS1, PKCS8: PRIVATE KEY, PKCS1: RSA PRIVATE KEY */
keyType?: "PKCS1" | "PKCS8";
/** 应用公钥证书文件路径 */
appCertPath?: string;
/** 应用公钥证书文件内容 */
appCertContent?: string | Buffer;
/** 应用公钥证书sn */
appCertSn?: string;
/** 支付宝根证书文件路径 */
alipayRootCertPath?: string;
/** 支付宝根证书文件内容 */
alipayRootCertContent?: string | Buffer;
/** 支付宝根证书sn */
alipayRootCertSn?: string;
/** 支付宝公钥证书文件路径 */
alipayPublicCertPath?: string;
/** 支付宝公钥证书文件内容 */
alipayPublicCertContent?: string | Buffer;
/** 支付宝公钥证书sn */
alipayCertSn?: string;
/** AES密钥调用AES加解密相关接口时需要 */
encryptKey?: string;
/** 服务器地址 */
wsServiceUrl?: string;
}
/**
* IOT配置
*/
export interface CoolIotConfig {
/** MQTT服务端口 */
port: number;
/** MQTT Websocket服务端口 */
wsPort: number;
/** redis 配置 mqtt cluster下必须要配置 */
redis?: {
/** host */
host: string;
/** port */
port: number;
/** password */
password: string;
/** db */
db: number;
};
/** 发布消息配置 */
publish?: PublishPacket;
/** 认证 */
auth?: {
/** 用户 */
username: string;
/** 密码 */
password: string;
};
/** 服务配置 */
serve?: AedesOptions;
}
export interface CoolSmsConfig {
/**
*
*/
ali: CoolSmsAliConfig;
/**
*
*/
tx: CoolSmsTxConfig;
/**
*
*/
yp: CoolSmsYpConfig;
/**
* aws短信配置
*/
aws: CoolSmsAwsConfig;
}
export interface CoolSmsAwsConfig {
/**
*
*/
region: string;
/**
* accessKeyId
*/
accessKeyId: string;
/**
* secretAccessKey
*/
secretAccessKey: string;
/**
*
*/
extend?: any;
}
/**
*
*/
export interface CoolSmsAliConfig {
/**
* accessKeyId
*/
accessKeyId: string;
/**
* accessKeySecret
*/
accessKeySecret: string;
/**
*
*/
signName?: string;
/**
*
*/
template?: string;
}
/**
*
*/
export interface CoolSmsTxConfig {
/**
* ID
*/
appId: string;
/**
* secretId
*/
secretId: string;
/**
* secretKey
*/
secretKey: string;
/**
*
*/
signName?: string;
/**
*
*/
template?: string;
}
/**
*
*/
export interface CoolSmsYpConfig {
/**
* apikey
*/
apikey: string;
/**
*
*/
signName?: string;
/**
*
*/
template?: string;
}

View File

@ -1,100 +0,0 @@
import { IMidwayApplication } from "@midwayjs/core";
import {
ALL,
App,
Config,
Init,
Provide,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import * as fs from "fs";
import { CoolCoreException } from "../exception/core";
import { ModuleConfig } from "../interface";
import * as _ from "lodash";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolModuleConfig {
@App()
app: IMidwayApplication;
@Config(ALL)
allConfig;
modules;
@Init()
async init() {
let modules = [];
// 模块路径
const moduleBasePath = `${this.app.getBaseDir()}/modules/`;
if (!fs.existsSync(moduleBasePath)) {
return;
}
if (!this.allConfig["module"]) {
this.allConfig["module"] = {};
}
// 全局中间件
const globalMiddlewareArr = [];
for (const module of fs.readdirSync(moduleBasePath)) {
const modulePath = `${moduleBasePath}/${module}`;
const dirStats = fs.statSync(modulePath);
if (dirStats.isDirectory()) {
const configPath = fs.existsSync(`${modulePath}/config.ts`)
? `${modulePath}/config.ts`
: `${modulePath}/config.js`;
if (fs.existsSync(configPath)) {
const moduleConfig: ModuleConfig = require(configPath).default({
app: this.app,
env: this.app.getEnv(),
});
modules.push({
order: moduleConfig.order || 0,
module: module,
});
await this.moduleConfig(module, moduleConfig);
// 处理全局中间件
if (!_.isEmpty(moduleConfig.globalMiddlewares)) {
globalMiddlewareArr.push({
order: moduleConfig.order || 0,
data: moduleConfig.globalMiddlewares,
});
}
} else {
throw new CoolCoreException(`模块【${module}】缺少config.ts配置文件`);
}
}
}
this.modules = _.orderBy(modules, ["order"], ["desc"]).map((e) => {
return e.module;
});
await this.globalMiddlewareArr(globalMiddlewareArr);
}
/**
*
* @param module
* @param config
*/
async moduleConfig(module, config) {
// 追加配置
this.allConfig["module"][module] = config;
}
/**
*
* @param middleware
*/
async globalMiddlewareArr(middlewares: any[]) {
middlewares = _.orderBy(middlewares, ["order"], ["desc"]);
for (const middleware of middlewares) {
for (const item of middleware.data) {
this.app.getMiddleware().insertLast(item);
}
}
}
}

View File

@ -1,260 +0,0 @@
import { ILogger, IMidwayApplication, Scope, ScopeEnum } from "@midwayjs/core";
import { App, Config, Inject, Logger, Provide } from "@midwayjs/decorator";
import { InjectDataSource, TypeORMDataSourceManager } from "@midwayjs/typeorm";
import * as fs from "fs";
import * as _ from "lodash";
import * as path from "path";
import { DataSource, Equal } from "typeorm";
import { CoolEventManager } from "../event";
import { CoolModuleConfig } from "./config";
import { CoolModuleMenu } from "./menu";
/**
* sql
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolModuleImport {
@Config("typeorm.dataSource")
ormConfig;
@InjectDataSource("default")
defaultDataSource: DataSource;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Config("cool")
coolConfig;
@Logger()
coreLogger: ILogger;
@Inject()
coolModuleConfig: CoolModuleConfig;
@Inject()
coolEventManager: CoolEventManager;
@App()
app: IMidwayApplication;
@Inject()
coolModuleMenu: CoolModuleMenu;
initJudge: "file" | "db";
/**
*
*/
async init() {
this.initJudge = this.coolConfig.initJudge;
if (!this.initJudge) {
this.initJudge = "file";
}
// 是否需要导入
if (this.coolConfig.initDB) {
const modules = this.coolModuleConfig.modules;
const metadatas = await this.getDbMetadatas();
setTimeout(async () => {
for (const module of modules) {
if (this.initJudge == "file") {
const { exist, lockPath } = this.checkFileExist(module);
if (!exist) {
await this.initDataBase(module, metadatas, lockPath);
}
}
if (this.initJudge == "db") {
const exist = await this.checkDbExist(module, metadatas);
if (!exist) {
await this.initDataBase(module, metadatas);
}
}
}
this.coolEventManager.emit("onDBInit", {});
this.coolModuleMenu.init();
}, 2000);
}
}
/**
*
*/
async getDbMetadatas() {
// 获得所有的实体
const entityMetadatas = this.defaultDataSource.entityMetadatas;
const metadatas = _.mapValues(
_.keyBy(entityMetadatas, "tableName"),
"target"
);
return metadatas;
}
/**
*
* @param module
* @param metadatas
*/
async checkDbExist(module: string, metadatas) {
const cKey = `init_db_${module}`;
const repository = this.defaultDataSource.getRepository(
metadatas["base_sys_conf"]
);
const data = await repository.findOneBy({ cKey: Equal(cKey) });
return !!data;
}
/**
*
* @param module
*/
checkFileExist(module: string) {
const importLockPath = path.join(
`${this.app.getBaseDir()}`,
"..",
"lock",
"db"
);
if (!fs.existsSync(importLockPath)) {
fs.mkdirSync(importLockPath, { recursive: true });
}
const lockPath = path.join(importLockPath, module + ".db.lock");
return {
exist: fs.existsSync(lockPath),
lockPath,
};
}
/**
*
* @param module
* @param lockPath
*/
async initDataBase(module: string, metadatas, lockPath?: string) {
// 计算耗时
const startTime = new Date().getTime();
// 模块路径
const modulePath = `${this.app.getBaseDir()}/modules/${module}`;
// 数据路径
const dataPath = `${modulePath}/db.json`;
// 判断文件是否存在
if (fs.existsSync(dataPath)) {
// 读取数据
const data = JSON.parse(fs.readFileSync(dataPath).toString() || "{}");
// 导入数据
for (const key in data) {
try {
for (const item of data[key]) {
await this.importData(metadatas, item, key);
}
} catch (e) {
this.coreLogger.error(
"\x1B[36m [cool:core] midwayjs cool core init " +
module +
" database err \x1B[0m"
);
continue;
}
}
const endTime = new Date().getTime();
await this.lockImportData(
module,
metadatas,
lockPath,
endTime - startTime
);
this.coreLogger.info(
"\x1B[36m [cool:core] midwayjs cool core init " +
module +
" database complete \x1B[0m"
);
}
}
/**
*
* @param module
* @param metadatas
* @param lockPath
* @param time
*/
async lockImportData(
module: string,
metadatas,
lockPath: string,
time: number
) {
if (this.initJudge == "file") {
fs.writeFileSync(lockPath, `time consuming${time}ms`);
}
if (this.initJudge == "db") {
const repository = this.defaultDataSource.getRepository(
metadatas["base_sys_conf"]
);
if (this.ormConfig.default.type == "postgres") {
await repository.save(
repository.create({
cKey: `init_db_${module}`,
cValue: `time consuming${time}ms`,
})
);
} else {
await repository.insert({
cKey: `init_db_${module}`,
cValue: `time consuming${time}ms`,
});
}
}
}
/**
*
* @param metadatas
* @param datas
* @param tableName
*/
async importData(
metadatas: any[],
item: any,
tableName: string,
parentItem: any = null
) {
const repository = this.defaultDataSource.getRepository(
metadatas[tableName]
);
// 处理当前项中的引用
if (parentItem) {
for (const key in item) {
if (typeof item[key] === "string" && item[key].startsWith("@")) {
const parentKey = item[key].substring(1); // 移除"@"符号
if (parentItem.hasOwnProperty(parentKey)) {
item[key] = parentItem[parentKey];
}
}
}
}
// 插入当前项到数据库
let insertedItem;
if (this.ormConfig.default.type == "postgres") {
insertedItem = await repository.save(repository.create(item));
if (item.id) {
await repository.update(insertedItem.id, { id: item.id });
await this.defaultDataSource.query(
`SELECT setval('${tableName}_id_seq', (SELECT MAX(id) FROM ${tableName}));`
);
}
} else {
insertedItem = await repository.insert(item);
}
// 递归处理@childDatas
if (!_.isEmpty(item["@childDatas"])) {
const childDatas = item["@childDatas"];
delete item["@childDatas"];
for (const childKey in childDatas) {
for (const childItem of childDatas[childKey]) {
await this.importData(metadatas, childItem, childKey, item);
}
}
}
}
}

View File

@ -1,186 +0,0 @@
import {
App,
Config,
ILogger,
IMidwayApplication,
Inject,
Logger,
Provide,
Scope,
ScopeEnum,
} from "@midwayjs/core";
import { InjectDataSource, TypeORMDataSourceManager } from "@midwayjs/typeorm";
import * as fs from "fs";
import * as _ from "lodash";
import * as path from "path";
import { DataSource, Equal } from "typeorm";
import { CoolEventManager } from "../event";
import { CoolConfig } from "../interface";
import { CoolModuleConfig } from "./config";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolModuleMenu {
@Inject()
coolModuleConfig: CoolModuleConfig;
@Config("cool")
coolConfig: CoolConfig;
@App()
app: IMidwayApplication;
@Logger()
coreLogger: ILogger;
@Inject()
coolEventManager: CoolEventManager;
initJudge: "file" | "db";
@Config("typeorm.dataSource")
ormConfig;
@InjectDataSource("default")
defaultDataSource: DataSource;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
datas = {};
async init() {
this.initJudge = this.coolConfig.initJudge;
if (!this.initJudge) {
this.initJudge = "file";
}
// 是否需要导入
if (this.coolConfig.initMenu) {
const modules = this.coolModuleConfig.modules;
const metadatas = await this.getDbMetadatas();
for (const module of modules) {
if (this.initJudge == "file") {
const { exist, lockPath } = this.checkFileExist(module);
if (!exist) {
await this.importMenu(module, metadatas, lockPath);
}
}
if (this.initJudge == "db") {
const exist = await this.checkDbExist(module, metadatas);
if (!exist) {
await this.importMenu(module, metadatas);
}
}
}
this.coolEventManager.emit("onMenuImport", this.datas);
}
}
/**
*
* @param module
* @param lockPath
*/
async importMenu(module: string, metadatas, lockPath?: string) {
// 模块路径
const modulePath = `${this.app.getBaseDir()}/modules/${module}`;
// json 路径
const menuPath = `${modulePath}/menu.json`;
// 导入
if (fs.existsSync(menuPath)) {
const data = fs.readFileSync(menuPath);
try {
// this.coolEventManager.emit("onMenuImport", module, JSON.parse(data.toString()));
this.datas[module] = JSON.parse(data.toString());
await this.lockImportData(module, metadatas, lockPath);
} catch (error) {
this.coreLogger.error(error);
this.coreLogger.error(
`自动初始化模块[${module}]菜单失败,请检查对应的数据结构是否正确`
);
}
}
}
/**
*
*/
async getDbMetadatas() {
// 获得所有的实体
const entityMetadatas = this.defaultDataSource.entityMetadatas;
const metadatas = _.mapValues(
_.keyBy(entityMetadatas, "tableName"),
"target"
);
return metadatas;
}
/**
*
* @param module
* @param metadatas
*/
async checkDbExist(module: string, metadatas) {
const cKey = `init_menu_${module}`;
const repository = this.defaultDataSource.getRepository(
metadatas["base_sys_conf"]
);
const data = await repository.findOneBy({ cKey: Equal(cKey) });
return !!data;
}
/**
*
* @param module
*/
checkFileExist(module: string) {
const importLockPath = path.join(
`${this.app.getBaseDir()}`,
"..",
"lock",
"menu"
);
if (!fs.existsSync(importLockPath)) {
fs.mkdirSync(importLockPath, { recursive: true });
}
const lockPath = path.join(importLockPath, module + ".menu.lock");
return {
exist: fs.existsSync(lockPath),
lockPath,
};
}
/**
*
* @param module
* @param metadatas
* @param lockPath
* @param time
*/
async lockImportData(module: string, metadatas, lockPath: string) {
if (this.initJudge == "file") {
fs.writeFileSync(lockPath, `success`);
}
if (this.initJudge == "db") {
const repository = this.defaultDataSource.getRepository(
metadatas["base_sys_conf"]
);
if (this.ormConfig.default.type == "postgres") {
await repository.save(
repository.create({
cKey: `init_menu_${module}`,
cValue: `success`,
})
);
} else {
await repository.insert({
cKey: `init_menu_${module}`,
cValue: `success`,
});
}
}
}
}

View File

@ -1,62 +0,0 @@
{
"name": "@cool-midway/core",
"version": "7.1.25",
"description": "",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [
"cool",
"cool-admin",
"cooljs"
],
"readme": "README.md",
"author": "COOL",
"files": [
"**/*.js",
"**/*.d.ts",
"index.d.ts"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"devDependencies": {
"@midwayjs/cli": "1.3.21",
"@midwayjs/core": "^3.9.0",
"@midwayjs/decorator": "^3.9.0",
"@midwayjs/koa": "^3.9.0",
"@midwayjs/mock": "^3.9.0",
"@midwayjs/typeorm": "^3.9.0",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.15",
"cross-env": "^7.0.3",
"jest": "^29.3.1",
"mwts": "^1.3.0",
"ts-jest": "^29.0.3",
"typeorm": "^0.3.19",
"typescript": "~4.9.4"
},
"dependencies": {
"@midwayjs/cache": "^3.14.0",
"@midwayjs/cache-manager": "^3.15.0",
"@cool-midway/cache-manager-fs-hash": "^7.0.0",
"axios": "^1.6.5",
"decompress": "^4.2.1",
"download": "^8.0.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"moment": "^2.30.1",
"sqlstring": "^2.3.3",
"uuid": "^9.0.1",
"ws": "^8.16.0",
"pm2": "^5.3.1"
}
}

View File

@ -1,169 +0,0 @@
import {
CONTROLLER_KEY,
getClassMetadata,
listModule,
Provide,
} from "@midwayjs/decorator";
import * as _ from "lodash";
import {
Scope,
ScopeEnum,
Config,
Inject,
MidwayWebRouterService,
} from "@midwayjs/core";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { CoolUrlTagData } from "../tag/data";
import { TagTypes } from "../decorator/tag";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolEps {
admin = {};
app = {};
module = {};
@Inject()
midwayWebRouterService: MidwayWebRouterService;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Config("cool.eps")
epsConfig: boolean;
@Config("module")
moduleConfig: any;
@Inject()
coolUrlTagData: CoolUrlTagData;
// @Init()
async init() {
if (!this.epsConfig) return;
const entitys = await this.entity();
const controllers = await this.controller();
const routers = await this.router();
await this.modules();
const adminArr = [];
const appArr = [];
for (const controller of controllers) {
const { prefix, module, curdOption, routerOptions } = controller;
const name = curdOption?.entity?.name;
(_.startsWith(prefix, "/admin/") ? adminArr : appArr).push({
module,
info: {
type: {
name: prefix.split("/").pop(),
description: routerOptions?.description || "",
},
},
api: routers[prefix],
name,
columns: entitys[name] || [],
prefix,
});
}
this.admin = _.groupBy(adminArr, "module");
this.app = _.groupBy(appArr, "module");
}
/**
*
* @param module
*/
async modules(module?: string) {
for (const key in this.moduleConfig) {
const config = this.moduleConfig[key];
this.module[key] = {
name: config.name,
description: config.description,
};
}
return module ? this.module[module] : this.module;
}
/**
* controller
* @returns
*/
async controller() {
const result = [];
const controllers = listModule(CONTROLLER_KEY);
for (const controller of controllers) {
result.push(getClassMetadata(CONTROLLER_KEY, controller));
}
return result;
}
/**
*
* @returns
*/
async router() {
let ignoreUrls: string[] = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN);
if (_.isEmpty(ignoreUrls)) {
ignoreUrls = [];
}
return _.groupBy(
(await this.midwayWebRouterService.getFlattenRouterTable()).map(
(item) => {
return {
method: item.requestMethod,
path: item.url,
summary: item.summary,
dts: {},
tag: "",
prefix: item.prefix,
ignoreToken: ignoreUrls.includes(item.prefix + item.url),
};
}
),
"prefix"
);
}
/**
*
* @returns
*/
async entity() {
const result = {};
const dataSourceNames = this.typeORMDataSourceManager.getDataSourceNames();
for (const dataSourceName of dataSourceNames) {
const entityMetadatas = await this.typeORMDataSourceManager.getDataSource(
dataSourceName
).entityMetadatas;
for (const entityMetadata of entityMetadatas) {
const commColums = [];
let columns = entityMetadata.columns;
if (entityMetadata.tableType != "regular") continue;
columns = _.filter(
columns.map((e) => {
return {
propertyName: e.propertyName,
type:
typeof e.type == "string" ? e.type : e.type.name.toLowerCase(),
length: e.length,
comment: e.comment,
nullable: e.isNullable,
};
}),
(o) => {
if (["createTime", "updateTime"].includes(o.propertyName)) {
commColums.push(o);
}
return o && !["createTime", "updateTime"].includes(o.propertyName);
}
).concat(commColums);
result[entityMetadata.name] = columns;
}
}
return result;
}
}

View File

@ -1,287 +0,0 @@
import { App, Config, Init, Inject, Provide } from "@midwayjs/decorator";
import { Scope, ScopeEnum } from "@midwayjs/core";
import { BaseMysqlService } from "./mysql";
import { BasePgService } from "./postgres";
import { CoolValidateException } from "../exception/validate";
import { ERRINFO } from "../constant/global";
import { Application, Context } from "@midwayjs/koa";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { Repository, SelectQueryBuilder } from "typeorm";
import { QueryOp } from "../decorator/controller";
import * as _ from "lodash";
import { CoolEventManager } from "../event";
import { CoolCoreException } from "../exception/core";
import { BaseSqliteService } from "./sqlite";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseService {
// mysql的基类
@Inject()
baseMysqlService: BaseMysqlService;
// postgres的基类
@Inject()
basePgService: BasePgService;
@Inject()
baseSqliteService: BaseSqliteService;
// 数据库类型
@Config("typeorm.dataSource.default.type")
ormType;
// 当前服务名称
service: BaseMysqlService | BasePgService | BaseSqliteService;
// 模型
protected entity: Repository<any>;
protected sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
@Inject("ctx")
baseCtx: Context;
@App()
baseApp: Application;
@Init()
async init() {
const services = {
mysql: this.baseMysqlService,
mariadb: this.baseMysqlService,
postgres: this.basePgService,
sqlite: this.baseSqliteService,
};
this.service = services[this.ormType];
if (!this.service) throw new CoolCoreException("暂不支持当前数据库类型");
this.sqlParams = this.service.sqlParams;
await this.service.init();
}
// 设置模型
setEntity(entity: any) {
this.entity = entity;
this.service.setEntity(entity);
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
this.service.setCtx(ctx);
}
// 设置应用对象
setApp(app: Application) {
this.baseApp = app;
this.service.setApp(app);
}
/**
* sql
* @param condition
* @param sql sql语句
* @param params
*/
setSql(condition, sql, params) {
return this.service.setSql(condition, sql, params);
}
/**
* SQL
* @param sql
*/
getCountSql(sql) {
return this.service.getCountSql(sql);
}
/**
*
* @param params
*/
async paramSafetyCheck(params) {
return await this.service.paramSafetyCheck(params);
}
/**
*
* @param sql
* @param params
* @param connectionName
*/
async nativeQuery(sql, params?, connectionName?) {
return await this.service.nativeQuery(sql, params, connectionName);
}
/**
* ORM管理
* @param connectionName
*/
getOrmManager(connectionName = "default") {
return this.service.getOrmManager(connectionName);
}
/**
* entity获得分页数据sql
* @param find QueryBuilder
* @param query
* @param autoSort
* @param connectionName
*/
async entityRenderPage(
find: SelectQueryBuilder<any>,
query,
autoSort = true
) {
return await this.service.entityRenderPage(find, query, autoSort);
}
/**
* SQL并获得分页数据
* @param sql sql语句
* @param query
* @param autoSort
* @param connectionName
*/
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
return await this.service.sqlRenderPage(
sql,
query,
autoSort,
connectionName
);
}
/**
* ID
* @param id ID
* @param infoIgnoreProperty
*/
async info(id: any, infoIgnoreProperty?: string[]) {
this.service.setEntity(this.entity);
return await this.service.info(id, infoIgnoreProperty);
}
/**
*
* @param ids ID集合 [1,2,3] 1,2,3
*/
async delete(ids: any) {
this.service.setEntity(this.entity);
await this.modifyBefore(ids, "delete");
await this.service.delete(ids);
await this.modifyAfter(ids, "delete");
}
/**
*
* @param ids ID数组
* @param entity
*/
async softDelete(ids: number[], entity?: Repository<any>) {
this.service.setEntity(this.entity);
await this.service.softDelete(ids, entity);
}
/**
*
* @param param
*/
async update(param: any) {
this.service.setEntity(this.entity);
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (!param.id && !(param instanceof Array))
throw new CoolValidateException(ERRINFO.NOID);
await this.addOrUpdate(param, "update");
}
/**
*
* @param param
*/
async add(param: any | any[]): Promise<Object> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
delete param.id;
await this.addOrUpdate(param, "add");
return {
id:
param instanceof Array
? param.map((e) => {
return e.id ? e.id : e._id;
})
: param.id
? param.id
: param._id,
};
}
/**
* |
* @param param
*/
async addOrUpdate(param: any | any[], type: "add" | "update" = "add") {
this.service.setEntity(this.entity);
await this.modifyBefore(param, type);
await this.service.addOrUpdate(param, type);
await this.modifyAfter(param, type);
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async list(query, option, connectionName?): Promise<any> {
this.service.setEntity(this.entity);
return await this.service.list(query, option, connectionName);
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async page(query, option, connectionName?) {
this.service.setEntity(this.entity);
return await this.service.page(query, option, connectionName);
}
/**
*
* @param query
* @param option
*/
async getOptionFind(query, option: QueryOp) {
this.service.setEntity(this.entity);
return await this.service.getOptionFind(query, option);
}
/**
* ||
* @param data
*/
async modifyAfter(
data: any,
type: "delete" | "update" | "add"
): Promise<void> {}
/**
* ||
* @param data
*/
async modifyBefore(
data: any,
type: "delete" | "update" | "add"
): Promise<void> {}
}

View File

@ -1,499 +0,0 @@
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
import { Scope, ScopeEnum } from "@midwayjs/core";
import { CoolValidateException } from "../exception/validate";
import { ERRINFO, EVENT } from "../constant/global";
import { Application, Context } from "@midwayjs/koa";
import * as SqlString from "sqlstring";
import { CoolConfig } from "../interface";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { Brackets, Equal, In, Repository, SelectQueryBuilder } from "typeorm";
import { QueryOp } from "../decorator/controller";
import * as _ from "lodash";
import { CoolEventManager } from "../event";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseMysqlService {
// 分页配置
@Config("cool")
private _coolConfig: CoolConfig;
// 模型
entity: Repository<any>;
sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
}
@App()
baseApp: Application;
// 设置应用对象
setApp(app: Application) {
this.baseApp = app;
}
@Inject("ctx")
baseCtx: Context;
// 初始化
@Init()
init() {
this.sqlParams = [];
}
/**
* sql
* @param condition
* @param sql sql语句
* @param params
*/
setSql(condition, sql, params) {
let rSql = false;
if (condition || condition === 0) {
rSql = true;
this.sqlParams = this.sqlParams.concat(params);
}
return rSql ? sql : "";
}
/**
* SQL
* @param sql
*/
getCountSql(sql) {
sql = sql
.replace(new RegExp("LIMIT", "gm"), "limit ")
.replace(new RegExp("\n", "gm"), " ");
if (sql.includes("limit")) {
const sqlArr = sql.split("limit ");
sqlArr.pop();
sql = sqlArr.join("limit ");
}
return `select count(*) as count from (${sql}) a`;
}
/**
*
* @param params
*/
async paramSafetyCheck(params) {
const lp = params.toLowerCase();
return !(
lp.indexOf("update ") > -1 ||
lp.indexOf("select ") > -1 ||
lp.indexOf("delete ") > -1 ||
lp.indexOf("insert ") > -1
);
}
/**
*
* @param sql
* @param params
* @param connectionName
*/
async nativeQuery(sql, params?, connectionName?) {
if (_.isEmpty(params)) {
params = this.sqlParams;
}
let newParams = [];
newParams = newParams.concat(params);
this.sqlParams = [];
for (const param of newParams) {
SqlString.escape(param);
}
return await this.getOrmManager(connectionName).query(sql, newParams || []);
}
/**
* ORM管理
* @param connectionName
*/
getOrmManager(connectionName = "default") {
return this.typeORMDataSourceManager.getDataSource(connectionName);
}
/**
* entity获得分页数据sql
* @param find QueryBuilder
* @param query
* @param autoSort
* @param connectionName
*/
async entityRenderPage(
find: SelectQueryBuilder<any>,
query,
autoSort = true
) {
const {
size = this._coolConfig.crud.pageSize,
page = 1,
order = "createTime",
sort = "desc",
isExport = false,
maxExportLimit,
} = query;
const count = await find.getCount();
let dataFind: SelectQueryBuilder<any>;
if (isExport && maxExportLimit > 0) {
dataFind = find.limit(maxExportLimit);
} else {
dataFind = find.offset((page - 1) * size).limit(size);
}
if (autoSort) {
find.addOrderBy(order, sort.toUpperCase());
}
return {
list: await dataFind.getMany(),
pagination: {
page: parseInt(page),
size: parseInt(size),
total: count,
},
};
}
/**
* SQL并获得分页数据
* @param sql sql语句
* @param query
* @param autoSort
* @param connectionName
*/
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
const {
size = this._coolConfig.crud.pageSize,
page = 1,
order = "createTime",
sort = "desc",
isExport = false,
maxExportLimit,
} = query;
if (order && sort && autoSort) {
if (!(await this.paramSafetyCheck(order + sort))) {
throw new CoolValidateException("非法传参~");
}
sql += ` ORDER BY ${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
}
if (isExport && maxExportLimit > 0) {
this.sqlParams.push(parseInt(maxExportLimit));
sql += " LIMIT ? ";
}
if (!isExport) {
this.sqlParams.push((page - 1) * size);
this.sqlParams.push(parseInt(size));
sql += " LIMIT ?,? ";
}
let params = [];
params = params.concat(this.sqlParams);
const result = await this.nativeQuery(sql, params, connectionName);
const countResult = await this.nativeQuery(
this.getCountSql(sql),
params,
connectionName
);
return {
list: result,
pagination: {
page: parseInt(page),
size: parseInt(size),
total: parseInt(countResult[0] ? countResult[0].count : 0),
},
};
}
/**
*
* @param sort
* @returns
*/
checkSort(sort) {
if (!["desc", "asc"].includes(sort.toLowerCase())) {
throw new CoolValidateException("sort 非法传参~");
}
return sort;
}
/**
* ID
* @param id ID
* @param infoIgnoreProperty
*/
async info(id: any, infoIgnoreProperty?: string[]) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (!id) {
throw new CoolValidateException(ERRINFO.NOID);
}
const info = await this.entity.findOneBy({ id: Equal(id) });
if (info && infoIgnoreProperty) {
for (const property of infoIgnoreProperty) {
delete info[property];
}
}
return info;
}
/**
*
* @param ids ID集合 [1,2,3] 1,2,3
*/
async delete(ids: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (ids instanceof String) {
ids = ids.split(",");
}
// 启动软删除发送事件
if (this._coolConfig.crud?.softDelete) {
this.softDelete(ids);
}
await this.entity.delete(ids);
}
/**
*
* @param ids ID数组
* @param entity
*/
async softDelete(ids: number[], entity?: Repository<any>) {
const data = await this.entity.find({
where: {
id: In(ids),
},
});
if (_.isEmpty(data)) return;
const _entity = entity ? entity : this.entity;
const params = {
data,
ctx: this.baseCtx,
entity: _entity,
};
if (data.length > 0) {
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
}
}
/**
* |
* @param param
*/
async addOrUpdate(param: any | any[], type: "add" | "update" = "add") {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
delete param.createTime;
// 判断是否是批量操作
if (param instanceof Array) {
param.forEach((item) => {
item.updateTime = new Date();
item.createTime = new Date();
});
await this.entity.save(param);
} else {
const upsert = this._coolConfig.crud?.upsert || "normal";
if (type == "update") {
if (upsert == "save") {
const info = await this.entity.findOneBy({ id: param.id });
param = {
...info,
...param,
};
}
param.updateTime = new Date();
upsert == "normal"
? await this.entity.update(param.id, param)
: await this.entity.save(param);
}
if (type == "add") {
param.createTime = new Date();
param.updateTime = new Date();
upsert == "normal"
? await this.entity.insert(param)
: await this.entity.save(param);
}
}
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async list(query, option, connectionName?): Promise<any> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, option);
return this.nativeQuery(sql, [], connectionName);
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async page(query, option, connectionName?) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, option);
return this.sqlRenderPage(sql, query, false, connectionName);
}
/**
*
* @param query
* @param option
*/
async getOptionFind(query, option: QueryOp) {
let { order = "createTime", sort = "desc", keyWord = "" } = query;
const sqlArr = ["SELECT"];
const selects = ["a.*"];
const find = this.entity.createQueryBuilder("a");
if (option) {
if (typeof option == "function") {
// @ts-ignore
option = await option(this.baseCtx, this.baseApp);
}
// 判断是否有关联查询,有的话取个别名
if (!_.isEmpty(option.join)) {
for (const item of option.join) {
selects.push(`${item.alias}.*`);
find[item.type || "leftJoin"](
item.entity,
item.alias,
item.condition
);
}
}
// 默认条件
if (option.where) {
const wheres =
typeof option.where == "function"
? await option.where(this.baseCtx, this.baseApp)
: option.where;
if (!_.isEmpty(wheres)) {
for (const item of wheres) {
if (
item.length == 2 ||
(item.length == 3 && (item[2] || item[2] === 0))
) {
for (const key in item[1]) {
this.sqlParams.push(item[1][key]);
}
find.andWhere(item[0], item[1]);
}
}
}
}
// 附加排序
if (!_.isEmpty(option.addOrderBy)) {
for (const key in option.addOrderBy) {
if (order && order == key) {
sort = option.addOrderBy[key].toUpperCase();
}
find.addOrderBy(
SqlString.escapeId(key),
this.checkSort(option.addOrderBy[key].toUpperCase())
);
}
}
// 关键字模糊搜索
if (keyWord || keyWord === 0) {
keyWord = `%${keyWord}%`;
find.andWhere(
new Brackets((qb) => {
const keyWordLikeFields = option.keyWordLikeFields || [];
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
keyWord,
});
this.sqlParams.push(keyWord);
}
})
);
}
// 筛选字段
if (!_.isEmpty(option.select)) {
sqlArr.push(option.select.join(","));
find.select(option.select);
} else {
sqlArr.push(selects.join(","));
}
// 字段全匹配
if (!_.isEmpty(option.fieldEq)) {
for (let key of option.fieldEq) {
const c = {};
// 如果key有包含.的情况下操作
if (typeof key === "string" && key.includes(".")) {
const keys = key.split(".");
const lastKey = keys.pop();
key = { requestParam: lastKey, column: key };
}
// 单表字段无别名的情况下操作
if (typeof key === "string") {
if (query[key] || query[key] === 0) {
c[key] = query[key];
const eq = query[key] instanceof Array ? "in" : "=";
if (eq === "in") {
find.andWhere(`${key} ${eq} (:${key})`, c);
} else {
find.andWhere(`${key} ${eq} :${key}`, c);
}
this.sqlParams.push(query[key]);
}
} else {
if (query[key.requestParam] || query[key.requestParam] === 0) {
c[key.column] = query[key.requestParam];
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
if (eq === "in") {
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
} else {
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
}
this.sqlParams.push(query[key.requestParam]);
}
}
}
}
} else {
sqlArr.push(selects.join(","));
}
// 接口请求的排序
if (sort && order) {
const sorts = sort.toUpperCase().split(",");
const orders = order.split(",");
if (sorts.length != orders.length) {
throw new CoolValidateException(ERRINFO.SORTFIELD);
}
for (const i in sorts) {
find.addOrderBy(
SqlString.escapeId(orders[i]),
this.checkSort(sorts[i])
);
}
}
if (option?.extend) {
await option?.extend(find, this.baseCtx, this.baseApp);
}
const sqls = find.getSql().split("FROM");
sqlArr.push("FROM");
// 取sqls的最后一个
sqlArr.push(sqls[sqls.length - 1]);
return sqlArr.join(" ");
}
}

View File

@ -1,639 +0,0 @@
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
import { CoolValidateException } from "../exception/validate";
import { ERRINFO, EVENT } from "../constant/global";
import { Application, Context } from "@midwayjs/koa";
import { Scope, ScopeEnum } from "@midwayjs/core";
import { CoolConfig } from "../interface";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { Brackets, In, Repository, SelectQueryBuilder } from "typeorm";
import { QueryOp } from "../decorator/controller";
import * as _ from "lodash";
import { CoolEventManager } from "../event";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BasePgService {
// 分页配置
@Config("cool")
private _coolConfig: CoolConfig;
// 模型
entity: Repository<any>;
sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
}
@App()
baseApp: Application;
// 设置应用对象
setApp(app: Application) {
this.baseApp = app;
}
@Inject("ctx")
baseCtx: Context;
// 初始化
@Init()
init() {
this.sqlParams = [];
}
/**
* sql
* @param condition
* @param sql sql语句
* @param params
*/
setSql(condition, sql, params) {
let rSql = false;
if (condition || condition === 0) {
rSql = true;
for (let i = 0; i < params.length; i++) {
const param = params[i];
if (param instanceof Array) {
// 将这个? 替换成 $1,$2,$3
const replaceStr = [];
for (let j = 0; j < param.length; j++) {
replaceStr.push("$" + (this.sqlParams.length + j + 1));
}
this.sqlParams = this.sqlParams.concat(...params);
sql = sql.replace("?", replaceStr.join(","));
} else {
sql = sql.replace("?", "$" + (this.sqlParams.length + 1));
this.sqlParams.push(param);
}
}
}
return rSql ? sql : "";
}
/**
* SQL
* @param sql
*/
getCountSql(sql) {
sql = sql
.replace(new RegExp("LIMIT", "gm"), "limit ")
.replace(new RegExp("\n", "gm"), " ");
if (sql.includes("limit")) {
const sqlArr = sql.split("limit ");
sqlArr.pop();
sql = sqlArr.join("limit ");
}
return `select count(*) as count from (${sql}) a`;
}
/**
*
* @param params
*/
async paramSafetyCheck(params) {
const lp = params.toLowerCase();
return !(
lp.indexOf("update ") > -1 ||
lp.indexOf("select ") > -1 ||
lp.indexOf("delete ") > -1 ||
lp.indexOf("insert ") > -1
);
}
/**
*
* @param sql
* @param params
* @param connectionName
*/
async nativeQuery(sql, params?, connectionName?) {
sql = this.convertToPostgres(sql);
if (_.isEmpty(params)) {
params = this.sqlParams;
}
let newParams = [];
// sql没处理过?的情况下
if (sql.includes("?")) {
for (const item of params) {
// 如果是数组,将这个? 替换成 $1,$2,$3
if (item instanceof Array) {
const replaceStr = [];
for (let i = 0; i < item.length; i++) {
replaceStr.push("$" + (newParams.length + i + 1));
}
newParams.push(...item);
sql = sql.replace("?", replaceStr.join(","));
} else {
sql = sql.replace("?", "$" + (newParams.length + 1));
newParams.push(item);
}
}
} else {
newParams = params;
}
this.sqlParams = [];
return await this.getOrmManager(connectionName).query(sql, newParams || []);
}
/**
* ORM管理
* @param connectionName
*/
getOrmManager(connectionName = "default") {
return this.typeORMDataSourceManager.getDataSource(connectionName);
}
/**
* entity获得分页数据sql
* @param find QueryBuilder
* @param query
* @param autoSort
* @param connectionName
*/
async entityRenderPage(
find: SelectQueryBuilder<any>,
query,
autoSort = true
) {
const {
size = this._coolConfig.crud.pageSize,
page = 1,
order = "createTime",
sort = "desc",
isExport = false,
maxExportLimit,
} = query;
const count = await find.getCount();
let dataFind: SelectQueryBuilder<any>;
if (isExport && maxExportLimit > 0) {
dataFind = find.limit(maxExportLimit);
} else {
dataFind = find.offset((page - 1) * size).limit(size);
}
if (autoSort) {
find.addOrderBy(order, sort.toUpperCase());
}
return {
list: await dataFind.getMany(),
pagination: {
page: parseInt(page),
size: parseInt(size),
total: count,
},
};
}
/**
* mysql语句转换为postgres语句
* @param sql
* @returns
*/
protected convertToPostgres(sql) {
// 首先确保表名被正确引用
sql = sql.replace(/(?<!")(\b\w+\b)\.(?!\w+")/g, '"$1".');
// 然后确保字段名被正确引用
return sql.replace(/\.(\w+)(?!\w)/g, '."$1"');
}
/**
* sql中的参数个数
* @param sql
* @returns
*/
protected countDollarSigns(sql) {
const matches = sql.match(/\$\d+/g);
return matches ? matches.length : 0;
}
/**
* SQL并获得分页数据
* @param sql sql语句
* @param query
* @param autoSort
* @param connectionName
*/
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
const {
size = this._coolConfig.crud.pageSize,
page = 1,
order = "createTime",
sort = "desc",
isExport = false,
maxExportLimit,
} = query;
sql = `SELECT * FROM (${sql}) a `;
if (order && sort && autoSort) {
if (!(await this.paramSafetyCheck(order + sort))) {
throw new CoolValidateException("非法传参~");
}
sql += `ORDER BY a."${order}" ${this.checkSort(sort)}`;
}
let cutParams = 0;
let paramCount = this.countDollarSigns(sql);
if (isExport && maxExportLimit > 0) {
this.sqlParams.push(parseInt(maxExportLimit));
cutParams = 1;
sql += ` LIMIT $${paramCount + 1}`;
}
if (!isExport) {
this.sqlParams.push(parseInt(size));
this.sqlParams.push((page - 1) * size);
cutParams = 2;
sql += ` LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`;
}
let params = [];
params = params.concat(this.sqlParams);
const result = await this.nativeQuery(sql, params, connectionName);
params = params.slice(0, -cutParams);
const countResult = await this.nativeQuery(
this.getCountSql(sql),
params,
connectionName
);
return {
list: result,
pagination: {
page: parseInt(page),
size: parseInt(size),
total: parseInt(countResult[0] ? countResult[0].count : 0),
},
};
}
/**
*
* @param sort
* @returns
*/
checkSort(sort) {
if (!["desc", "asc"].includes(sort.toLowerCase())) {
throw new CoolValidateException("sort 非法传参~");
}
return sort;
}
/**
* ID
* @param id ID
* @param infoIgnoreProperty
*/
async info(id: any, infoIgnoreProperty?: string[]) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (!id) {
throw new CoolValidateException(ERRINFO.NOID);
}
const info = await this.entity.findOneBy({ id });
if (info && infoIgnoreProperty) {
for (const property of infoIgnoreProperty) {
delete info[property];
}
}
return info;
}
/**
*
* @param ids ID集合 [1,2,3] 1,2,3
*/
async delete(ids: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (ids instanceof String) {
ids = ids.split(",");
}
// 启动软删除发送事件
if (this._coolConfig.crud?.softDelete) {
this.softDelete(ids);
}
await this.entity.delete(ids);
}
/**
*
* @param ids ID数组
* @param entity
*/
async softDelete(ids: number[], entity?: Repository<any>) {
const data = await this.entity.find({
where: {
id: In(ids),
},
});
if (_.isEmpty(data)) return;
const _entity = entity ? entity : this.entity;
const params = {
data,
ctx: this.baseCtx,
entity: _entity,
};
if (data.length > 0) {
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
}
}
/**
* |
* @param param
*/
async addOrUpdate(param: any | any[], type: "add" | "update" = "add") {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
delete param.createTime;
// 判断是否是批量操作
if (param instanceof Array) {
param.forEach((item) => {
item.updateTime = new Date();
item.createTime = new Date();
});
await this.entity.save(param);
} else {
const upsert = this._coolConfig.crud?.upsert || "normal";
if (type == "update") {
if (upsert == "save") {
const info = await this.entity.findOneBy({ id: param.id });
param = {
...info,
...param,
};
}
param.updateTime = new Date();
upsert == "normal"
? await this.entity.update(param.id, param)
: await this.entity.save(param);
}
if (type == "add") {
param.createTime = new Date();
param.updateTime = new Date();
upsert == "normal"
? await this.entity.insert(param)
: await this.entity.save(param);
}
}
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async list(query, option, connectionName?): Promise<any> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, option);
return this.nativeQuery(sql, [], connectionName);
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async page(query, option, connectionName?) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, option);
return this.sqlRenderPage(sql, query, false, connectionName);
}
/**
*
* @param query
* @param option
*/
async getOptionFind(query, option: QueryOp) {
let { order = "createTime", sort = "desc", keyWord = "" } = query;
const sqlArr = ["SELECT"];
const selects = ["a.*"];
const find = this.entity.createQueryBuilder("a");
if (option) {
if (typeof option == "function") {
// @ts-ignore
option = await option(this.baseCtx, this.baseApp);
}
// 判断是否有关联查询,有的话取个别名
if (!_.isEmpty(option.join)) {
for (const item of option.join) {
selects.push(`${item.alias}.*`);
find[item.type || "leftJoin"](
item.entity,
item.alias,
item.condition
);
}
}
// 默认条件
if (option.where) {
const wheres =
typeof option.where == "function"
? await option.where(this.baseCtx, this.baseApp)
: option.where;
if (!_.isEmpty(wheres)) {
for (const item of wheres) {
if (
item.length == 2 ||
(item.length == 3 && (item[2] || item[2] === 0))
) {
for (const key in item[1]) {
this.sqlParams.push(item[1][key]);
}
find.andWhere(item[0], item[1]);
}
}
}
}
// 附加排序
if (!_.isEmpty(option.addOrderBy)) {
for (const key in option.addOrderBy) {
if (order && order == key) {
sort = option.addOrderBy[key].toUpperCase();
}
find.addOrderBy(
`${this.matchColumn(option?.select, key)}.${key}`,
this.checkSort(option.addOrderBy[key].toUpperCase())
);
}
}
// 关键字模糊搜索
if (keyWord || keyWord == 0) {
keyWord = `%${keyWord}%`;
find.andWhere(
new Brackets((qb) => {
const keyWordLikeFields = option.keyWordLikeFields || [];
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
let column = keyWordLikeFields[i];
column = column.includes(".") ? column : `a.${column}`;
const values = {};
values[`keyWord${i}`] = keyWord;
qb.orWhere(`${column} like :keyWord${i}`, values);
this.sqlParams.push(keyWord);
}
})
);
}
// 筛选字段
if (!_.isEmpty(option.select)) {
sqlArr.push(option.select.join(","));
find.select(option.select);
} else {
sqlArr.push(selects.join(","));
}
// 字段全匹配
if (!_.isEmpty(option.fieldEq)) {
for (let key of option.fieldEq) {
const c = {};
let column;
// 如果key有包含.的情况下操作
if (typeof key === "string" && key.includes(".")) {
const keys = key.split(".");
const lastKey = keys.pop();
key = { requestParam: lastKey, column: key };
column = key;
} else {
column = `a.${key}`;
}
// 单表字段无别名的情况下操作
if (typeof key === "string") {
if (query[key] || query[key] == 0) {
c[key] = query[key];
const eq = query[key] instanceof Array ? "in" : "=";
if (eq === "in") {
find.andWhere(`${column} ${eq} (:...${key})`, c);
} else {
find.andWhere(`${column} ${eq} :${key}`, c);
}
this.sqlParams.push(query[key]);
}
} else {
if (query[key.requestParam] || query[key.requestParam] == 0) {
c[key.column] = query[key.requestParam];
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
if (eq === "in") {
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
} else {
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
}
this.sqlParams.push(query[key.requestParam]);
}
}
}
}
} else {
sqlArr.push(selects.join(","));
}
// 接口请求的排序
if (sort && order) {
const sorts = sort.toUpperCase().split(",");
const orders = order.split(",");
if (sorts.length != orders.length) {
throw new CoolValidateException(ERRINFO.SORTFIELD);
}
for (const i in sorts) {
find.addOrderBy(
`${this.matchColumn(option?.select, orders[i])}.${orders[i]}`,
this.checkSort(sorts[i])
);
}
}
if (option?.extend) {
await option?.extend(find, this.baseCtx, this.baseApp);
}
const sqls = find.getSql().split("FROM");
sqlArr.push("FROM");
// 取sqls的最后一个
sqlArr.push(sqls[sqls.length - 1]);
sqlArr.forEach((item, index) => {
if (item.includes("ORDER BY")) {
sqlArr[index] = this.replaceOrderByPrefix(item);
}
});
return sqlArr.join(" ");
}
/**
* sql中的表别名
* @param sql
* @returns
*/
replaceOrderByPrefix(sql) {
// 使用正则表达式匹配 ORDER BY 后面的部分
// 这里假设 ORDER BY 后面跟着的是由空格分隔的字段名,且字段名由双引号包围
const orderByRegex =
/ORDER BY\s+("[^"]+_[^"]+")(\s*(ASC|DESC)?\s*(,\s*"[^"]+_[^"]+")*)/gi;
// 定义替换函数
// @ts-ignore
function replaceMatch(match, p1, p2) {
// 将 p1 中的 "a_" 替换为 "a."
const replacedField = p1.replace(/a_([^"]+)/g, "a.$1");
// 如果有其他字段,递归调用替换函数
const replacedRest = p2.replace(/("[^"]+_)/g, (m, p) =>
p.replace("a_", "a.")
);
// 组合替换后的字段和其他部分
return `ORDER BY ${replacedField.replace(/"/g, "")}${replacedRest.replace(
/"/g,
""
)}`;
}
// 使用替换函数替换匹配到的内容
const replacedOrderBySql = sql.replace(orderByRegex, replaceMatch);
// 移除所有双引号
const sqlWithoutQuotes = replacedOrderBySql.replace(/"/g, "");
return sqlWithoutQuotes;
}
/**
*
* @param select
* @param field
* @returns
*/
protected matchColumn(select: string[] = [], field: string) {
for (const column of select) {
// 检查字段是否有别名,考虑 'AS' 关键字的不同大小写形式
const aliasPattern = new RegExp(`\\b\\w+\\s+as\\s+${field}\\b`, "i");
const aliasMatch = column.match(aliasPattern);
if (aliasMatch) {
// 提取别名前的字段和表名
const fieldPattern = new RegExp(
`(\\w+)\\.(\\w+)\\s+as\\s+${field}`,
"i"
);
const fieldMatch = column.match(fieldPattern);
if (fieldMatch) {
// 返回匹配到的表名
return fieldMatch[1];
}
}
// 检查字段是否直接在选择列表中
const fieldPattern = new RegExp(`\\b(\\w+)\\.${field}\\b`, "i");
const fieldMatch = column.match(fieldPattern);
if (fieldMatch) {
// 如果直接匹配到字段,返回字段所属的表名
return fieldMatch[1];
}
}
// 如果没有匹配到任何特定的表或别名,返回默认的 'a' 表
return "a";
}
}

View File

@ -1,532 +0,0 @@
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
import { Scope, ScopeEnum } from "@midwayjs/core";
import { CoolValidateException } from "../exception/validate";
import { ERRINFO, EVENT } from "../constant/global";
import { Application, Context } from "@midwayjs/koa";
import * as SqlString from "sqlstring";
import { CoolConfig } from "../interface";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { Brackets, In, Repository, SelectQueryBuilder } from "typeorm";
import { QueryOp } from "../decorator/controller";
import * as _ from "lodash";
import { CoolEventManager } from "../event";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseSqliteService {
// 分页配置
@Config("cool")
private _coolConfig: CoolConfig;
// 模型
entity: Repository<any>;
sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
}
@App()
baseApp: Application;
// 设置应用对象
setApp(app: Application) {
this.baseApp = app;
}
@Inject("ctx")
baseCtx: Context;
// 初始化
@Init()
init() {
this.sqlParams = [];
}
/**
* sql
* @param condition
* @param sql sql语句
* @param params
*/
setSql(condition, sql, params) {
let rSql = false;
if (condition || condition === 0) {
rSql = true;
for (let i = 0; i < params.length; i++) {
const param = params[i];
if (param instanceof Array) {
// 将这个? 替换成 $1,$2,$3
const replaceStr = [];
for (let j = 0; j < param.length; j++) {
replaceStr.push("$" + (this.sqlParams.length + j + 1));
}
this.sqlParams = this.sqlParams.concat(...params);
sql = sql.replace("?", replaceStr.join(","));
} else {
sql = sql.replace("?", "$" + (this.sqlParams.length + 1));
this.sqlParams.push(param);
}
}
}
return (rSql ? sql : "").replace(/\$\d+/g, "?");
}
/**
* SQL
* @param sql
*/
getCountSql(sql) {
sql = sql
.replace(new RegExp("LIMIT", "gm"), "limit ")
.replace(new RegExp("\n", "gm"), " ");
if (sql.includes("limit")) {
const sqlArr = sql.split("limit ");
sqlArr.pop();
sql = sqlArr.join("limit ");
}
return `select count(*) as count from (${sql}) a`;
}
/**
*
* @param params
*/
async paramSafetyCheck(params) {
const lp = params.toLowerCase();
return !(
lp.indexOf("update ") > -1 ||
lp.indexOf("select ") > -1 ||
lp.indexOf("delete ") > -1 ||
lp.indexOf("insert ") > -1
);
}
/**
*
* @param sql
* @param params
* @param connectionName
*/
async nativeQuery(sql, params?, connectionName?) {
if (_.isEmpty(params)) {
params = this.sqlParams;
}
let newParams = [];
// sql没处理过?的情况下
for (const item of params) {
// 如果是数组,将这个? 替换成 $1,$2,$3
if (item instanceof Array) {
const replaceStr = [];
for (let i = 0; i < item.length; i++) {
replaceStr.push("$" + (newParams.length + i + 1));
}
newParams.push(...item);
sql = sql.replace("?", replaceStr.join(","));
} else {
sql = sql.replace("?", "$" + (newParams.length + 1));
newParams.push(item);
}
}
this.sqlParams = [];
return await this.getOrmManager(connectionName).query(
sql.replace(/\$\d+/g, "?"),
newParams || []
);
}
/**
* ORM管理
* @param connectionName
*/
getOrmManager(connectionName = "default") {
return this.typeORMDataSourceManager.getDataSource(connectionName);
}
/**
* entity获得分页数据sql
* @param find QueryBuilder
* @param query
* @param autoSort
* @param connectionName
*/
async entityRenderPage(
find: SelectQueryBuilder<any>,
query,
autoSort = true
) {
const {
size = this._coolConfig.crud.pageSize,
page = 1,
order = "createTime",
sort = "desc",
isExport = false,
maxExportLimit,
} = query;
const count = await find.getCount();
let dataFind: SelectQueryBuilder<any>;
if (isExport && maxExportLimit > 0) {
dataFind = find.limit(maxExportLimit);
} else {
dataFind = find.offset((page - 1) * size).limit(size);
}
if (autoSort) {
find.addOrderBy(order, sort.toUpperCase());
}
return {
list: await dataFind.getMany(),
pagination: {
page: parseInt(page),
size: parseInt(size),
total: count,
},
};
}
/**
* SQL并获得分页数据
* @param sql sql语句
* @param query
* @param autoSort
* @param connectionName
*/
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
const {
size = this._coolConfig.crud.pageSize,
page = 1,
order = "createTime",
sort = "desc",
isExport = false,
maxExportLimit,
} = query;
sql = `SELECT * FROM (${sql}) a`;
if (order && sort && autoSort) {
if (!(await this.paramSafetyCheck(order + sort))) {
throw new CoolValidateException("非法传参~");
}
sql += ` ORDER BY a.${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
}
let cutParams = 0;
if (isExport && maxExportLimit > 0) {
this.sqlParams.push(parseInt(maxExportLimit));
cutParams = 1;
sql += " LIMIT ? ";
}
if (!isExport) {
this.sqlParams.push((page - 1) * size);
this.sqlParams.push(parseInt(size));
cutParams = 2;
sql += " LIMIT ?,? ";
}
let params = [];
params = params.concat(this.sqlParams);
const result = await this.nativeQuery(sql, params, connectionName);
params = params.slice(0, -cutParams);
const countResult = await this.nativeQuery(
this.getCountSql(sql),
params,
connectionName
);
return {
list: result,
pagination: {
page: parseInt(page),
size: parseInt(size),
total: parseInt(countResult[0] ? countResult[0].count : 0),
},
};
}
/**
*
* @param sort
* @returns
*/
checkSort(sort) {
if (!["desc", "asc"].includes(sort.toLowerCase())) {
throw new CoolValidateException("sort 非法传参~");
}
return sort;
}
/**
* ID
* @param id ID
* @param infoIgnoreProperty
*/
async info(id: any, infoIgnoreProperty?: string[]) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (!id) {
throw new CoolValidateException(ERRINFO.NOID);
}
const info = await this.entity.findOneBy({ id });
if (info && infoIgnoreProperty) {
for (const property of infoIgnoreProperty) {
delete info[property];
}
}
return info;
}
/**
*
* @param ids ID集合 [1,2,3] 1,2,3
*/
async delete(ids: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
if (ids instanceof String) {
ids = ids.split(",");
}
// 启动软删除发送事件
if (this._coolConfig.crud?.softDelete) {
this.softDelete(ids);
}
await this.entity.delete(ids);
}
/**
*
* @param ids ID数组
* @param entity
*/
async softDelete(ids: number[], entity?: Repository<any>) {
const data = await this.entity.find({
where: {
id: In(ids),
},
});
if (_.isEmpty(data)) return;
const _entity = entity ? entity : this.entity;
const params = {
data,
ctx: this.baseCtx,
entity: _entity,
};
if (data.length > 0) {
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
}
}
/**
* |
* @param param
*/
async addOrUpdate(param: any | any[], type: "add" | "update" = "add") {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
delete param.createTime;
// 判断是否是批量操作
if (param instanceof Array) {
param.forEach((item) => {
item.updateTime = new Date();
item.createTime = new Date();
});
await this.entity.save(param);
} else {
const upsert = this._coolConfig.crud?.upsert || "normal";
if (type == "update") {
if (upsert == "save") {
const info = await this.entity.findOneBy({ id: param.id });
param = {
...info,
...param,
};
}
param.updateTime = new Date();
upsert == "normal"
? await this.entity.update(param.id, param)
: await this.entity.save(param);
}
if (type == "add") {
param.createTime = new Date();
param.updateTime = new Date();
upsert == "normal"
? await this.entity.insert(param)
: await this.entity.save(param);
}
}
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async list(query, option, connectionName?): Promise<any> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, option);
return this.nativeQuery(sql, [], connectionName);
}
/**
*
* @param query
* @param option
* @param connectionName
*/
async page(query, option, connectionName?) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
const sql = await this.getOptionFind(query, option);
return this.sqlRenderPage(sql, query, false, connectionName);
}
/**
*
* @param query
* @param option
*/
async getOptionFind(query, option: QueryOp) {
let { order = "createTime", sort = "desc", keyWord = "" } = query;
const sqlArr = ["SELECT"];
const selects = ["a.*"];
const find = this.entity.createQueryBuilder("a");
if (option) {
if (typeof option == "function") {
// @ts-ignore
option = await option(this.baseCtx, this.baseApp);
}
// 判断是否有关联查询,有的话取个别名
if (!_.isEmpty(option.join)) {
for (const item of option.join) {
selects.push(`${item.alias}.*`);
find[item.type || "leftJoin"](
item.entity,
item.alias,
item.condition
);
}
}
// 默认条件
if (option.where) {
const wheres =
typeof option.where == "function"
? await option.where(this.baseCtx, this.baseApp)
: option.where;
if (!_.isEmpty(wheres)) {
for (const item of wheres) {
if (
item.length == 2 ||
(item.length == 3 && (item[2] || item[2] === 0))
) {
for (const key in item[1]) {
this.sqlParams.push(item[1][key]);
}
find.andWhere(item[0], item[1]);
}
}
}
}
// 附加排序
if (!_.isEmpty(option.addOrderBy)) {
for (const key in option.addOrderBy) {
if (order && order == key) {
sort = option.addOrderBy[key].toUpperCase();
}
find.addOrderBy(
SqlString.escapeId(key),
this.checkSort(option.addOrderBy[key].toUpperCase())
);
}
}
// 关键字模糊搜索
if (keyWord || keyWord === 0) {
keyWord = `%${keyWord}%`;
find.andWhere(
new Brackets((qb) => {
const keyWordLikeFields = option.keyWordLikeFields || [];
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
keyWord,
});
this.sqlParams.push(keyWord);
}
})
);
}
// 筛选字段
if (!_.isEmpty(option.select)) {
sqlArr.push(option.select.join(","));
find.select(option.select);
} else {
sqlArr.push(selects.join(","));
}
// 字段全匹配
if (!_.isEmpty(option.fieldEq)) {
for (let key of option.fieldEq) {
const c = {};
// 如果key有包含.的情况下操作
if (typeof key === "string" && key.includes(".")) {
const keys = key.split(".");
const lastKey = keys.pop();
key = { requestParam: lastKey, column: key };
}
// 单表字段无别名的情况下操作
if (typeof key === "string") {
if (query[key] || query[key] == 0) {
c[key] = query[key];
const eq = query[key] instanceof Array ? "in" : "=";
if (eq === "in") {
find.andWhere(`${key} ${eq} (:${key})`, c);
} else {
find.andWhere(`${key} ${eq} :${key}`, c);
}
// this.sqlParams.push(query[key]);
}
} else {
if (query[key.requestParam] || query[key.requestParam] == 0) {
c[key.column] = query[key.requestParam];
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
if (eq === "in") {
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
} else {
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
}
// this.sqlParams.push(query[key.requestParam]);
}
}
}
}
} else {
sqlArr.push(selects.join(","));
}
// 接口请求的排序
if (sort && order) {
const sorts = sort.toUpperCase().split(",");
const orders = order.split(",");
if (sorts.length != orders.length) {
throw new CoolValidateException(ERRINFO.SORTFIELD);
}
for (const i in sorts) {
find.addOrderBy(
SqlString.escapeId(orders[i]),
this.checkSort(sorts[i])
);
}
}
if (option?.extend) {
await option?.extend(find, this.baseCtx, this.baseApp);
}
const sqls = find.getSql().split("FROM");
sqlArr.push("FROM");
// 取sqls的最后一个
sqlArr.push(sqls[sqls.length - 1]);
return sqlArr.join(" ");
}
}

View File

@ -1,89 +0,0 @@
import { COOL_METHOD_TAG_KEY, CoolUrlTagConfig } from './../decorator/tag';
import {
CONTROLLER_KEY,
getClassMetadata,
listPropertyDataFromClass,
listModule,
Provide,
Scope,
ScopeEnum,
WEB_ROUTER_KEY,
} from '@midwayjs/decorator';
import { COOL_URL_TAG_KEY } from '../decorator/tag';
import * as _ from 'lodash';
/**
* URL标签
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolUrlTagData {
data = {};
/**
*
*/
async init() {
// 类标记
await this.classTag();
// 方法标记
await this.methodTag();
}
/**
*
*/
async classTag() {
const tags = listModule(COOL_URL_TAG_KEY);
for (const controller of tags) {
// class的标记
const controllerOption = getClassMetadata(CONTROLLER_KEY, controller);
const tagOption: CoolUrlTagConfig = getClassMetadata(
COOL_URL_TAG_KEY,
controller
);
if(tagOption?.key){
const data: string[] = this.data[tagOption.key] || [];
this.data[tagOption.key] = _.uniq(data.concat(
(tagOption?.value || []).map(e => {
return controllerOption.prefix + '/' + e;
}))
);
}
}
}
/**
*
*/
async methodTag() {
const controllers = listModule(CONTROLLER_KEY);
for (const controller of controllers) {
const controllerOption = getClassMetadata(CONTROLLER_KEY, controller);
// 方法标记
const listPropertyMetas = listPropertyDataFromClass(COOL_METHOD_TAG_KEY, controller);
const requestMetas = getClassMetadata(WEB_ROUTER_KEY, controller);
for (const propertyMeta of listPropertyMetas) {
const _data = this.data[propertyMeta.tag] || [];
const requestMeta = _.find(requestMetas, { method: propertyMeta.key })
if(requestMeta){
this.data[propertyMeta.tag] = _.uniq(_data.concat(
controllerOption.prefix + requestMeta.path
))
}
}
}
}
/**
*
* @param key
* @param type
* @returns
*/
byKey(key: string, type?: 'app' | 'admin'): string[] {
return this.data[key].filter(e => {
return type? _.startsWith(e, `/${type}/`): true;
});
}
}

View File

@ -1,27 +0,0 @@
import { ILogger } from '@midwayjs/core';
import { Init, Logger, Provide, Scope, ScopeEnum } from '@midwayjs/decorator';
import * as moment from 'moment';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class FuncUtil {
@Logger()
coreLogger: ILogger;
@Init()
async init() {
Date.prototype.toJSON = function () {
return moment(this).format('YYYY-MM-DD HH:mm:ss');
};
// 新增String支持replaceAll方法
String.prototype['replaceAll'] = function (s1, s2) {
return this.replace(new RegExp(s1, 'gm'), s2);
};
this.coreLogger.info(
'\x1B[36m [cool:core] midwayjs cool core func handler \x1B[0m'
);
}
}

View File

@ -1,52 +0,0 @@
/**
* Location
*/
class LocationUtil {
private locationCache = new Map<string, any>();
/**
*
* @param target
* @returns
*/
async scriptPath(target: any) {
const targetName = target.name;
// 检查缓存
if (this.locationCache.has(targetName)) {
return this.locationCache.get(targetName);
}
const originalPrepareStackTrace = Error.prepareStackTrace;
let targetFile;
try {
Error.prepareStackTrace = (error, stack) => stack;
const stack = new Error().stack as any;
for (const site of stack) {
const fileName = site.getFileName();
if (!fileName) continue;
const content = require('fs').readFileSync(fileName, 'utf-8');
if (content.includes(`class ${targetName}`)) {
targetFile = {
path: fileName,
line: site.getLineNumber(),
column: site.getColumnNumber(),
source: `file://${fileName}`
};
this.locationCache.set(targetName, targetFile);
break;
}
}
} finally {
Error.prepareStackTrace = originalPrepareStackTrace;
}
return targetFile;
}
}
// 不再需要单例模式,直接导出实例即可
export default new LocationUtil();

View File

@ -1,25 +0,0 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
"skipLibCheck": true,
"noImplicitReturns": false,
"pretty": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist"
},
"exclude": [
"dist",
"node_modules",
"test"
]
}

View File

@ -1,11 +0,0 @@
# 🎨 editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,28 +0,0 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": [
"node_modules",
"dist",
"test",
"jest.config.js",
"typings",
"public/**/**",
"view/**/**"
],
"env": {
"jest": true
},
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"node/no-extraneous-import": "off",
"no-empty": "off",
"node/no-extraneous-require": "off",
"eqeqeq": "off",
"node/no-unsupported-features/node-builtins": "off",
"@typescript-eslint/ban-types": "off",
"no-control-regex": "off",
"prefer-const": "off"
}
}

15
es/.gitignore vendored
View File

@ -1,15 +0,0 @@
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
dist/
.idea/
run/
.DS_Store
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*

View File

@ -1,3 +0,0 @@
module.exports = {
...require('mwts/.prettierrc.json')
}

View File

@ -1,3 +0,0 @@
# cool-admin
https://cool-js.com

10
es/index.d.ts vendored
View File

@ -1,10 +0,0 @@
export * from './dist/index';
declare module '@midwayjs/core/dist/interface' {
interface MidwayConfig {
book?: PowerPartial<{
a: number;
b: string;
}>;
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
setupFilesAfterEnv: ['./jest.setup.js']
};

View File

@ -1 +0,0 @@
jest.setTimeout(30000);

View File

@ -1,43 +0,0 @@
{
"name": "@cool-midway/es",
"version": "7.0.0",
"description": "cool-js.com elasticsearch",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [
"cool",
"cool-admin",
"cooljs"
],
"author": "COOL",
"readme": "README.md",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"index.d.ts"
],
"license": "MIT",
"devDependencies": {
"@cool-midway/core": "^7.0.0",
"@midwayjs/cli": "^2.0.9",
"@midwayjs/core": "^3.9.0",
"@midwayjs/decorator": "^3.9.0",
"@midwayjs/mock": "^3.9.0",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"cross-env": "^7.0.3",
"jest": "^29.3.1",
"mwts": "^1.3.0",
"ts-jest": "^29.0.3",
"typescript": "^4.9.4"
},
"dependencies": {
"@elastic/elasticsearch": "^8.5.0"
}
}

View File

@ -1,27 +0,0 @@
### COOL-ADMIN
cool-admin一个很酷的后台权限管理系统开源免费模块化、插件化、极速开发CRUD方便快速构建迭代后台管理系统
大数据、微服务、AI编码快速开发
### 技术栈
- 后端node.js midway.js koa.js mysql typescript
- 前端vue.js element-plus jsx pinia vue-router
### 官网
[https://cool-js.com](https://cool-js.com)
### 演示地址
[https://show.cool-admin.com](https://show.cool-admin.com)
- 账户admin
- 密码123456
### 项目地址
[https://github.com/cool-team-official/cool-admin-midway](https://github.com/cool-team-official/cool-admin-midway)

View File

@ -1,615 +0,0 @@
import { CoolEventManager } from '@cool-midway/core';
import { Client } from '@elastic/elasticsearch';
import { WaitForActiveShards } from '@elastic/elasticsearch/lib/api/types';
import { Inject, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
import { EsConfig } from '.';
/**
* Es索引基类
*/
export class BaseEsIndex {
// 索引
public index: string;
// es客户端
public client: Client;
// 日志
@Logger()
coreLogger: ILogger;
// 事件
@Inject('cool:coolEventManager')
coolEventManager: CoolEventManager;
/**
*
* @param index
*/
setIndex(index: string) {
this.index = index;
}
/**
* es数据变更事件
* @param method
* @param data
*/
async handleDataChange(index, method, data) {
this.index = index;
const {
id,
ids,
bodys,
body,
type,
refresh,
waitForActiveShards,
properties,
config,
} = data;
switch (method) {
case 'upsert':
await this.upsert(body, refresh, waitForActiveShards);
break;
case 'batchIndex':
await this.batchIndex(bodys, type, refresh, waitForActiveShards);
break;
case 'deleteById':
await this.deleteById(id, refresh, waitForActiveShards);
break;
case 'deleteByIds':
await this.deleteByIds(ids, refresh, waitForActiveShards);
break;
case 'deleteByQuery':
await this.deleteByQuery(body, refresh, waitForActiveShards);
break;
case 'updateById':
await this.updateById(body, refresh, waitForActiveShards);
break;
case 'updateByQuery':
await this.updateByQuery(body, refresh, waitForActiveShards);
break;
case 'createIndex':
await this.updateByQuery(properties, config);
break;
}
}
/**
*
* @param method
* @param data
*/
async esDataChange(method, data) {
this.coolEventManager?.emit('esDataChange', this.index, method, data);
}
/**
*
* @param client
*/
setClient(client: Client) {
this.client = client;
}
/**
* body
* @param condition
*/
async objToBody(condition: any){
const body = {
query: {
bool: {
must: [],
},
}
}
for(const key in condition){
body.query.bool.must.push({
term: {
[key]: condition[key]
}
})
}
return body;
}
/**
*
* @param condition
* @param size
* @returns
*/
async findBy(condition: any, size?: number){
const body = await this.objToBody(condition)
return this.find(body, size);
}
/**
*
* @param condition
* @param page
* @param size
*/
async findPageBy(condition: any, page?: number, size?: number){
const body = await this.objToBody(condition)
return this.findPage(body, page, size);
}
/**
*
* @param body
*/
async find(body?: any, size?: number) {
if (!body) {
body = {};
}
if(!body.size){
body.size = size ? size : 10000;
}
return this.client
.search({
index: this.index,
body,
})
.then(res => {
return (
res.hits.hits.map(e => {
e._source['id'] = e._id;
const _source: any = e._source;
['_id', '_index', '_score', '_source'].forEach(key => {
delete e[key];
});
return {
..._source,
...e,
};
}) || []
);
});
}
/**
*
* @param body
* @param page
* @param size
*/
async findPage(body?: any, page?: number, size?: number) {
if (!page) {
page = 1;
}
if (!body) {
body = {};
}
if (!size && !body.size) {
size = 20;
body.size = size;
}
const total = await this.findCount(body);
body.from = (page - 1) * size;
return this.client.search({ index: this.index, body }).then(res => {
const result =
res.hits.hits.map(e => {
e._source['id'] = e._id;
const _source: any = e._source;
['_id', '_index', '_score', '_source'].forEach(key => {
delete e[key];
});
return {
..._source,
...e,
};
}) || [];
return {
list: result,
pagination: {
page,
size,
total,
},
};
});
}
/**
* ID查询
* @param id
* @returns
*/
async findById(id) {
return this.client
.get({
index: this.index,
id,
})
.then(res => {
res._source['id'] = res._id;
return res._source || undefined;
})
.catch(e => {
return undefined;
});
}
/**
* ID查询
* @param ids
* @returns
*/
async findByIds(ids: string[]) {
return this.client
.mget({ index: this.index, body: { ids } })
.then(res => {
const result = res.docs.map((e: any) => {
e._source.id = e._id;
return e._source || 'undefined';
});
return result.filter(e => {
return e !== 'undefined';
});
})
.catch(e => {
return undefined;
});
}
/**
*
* @param body
* @param refresh
* @param waitForActiveShards
* @returns
*/
async upsert(
body: any,
refresh?: boolean | 'wait_for',
waitForActiveShards?: WaitForActiveShards
) {
if (refresh == undefined) {
refresh = true;
}
if (body.id) {
this.esDataChange('upsert', {
body,
refresh,
waitForActiveShards,
});
const id = body.id;
delete body.id;
return this.client.index({
id,
index: this.index,
wait_for_active_shards: waitForActiveShards,
refresh,
body,
});
} else {
return this.client
.index({
index: this.index,
wait_for_active_shards: waitForActiveShards,
refresh,
body,
})
.then(res => {
this.esDataChange('upsert', {
body: {
...body,
id: res._id,
},
refresh,
waitForActiveShards,
});
return res;
});
}
}
/**
*
* @param bodys
* @param type
* @param refresh
* @param waitForActiveShards
* @returns
*/
async batchIndex(
bodys: any[],
type: 'index' | 'create' | 'delete' | 'update',
refresh?: boolean | 'wait_for',
waitForActiveShards?: WaitForActiveShards
) {
this.esDataChange('batchIndex', {
bodys,
type,
refresh,
waitForActiveShards,
});
if (refresh == undefined) {
refresh = true;
}
const list = [];
for (const body of bodys) {
const typeO = {};
typeO[type] = { _index: this.index, _id: body.id };
if (body.id) {
delete body.id;
}
list.push(typeO);
if (type !== 'delete') {
if (type == 'update') {
list.push({ doc: body });
} else {
list.push(body);
}
}
}
return this.client.bulk({
wait_for_active_shards: waitForActiveShards,
index: this.index,
refresh,
body: list,
});
}
/**
*
* @param id
* @param refresh
* @param waitForActiveShards
* @returns
*/
async deleteById(
id,
refresh?: boolean | 'wait_for',
waitForActiveShards?: WaitForActiveShards
) {
this.esDataChange('deleteById', {
id,
refresh,
waitForActiveShards,
});
if (refresh == undefined) {
refresh = true;
}
try {
return this.client.delete({
index: this.index,
refresh,
wait_for_active_shards: waitForActiveShards,
id,
});
} catch {}
}
/**
*
* @param ids
* @param refresh
* @param waitForActiveShards
* @returns
*/
async deleteByIds(
ids: string[],
refresh?: boolean,
waitForActiveShards?: WaitForActiveShards
) {
this.esDataChange('deleteByIds', {
ids,
refresh,
waitForActiveShards,
});
if (refresh == undefined) {
refresh = true;
}
const body = {
query: {
bool: {
must: [
{
terms: {
_id: ids,
},
},
],
},
},
};
return this.client.deleteByQuery({
index: this.index,
refresh,
wait_for_active_shards: waitForActiveShards,
body,
});
}
/**
*
* @param body
* @param refresh
* @param waitForActiveShards
* @returns
*/
async deleteByQuery(
body,
refresh?: boolean,
waitForActiveShards?: WaitForActiveShards
) {
this.esDataChange('deleteByQuery', {
body,
refresh,
waitForActiveShards,
});
if (refresh == undefined) {
refresh = true;
}
return this.client.deleteByQuery({
index: this.index,
refresh,
wait_for_active_shards: waitForActiveShards,
body,
});
}
/**
*
* @param body
* @param refresh
* @param waitForActiveShards
* @returns
*/
async updateById(
body,
refresh?: boolean | 'wait_for',
waitForActiveShards?: WaitForActiveShards
) {
this.esDataChange('updateById', {
body,
refresh,
waitForActiveShards,
});
if (refresh == undefined) {
refresh = true;
}
const id = body.id;
delete body.id;
return this.client.update({
wait_for_active_shards: waitForActiveShards,
index: this.index,
id: id,
refresh,
body: {
doc: body,
},
});
}
/**
*
* @param body
* @param refresh
* @param waitForActiveShards
*/
async updateByQuery(
body,
refresh?: boolean,
waitForActiveShards?: WaitForActiveShards
) {
this.esDataChange('updateByQuery', {
body,
refresh,
waitForActiveShards,
});
if (refresh == undefined) {
refresh = true;
}
return this.client.updateByQuery({
index: this.index,
refresh,
wait_for_active_shards: waitForActiveShards,
body,
});
}
/**
*
* @param body
*/
async findCount(body?: any) {
let _body = Object.assign({}, body || {});
delete _body.from;
delete _body.size;
delete _body.sort;
return this.client
.count({
index: this.index,
body: _body,
})
.then(res => {
return res.count;
})
.catch(e => {
return undefined;
});
}
/**
*
* @param config
*/
async createIndex(
properties: {},
config: EsConfig = {
name: '',
replicas: 1,
shards: 8,
analyzers: [],
}
) {
this.esDataChange('createIndex', {
properties,
config,
});
const body = {
settings: {
number_of_shards: config.shards,
number_of_replicas: config.replicas,
analysis: {
analyzer: {
comma: { type: 'pattern', pattern: ',' },
blank: { type: 'pattern', pattern: ' ' },
},
},
mapping: {
nested_fields: {
limit: 100,
},
},
},
mappings: {
properties: {},
},
};
if (config.analyzers) {
for (const analyzer of config.analyzers) {
for (const key in analyzer) {
body.settings.analysis.analyzer[key] = analyzer[key];
}
}
}
const param = {
index: this.index,
body,
};
param.body = body;
param.body.mappings.properties = properties;
this.client.indices.exists({ index: this.index }).then(async res => {
if (!res) {
await this.client.indices.create(param).then(res => {
if (res.acknowledged) {
console.info(
'\x1B[36m [cool:core] midwayjs cool elasticsearch ES索引创建成功: ' +
this.index +
' \x1B[0m'
);
}
});
} else {
const updateParam = {
index: this.index,
body: param.body.mappings,
};
await this.client.indices.putMapping(updateParam).then(res => {
if (res.acknowledged) {
console.info(
'\x1B[36m [cool:core] midwayjs cool elasticsearch ES索引更新成功: ' +
this.index +
' \x1B[0m'
);
}
});
}
});
}
}

View File

@ -1,4 +0,0 @@
export const customKey = {
a: 1,
b: 'hello',
};

View File

@ -1,19 +0,0 @@
import { Configuration } from '@midwayjs/decorator';
import * as DefaultConfig from './config/config.default';
import { IMidwayContainer } from '@midwayjs/core';
import { CoolElasticSearch } from './elasticsearch';
@Configuration({
namespace: 'cool:es',
importConfigs: [
{
default: DefaultConfig,
},
],
})
export class CoolEsConfiguration {
async onReady(container: IMidwayContainer) {
await container.getAsync(CoolElasticSearch);
// TODO something
}
}

View File

@ -1,41 +0,0 @@
import {
Scope,
ScopeEnum,
saveClassMetadata,
saveModule,
} from '@midwayjs/decorator';
export const COOL_ES_KEY = 'decorator:cool:es';
export interface EsConfig {
shards?: number;
name: string;
replicas?: number;
analyzers?: any[];
}
/**
*
* @param config
* @returns
*/
export function CoolEsIndex(
config: EsConfig | string = {
name: '',
replicas: 1,
shards: 8,
analyzers: [],
}
): ClassDecorator {
if (typeof config == 'string') {
config = { name: config, replicas: 1, shards: 8, analyzers: [] };
}
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(COOL_ES_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(COOL_ES_KEY, config, target);
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Singleton)(target);
};
}

View File

@ -1,154 +0,0 @@
import {
Provide,
getClassMetadata,
App,
Logger,
Inject,
Init,
Scope,
ScopeEnum,
Config,
} from '@midwayjs/decorator';
import { COOL_ES_KEY, EsConfig } from './decorator/elasticsearch';
import { listModule } from '@midwayjs/decorator';
import { IMidwayApplication } from '@midwayjs/core';
import { CoolCoreException, CoolEventManager } from '@cool-midway/core';
import { ILogger } from '@midwayjs/logger';
import { Client } from '@elastic/elasticsearch';
import * as _ from 'lodash';
import { CoolEsConfig, ICoolEs } from '.';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolElasticSearch {
@App()
app: IMidwayApplication;
@Logger()
coreLogger: ILogger;
@Config('cool.es')
esConfig: CoolEsConfig;
client: Client;
@Inject('cool:coolEventManager')
coolEventManager: CoolEventManager;
@Init()
async init() {
if (!this.esConfig?.nodes) {
throw new CoolCoreException('es.nodes config is require');
}
if (this.esConfig.nodes.length == 1) {
this.client = new Client({ node: this.esConfig.nodes[0], ...this.esConfig.options });
} else {
this.client = new Client({ nodes: this.esConfig.nodes, ...this.esConfig.options });
}
this.client.ping({}, { requestTimeout: 30000 }).then(res => {
if (res) {
this.coolEventManager.emit('esReady', this.client);
this.scan();
}
});
}
async scan() {
const modules = listModule(COOL_ES_KEY);
for (let module of modules) {
const cls: ICoolEs = await this.app
.getApplicationContext()
.getAsync(module);
const data = getClassMetadata(COOL_ES_KEY, module);
this.createIndex(cls, data);
}
}
/**
*
* @param method
* @param data
*/
async esDataChange(method, data) {
//this.coolEventManager.emit('esDataChange', { method, data });
}
/**
*
* @param cls
* @param config
*/
async createIndex(cls, config: EsConfig) {
cls.index = config.name;
cls.client = this.client;
const body = {
settings: {
number_of_shards: config.shards,
number_of_replicas: config.replicas,
analysis: {
analyzer: {
comma: { type: 'pattern', pattern: ',' },
blank: { type: 'pattern', pattern: ' ' },
},
},
mapping: {
nested_fields: {
limit: 100,
},
},
},
mappings: {
properties: {},
},
};
if (config.analyzers) {
for (const analyzer of config.analyzers) {
for (const key in analyzer) {
body.settings.analysis.analyzer[key] = analyzer[key];
}
}
}
const param = {
index: config.name,
body,
};
param.body = body;
param.body.mappings.properties = cls.indexInfo();
this.esDataChange('createIndex', {
properties: param.body.mappings.properties,
config,
});
this.client.indices.exists({ index: config.name }).then(async res => {
if (!res) {
await this.client.indices.create(param).then(res => {
if (res.acknowledged) {
this.coreLogger.info(
'\x1B[36m [cool:core] midwayjs cool elasticsearch ES索引创建成功: ' +
config.name +
' \x1B[0m'
);
}
});
} else {
const updateParam = {
index: config.name,
body: param.body.mappings,
};
await this.client.indices.putMapping(updateParam).then(res => {
if (res.acknowledged) {
this.coreLogger.info(
'\x1B[36m [cool:core] midwayjs cool elasticsearch ES索引更新成功: ' +
config.name +
' \x1B[0m'
);
}
});
}
});
}
}

View File

@ -1,18 +0,0 @@
import { ClientOptions } from '@elastic/elasticsearch';
export { CoolEsConfiguration as Configuration } from './configuration';
export * from './elasticsearch';
export * from './decorator/elasticsearch';
export * from './base';
export interface ICoolEs {
indexInfo(): Object;
}
export interface CoolEsConfig {
nodes: string[];
options?: ClientOptions
}

View File

@ -1,41 +0,0 @@
{
"name": "@cool-midway/es",
"version": "7.0.0",
"description": "cool-js.com elasticsearch",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [],
"author": "",
"files": [
"**/*.js",
"**/*.d.ts"
],
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"license": "MIT",
"devDependencies": {
"@cool-midway/core": "^7.0.0",
"@midwayjs/cli": "^1.2.38",
"@midwayjs/core": "^3.0.0",
"@midwayjs/decorator": "^3.0.0",
"@midwayjs/mock": "^3.0.0",
"@types/jest": "^27.4.0",
"@types/node": "^16.11.22",
"cross-env": "^6.0.0",
"jest": "^27.5.1",
"mwts": "^1.0.5",
"ts-jest": "^27.1.3",
"typescript": "^4.0.0"
},
"dependencies": {
"@elastic/elasticsearch": "^8.1.0"
}
}

View File

@ -1,24 +0,0 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":false,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
"skipLibCheck": true,
"noImplicitReturns": false,
"pretty": true,
"declaration": true,
"outDir": "dist"
},
"exclude": [
"dist",
"node_modules",
"test"
]
}

View File

@ -1,11 +0,0 @@
# 🎨 editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,20 +0,0 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"env": {
"jest": true
},
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"node/no-extraneous-import": "off",
"no-empty": "off",
"node/no-extraneous-require": "off",
"eqeqeq": "off",
"node/no-unsupported-features/node-builtins": "off",
"@typescript-eslint/ban-types": "off",
"no-control-regex": "off",
"prefer-const": "off"
}
}

15
file/.gitignore vendored
View File

@ -1,15 +0,0 @@
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
dist/
.idea/
run/
.DS_Store
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*

View File

@ -1,3 +0,0 @@
module.exports = {
...require('mwts/.prettierrc.json')
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2013 - Now midwayjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
file/index.d.ts vendored
View File

@ -1,10 +0,0 @@
export * from './dist/index';
declare module '@midwayjs/core/dist/interface' {
interface MidwayConfig {
book?: PowerPartial<{
a: number;
b: string;
}>;
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
setupFilesAfterEnv: ['./jest.setup.js']
};

View File

@ -1 +0,0 @@
jest.setTimeout(30000);

View File

@ -1,54 +0,0 @@
{
"name": "@cool-midway/file",
"version": "7.0.5",
"description": "",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"cov": "cross-env midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [
"cool",
"cool-admin",
"cooljs"
],
"author": "COOL",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"index.d.ts"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"devDependencies": {
"@cool-midway/core": "^7.0.0",
"@midwayjs/cli": "^2.0.9",
"@midwayjs/core": "^3.9.0",
"@midwayjs/decorator": "^3.9.0",
"@midwayjs/mock": "^3.9.0",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"cross-env": "^7.0.3",
"jest": "^29.3.1",
"lodash": "^4.17.21",
"mwts": "^1.3.0",
"ts-jest": "^29.0.3",
"typescript": "^4.9.4"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.414.0",
"@aws-sdk/s3-presigned-post": "^3.414.0",
"@midwayjs/upload": "^3.9.9",
"ali-oss": "^6.17.1",
"cos-nodejs-sdk-v5": "^2.11.19",
"download": "^8.0.0",
"qcloud-cos-sts": "^3.1.0",
"qiniu": "^7.8.0"
}
}

Some files were not shown because too many files have changed in this diff Show More