npm 核心包源码

This commit is contained in:
啊平 2023-04-04 09:15:48 +08:00
parent 68f91fa9f8
commit 600e2396c4
186 changed files with 8826 additions and 0 deletions

View File

@ -0,0 +1,11 @@
# 🎨 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

@ -0,0 +1,20 @@
{
"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
packages/cloud/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
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

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

21
packages/cloud/LICENSE Normal file
View File

@ -0,0 +1,21 @@
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
packages/cloud/index.d.ts vendored Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,47 @@
{
"name": "@cool-midway/cloud",
"version": "6.0.0",
"description": "",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "^6.0.0",
"@midwayjs/cli": "^2.0.0",
"@midwayjs/core": "^3.9.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"
}
}

View File

@ -0,0 +1,21 @@
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

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

View File

@ -0,0 +1,22 @@
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

@ -0,0 +1,123 @@
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,
}
);
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

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

View File

@ -0,0 +1,525 @@
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

@ -0,0 +1,17 @@
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

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

View File

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

View File

@ -0,0 +1,42 @@
{
"name": "@cool-midway/cloud",
"version": "6.0.0",
"description": "",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "^6.0.0",
"@midwayjs/cli": "^2.0.0",
"@midwayjs/core": "^3.0.0",
"@midwayjs/decorator": "^3.0.0",
"@midwayjs/mock": "^3.0.0",
"@midwayjs/typeorm": "^3.8.3",
"@types/jest": "^29.2.0",
"@types/node": "^16.11.22",
"cross-env": "^6.0.0",
"jest": "^29.2.2",
"mwts": "^1.0.5",
"ts-jest": "^29.0.3",
"typeorm": "^0.3.11",
"typescript": "^4.9.4"
}
}

View File

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

View File

@ -0,0 +1,14 @@
import { createLightApp } from '@midwayjs/mock';
import * as custom from '../src';
describe('/test/index.test.ts', () => {
it('test component', async () => {
const app = await createLightApp('', {
imports: [
custom
]
});
const bookService = await app.getApplicationContext().getAsync(custom.BookService);
expect(await bookService.getBookById()).toEqual('hello world');
});
});

View File

@ -0,0 +1,25 @@
{
"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"
]
}

21
packages/core/LICENSE Normal file
View File

@ -0,0 +1,21 @@
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.

3
packages/core/README.md Normal file
View File

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

View File

@ -0,0 +1,11 @@
# 🎨 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

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

15
packages/core/_.gitignore Normal file
View File

@ -0,0 +1,15 @@
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

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

10
packages/core/index.d.ts vendored Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,56 @@
{
"name": "@cool-midway/core",
"version": "6.0.2",
"description": "",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "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",
"aedes": "^0.48.1",
"cross-env": "^7.0.3",
"jest": "^29.3.1",
"mwts": "^1.3.0",
"ts-jest": "^29.0.3",
"typeorm": "^0.3.11",
"typescript": "~4.9.4"
},
"dependencies": {
"@midwayjs/cache": "^3.9.0",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"moment": "^2.29.4",
"mysql2-import": "^5.0.22",
"sqlstring": "^2.3.3"
}
}

21
packages/core/src/LICENSE Normal file
View File

@ -0,0 +1,21 @@
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

@ -0,0 +1,18 @@
import { CoolConfig } from "../interface";
/**
* cool的配置
*/
export default {
cool: {
// 是否自动导入数据库
initDB: false,
// crud配置
crud: {
// 软删除
softDelete: true,
// 分页查询每页条数
pageSize: 15,
},
} as CoolConfig,
};

View File

@ -0,0 +1,82 @@
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 location from "./util/location";
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 { CacheManager } from "@midwayjs/cache";
import * as cache from "@midwayjs/cache";
import { CoolDecorator } from "./decorator";
@Configuration({
namespace: "cool",
imports: [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(CoolModuleImport);
// 常用函数处理
await container.getAsync(FuncUtil);
// 事件
await container.getAsync(CoolEventManager);
// 异常处理
this.app.useFilter([CoolExceptionFilter]);
// 装饰器
await container.getAsync(CoolDecorator);
if (this.app.getEnv() == "local") {
// 实体与路径
const eps: CoolEps = await container.getAsync(CoolEps);
eps.init();
}
// 缓存设置为全局
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() {
this.coolEventManager.emit("onServerReady");
location.clean();
}
}

View File

@ -0,0 +1,50 @@
/**
*
*/
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",
}

View File

@ -0,0 +1,201 @@
import {
App,
CONTROLLER_KEY,
getClassMetadata,
Init,
Inject,
Provide,
} from "@midwayjs/decorator";
import { RESCODE, RESMESSAGE } 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;
}
this.baseCtx.request.body = {
...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);
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() {
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 res = {
code: RESCODE.SUCCESS,
message: RESMESSAGE.SUCCESS,
};
if (data || data == 0) {
res["data"] = data;
}
return res;
}
/**
*
* @param message
*/
fail(message?: string, code?: RESCODE) {
return {
code: code ? code : RESCODE.COMMFAIL,
message: message
? message
: code == RESCODE.VALIDATEFAIL
? RESMESSAGE.VALIDATEFAIL
: RESMESSAGE.COMMFAIL,
};
}
}

View File

@ -0,0 +1,8 @@
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

@ -0,0 +1,208 @@
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 * as os from "os";
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[];
// 添加排序条件
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;
};
}
// COOL的装饰器
export function CoolController(
curdOption?: CurdOption | string,
routerOptions: {
sensitive?: boolean;
middleware?: MiddlewareParamArray;
description?: string;
tagName?: string;
ignoreGlobalPrefix?: boolean;
} = { middleware: [], sensitive: true }
): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(CONTROLLER_KEY, target);
let prefix;
if (typeof curdOption === "string") {
prefix = curdOption;
} else {
prefix = curdOption?.prefix || "";
}
// 如果不存在路由前缀,那么自动根据当前文件夹路径
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(`modules/${module}`)[0]
}modules/${module}/config.${_.endsWith(res.path, "ts") ? "ts" : "js"}`;
if (os.type() == "Windows_NT") {
path = path.substr(1);
}
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

@ -0,0 +1,42 @@
import {
Scope,
ScopeEnum,
saveClassMetadata,
saveModule,
attachClassMetadata,
} from "@midwayjs/decorator";
export const COOL_CLS_EVENT_KEY = "decorator:cool:cls:event";
export function CoolEvent(): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(COOL_CLS_EVENT_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(COOL_CLS_EVENT_KEY, {}, 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

@ -0,0 +1,103 @@
import { COOL_CACHE } from "./cache";
import { CacheManager } from "@midwayjs/cache";
import {
Init,
Inject,
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";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolDecorator {
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
decoratorService: MidwayDecoratorService;
@Inject()
cacheManager: CacheManager;
@Init()
async init() {
// 事务
await this.transaction();
// 缓存
await this.cache();
}
/**
*
*/
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.cacheManager.get(key);
if (data) {
return JSON.parse(data);
} else {
// 执行原始方法
data = await joinPoint.proceed(...joinPoint.args);
await this.cacheManager.set(key, JSON.stringify(data), {
ttl: options.metadata.ttl,
});
}
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

@ -0,0 +1,27 @@
import { saveClassMetadata, saveModule } from "@midwayjs/decorator";
export const COOL_URL_TAG_KEY = "decorator:cool:url: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);
};
}

View File

@ -0,0 +1,19 @@
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

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

View File

@ -0,0 +1,24 @@
import {
Index,
UpdateDateColumn,
CreateDateColumn,
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

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

View File

@ -0,0 +1,43 @@
import {
App,
getClassMetadata,
Init,
listModule,
Provide,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import * as Events from "events";
import { IMidwayApplication } from "@midwayjs/core";
import { COOL_CLS_EVENT_KEY, COOL_EVENT_KEY } from "../decorator/event";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolEventManager extends Events {
@App()
app: IMidwayApplication;
@Init()
async init() {
const eventModules = listModule(COOL_CLS_EVENT_KEY);
for (const module of eventModules) {
this.handlerEvent(module);
}
}
async handlerEvent(module) {
const events = getClassMetadata(COOL_EVENT_KEY, module);
for (const event of events) {
const method = event.eventName ? event.eventName : event.propertyKey;
this.on(method, async (...args) => {
const moduleInstance = await this.app
.getApplicationContext()
.getAsync(module);
moduleInstance[event.propertyKey](...args);
});
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,45 @@
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";
// entity
export * from "./entity/base";
export * from "./entity/typeorm";
export * from "./entity/mongo";
// service
export * from "./service/base";
// 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 "./interface";
export * from "./util/func";
export * from "./constant/global";

View File

@ -0,0 +1,300 @@
import { MiddlewareParamArray } from "@midwayjs/core";
import { AedesOptions } from "aedes";
import { PublishPacket } from "packet";
/**
*
*/
export interface ModuleConfig {
/** 名称 */
name: string;
/** 描述 */
description: string;
/** 模块中间件 */
middlewares?: MiddlewareParamArray;
/** 全局中间件 */
globalMiddlewares?: MiddlewareParamArray;
/** 模块加载顺序默认为0值越大越优先加载 */
order?: number;
}
export interface CoolConfig {
/** 是否自动导入数据库 */
initDB?: boolean;
// 实体配置
// entity?: {
// primaryType: "uuid" | "increment" | "rowid" | "identity";
// };
/** crud配置 */
crud?: {
/** 软删除 */
softDelete: boolean;
/** 分页查询每页条数 */
pageSize: number;
// 多租户
// tenant: boolean;
};
/** elasticsearch配置 */
es?: {
nodes: string[];
};
/** 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;
};
/** 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",
}
/**
*
*/
export interface Mode {
/** 模式 */
mode: MODETYPE;
/** 类型 */
type: string;
}
/**
*
*/
export interface CoolFileConfig {
/** 上传模式 */
mode: MODETYPE;
/** 阿里云oss 配置 */
oss: OSSConfig;
/** 腾讯云 cos配置 */
cos: COSConfig;
/** 七牛云 配置 */
qiniu: QINIUConfig;
/** 文件前缀 */
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;
}
/**
* 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 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;
}

View File

@ -0,0 +1,100 @@
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 = `${modulePath}/config.${
this.app.getEnv() == 'local' ? 'ts' : '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

@ -0,0 +1,150 @@
import { ILogger, IMidwayApplication } from "@midwayjs/core";
import {
App,
Config,
Init,
Inject,
Logger,
Provide,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import { CoolCoreException } from "../exception/core";
import * as Importer from "mysql2-import";
import * as fs from "fs";
import { CoolModuleConfig } from "./config";
import * as path from "path";
import { InjectDataSource, TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { DataSource } from "typeorm";
import { CoolEventManager } from "../event";
/**
* 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;
@Init()
async init() {
// 是否需要导入
if (this.coolConfig.initDB) {
await this.checkDbVersion();
const modules = this.coolModuleConfig.modules;
const importLockPath = path.join(
`${this.app.getBaseDir()}`,
"..",
"lock"
);
if (!fs.existsSync(importLockPath)) {
fs.mkdirSync(importLockPath);
}
setTimeout(async () => {
for (const module of modules) {
const lockPath = path.join(importLockPath, module + ".sql.lock");
if (!fs.existsSync(lockPath)) {
await this.initDataBase(module, lockPath);
}
}
this.coolEventManager.emit("onDBInit", {});
}, 2000);
}
}
/**
*
* @param module
* @param lockPath
*/
async initDataBase(module: string, lockPath: string) {
// 模块路径
const modulePath = `${this.app.getBaseDir()}/modules/${module}`;
// sql 路径
const sqlPath = `${modulePath}/init.sql`;
// 延迟2秒再导入数据库
if (fs.existsSync(sqlPath)) {
let second = 0;
const t = setInterval(() => {
this.coreLogger.info(
"\x1B[36m [cool:core] midwayjs cool core init " +
module +
" database... \x1B[0m"
);
second++;
}, 1000);
const { host, username, password, database, charset, port } = this
.ormConfig?.default
? this.ormConfig.default
: this.ormConfig;
const importer = new Importer({
host,
password,
database,
charset,
port,
user: username,
});
await importer
.import(sqlPath)
.then(async () => {
clearInterval(t);
this.coreLogger.info(
"\x1B[36m [cool:core] midwayjs cool core init " +
module +
" database complete \x1B[0m"
);
fs.writeFileSync(lockPath, `time consuming${second}s`);
})
.catch((err) => {
clearTimeout(t);
this.coreLogger.error(
"\x1B[36m [cool:core] midwayjs cool core init " +
module +
" database err please manual import \x1B[0m"
);
fs.writeFileSync(lockPath, `time consuming${second}s`);
this.coreLogger.error(err);
this.coreLogger.error(
`自动初始化模块[${module}]数据库失败,尝试手动导入数据库`
);
});
}
}
/**
*
*/
async checkDbVersion() {
const versions = (
await this.defaultDataSource.query("SELECT VERSION() AS version")
)[0].version.split(".");
if ((versions[0] == 5 && versions[1] < 7) || versions[0] < 5) {
throw new CoolCoreException(
"数据库不满足要求mysql>=5.7,请升级数据库版本"
);
}
}
}

View File

@ -0,0 +1,55 @@
{
"name": "@cool-midway/core",
"version": "6.0.2",
"description": "",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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.11",
"typescript": "~4.9.4"
},
"dependencies": {
"@midwayjs/cache": "^3.9.0",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"moment": "^2.29.4",
"mysql2-import": "^5.0.22",
"sqlstring": "^2.3.3"
}
}

View File

@ -0,0 +1,124 @@
import {
CONTROLLER_KEY,
getClassMetadata,
listModule,
Provide,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import * as _ from "lodash";
import { Inject, MidwayWebRouterService } from "@midwayjs/core";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolEps {
admin = {};
app = {};
@Inject()
midwayWebRouterService: MidwayWebRouterService;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
// @Init()
async init() {
const entitys = await this.entity();
const controllers = await this.controller();
const routers = await this.router();
const adminArr = [];
const appArr = [];
for (const controller of controllers) {
const { prefix, module, curdOption } = controller;
const name = curdOption?.entity?.name;
(_.startsWith(prefix, "/admin/") ? adminArr : appArr).push({
module,
api: routers[prefix],
name,
columns: entitys[name] || [],
prefix,
});
}
this.admin = _.groupBy(adminArr, "module");
this.app = _.groupBy(appArr, "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() {
return _.groupBy(
(await await this.midwayWebRouterService.getFlattenRouterTable()).map(
(item) => {
return {
method: item.requestMethod,
path: item.url,
summary: item.summary,
dts: {},
tag: "",
prefix: item.prefix,
};
}
),
"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;
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

@ -0,0 +1,526 @@
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 * 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()
export abstract class BaseService {
// 分页配置
@Config("cool")
private _coolConfig: CoolConfig;
// 模型
protected entity: Repository<any>;
protected 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 && condition !== "")) {
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
*/
private 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);
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: 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 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");
}
/**
*
* @param param
*/
async add(param: any | any[]): Promise<Object> {
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 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
*/
private 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 && 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.baseCtx, this.baseApp);
}
const sqls = find.getSql().split("FROM");
sqlArr.push("FROM");
sqlArr.push(sqls[1]);
return sqlArr.join(" ");
}
/**
* ||
* @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

@ -0,0 +1,46 @@
import { CoolUrlTagConfig } from './../decorator/tag';
import {
CONTROLLER_KEY,
getClassMetadata,
Init,
listModule,
Provide,
Scope,
ScopeEnum,
} from '@midwayjs/decorator';
import { COOL_URL_TAG_KEY } from '../decorator/tag';
/**
* URL标签
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolUrlTagData {
data = {};
@Init()
async init() {
const tags = listModule(COOL_URL_TAG_KEY);
for (const controller of tags) {
const controllerOption = getClassMetadata(CONTROLLER_KEY, controller);
const tagOption: CoolUrlTagConfig = getClassMetadata(
COOL_URL_TAG_KEY,
controller
);
const data: string[] = this.data[tagOption.key] || [];
this.data[tagOption.key] = data.concat(
tagOption.value.map(e => {
return controllerOption.prefix + '/' + e;
})
);
}
}
/**
*
* @param key
* @returns
*/
byKey(key: string): string[] {
return this.data[key];
}
}

View File

@ -0,0 +1,27 @@
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

@ -0,0 +1,95 @@
import { Session } from 'inspector';
import { v1 as uuid } from 'uuid';
import * as util from 'util';
/**
* Location
*/
class LocationUtil {
static instance = null;
session: Session;
PREFIX = '__functionLocation__';
scripts = {};
post$ = null;
constructor() {
if (!LocationUtil.instance) {
this.init();
LocationUtil.instance = this;
}
return LocationUtil.instance;
}
init() {
if (!global[this.PREFIX]) {
global[this.PREFIX] = {};
}
if (this.session) {
return;
}
this.session = new Session();
this.session.connect();
this.post$ = util.promisify(this.session.post).bind(this.session);
this.session.on('Debugger.scriptParsed', res => {
this.scripts[res.params.scriptId] = res.params;
LocationUtil.instance = this;
});
this.post$('Debugger.enable');
LocationUtil.instance = this;
}
/**
*
* @param target
*/
async scriptPath(target: any) {
const id = uuid();
global[this.PREFIX][id] = target;
const evaluated = await this.post$('Runtime.evaluate', {
expression: `global['${this.PREFIX}']['${id}']`,
objectGroup: this.PREFIX,
});
const properties = await this.post$('Runtime.getProperties', {
objectId: evaluated.result.objectId,
});
const location = properties.internalProperties.find(
prop => prop.name === '[[FunctionLocation]]'
);
const script = this.scripts[location.value.value.scriptId];
delete global[this.PREFIX][id];
let source = decodeURI(script.url);
if (!source.startsWith('file://')) {
source = `file://${source}`;
}
return {
column: location.value.value.columnNumber + 1,
line: location.value.value.lineNumber + 1,
path: source.substr(7),
source,
};
}
/**
*
*/
async clean() {
if (this.session) {
await this.post$('Runtime.releaseObjectGroup', {
objectGroup: this.PREFIX,
});
this.session.disconnect();
}
this.session = null;
this.post$ = null;
this.scripts = null;
delete global[this.PREFIX];
LocationUtil.instance = null;
}
}
export default new LocationUtil();

View File

@ -0,0 +1,14 @@
import { createLightApp } from '@midwayjs/mock';
import * as custom from '../src';
describe('/test/index.test.ts', () => {
it('test component', async () => {
const app = await createLightApp('', {
imports: [
custom
]
});
const bookService = await app.getApplicationContext().getAsync(custom.BookService);
expect(await bookService.getBookById()).toEqual('hello world');
});
});

View File

@ -0,0 +1,25 @@
{
"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"
]
}

11
packages/es/.editorconfig Normal file
View File

@ -0,0 +1,11 @@
# 🎨 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

@ -0,0 +1,28 @@
{
"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
packages/es/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
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

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

3
packages/es/README.md Normal file
View File

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

10
packages/es/index.d.ts vendored Normal file
View File

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

View File

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

View File

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

44
packages/es/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "@cool-midway/es",
"version": "6.0.0",
"description": "cool-js.com elasticsearch",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "^6.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"
}
}

569
packages/es/src/base.ts Normal file
View File

@ -0,0 +1,569 @@
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;
}
/**
*
* @param body
*/
async find(body?: any, size?: number) {
if (!body) {
body = {};
}
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 (!size) {
size = 20;
}
if (!body) {
body = {};
}
const total = await this.findCount(body);
body.from = (page - 1) * size;
body.size = 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

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

View File

@ -0,0 +1,19 @@
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

@ -0,0 +1,41 @@
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

@ -0,0 +1,154 @@
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] });
} else {
this.client = new Client({ nodes: this.esConfig.nodes });
}
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'
);
}
});
}
});
}
}

15
packages/es/src/index.ts Normal file
View File

@ -0,0 +1,15 @@
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[];
}

View File

@ -0,0 +1,42 @@
{
"name": "@cool-midway/es",
"version": "6.0.0",
"description": "cool-js.com elasticsearch",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "^6.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

@ -0,0 +1,14 @@
import { createLightApp } from '@midwayjs/mock';
import * as custom from '../src';
describe('/test/index.test.ts', () => {
it('test component', async () => {
const app = await createLightApp('', {
imports: [
custom
]
});
const bookService = await app.getApplicationContext().getAsync(custom.BookService);
expect(await bookService.getBookById()).toEqual('hello world');
});
});

24
packages/es/tsconfig.json Normal file
View File

@ -0,0 +1,24 @@
{
"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

@ -0,0 +1,11 @@
# 🎨 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

@ -0,0 +1,20 @@
{
"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
packages/file/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
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

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

21
packages/file/LICENSE Normal file
View File

@ -0,0 +1,21 @@
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
packages/file/index.d.ts vendored Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,53 @@
{
"name": "@cool-midway/file",
"version": "6.0.1",
"description": "",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "^6.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": {
"@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"
}
}

View File

@ -0,0 +1,16 @@
import { CoolFileConfig } from './../interface';
import { MODETYPE } from '../interface';
/**
* cool的配置
*/
export default {
cool: {
file: {
// 上传模式
mode: MODETYPE.LOCAL,
// 文件路径前缀 本地上传模式下 有效
domain: 'http://127.0.0.1:8001',
} as CoolFileConfig,
},
};

View File

@ -0,0 +1,21 @@
import { Configuration } from '@midwayjs/decorator';
import * as DefaultConfig from './config/config.default';
import * as upload from '@midwayjs/upload';
import { IMidwayContainer } from '@midwayjs/core';
import { CoolFile } from './file';
@Configuration({
namespace: 'cool:file',
importConfigs: [
{
default: DefaultConfig,
},
],
imports: [upload],
})
export class CoolFileConfiguration {
async onReady(container: IMidwayContainer) {
await container.getAsync(CoolFile);
// TODO something
}
}

475
packages/file/src/file.ts Normal file
View File

@ -0,0 +1,475 @@
import {
App,
Config,
Init,
Logger,
Provide,
Scope,
ScopeEnum,
} from '@midwayjs/decorator';
import { Mode, CoolFileConfig, MODETYPE, CLOUDTYPE } from './interface';
import { CoolCommException } from '@cool-midway/core';
import * as moment from 'moment';
import { v1 as uuid } from 'uuid';
import * as path from 'path';
import * as fs from 'fs';
import { ILogger, IMidwayApplication } from '@midwayjs/core';
import * as _ from 'lodash';
import * as OSS from 'ali-oss';
import * as crypto from 'crypto';
import * as STS from 'qcloud-cos-sts';
import * as download from 'download';
import * as COS from 'cos-nodejs-sdk-v5';
import * as QINIU from 'qiniu';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolFile {
@Config('cool.file')
config: CoolFileConfig;
@Logger()
coreLogger: ILogger;
client: OSS & COS & QINIU.auth.digest.Mac;
@App()
app: IMidwayApplication;
@Init()
async init(config: CoolFileConfig) {
const filePath = path.join(this.app.getBaseDir(), '..', 'public');
const uploadsPath = path.join(filePath, 'uploads');
const tempPath = path.join(filePath, 'temp');
if (!fs.existsSync(uploadsPath)) {
fs.mkdirSync(uploadsPath);
}
if (!fs.existsSync(tempPath)) {
fs.mkdirSync(tempPath);
}
if (config) {
this.config = config;
}
const { mode, oss, cos, qiniu } = this.config;
if (mode == MODETYPE.CLOUD) {
if (oss) {
const { accessKeyId, accessKeySecret, bucket, endpoint } = oss;
this.client = new OSS({
region: endpoint.split('.')[0],
accessKeyId,
accessKeySecret,
bucket,
});
}
if (cos) {
const { accessKeyId, accessKeySecret } = cos;
this.client = new COS({
SecretId: accessKeyId,
SecretKey: accessKeySecret,
});
}
if (qiniu) {
const { accessKeyId, accessKeySecret } = qiniu;
this.client = new QINIU.auth.digest.Mac(accessKeyId, accessKeySecret);
}
}
}
/**
*
* @returns
*/
async getMode(): Promise<Mode> {
const { mode, oss, cos, qiniu } = this.config;
if (mode == MODETYPE.LOCAL) {
return {
mode: MODETYPE.LOCAL,
type: MODETYPE.LOCAL,
};
}
if (oss) {
return {
mode: MODETYPE.CLOUD,
type: CLOUDTYPE.OSS,
};
}
if (cos) {
return {
mode: MODETYPE.CLOUD,
type: CLOUDTYPE.COS,
};
}
if (qiniu) {
return {
mode: MODETYPE.CLOUD,
type: CLOUDTYPE.QINIU,
};
}
}
/**
*
* @returns
*/
getMetaFileObj(): OSS & COS & QINIU.auth.digest.Mac {
return this.client;
}
/**
*
* @param url
* @param fileName
*/
async downAndUpload(url: string, fileName?: string) {
const { mode, oss, cos, qiniu, domain } = this.config;
let extend = '';
if (url.includes('.')) {
const urlArr = url.split('.');
extend = '.' + urlArr[urlArr.length - 1].split('?')[0];
}
const data = url.includes('http')
? await download(url)
: fs.readFileSync(url);
const isCloud = mode == MODETYPE.CLOUD;
// 创建文件夹
const dirPath = path.join(
this.app.getBaseDir(),
'..',
`public/${isCloud ? 'temp' : 'uploads'}/${moment().format('YYYYMMDD')}`
);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath);
}
const uuidStr = uuid();
const name = `uploads/${moment().format('YYYYMMDD')}/${
fileName ? fileName : uuidStr + extend
}`;
if (isCloud) {
if (oss) {
const ossClient: OSS = this.getMetaFileObj();
return (await ossClient.put(name, data)).url;
}
if (cos) {
const cosClient: COS = this.getMetaFileObj();
await cosClient.putObject({
Bucket: cos.bucket,
Region: cos.region,
Key: name,
Body: data,
});
return cos.publicDomain + '/' + name;
}
if (qiniu) {
let uploadToken = (await this.qiniu())['token'];
const formUploader = new QINIU.form_up.FormUploader();
const putExtra = new QINIU.form_up.PutExtra();
return new Promise((resolve, reject) => {
formUploader.put(
uploadToken,
name,
data,
putExtra,
(respErr, respBody, respInfo) => {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode == 200) {
resolve(qiniu.publicDomain + '/' + name);
}
}
);
});
}
} else {
fs.writeFileSync(
`${dirPath}/${fileName ? fileName : uuidStr + extend}`,
data
);
return `${domain}/public/${name}`;
}
}
/**
* Key()
* @param file
* @param key
*/
async uploadWithKey(file, key) {
const { mode, oss, cos, qiniu } = this.config;
const data = fs.readFileSync(file.data);
if (mode == MODETYPE.LOCAL) {
fs.writeFileSync(path.join(this.app.getBaseDir(), '..', key), data);
return this.config.domain + key;
}
if (mode == MODETYPE.CLOUD) {
if (oss) {
const ossClient: OSS = this.getMetaFileObj();
return (await ossClient.put(key, data)).url;
}
if (cos) {
const cosClient: COS = this.getMetaFileObj();
await cosClient.putObject({
Bucket: cos.bucket,
Region: cos.region,
Key: key,
Body: data,
});
return cos.publicDomain + '/' + key;
}
if (qiniu) {
let uploadToken = (await this.qiniu())['token'];
const formUploader = new QINIU.form_up.FormUploader();
const putExtra = new QINIU.form_up.PutExtra();
return new Promise((resolve, reject) => {
formUploader.put(
uploadToken,
key,
data,
putExtra,
(respErr, respBody, respInfo) => {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode == 200) {
resolve(qiniu.publicDomain + '/' + name);
}
}
);
});
}
}
}
/**
*
* @param ctx
* @param key
*/
async upload(ctx) {
const { mode, oss, cos, qiniu } = this.config;
if (mode == MODETYPE.LOCAL) {
return await this.local(ctx);
}
if (mode == MODETYPE.CLOUD) {
if (oss) {
return await this.oss(ctx);
}
if (cos) {
return await this.cos(ctx);
}
if (qiniu) {
return await this.qiniu(ctx);
}
}
}
/**
*
* @param ctx
* @returns
*/
private async qiniu(ctx?) {
const {
bucket,
publicDomain,
region,
uploadUrl = `https://upload-${region}.qiniup.com/`,
fileKey = 'file',
} = this.config.qiniu;
let options = {
scope: bucket,
};
const putPolicy = new QINIU.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(this.client);
return new Promise((resolve, reject) => {
resolve({
uploadUrl,
publicDomain,
token: uploadToken,
fileKey,
});
});
}
/**
* OSS
* @param ctx
*/
private async oss(ctx) {
const {
accessKeyId,
accessKeySecret,
bucket,
endpoint,
expAfter = 300000,
maxSize = 200 * 1024 * 1024,
} = this.config.oss;
const oss = {
bucket,
region: endpoint.split('.')[0], // 我的是 hangzhou
accessKeyId,
accessKeySecret,
expAfter, // 签名失效时间,毫秒
maxSize, // 文件最大的 size
};
const host = `https://${bucket}.${endpoint}`;
const expireTime = new Date().getTime() + oss.expAfter;
const expiration = new Date(expireTime).toISOString();
const policyString = JSON.stringify({
expiration,
conditions: [
['content-length-range', 0, oss.maxSize], // 设置上传文件的大小限制,200mb
],
});
const policy = Buffer.from(policyString).toString('base64');
const signature = crypto
.createHmac('sha1', oss.accessKeySecret)
.update(policy)
.digest('base64');
return {
signature,
policy,
host,
OSSAccessKeyId: accessKeyId,
success_action_status: 200,
};
}
/**
* COS
* @param ctx
*/
private async cos(ctx) {
const {
accessKeyId,
accessKeySecret,
bucket,
region,
publicDomain,
durationSeconds = 1800,
allowPrefix = '_ALLOW_DIR_/*',
allowActions = [
// 所有 action 请看文档 https://cloud.tencent.com/document/product/436/31923
// 简单上传
'name/cos:PutObject',
'name/cos:PostObject',
// 分片上传
'name/cos:InitiateMultipartUpload',
'name/cos:ListMultipartUploads',
'name/cos:ListParts',
'name/cos:UploadPart',
'name/cos:CompleteMultipartUpload',
],
} = this.config.cos;
// 配置参数
let config = {
secretId: accessKeyId,
secretKey: accessKeySecret,
durationSeconds,
bucket: bucket,
region: region,
// 允许操作(上传)的对象前缀,可以根据自己网站的用户登录态判断允许上传的目录,例子: user1/* 或者 * 或者a.jpg
// 请注意当使用 * 时可能存在安全风险详情请参阅https://cloud.tencent.com/document/product/436/40265
allowPrefix,
// 密钥的权限列表
allowActions,
};
// 获取临时密钥
let LongBucketName = config.bucket;
let ShortBucketName = LongBucketName.substring(
0,
LongBucketName.lastIndexOf('-')
);
let AppId = LongBucketName.substring(LongBucketName.lastIndexOf('-') + 1);
let policy = {
version: '2.0',
statement: [
{
action: config.allowActions,
effect: 'allow',
resource: [
'qcs::cos:' +
config.region +
':uid/' +
AppId +
':prefix//' +
AppId +
'/' +
ShortBucketName +
'/' +
config.allowPrefix,
],
},
],
};
return new Promise((resolve, reject) => {
STS.getCredential(
{
secretId: config.secretId,
secretKey: config.secretKey,
durationSeconds: config.durationSeconds,
policy: policy,
},
(err, tempKeys) => {
if (err) {
reject(err);
}
if (tempKeys) {
tempKeys.startTime = Math.round(Date.now() / 1000);
}
resolve({
...tempKeys,
url: publicDomain,
});
}
);
});
}
/**
*
* @param ctx
* @returns
*/
private async local(ctx) {
try {
const { key } = ctx.fields;
if (_.isEmpty(ctx.files)) {
throw new CoolCommException('上传文件为空');
}
const file = ctx.files[0];
const extension = file.filename.split('.').pop();
const name =
moment().format('YYYYMMDD') + '/' + (key || `${uuid()}.${extension}`);
const target = path.join(
this.app.getBaseDir(),
'..',
`public/uploads/${name}`
);
const dirPath = path.join(
this.app.getBaseDir(),
'..',
`public/uploads/${moment().format('YYYYMMDD')}`
);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath);
}
const data = fs.readFileSync(file.data);
fs.writeFileSync(target, data);
return this.config.domain + '/public/uploads/' + name;
} catch (err) {
this.coreLogger.error(err);
throw new CoolCommException('上传失败');
}
}
}

View File

@ -0,0 +1,5 @@
export { CoolFileConfiguration as Configuration } from './configuration';
export * from './interface';
export * from './file';

View File

@ -0,0 +1,103 @@
// 模式
export enum MODETYPE {
// 本地
LOCAL = 'local',
// 云存储
CLOUD = 'cloud',
// 其他
OTHER = 'other',
}
export enum CLOUDTYPE {
// 阿里云存储
OSS = 'oss',
// 腾讯云存储
COS = 'cos',
// 七牛云存储
QINIU = 'qiniu',
}
/**
*
*/
export interface Mode {
// 模式
mode: MODETYPE;
// 类型
type: string;
}
/**
*
*/
export interface CoolFileConfig {
// 上传模式
mode: MODETYPE;
// 阿里云oss 配置
oss: OSSConfig;
// 腾讯云 cos配置
cos: COSConfig;
// 七牛云 配置
qiniu: QINIUConfig;
// 文件前缀
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;
}
/**
* 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;
}

View File

@ -0,0 +1,53 @@
{
"name": "@cool-midway/file",
"version": "6.0.1",
"description": "",
"main": "index.js",
"typings": "index.d.ts",
"scripts": {
"build": "cross-env midway-bin build -c",
"test": "cross-env midway-bin test --ts",
"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": "^6.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": {
"@midwayjs/upload": "^3.9.1",
"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"
}
}

View File

@ -0,0 +1,14 @@
import { createLightApp } from '@midwayjs/mock';
import * as custom from '../src';
describe('/test/index.test.ts', () => {
it('test component', async () => {
const app = await createLightApp('', {
imports: [
custom
]
});
const bookService = await app.getApplicationContext().getAsync(custom.BookService);
expect(await bookService.getBookById()).toEqual('hello world');
});
});

View File

@ -0,0 +1,25 @@
{
"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

@ -0,0 +1,11 @@
# 🎨 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

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