mirror of
https://github.com/cool-team-official/cool-admin-midway-packages.git
synced 2026-01-24 06:48:12 +00:00
支持打包成二进制
This commit is contained in:
parent
ffe8c9f199
commit
7d1c55d842
@ -3,5 +3,19 @@
|
|||||||
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
|
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
|
||||||
"env": {
|
"env": {
|
||||||
"jest": true
|
"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",
|
||||||
|
"node/no-unpublished-import": "off",
|
||||||
|
"eqeqeq": "off",
|
||||||
|
"node/no-unsupported-features/node-builtins": "off",
|
||||||
|
"@typescript-eslint/ban-types": "off",
|
||||||
|
"no-control-regex": "off",
|
||||||
|
"prefer-const": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +1,69 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json.schemastore.org/package",
|
|
||||||
"name": "@cool-midway/core",
|
"name": "@cool-midway/core",
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"description": "cool-admin-midway core",
|
"description": "cool-admin core",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "index.d.ts",
|
"typings": "index.d.ts",
|
||||||
"bin": {
|
"bin": {
|
||||||
"cool": "./dist/bin/index.js"
|
"cool": "dist/bin/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "mwtsc --cleanOutDir",
|
"build": "cross-env midway-bin build -c",
|
||||||
"test": "cross-env NODE_ENV=unittest jest",
|
"cov": "cross-env midway-bin cov --ts",
|
||||||
"cov": "jest --coverage",
|
|
||||||
"lint": "mwts check",
|
"lint": "mwts check",
|
||||||
"lint:fix": "mwts fix"
|
"lint:fix": "mwts fix"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [
|
||||||
"author": "",
|
"cool",
|
||||||
|
"cool-admin",
|
||||||
|
"cooljs"
|
||||||
|
],
|
||||||
|
"author": "COOL",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**/*.js",
|
"dist/**/*.js",
|
||||||
"dist/**/*.d.ts",
|
"dist/**/*.d.ts",
|
||||||
"index.d.ts"
|
"index.d.ts"
|
||||||
],
|
],
|
||||||
|
"readme": "README.md",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://cool-js.com"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@midwayjs/cli": "2.1.1",
|
||||||
"@midwayjs/core": "^3.19.0",
|
"@midwayjs/core": "^3.19.0",
|
||||||
"@midwayjs/logger": "^3.4.2",
|
"@midwayjs/koa": "^3.19.2",
|
||||||
"@midwayjs/mock": "^3.19.2",
|
"@midwayjs/mock": "^3.19.2",
|
||||||
|
"@midwayjs/typeorm": "^3.19.2",
|
||||||
|
"@types/download": "^8.0.5",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
|
"aedes": "^0.51.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"mwts": "^1.3.0",
|
"mwts": "^1.3.0",
|
||||||
"mwtsc": "^1.15.1",
|
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
|
"typeorm": "^0.3.20",
|
||||||
"typescript": "~5.7.3"
|
"typescript": "~5.7.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^11.1.0"
|
"@cool-midway/cache-manager-fs-hash": "^7.0.0",
|
||||||
|
"@midwayjs/cache": "^3.14.0",
|
||||||
|
"@midwayjs/cache-manager": "^3.19.3",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"commander": "^13.0.0",
|
||||||
|
"decompress": "^4.2.1",
|
||||||
|
"download": "^8.0.0",
|
||||||
|
"glob": "^11.0.1",
|
||||||
|
"javascript-obfuscator": "^4.1.1",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"pm2": "^5.4.3",
|
||||||
|
"sqlstring": "^2.3.3",
|
||||||
|
"uuid": "^11.0.5",
|
||||||
|
"ws": "^8.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3825
core/pnpm-lock.yaml
generated
3825
core/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,48 @@
|
|||||||
export async function check() {
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||||
try {
|
import { v4 as uuid } from 'uuid';
|
||||||
console.log('Running check command...');
|
import { join } from 'path';
|
||||||
// 在这里实现检查逻辑
|
|
||||||
} catch (error) {
|
interface CheckConfig {
|
||||||
console.error('Check failed:', error);
|
path: string;
|
||||||
process.exit(1);
|
pattern: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并替换单个配置文件
|
||||||
|
*/
|
||||||
|
async function checkAndReplaceFile(config: CheckConfig): Promise<void> {
|
||||||
|
const filePath = join(process.cwd(), config.path);
|
||||||
|
|
||||||
|
if (!existsSync(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = readFileSync(filePath, 'utf-8');
|
||||||
|
if (content.includes(config.pattern)) {
|
||||||
|
console.log(`${config.path},key is default, auto replace it`);
|
||||||
|
content = content.replace(config.pattern, uuid());
|
||||||
|
writeFileSync(filePath, content, 'utf-8');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查配置文件
|
||||||
|
*/
|
||||||
|
export async function check() {
|
||||||
|
const configs: CheckConfig[] = [
|
||||||
|
{
|
||||||
|
path: 'src/config/config.default.ts',
|
||||||
|
pattern: 'cool-admin-keys-xxxxxx',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'src/modules/base/config.ts',
|
||||||
|
pattern: 'cool-admin-xxxxxx',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'src/modules/user/config.ts',
|
||||||
|
pattern: 'cool-app-xxxxx',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(configs.map(config => checkAndReplaceFile(config)));
|
||||||
|
}
|
||||||
|
|||||||
42
core/src/bin/entity.ts
Normal file
42
core/src/bin/entity.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as glob from 'glob';
|
||||||
|
|
||||||
|
const MODULES_PATH = 'src/modules';
|
||||||
|
const OUTPUT_FILE = 'src/entities.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 entities.ts 文件
|
||||||
|
*/
|
||||||
|
export function generateEntitiesFile() {
|
||||||
|
// 扫描所有的 ts 文件
|
||||||
|
const entityFiles = glob.sync('*/entity/**/*.ts', {
|
||||||
|
cwd: MODULES_PATH,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成导入语句和导出数组
|
||||||
|
const imports = entityFiles.map((file, index) => {
|
||||||
|
const relativePath = path.relative(path.dirname(OUTPUT_FILE), file);
|
||||||
|
return `import * as entity${index} from './${relativePath.replace(
|
||||||
|
/\.ts$/,
|
||||||
|
''
|
||||||
|
)}';`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportEntities = `export const entities = [
|
||||||
|
${entityFiles
|
||||||
|
.map((_, index) => `...Object.values(entity${index})`)
|
||||||
|
.join(',\n ')},
|
||||||
|
];`;
|
||||||
|
|
||||||
|
// 生成最终的文件内容
|
||||||
|
const fileContent = `// 自动生成的文件,请勿手动修改
|
||||||
|
${imports.join('\n')}
|
||||||
|
${exportEntities}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
fs.writeFileSync(OUTPUT_FILE, fileContent);
|
||||||
|
console.log('Entities file generated successfully!');
|
||||||
|
}
|
||||||
@ -2,18 +2,38 @@
|
|||||||
|
|
||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { check } from './check';
|
import { check } from './check';
|
||||||
|
import { generateEntitiesFile } from './entity';
|
||||||
|
import { obfuscate } from './obfuscate';
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
// 设置版本号(从 package.json 中获取)
|
// 设置版本号(从 package.json 中获取)
|
||||||
program.version(require('../../package.json').version);
|
program.version(require('../../package.json').version);
|
||||||
|
|
||||||
// 添加 check 命令
|
// 修改命令定义部分
|
||||||
|
const commands = {
|
||||||
|
check: async () => await check(),
|
||||||
|
entity: async () => await generateEntitiesFile(),
|
||||||
|
obfuscate: async () => await obfuscate(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除原有的单独命令定义
|
||||||
program
|
program
|
||||||
.command('check')
|
.arguments('[cmds...]')
|
||||||
.description('Run check command')
|
.description('Run one or multiple commands: check, entity, obfuscate')
|
||||||
.action(async () => {
|
.action(async (cmds: string[]) => {
|
||||||
await check();
|
if (!cmds.length) {
|
||||||
|
program.outputHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const cmd of cmds) {
|
||||||
|
if (cmd in commands) {
|
||||||
|
console.log(`Executing ${cmd}...`);
|
||||||
|
await commands[cmd]();
|
||||||
|
} else {
|
||||||
|
console.error(`Unknown command: ${cmd}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 解析命令行参数
|
// 解析命令行参数
|
||||||
|
|||||||
72
core/src/bin/obfuscate.ts
Normal file
72
core/src/bin/obfuscate.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as JavaScriptObfuscator from 'javascript-obfuscator';
|
||||||
|
|
||||||
|
// 混淆配置
|
||||||
|
const obfuscatorOptions: JavaScriptObfuscator.ObfuscatorOptions = {
|
||||||
|
compact: true,
|
||||||
|
controlFlowFlattening: true,
|
||||||
|
controlFlowFlatteningThreshold: 0.7,
|
||||||
|
deadCodeInjection: true,
|
||||||
|
deadCodeInjectionThreshold: 0.4,
|
||||||
|
debugProtection: false,
|
||||||
|
debugProtectionInterval: 0,
|
||||||
|
disableConsoleOutput: true,
|
||||||
|
identifierNamesGenerator: 'hexadecimal' as const,
|
||||||
|
log: false,
|
||||||
|
numbersToExpressions: true,
|
||||||
|
renameGlobals: false,
|
||||||
|
rotateStringArray: true,
|
||||||
|
selfDefending: true,
|
||||||
|
shuffleStringArray: true,
|
||||||
|
splitStrings: true,
|
||||||
|
splitStringsChunkLength: 10,
|
||||||
|
stringArray: true,
|
||||||
|
stringArrayEncoding: ['base64'],
|
||||||
|
stringArrayThreshold: 0.75,
|
||||||
|
transformObjectKeys: true,
|
||||||
|
unicodeEscapeSequence: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理单个文件的函数
|
||||||
|
function obfuscateFile(filePath: string): void {
|
||||||
|
try {
|
||||||
|
const code = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const obfuscationResult = JavaScriptObfuscator.obfuscate(
|
||||||
|
code,
|
||||||
|
obfuscatorOptions
|
||||||
|
);
|
||||||
|
fs.writeFileSync(filePath, obfuscationResult.getObfuscatedCode());
|
||||||
|
// console.log(`成功混淆文件: ${filePath}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`处理文件 ${filePath} 时出错:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理目录的函数
|
||||||
|
function processDirectory(directory: string): void {
|
||||||
|
const files = fs.readdirSync(directory);
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
const fullPath = path.join(directory, file);
|
||||||
|
const stat = fs.statSync(fullPath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
processDirectory(fullPath);
|
||||||
|
} else if (path.extname(file) === '.js') {
|
||||||
|
obfuscateFile(fullPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出主函数
|
||||||
|
export async function obfuscate(): Promise<void> {
|
||||||
|
const distPath = path.join(process.cwd(), 'dist');
|
||||||
|
if (fs.existsSync(distPath)) {
|
||||||
|
console.log('开始混淆 dist 目录下的 JS 文件...');
|
||||||
|
processDirectory(distPath);
|
||||||
|
console.log('混淆完成!打包成功!');
|
||||||
|
} else {
|
||||||
|
console.error('错误: dist 目录不存在!');
|
||||||
|
}
|
||||||
|
}
|
||||||
64
core/src/cache/store.ts
vendored
Normal file
64
core/src/cache/store.ts
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as FsStore from '@cool-midway/cache-manager-fs-hash';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cool 基于磁盘的缓存
|
||||||
|
*/
|
||||||
|
class FsCacheStore {
|
||||||
|
options: any;
|
||||||
|
|
||||||
|
store: FsStore;
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
options = {
|
||||||
|
...options,
|
||||||
|
path: 'cache',
|
||||||
|
ttl: -1,
|
||||||
|
};
|
||||||
|
this.options = options;
|
||||||
|
this.store = FsStore.create(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async get<T>(key: string): Promise<T> {
|
||||||
|
return await this.store.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @param ttl
|
||||||
|
*/
|
||||||
|
async set<T>(key: string, value: T, ttl: number): Promise<void> {
|
||||||
|
let t = ttl ? ttl : this.options.ttl;
|
||||||
|
if (t > 0) {
|
||||||
|
t = t / 1000;
|
||||||
|
}
|
||||||
|
await this.store.set(key, value, {
|
||||||
|
ttl: t,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
async del(key: string): Promise<void> {
|
||||||
|
await this.store.del(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置
|
||||||
|
*/
|
||||||
|
async reset(): Promise<void> {
|
||||||
|
await this.store.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoolCacheStore = function (options = {}) {
|
||||||
|
return new FsCacheStore(options);
|
||||||
|
};
|
||||||
@ -1,4 +1,22 @@
|
|||||||
export const customKey = {
|
import { CoolConfig } from '../interface';
|
||||||
a: 1,
|
|
||||||
b: 'hello',
|
/**
|
||||||
|
* cool的配置
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
cool: {
|
||||||
|
// 是否自动导入数据库
|
||||||
|
initDB: false,
|
||||||
|
// 是否自动导入模块菜单
|
||||||
|
initMenu: true,
|
||||||
|
// 判断是否初始化的方式
|
||||||
|
initJudge: 'file',
|
||||||
|
// crud配置
|
||||||
|
crud: {
|
||||||
|
// 软删除
|
||||||
|
softDelete: true,
|
||||||
|
// 分页查询每页条数
|
||||||
|
pageSize: 15,
|
||||||
|
},
|
||||||
|
} as CoolConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,16 +1,74 @@
|
|||||||
|
import {
|
||||||
|
App,
|
||||||
|
ILifeCycle,
|
||||||
|
ILogger,
|
||||||
|
IMidwayContainer,
|
||||||
|
Inject,
|
||||||
|
Logger,
|
||||||
|
} from '@midwayjs/core';
|
||||||
import { Configuration } from '@midwayjs/core';
|
import { Configuration } from '@midwayjs/core';
|
||||||
import * as DefaultConfig from './config/config.default';
|
import * as DefaultConfig from './config/config.default';
|
||||||
|
import { CoolExceptionFilter } from './exception/filter';
|
||||||
|
import { FuncUtil } from './util/func';
|
||||||
|
import * as koa from '@midwayjs/koa';
|
||||||
|
import { CoolModuleConfig } from './module/config';
|
||||||
|
import { CoolModuleImport } from './module/import';
|
||||||
|
import { CoolEventManager } from './event';
|
||||||
|
import { CoolEps } from './rest/eps';
|
||||||
|
import { CoolDecorator } from './decorator';
|
||||||
|
import * as cache from '@midwayjs/cache-manager';
|
||||||
|
import * as _cache from '@midwayjs/cache';
|
||||||
|
|
||||||
@Configuration({
|
@Configuration({
|
||||||
namespace: 'cool',
|
namespace: 'cool',
|
||||||
|
imports: [_cache, cache],
|
||||||
importConfigs: [
|
importConfigs: [
|
||||||
{
|
{
|
||||||
default: DefaultConfig,
|
default: DefaultConfig,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class BookConfiguration {
|
export class CoolConfiguration implements ILifeCycle {
|
||||||
async onReady() {
|
@Logger()
|
||||||
// TODO something
|
coreLogger: ILogger;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: koa.Application;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
async onReady(container: IMidwayContainer) {
|
||||||
|
this.coolEventManager.emit('onReady');
|
||||||
|
// 处理模块配置
|
||||||
|
await container.getAsync(CoolModuleConfig);
|
||||||
|
// 常用函数处理
|
||||||
|
await container.getAsync(FuncUtil);
|
||||||
|
// 异常处理
|
||||||
|
this.app.useFilter([CoolExceptionFilter]);
|
||||||
|
// 装饰器
|
||||||
|
await container.getAsync(CoolDecorator);
|
||||||
|
|
||||||
|
// 缓存设置为全局
|
||||||
|
// global["COOL-CACHE"] = await container.getAsync(CacheManager);
|
||||||
|
// // 清除 location
|
||||||
|
// setTimeout(() => {
|
||||||
|
// location.clean();
|
||||||
|
// this.coreLogger.info("\x1B[36m [cool:core] location clean \x1B[0m");
|
||||||
|
// }, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onConfigLoad() {}
|
||||||
|
|
||||||
|
async onServerReady(container: IMidwayContainer) {
|
||||||
|
// 事件
|
||||||
|
await (await container.getAsync(CoolEventManager)).init();
|
||||||
|
// 导入模块数据
|
||||||
|
(await container.getAsync(CoolModuleImport)).init();
|
||||||
|
// 实体与路径
|
||||||
|
const eps: CoolEps = await container.getAsync(CoolEps);
|
||||||
|
eps.init();
|
||||||
|
this.coolEventManager.emit('onServerReady');
|
||||||
|
// location.clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
core/src/constant/global.ts
Normal file
79
core/src/constant/global.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* 返回码
|
||||||
|
*/
|
||||||
|
export enum RESCODE {
|
||||||
|
// 成功
|
||||||
|
SUCCESS = 1000,
|
||||||
|
// 失败
|
||||||
|
COMMFAIL = 1001,
|
||||||
|
// 参数验证失败
|
||||||
|
VALIDATEFAIL = 1002,
|
||||||
|
// 参数验证失败
|
||||||
|
COREFAIL = 1003,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回信息
|
||||||
|
*/
|
||||||
|
export enum RESMESSAGE {
|
||||||
|
// 成功
|
||||||
|
SUCCESS = 'success',
|
||||||
|
// 失败
|
||||||
|
COMMFAIL = 'comm fail',
|
||||||
|
// 参数验证失败
|
||||||
|
VALIDATEFAIL = 'validate fail',
|
||||||
|
// 核心异常
|
||||||
|
COREFAIL = 'core fail',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误提示
|
||||||
|
*/
|
||||||
|
export enum ERRINFO {
|
||||||
|
NOENTITY = '未设置操作实体',
|
||||||
|
NOID = '查询参数[id]不存在',
|
||||||
|
SORTFIELD = '排序参数不正确',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件
|
||||||
|
*/
|
||||||
|
export enum EVENT {
|
||||||
|
// 软删除
|
||||||
|
SOFT_DELETE = 'onSoftDelete',
|
||||||
|
// 服务成功启动
|
||||||
|
SERVER_READY = 'onServerReady',
|
||||||
|
// 服务就绪
|
||||||
|
READY = 'onReady',
|
||||||
|
// ES 数据改变
|
||||||
|
ES_DATA_CHANGE = 'esDataChange',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GlobalConfig {
|
||||||
|
private static instance: GlobalConfig;
|
||||||
|
|
||||||
|
RESCODE = {
|
||||||
|
SUCCESS: 1000,
|
||||||
|
COMMFAIL: 1001,
|
||||||
|
VALIDATEFAIL: 1002,
|
||||||
|
COREFAIL: 1003,
|
||||||
|
};
|
||||||
|
|
||||||
|
RESMESSAGE = {
|
||||||
|
SUCCESS: 'success',
|
||||||
|
COMMFAIL: 'comm fail',
|
||||||
|
VALIDATEFAIL: 'validate fail',
|
||||||
|
COREFAIL: 'core fail',
|
||||||
|
};
|
||||||
|
|
||||||
|
// ... 其他的配置 ...
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
static getInstance(): GlobalConfig {
|
||||||
|
if (!GlobalConfig.instance) {
|
||||||
|
GlobalConfig.instance = new GlobalConfig();
|
||||||
|
}
|
||||||
|
return GlobalConfig.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
220
core/src/controller/base.ts
Normal file
220
core/src/controller/base.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import {
|
||||||
|
App,
|
||||||
|
CONTROLLER_KEY,
|
||||||
|
getClassMetadata,
|
||||||
|
Init,
|
||||||
|
Inject,
|
||||||
|
Provide,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { GlobalConfig } from '../constant/global';
|
||||||
|
import { ControllerOption, CurdOption } from '../decorator/controller';
|
||||||
|
import { BaseService } from '../service/base';
|
||||||
|
import { IMidwayApplication } from '@midwayjs/core';
|
||||||
|
import { Context } from '@midwayjs/koa';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制器基类
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
export abstract class BaseController {
|
||||||
|
@Inject('ctx')
|
||||||
|
baseCtx: Context;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
service: BaseService;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
baseApp: IMidwayApplication;
|
||||||
|
|
||||||
|
curdOption: CurdOption;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
connectionName;
|
||||||
|
|
||||||
|
@Init()
|
||||||
|
async init() {
|
||||||
|
const option: ControllerOption = getClassMetadata(CONTROLLER_KEY, this);
|
||||||
|
const curdOption: CurdOption = option.curdOption;
|
||||||
|
this.curdOption = curdOption;
|
||||||
|
if (!this.curdOption) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 操作之前
|
||||||
|
await this.before(curdOption);
|
||||||
|
// 设置service
|
||||||
|
await this.setService(curdOption);
|
||||||
|
// 设置实体
|
||||||
|
await this.setEntity(curdOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async before(curdOption: CurdOption) {
|
||||||
|
if (!curdOption?.before) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await curdOption.before(this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入参数值
|
||||||
|
* @param curdOption 配置
|
||||||
|
*/
|
||||||
|
private async insertParam(curdOption: CurdOption) {
|
||||||
|
if (!curdOption?.insertParam) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const body = this.baseCtx.request.body;
|
||||||
|
if (body) {
|
||||||
|
// 判断body是否是数组
|
||||||
|
if (Array.isArray(body)) {
|
||||||
|
for (let i = 0; i < body.length; i++) {
|
||||||
|
body[i] = {
|
||||||
|
...body[i],
|
||||||
|
...(await curdOption.insertParam(this.baseCtx, this.baseApp)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.baseCtx.request.body = body;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.baseCtx.request.body = {
|
||||||
|
// @ts-ignore
|
||||||
|
...this.baseCtx.request.body,
|
||||||
|
...(await curdOption.insertParam(this.baseCtx, this.baseApp)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置实体
|
||||||
|
* @param curdOption 配置
|
||||||
|
*/
|
||||||
|
private async setEntity(curdOption: CurdOption) {
|
||||||
|
const entity = curdOption?.entity;
|
||||||
|
if (entity) {
|
||||||
|
const dataSourceName =
|
||||||
|
this.typeORMDataSourceManager.getDataSourceNameByModel(entity);
|
||||||
|
this.connectionName = dataSourceName;
|
||||||
|
const entityModel = this.typeORMDataSourceManager
|
||||||
|
.getDataSource(dataSourceName)
|
||||||
|
.getRepository(entity);
|
||||||
|
this.service.setEntity(entityModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置service
|
||||||
|
* @param curdOption
|
||||||
|
*/
|
||||||
|
private async setService(curdOption: CurdOption) {
|
||||||
|
if (curdOption.service) {
|
||||||
|
this.service = await this.baseCtx.requestContext.getAsync(
|
||||||
|
curdOption.service
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async add() {
|
||||||
|
// 插入参数
|
||||||
|
await this.insertParam(this.curdOption);
|
||||||
|
const { body } = this.baseCtx.request;
|
||||||
|
return this.ok(await this.service.add(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async delete() {
|
||||||
|
// @ts-ignore
|
||||||
|
const { ids } = this.baseCtx.request.body;
|
||||||
|
return this.ok(await this.service.delete(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async update() {
|
||||||
|
const { body } = this.baseCtx.request;
|
||||||
|
return this.ok(await this.service.update(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async page() {
|
||||||
|
const { body } = this.baseCtx.request;
|
||||||
|
return this.ok(
|
||||||
|
await this.service.page(
|
||||||
|
body,
|
||||||
|
this.curdOption.pageQueryOp,
|
||||||
|
this.connectionName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表查询
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async list() {
|
||||||
|
const { body } = this.baseCtx.request;
|
||||||
|
return this.ok(
|
||||||
|
await this.service.list(
|
||||||
|
body,
|
||||||
|
this.curdOption.listQueryOp,
|
||||||
|
this.connectionName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询信息
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async info() {
|
||||||
|
const { id } = this.baseCtx.query;
|
||||||
|
return this.ok(
|
||||||
|
await this.service.info(id, this.curdOption.infoIgnoreProperty)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功返回
|
||||||
|
* @param data 返回数据
|
||||||
|
*/
|
||||||
|
ok(data?: any) {
|
||||||
|
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
|
||||||
|
const res = {
|
||||||
|
code: RESCODE.SUCCESS,
|
||||||
|
message: RESMESSAGE.SUCCESS,
|
||||||
|
};
|
||||||
|
if (data || data == 0) {
|
||||||
|
res['data'] = data;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败返回
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
fail(message?: string, code?: number) {
|
||||||
|
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
|
||||||
|
return {
|
||||||
|
code: code ? code : RESCODE.COMMFAIL,
|
||||||
|
message: message
|
||||||
|
? message
|
||||||
|
: code == RESCODE.VALIDATEFAIL
|
||||||
|
? RESMESSAGE.VALIDATEFAIL
|
||||||
|
: RESMESSAGE.COMMFAIL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
8
core/src/decorator/cache.ts
Normal file
8
core/src/decorator/cache.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createCustomMethodDecorator } from '@midwayjs/core';
|
||||||
|
|
||||||
|
// 装饰器内部的唯一 id
|
||||||
|
export const COOL_CACHE = 'decorator:cool_cache';
|
||||||
|
|
||||||
|
export function CoolCache(ttl?: number): MethodDecorator {
|
||||||
|
return createCustomMethodDecorator(COOL_CACHE, ttl);
|
||||||
|
}
|
||||||
215
core/src/decorator/controller.ts
Normal file
215
core/src/decorator/controller.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import { ModuleConfig } from './../interface';
|
||||||
|
import {
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
saveClassMetadata,
|
||||||
|
saveModule,
|
||||||
|
CONTROLLER_KEY,
|
||||||
|
MiddlewareParamArray,
|
||||||
|
WEB_ROUTER_KEY,
|
||||||
|
attachClassMetadata,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import location from '../util/location';
|
||||||
|
|
||||||
|
export type ApiTypes = 'add' | 'delete' | 'update' | 'page' | 'info' | 'list';
|
||||||
|
// Crud配置
|
||||||
|
|
||||||
|
export interface CurdOption {
|
||||||
|
// 路由前缀,不配置默认是按Controller下的文件夹路径
|
||||||
|
prefix?: string;
|
||||||
|
// curd api接口
|
||||||
|
api: ApiTypes[];
|
||||||
|
// 分页查询配置
|
||||||
|
pageQueryOp?: QueryOp | Function;
|
||||||
|
// 非分页查询配置
|
||||||
|
listQueryOp?: QueryOp | Function;
|
||||||
|
// 插入参数
|
||||||
|
insertParam?: Function;
|
||||||
|
// 操作之前
|
||||||
|
before?: Function;
|
||||||
|
// info 忽略返回属性
|
||||||
|
infoIgnoreProperty?: string[];
|
||||||
|
// 实体
|
||||||
|
entity: any;
|
||||||
|
// 服务
|
||||||
|
service?: any;
|
||||||
|
// api标签
|
||||||
|
urlTag?: {
|
||||||
|
name: 'ignoreToken' | string;
|
||||||
|
url: ApiTypes[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface JoinOp {
|
||||||
|
// 实体
|
||||||
|
entity: any;
|
||||||
|
// 别名
|
||||||
|
alias: string;
|
||||||
|
// 关联条件
|
||||||
|
condition: string;
|
||||||
|
// 关联类型
|
||||||
|
type?: 'innerJoin' | 'leftJoin';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字段匹配
|
||||||
|
export interface FieldEq {
|
||||||
|
// 字段
|
||||||
|
column: string;
|
||||||
|
// 请求参数
|
||||||
|
requestParam: string;
|
||||||
|
}
|
||||||
|
// 查询配置
|
||||||
|
export interface QueryOp {
|
||||||
|
// 需要模糊查询的字段
|
||||||
|
keyWordLikeFields?: string[];
|
||||||
|
// 查询条件
|
||||||
|
where?: Function;
|
||||||
|
// 查询字段
|
||||||
|
select?: string[];
|
||||||
|
// 字段相等
|
||||||
|
fieldEq?: string[] | FieldEq[] | (string | FieldEq)[];
|
||||||
|
// 添加排序条件
|
||||||
|
addOrderBy?: {};
|
||||||
|
// 关联配置
|
||||||
|
join?: JoinOp[];
|
||||||
|
// 其他条件
|
||||||
|
extend?: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controller 配置
|
||||||
|
export interface ControllerOption {
|
||||||
|
// crud配置 如果是字符串则为路由前缀,不配置默认是按Controller下的文件夹路径
|
||||||
|
curdOption?: CurdOption & string;
|
||||||
|
// 路由配置
|
||||||
|
routerOptions?: {
|
||||||
|
// 是否敏感
|
||||||
|
sensitive?: boolean;
|
||||||
|
// 路由中间件
|
||||||
|
middleware?: MiddlewareParamArray;
|
||||||
|
// 别名
|
||||||
|
alias?: string[];
|
||||||
|
// 描述
|
||||||
|
description?: string;
|
||||||
|
// 标签名称
|
||||||
|
tagName?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由配置
|
||||||
|
export interface RouterOptions {
|
||||||
|
sensitive?: boolean;
|
||||||
|
middleware?: MiddlewareParamArray;
|
||||||
|
description?: string;
|
||||||
|
tagName?: string;
|
||||||
|
ignoreGlobalPrefix?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// COOL的装饰器
|
||||||
|
export function CoolController(
|
||||||
|
curdOption?: CurdOption | string | RouterOptions,
|
||||||
|
routerOptions: RouterOptions = { middleware: [], sensitive: true }
|
||||||
|
): ClassDecorator {
|
||||||
|
return (target: any) => {
|
||||||
|
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
|
||||||
|
saveModule(CONTROLLER_KEY, target);
|
||||||
|
let prefix;
|
||||||
|
if (curdOption) {
|
||||||
|
// 判断 curdOption 的类型
|
||||||
|
if (typeof curdOption === 'string') {
|
||||||
|
prefix = curdOption;
|
||||||
|
} else if (curdOption && 'api' in curdOption) {
|
||||||
|
// curdOption 是 CurdOption 类型
|
||||||
|
prefix = curdOption.prefix || '';
|
||||||
|
} else {
|
||||||
|
// curdOption 是 RouterOptions 类型 合并到 routerOptions
|
||||||
|
routerOptions = { ...curdOption, ...routerOptions };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果不存在路由前缀,那么自动根据当前文件夹路径
|
||||||
|
location.scriptPath(target).then(async (res: any) => {
|
||||||
|
if (!res?.path) return;
|
||||||
|
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);
|
||||||
|
// 追加模块中间件
|
||||||
|
const path = `${
|
||||||
|
res.path.split(new RegExp(`modules[/\\\\]${module}`))[0]
|
||||||
|
}modules/${module}/config.${_.endsWith(res.path, 'ts') ? 'ts' : 'js'}`;
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
const config: ModuleConfig = require(path).default();
|
||||||
|
routerOptions.middleware = (config.middlewares || []).concat(
|
||||||
|
routerOptions.middleware || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!prefix) {
|
||||||
|
prefix = `/${pathArr.join('/')}`;
|
||||||
|
}
|
||||||
|
saveMetadata(prefix, routerOptions, target, curdOption, module);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const apiDesc = {
|
||||||
|
add: '新增',
|
||||||
|
delete: '删除',
|
||||||
|
update: '修改',
|
||||||
|
page: '分页查询',
|
||||||
|
list: '列表查询',
|
||||||
|
info: '单个信息',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存一些元数据信息,任意你希望存的东西
|
||||||
|
function saveMetadata(prefix, routerOptions, target, curdOption, module) {
|
||||||
|
if (module && !routerOptions.tagName) {
|
||||||
|
routerOptions = routerOptions || {};
|
||||||
|
routerOptions.tagName = module;
|
||||||
|
}
|
||||||
|
saveClassMetadata(
|
||||||
|
CONTROLLER_KEY,
|
||||||
|
{
|
||||||
|
prefix,
|
||||||
|
routerOptions,
|
||||||
|
curdOption,
|
||||||
|
module,
|
||||||
|
} as ControllerOption,
|
||||||
|
target
|
||||||
|
);
|
||||||
|
// 追加CRUD路由
|
||||||
|
if (!_.isEmpty(curdOption?.api)) {
|
||||||
|
curdOption?.api.forEach(path => {
|
||||||
|
attachClassMetadata(
|
||||||
|
WEB_ROUTER_KEY,
|
||||||
|
{
|
||||||
|
path: `/${path}`,
|
||||||
|
requestMethod: path == 'info' ? 'get' : 'post',
|
||||||
|
method: path,
|
||||||
|
summary: apiDesc[path],
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
target
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Scope(ScopeEnum.Request)(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
core/src/decorator/event.ts
Normal file
54
core/src/decorator/event.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
saveClassMetadata,
|
||||||
|
saveModule,
|
||||||
|
attachClassMetadata,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
|
||||||
|
export const COOL_CLS_EVENT_KEY = 'decorator:cool:cls:event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件配置
|
||||||
|
*/
|
||||||
|
export interface CoolEventOptions {
|
||||||
|
/** 是否全局 */
|
||||||
|
isGlobal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件
|
||||||
|
* @param options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function CoolEvent(options = {} as CoolEventOptions): ClassDecorator {
|
||||||
|
return (target: any) => {
|
||||||
|
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
|
||||||
|
saveModule(COOL_CLS_EVENT_KEY, target);
|
||||||
|
// 保存一些元数据信息,任意你希望存的东西
|
||||||
|
saveClassMetadata(COOL_CLS_EVENT_KEY, options, target);
|
||||||
|
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
|
||||||
|
Scope(ScopeEnum.Singleton)(target);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COOL_EVENT_KEY = 'decorator:cool:event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件
|
||||||
|
* @param eventName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function Event(eventName?: string): MethodDecorator {
|
||||||
|
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||||
|
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
|
||||||
|
attachClassMetadata(
|
||||||
|
COOL_EVENT_KEY,
|
||||||
|
{
|
||||||
|
eventName,
|
||||||
|
propertyKey,
|
||||||
|
descriptor,
|
||||||
|
},
|
||||||
|
target
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
112
core/src/decorator/index.ts
Normal file
112
core/src/decorator/index.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { COOL_CACHE } from './cache';
|
||||||
|
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
||||||
|
import {
|
||||||
|
Init,
|
||||||
|
Inject,
|
||||||
|
InjectClient,
|
||||||
|
JoinPoint,
|
||||||
|
MidwayDecoratorService,
|
||||||
|
Provide,
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import { CoolCommException } from '../exception/comm';
|
||||||
|
import { COOL_TRANSACTION, TransactionOptions } from './transaction';
|
||||||
|
import * as md5 from 'md5';
|
||||||
|
import { CoolUrlTagData } from '../tag/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装饰器
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolDecorator {
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
decoratorService: MidwayDecoratorService;
|
||||||
|
|
||||||
|
@InjectClient(CachingFactory, 'default')
|
||||||
|
midwayCache: MidwayCache;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolUrlTagData: CoolUrlTagData;
|
||||||
|
|
||||||
|
@Init()
|
||||||
|
async init() {
|
||||||
|
// 事务
|
||||||
|
await this.transaction();
|
||||||
|
// 缓存
|
||||||
|
await this.cache();
|
||||||
|
// URL标签
|
||||||
|
await this.coolUrlTagData.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存
|
||||||
|
*/
|
||||||
|
async cache() {
|
||||||
|
this.decoratorService.registerMethodHandler(COOL_CACHE, options => {
|
||||||
|
return {
|
||||||
|
around: async (joinPoint: JoinPoint) => {
|
||||||
|
const key = md5(
|
||||||
|
joinPoint.target.constructor.name +
|
||||||
|
joinPoint.methodName +
|
||||||
|
JSON.stringify(joinPoint.args)
|
||||||
|
);
|
||||||
|
// 缓存有数据就返回
|
||||||
|
let data: any = await this.midwayCache.get(key);
|
||||||
|
if (data) {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} else {
|
||||||
|
// 执行原始方法
|
||||||
|
data = await joinPoint.proceed(...joinPoint.args);
|
||||||
|
await this.midwayCache.set(
|
||||||
|
key,
|
||||||
|
JSON.stringify(data),
|
||||||
|
options.metadata
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事务
|
||||||
|
*/
|
||||||
|
async transaction() {
|
||||||
|
this.decoratorService.registerMethodHandler(COOL_TRANSACTION, options => {
|
||||||
|
return {
|
||||||
|
around: async (joinPoint: JoinPoint) => {
|
||||||
|
const option: TransactionOptions = options.metadata;
|
||||||
|
const dataSource = this.typeORMDataSourceManager.getDataSource(
|
||||||
|
option?.connectionName || 'default'
|
||||||
|
);
|
||||||
|
const queryRunner = dataSource.createQueryRunner();
|
||||||
|
await queryRunner.connect();
|
||||||
|
if (option && option.isolation) {
|
||||||
|
await queryRunner.startTransaction(option.isolation);
|
||||||
|
} else {
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
}
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
joinPoint.args.push(queryRunner);
|
||||||
|
data = await joinPoint.proceed(...joinPoint.args);
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
throw new CoolCommException(error.message);
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
53
core/src/decorator/tag.ts
Normal file
53
core/src/decorator/tag.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
saveClassMetadata,
|
||||||
|
savePropertyDataToClass,
|
||||||
|
saveModule,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
|
||||||
|
export const COOL_URL_TAG_KEY = 'decorator:cool:url:tag';
|
||||||
|
|
||||||
|
export const COOL_METHOD_TAG_KEY = 'decorator:cool:method:tag';
|
||||||
|
|
||||||
|
export enum TagTypes {
|
||||||
|
IGNORE_TOKEN = 'ignoreToken',
|
||||||
|
IGNORE_SIGN = 'ignoreSign',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoolUrlTagConfig {
|
||||||
|
key: TagTypes | string;
|
||||||
|
value?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打标记
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function CoolUrlTag(data?: CoolUrlTagConfig): ClassDecorator {
|
||||||
|
return (target: any) => {
|
||||||
|
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
|
||||||
|
saveModule(COOL_URL_TAG_KEY, target);
|
||||||
|
// 保存一些元数据信息,任意你希望存的东西
|
||||||
|
saveClassMetadata(COOL_URL_TAG_KEY, data, target);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法打标记
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function CoolTag(tag: TagTypes | string): MethodDecorator {
|
||||||
|
return (target, key, descriptor: PropertyDescriptor) => {
|
||||||
|
savePropertyDataToClass(
|
||||||
|
COOL_METHOD_TAG_KEY,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
tag,
|
||||||
|
},
|
||||||
|
target,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
return descriptor;
|
||||||
|
};
|
||||||
|
}
|
||||||
19
core/src/decorator/transaction.ts
Normal file
19
core/src/decorator/transaction.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { createCustomMethodDecorator } from '@midwayjs/core';
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
26
core/src/entity/base.ts
Normal file
26
core/src/entity/base.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
Index,
|
||||||
|
UpdateDateColumn,
|
||||||
|
CreateDateColumn,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { CoolBaseEntity } from './typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型基类
|
||||||
|
*/
|
||||||
|
export abstract class BaseEntity extends CoolBaseEntity {
|
||||||
|
// 默认自增
|
||||||
|
@PrimaryGeneratedColumn('increment', {
|
||||||
|
comment: 'ID',
|
||||||
|
})
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@CreateDateColumn({ comment: '创建时间' })
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@UpdateDateColumn({ comment: '更新时间' })
|
||||||
|
updateTime: Date;
|
||||||
|
}
|
||||||
25
core/src/entity/mongo.ts
Normal file
25
core/src/entity/mongo.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
Index,
|
||||||
|
UpdateDateColumn,
|
||||||
|
CreateDateColumn,
|
||||||
|
// @ts-ignore
|
||||||
|
ObjectID,
|
||||||
|
ObjectIdColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { CoolBaseEntity } from './typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型基类
|
||||||
|
*/
|
||||||
|
export abstract class BaseMongoEntity extends CoolBaseEntity {
|
||||||
|
@ObjectIdColumn({ comment: 'id' })
|
||||||
|
id: ObjectID;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@CreateDateColumn({ comment: '创建时间' })
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@UpdateDateColumn({ comment: '更新时间' })
|
||||||
|
updateTime: Date;
|
||||||
|
}
|
||||||
3
core/src/entity/typeorm.ts
Normal file
3
core/src/entity/typeorm.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { BaseEntity } from 'typeorm';
|
||||||
|
|
||||||
|
export abstract class CoolBaseEntity extends BaseEntity {}
|
||||||
185
core/src/event/index.ts
Normal file
185
core/src/event/index.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { App, getClassMetadata, listModule, Provide } from '@midwayjs/core';
|
||||||
|
import * as Events from 'events';
|
||||||
|
import { Scope, ScopeEnum, IMidwayApplication, Config } from '@midwayjs/core';
|
||||||
|
import { COOL_CLS_EVENT_KEY, COOL_EVENT_KEY } from '../decorator/event';
|
||||||
|
import * as pm2 from 'pm2';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
export const COOL_EVENT_MESSAGE = 'cool:event:message';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolEventManager extends Events {
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Config('keys')
|
||||||
|
keys: string;
|
||||||
|
|
||||||
|
// 事件数据 某个事件对应的模块对应的方法
|
||||||
|
eventData = {} as {
|
||||||
|
[key: string]: {
|
||||||
|
module: any;
|
||||||
|
method: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
const eventModules = listModule(COOL_CLS_EVENT_KEY);
|
||||||
|
for (const module of eventModules) {
|
||||||
|
await this.handlerEvent(module);
|
||||||
|
}
|
||||||
|
await this.commEvent();
|
||||||
|
await this.globalEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送事件
|
||||||
|
* @param event
|
||||||
|
* @param args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
emit(event: string | symbol, ...args: any[]): boolean {
|
||||||
|
return super.emit(COOL_EVENT_MESSAGE, {
|
||||||
|
type: COOL_EVENT_MESSAGE,
|
||||||
|
data: {
|
||||||
|
event,
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送全局事件
|
||||||
|
* @param event 事件
|
||||||
|
* @param random 是否随机一个
|
||||||
|
* @param args 参数
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async globalEmit(event: string, random = false, ...args) {
|
||||||
|
// 如果是本地运行还是转普通模式
|
||||||
|
if (this.app.getEnv() === 'local') {
|
||||||
|
this.emit(event, ...args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pm2.connect(() => {
|
||||||
|
pm2.list((err, list) => {
|
||||||
|
const ps = list.map(e => {
|
||||||
|
return {
|
||||||
|
id: e.pm_id,
|
||||||
|
name: e.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// random 为 true 时随机发给同名称的一个进程
|
||||||
|
if (random) {
|
||||||
|
// 按名称分组
|
||||||
|
const group = _.groupBy(ps, 'name');
|
||||||
|
const names = Object.keys(group);
|
||||||
|
// 遍历名称
|
||||||
|
names.forEach(name => {
|
||||||
|
const pss = group[name];
|
||||||
|
// 随机一个
|
||||||
|
const index = _.random(0, pss.length - 1);
|
||||||
|
const ps = pss[index];
|
||||||
|
// 发给这个进程
|
||||||
|
// @ts-ignore
|
||||||
|
pm2.sendDataToProcessId(
|
||||||
|
{
|
||||||
|
type: 'process:msg',
|
||||||
|
data: {
|
||||||
|
type: `${COOL_EVENT_MESSAGE}@${this.keys}`,
|
||||||
|
event,
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
id: ps.id,
|
||||||
|
topic: 'cool:event:topic',
|
||||||
|
},
|
||||||
|
(err, res) => {}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 发给所有进程
|
||||||
|
ps.forEach(e => {
|
||||||
|
// @ts-ignore
|
||||||
|
pm2.sendDataToProcessId(
|
||||||
|
{
|
||||||
|
type: 'process:msg',
|
||||||
|
data: {
|
||||||
|
type: `${COOL_EVENT_MESSAGE}@${this.keys}`,
|
||||||
|
event,
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
id: e.id,
|
||||||
|
topic: 'cool:event:topic',
|
||||||
|
},
|
||||||
|
(err, res) => {}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理事件
|
||||||
|
* @param module
|
||||||
|
*/
|
||||||
|
async handlerEvent(module) {
|
||||||
|
const events = getClassMetadata(COOL_EVENT_KEY, module);
|
||||||
|
for (const event of events) {
|
||||||
|
const listen = event.eventName ? event.eventName : event.propertyKey;
|
||||||
|
if (!this.eventData[listen]) {
|
||||||
|
this.eventData[listen] = [];
|
||||||
|
}
|
||||||
|
this.eventData[listen].push({
|
||||||
|
module,
|
||||||
|
method: event.propertyKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局事件
|
||||||
|
*/
|
||||||
|
async globalEvent() {
|
||||||
|
process.on('message', async (message: any) => {
|
||||||
|
const data = message?.data;
|
||||||
|
if (!data) return;
|
||||||
|
if (data.type != `${COOL_EVENT_MESSAGE}@${this.keys}`) return;
|
||||||
|
await this.doAction(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通事件
|
||||||
|
*/
|
||||||
|
async commEvent() {
|
||||||
|
this.on(COOL_EVENT_MESSAGE, async (message: any) => {
|
||||||
|
await this.doAction(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行事件
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
async doAction(message) {
|
||||||
|
const data = message.data;
|
||||||
|
const method = data.event;
|
||||||
|
const args = data.args;
|
||||||
|
if (this.eventData[method]) {
|
||||||
|
for (const event of this.eventData[method]) {
|
||||||
|
const moduleInstance = await this.app
|
||||||
|
.getApplicationContext()
|
||||||
|
.getAsync(event.module);
|
||||||
|
moduleInstance[event.method](...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
core/src/exception/base.ts
Normal file
13
core/src/exception/base.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
core/src/exception/comm.ts
Normal file
16
core/src/exception/comm.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { GlobalConfig } from '../constant/global';
|
||||||
|
import { BaseException } from './base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用异常
|
||||||
|
*/
|
||||||
|
export class CoolCommException extends BaseException {
|
||||||
|
constructor(message: string) {
|
||||||
|
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
|
||||||
|
super(
|
||||||
|
'CoolCommException',
|
||||||
|
RESCODE.COMMFAIL,
|
||||||
|
message ? message : RESMESSAGE.COMMFAIL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
core/src/exception/core.ts
Normal file
16
core/src/exception/core.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { GlobalConfig } from '../constant/global';
|
||||||
|
import { BaseException } from './base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心异常
|
||||||
|
*/
|
||||||
|
export class CoolCoreException extends BaseException {
|
||||||
|
constructor(message: string) {
|
||||||
|
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
|
||||||
|
super(
|
||||||
|
'CoolCoreException',
|
||||||
|
RESCODE.COREFAIL,
|
||||||
|
message ? message : RESMESSAGE.COREFAIL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
core/src/exception/filter.ts
Normal file
21
core/src/exception/filter.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ILogger } from '@midwayjs/core';
|
||||||
|
import { Catch, Logger } from '@midwayjs/core';
|
||||||
|
import { GlobalConfig } from '../constant/global';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常处理
|
||||||
|
*/
|
||||||
|
@Catch()
|
||||||
|
export class CoolExceptionFilter {
|
||||||
|
@Logger()
|
||||||
|
coreLogger: ILogger;
|
||||||
|
|
||||||
|
async catch(err) {
|
||||||
|
const { RESCODE } = GlobalConfig.getInstance();
|
||||||
|
this.coreLogger.error(err);
|
||||||
|
return {
|
||||||
|
code: err.status || RESCODE.COMMFAIL,
|
||||||
|
message: err.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
16
core/src/exception/validate.ts
Normal file
16
core/src/exception/validate.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { GlobalConfig } from '../constant/global';
|
||||||
|
import { BaseException } from './base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验异常
|
||||||
|
*/
|
||||||
|
export class CoolValidateException extends BaseException {
|
||||||
|
constructor(message: string) {
|
||||||
|
const { RESCODE, RESMESSAGE } = GlobalConfig.getInstance();
|
||||||
|
super(
|
||||||
|
'CoolValidateException',
|
||||||
|
RESCODE.VALIDATEFAIL,
|
||||||
|
message ? message : RESMESSAGE.VALIDATEFAIL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,52 @@
|
|||||||
export { BookConfiguration as Configuration } from './configuration';
|
export { CoolConfiguration as Configuration } from './configuration';
|
||||||
export * from './service/book.service';
|
|
||||||
|
// 异常处理
|
||||||
|
export * from './exception/filter';
|
||||||
|
export * from './exception/core';
|
||||||
|
export * from './exception/base';
|
||||||
|
export * from './exception/comm';
|
||||||
|
export * from './exception/validate';
|
||||||
|
|
||||||
|
// cache
|
||||||
|
export * from './cache/store';
|
||||||
|
|
||||||
|
// entity
|
||||||
|
export * from './entity/base';
|
||||||
|
export * from './entity/typeorm';
|
||||||
|
export * from './entity/mongo';
|
||||||
|
|
||||||
|
// service
|
||||||
|
export * from './service/base';
|
||||||
|
export * from './service/mysql';
|
||||||
|
export * from './service/postgres';
|
||||||
|
export * from './service/sqlite';
|
||||||
|
|
||||||
|
// controller
|
||||||
|
export * from './controller/base';
|
||||||
|
|
||||||
|
// 事件
|
||||||
|
export * from './event/index';
|
||||||
|
|
||||||
|
// 装饰器
|
||||||
|
export * from './decorator/controller';
|
||||||
|
export * from './decorator/cache';
|
||||||
|
export * from './decorator/event';
|
||||||
|
export * from './decorator/transaction';
|
||||||
|
export * from './decorator/tag';
|
||||||
|
export * from './decorator/index';
|
||||||
|
|
||||||
|
// rest
|
||||||
|
export * from './rest/eps';
|
||||||
|
|
||||||
|
// tag
|
||||||
|
export * from './tag/data';
|
||||||
|
|
||||||
|
// 模块
|
||||||
|
export * from './module/config';
|
||||||
|
export * from './module/import';
|
||||||
|
export * from './module/menu';
|
||||||
|
|
||||||
|
// 其他
|
||||||
|
export * from './interface';
|
||||||
|
export * from './util/func';
|
||||||
|
export * from './constant/global';
|
||||||
|
|||||||
447
core/src/interface.ts
Normal file
447
core/src/interface.ts
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
import { MiddlewareParamArray } from '@midwayjs/core';
|
||||||
|
import { AedesOptions } from 'aedes';
|
||||||
|
// @ts-ignore
|
||||||
|
import { PublishPacket } from 'packet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块配置
|
||||||
|
*/
|
||||||
|
export interface ModuleConfig {
|
||||||
|
/** 名称 */
|
||||||
|
name: string;
|
||||||
|
/** 描述 */
|
||||||
|
description: string;
|
||||||
|
/** 模块中间件 */
|
||||||
|
middlewares?: MiddlewareParamArray;
|
||||||
|
/** 全局中间件 */
|
||||||
|
globalMiddlewares?: MiddlewareParamArray;
|
||||||
|
/** 模块加载顺序,默认为0,值越大越优先加载 */
|
||||||
|
order?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoolConfig {
|
||||||
|
/** 短信 */
|
||||||
|
sms?: CoolSmsConfig;
|
||||||
|
/** 是否自动导入数据库 */
|
||||||
|
initDB?: boolean;
|
||||||
|
/** Eps */
|
||||||
|
eps?: boolean;
|
||||||
|
/** 是否自动导入模块菜单 */
|
||||||
|
initMenu?: boolean;
|
||||||
|
/** 判断是否初始化的方式 */
|
||||||
|
initJudge: 'file' | 'db';
|
||||||
|
// 实体配置
|
||||||
|
// entity?: {
|
||||||
|
// primaryType: "uuid" | "increment" | "rowid" | "identity";
|
||||||
|
// };
|
||||||
|
/** crud配置 */
|
||||||
|
crud?: {
|
||||||
|
/** 软删除 */
|
||||||
|
softDelete: boolean;
|
||||||
|
/** 分页查询每页条数 */
|
||||||
|
pageSize: number;
|
||||||
|
/** 插入方式 */
|
||||||
|
upsert: 'normal' | 'save';
|
||||||
|
// 多租户
|
||||||
|
// tenant: boolean;
|
||||||
|
};
|
||||||
|
/** elasticsearch配置 */
|
||||||
|
es?: {
|
||||||
|
nodes: string[];
|
||||||
|
options?: any;
|
||||||
|
};
|
||||||
|
/** pay */
|
||||||
|
pay?: {
|
||||||
|
/** 微信支付 */
|
||||||
|
wx?: CoolWxPayConfig;
|
||||||
|
/** 支付宝支付 */
|
||||||
|
ali?: CoolAliPayConfig;
|
||||||
|
};
|
||||||
|
/** rpc */
|
||||||
|
rpc?: CoolRpcConfig;
|
||||||
|
/** redis */
|
||||||
|
redis?: RedisConfig | RedisConfig[];
|
||||||
|
/** 文件上传 */
|
||||||
|
file?: {
|
||||||
|
/** 上传模式 */
|
||||||
|
mode: MODETYPE;
|
||||||
|
/** 本地上传 文件地址前缀 */
|
||||||
|
domain?: string;
|
||||||
|
/** oss */
|
||||||
|
oss?: OSSConfig;
|
||||||
|
/** cos */
|
||||||
|
cos?: COSConfig;
|
||||||
|
/** qiniu */
|
||||||
|
qiniu?: QINIUConfig;
|
||||||
|
/** aws */
|
||||||
|
aws: AWSConfig;
|
||||||
|
};
|
||||||
|
/** IOT 配置 */
|
||||||
|
iot?: CoolIotConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoolRpcConfig {
|
||||||
|
/** 服务名称 */
|
||||||
|
name: string;
|
||||||
|
/** redis */
|
||||||
|
redis: RedisConfig & RedisConfig[] & unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RedisConfig {
|
||||||
|
/** host */
|
||||||
|
host: string;
|
||||||
|
/** password */
|
||||||
|
password: string;
|
||||||
|
/** port */
|
||||||
|
port: number;
|
||||||
|
/** db */
|
||||||
|
db: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模式
|
||||||
|
export enum MODETYPE {
|
||||||
|
/** 本地 */
|
||||||
|
LOCAL = 'local',
|
||||||
|
/** 云存储 */
|
||||||
|
CLOUD = 'cloud',
|
||||||
|
/** 其他 */
|
||||||
|
OTHER = 'other',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CLOUDTYPE {
|
||||||
|
/** 阿里云存储 */
|
||||||
|
OSS = 'oss',
|
||||||
|
/** 腾讯云存储 */
|
||||||
|
COS = 'cos',
|
||||||
|
/** 七牛云存储 */
|
||||||
|
QINIU = 'qiniu',
|
||||||
|
/** AWS S3 */
|
||||||
|
AWS = 'aws',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传模式
|
||||||
|
*/
|
||||||
|
export interface Mode {
|
||||||
|
/** 模式 */
|
||||||
|
mode: MODETYPE;
|
||||||
|
/** 类型 */
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块配置
|
||||||
|
*/
|
||||||
|
export interface CoolFileConfig {
|
||||||
|
/** 上传模式 */
|
||||||
|
mode: MODETYPE;
|
||||||
|
/** 阿里云oss 配置 */
|
||||||
|
oss: OSSConfig;
|
||||||
|
/** 腾讯云 cos配置 */
|
||||||
|
cos: COSConfig;
|
||||||
|
/** 七牛云 配置 */
|
||||||
|
qiniu: QINIUConfig;
|
||||||
|
/** AWS s3 配置 */
|
||||||
|
aws: AWSConfig;
|
||||||
|
/** 文件前缀 */
|
||||||
|
domain: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSS 配置
|
||||||
|
*/
|
||||||
|
export interface OSSConfig {
|
||||||
|
/** 阿里云accessKeyId */
|
||||||
|
accessKeyId: string;
|
||||||
|
/** 阿里云accessKeySecret */
|
||||||
|
accessKeySecret: string;
|
||||||
|
/** 阿里云oss的bucket */
|
||||||
|
bucket: string;
|
||||||
|
/** 阿里云oss的endpoint */
|
||||||
|
endpoint: string;
|
||||||
|
/** 阿里云oss的timeout */
|
||||||
|
timeout: string;
|
||||||
|
/** 签名失效时间,毫秒 */
|
||||||
|
expAfter?: number;
|
||||||
|
/** 文件最大的 size */
|
||||||
|
maxSize?: number;
|
||||||
|
// host
|
||||||
|
host?: string;
|
||||||
|
// 阿里云oss的公网访问地址
|
||||||
|
publicDomain?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COS 配置
|
||||||
|
*/
|
||||||
|
export interface COSConfig {
|
||||||
|
/** 腾讯云accessKeyId */
|
||||||
|
accessKeyId: string;
|
||||||
|
/** 腾讯云accessKeySecret */
|
||||||
|
accessKeySecret: string;
|
||||||
|
/** 腾讯云cos的bucket */
|
||||||
|
bucket: string;
|
||||||
|
/** 腾讯云cos的区域 */
|
||||||
|
region: string;
|
||||||
|
/** 腾讯云cos的公网访问地址 */
|
||||||
|
publicDomain: string;
|
||||||
|
/** 上传持续时间 */
|
||||||
|
durationSeconds?: number;
|
||||||
|
/** 允许操作(上传)的对象前缀 */
|
||||||
|
allowPrefix?: string;
|
||||||
|
/** 密钥的权限列表 */
|
||||||
|
allowActions?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QINIUConfig {
|
||||||
|
/** 七牛云accessKeyId */
|
||||||
|
accessKeyId: string;
|
||||||
|
/** 七牛云accessKeySecret */
|
||||||
|
accessKeySecret: string;
|
||||||
|
/** 七牛云cos的bucket */
|
||||||
|
bucket: string;
|
||||||
|
/** 七牛云cos的区域 */
|
||||||
|
region: string;
|
||||||
|
/** 七牛云cos的公网访问地址 */
|
||||||
|
publicDomain: string;
|
||||||
|
/** 上传地址 */
|
||||||
|
uploadUrl?: string;
|
||||||
|
/** 上传fileKey */
|
||||||
|
fileKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AWSConfig {
|
||||||
|
/** accessKeyId */
|
||||||
|
accessKeyId: string;
|
||||||
|
/** secretAccessKey */
|
||||||
|
secretAccessKey: string;
|
||||||
|
/** bucket */
|
||||||
|
bucket: string;
|
||||||
|
/** region */
|
||||||
|
region: string;
|
||||||
|
/** fields */
|
||||||
|
fields?: any;
|
||||||
|
/** conditions */
|
||||||
|
conditions?: any[];
|
||||||
|
/** expires */
|
||||||
|
expires?: number;
|
||||||
|
/** publicDomain */
|
||||||
|
publicDomain?: string;
|
||||||
|
/** forcePathStyle */
|
||||||
|
forcePathStyle?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付配置
|
||||||
|
*/
|
||||||
|
export interface CoolWxPayConfig {
|
||||||
|
/** 直连商户申请的公众号或移动应用appid。 */
|
||||||
|
appid: string;
|
||||||
|
/** 商户号 */
|
||||||
|
mchid: string;
|
||||||
|
/** 可选参数 证书序列号 */
|
||||||
|
serial_no?: string;
|
||||||
|
/** 回调链接 */
|
||||||
|
notify_url: string;
|
||||||
|
/** 公钥 */
|
||||||
|
publicKey: Buffer;
|
||||||
|
/** 私钥 */
|
||||||
|
privateKey: Buffer;
|
||||||
|
/** 可选参数 认证类型,目前为WECHATPAY2-SHA256-RSA2048 */
|
||||||
|
authType?: string;
|
||||||
|
/** 可选参数 User-Agent */
|
||||||
|
userAgent?: string;
|
||||||
|
/** 可选参数 APIv3密钥 */
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付配置
|
||||||
|
*/
|
||||||
|
export interface CoolAliPayConfig {
|
||||||
|
/** 支付回调地址 */
|
||||||
|
notifyUrl: string;
|
||||||
|
/** 应用ID */
|
||||||
|
appId: string;
|
||||||
|
/**
|
||||||
|
* 应用私钥字符串
|
||||||
|
* RSA签名验签工具:https://docs.open.alipay.com/291/106097)
|
||||||
|
* 密钥格式一栏请选择 “PKCS1(非JAVA适用)”
|
||||||
|
*/
|
||||||
|
privateKey: string;
|
||||||
|
/** 签名类型 */
|
||||||
|
signType?: 'RSA2' | 'RSA';
|
||||||
|
/** 支付宝公钥(需要对返回值做验签时候必填) */
|
||||||
|
alipayPublicKey?: string;
|
||||||
|
/** 网关 */
|
||||||
|
gateway?: string;
|
||||||
|
/** 网关超时时间(单位毫秒,默认 5s) */
|
||||||
|
timeout?: number;
|
||||||
|
/** 是否把网关返回的下划线 key 转换为驼峰写法 */
|
||||||
|
camelcase?: boolean;
|
||||||
|
/** 编码(只支持 utf-8) */
|
||||||
|
charset?: 'utf-8';
|
||||||
|
/** api版本 */
|
||||||
|
version?: '1.0';
|
||||||
|
urllib?: any;
|
||||||
|
/** 指定private key类型, 默认: PKCS1, PKCS8: PRIVATE KEY, PKCS1: RSA PRIVATE KEY */
|
||||||
|
keyType?: 'PKCS1' | 'PKCS8';
|
||||||
|
/** 应用公钥证书文件路径 */
|
||||||
|
appCertPath?: string;
|
||||||
|
/** 应用公钥证书文件内容 */
|
||||||
|
appCertContent?: string | Buffer;
|
||||||
|
/** 应用公钥证书sn */
|
||||||
|
appCertSn?: string;
|
||||||
|
/** 支付宝根证书文件路径 */
|
||||||
|
alipayRootCertPath?: string;
|
||||||
|
/** 支付宝根证书文件内容 */
|
||||||
|
alipayRootCertContent?: string | Buffer;
|
||||||
|
/** 支付宝根证书sn */
|
||||||
|
alipayRootCertSn?: string;
|
||||||
|
/** 支付宝公钥证书文件路径 */
|
||||||
|
alipayPublicCertPath?: string;
|
||||||
|
/** 支付宝公钥证书文件内容 */
|
||||||
|
alipayPublicCertContent?: string | Buffer;
|
||||||
|
/** 支付宝公钥证书sn */
|
||||||
|
alipayCertSn?: string;
|
||||||
|
/** AES密钥,调用AES加解密相关接口时需要 */
|
||||||
|
encryptKey?: string;
|
||||||
|
/** 服务器地址 */
|
||||||
|
wsServiceUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IOT配置
|
||||||
|
*/
|
||||||
|
export interface CoolIotConfig {
|
||||||
|
/** MQTT服务端口 */
|
||||||
|
port: number;
|
||||||
|
/** MQTT Websocket服务端口 */
|
||||||
|
wsPort: number;
|
||||||
|
/** redis 配置 mqtt cluster下必须要配置 */
|
||||||
|
redis?: {
|
||||||
|
/** host */
|
||||||
|
host: string;
|
||||||
|
/** port */
|
||||||
|
port: number;
|
||||||
|
/** password */
|
||||||
|
password: string;
|
||||||
|
/** db */
|
||||||
|
db: number;
|
||||||
|
};
|
||||||
|
/** 发布消息配置 */
|
||||||
|
publish?: PublishPacket;
|
||||||
|
/** 认证 */
|
||||||
|
auth?: {
|
||||||
|
/** 用户 */
|
||||||
|
username: string;
|
||||||
|
/** 密码 */
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
/** 服务配置 */
|
||||||
|
serve?: AedesOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoolSmsConfig {
|
||||||
|
/**
|
||||||
|
* 阿里云短信配置
|
||||||
|
*/
|
||||||
|
ali: CoolSmsAliConfig;
|
||||||
|
/**
|
||||||
|
* 腾讯云短信配置
|
||||||
|
*/
|
||||||
|
tx: CoolSmsTxConfig;
|
||||||
|
/**
|
||||||
|
* 云片短信配置
|
||||||
|
*/
|
||||||
|
yp: CoolSmsYpConfig;
|
||||||
|
/**
|
||||||
|
* aws短信配置
|
||||||
|
*/
|
||||||
|
aws: CoolSmsAwsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoolSmsAwsConfig {
|
||||||
|
/**
|
||||||
|
* 区域
|
||||||
|
*/
|
||||||
|
region: string;
|
||||||
|
/**
|
||||||
|
* accessKeyId
|
||||||
|
*/
|
||||||
|
accessKeyId: string;
|
||||||
|
/**
|
||||||
|
* secretAccessKey
|
||||||
|
*/
|
||||||
|
secretAccessKey: string;
|
||||||
|
/**
|
||||||
|
* 扩展配置
|
||||||
|
*/
|
||||||
|
extend?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云配置
|
||||||
|
*/
|
||||||
|
export interface CoolSmsAliConfig {
|
||||||
|
/**
|
||||||
|
* 阿里云accessKeyId
|
||||||
|
*/
|
||||||
|
accessKeyId: string;
|
||||||
|
/**
|
||||||
|
* 阿里云accessKeySecret
|
||||||
|
*/
|
||||||
|
accessKeySecret: string;
|
||||||
|
/**
|
||||||
|
* 签名,非必填,调用时可以传入
|
||||||
|
*/
|
||||||
|
signName?: string;
|
||||||
|
/**
|
||||||
|
* 模板,非必填,调用时可以传入
|
||||||
|
*/
|
||||||
|
template?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 腾讯云配置
|
||||||
|
*/
|
||||||
|
export interface CoolSmsTxConfig {
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
appId: string;
|
||||||
|
/**
|
||||||
|
* 腾讯云secretId
|
||||||
|
*/
|
||||||
|
secretId: string;
|
||||||
|
/**
|
||||||
|
* 腾讯云secretKey
|
||||||
|
*/
|
||||||
|
secretKey: string;
|
||||||
|
/**
|
||||||
|
* 签名,非必填,调用时可以传入
|
||||||
|
*/
|
||||||
|
signName?: string;
|
||||||
|
/**
|
||||||
|
* 模板,非必填,调用时可以传入
|
||||||
|
*/
|
||||||
|
template?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云片短信配置
|
||||||
|
*/
|
||||||
|
export interface CoolSmsYpConfig {
|
||||||
|
/**
|
||||||
|
* 云片apikey
|
||||||
|
*/
|
||||||
|
apikey: string;
|
||||||
|
/**
|
||||||
|
* 签名,非必填,调用时可以传入
|
||||||
|
*/
|
||||||
|
signName?: string;
|
||||||
|
/**
|
||||||
|
* 模板,非必填,调用时可以传入
|
||||||
|
*/
|
||||||
|
template?: string;
|
||||||
|
}
|
||||||
103
core/src/module/config.ts
Normal file
103
core/src/module/config.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { IMidwayApplication } from '@midwayjs/core';
|
||||||
|
import {
|
||||||
|
ALL,
|
||||||
|
App,
|
||||||
|
Config,
|
||||||
|
Init,
|
||||||
|
Provide,
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { CoolCoreException } from '../exception/core';
|
||||||
|
import { ModuleConfig } from '../interface';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import location from '../util/location';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块配置
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolModuleConfig {
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Config(ALL)
|
||||||
|
allConfig;
|
||||||
|
|
||||||
|
modules;
|
||||||
|
|
||||||
|
@Init()
|
||||||
|
async init() {
|
||||||
|
const modules = [];
|
||||||
|
// 模块路径
|
||||||
|
const moduleBasePath = `${location.getRootPath()}/modules/`;
|
||||||
|
|
||||||
|
if (!fs.existsSync(moduleBasePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.allConfig['module']) {
|
||||||
|
this.allConfig['module'] = {};
|
||||||
|
}
|
||||||
|
// 全局中间件
|
||||||
|
const globalMiddlewareArr = [];
|
||||||
|
for (const module of fs.readdirSync(moduleBasePath)) {
|
||||||
|
const modulePath = `${moduleBasePath}/${module}`;
|
||||||
|
const dirStats = fs.statSync(modulePath);
|
||||||
|
if (dirStats.isDirectory()) {
|
||||||
|
const configPath = fs.existsSync(`${modulePath}/config.ts`)
|
||||||
|
? `${modulePath}/config.ts`
|
||||||
|
: `${modulePath}/config.js`;
|
||||||
|
console.log('configPath', configPath);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
262
core/src/module/import.ts
Normal file
262
core/src/module/import.ts
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
import { ILogger, IMidwayApplication, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { App, Config, Inject, Logger, Provide } from '@midwayjs/core';
|
||||||
|
import { InjectDataSource, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { DataSource, Equal } from 'typeorm';
|
||||||
|
import { CoolEventManager } from '../event';
|
||||||
|
import { CoolModuleConfig } from './config';
|
||||||
|
import { CoolModuleMenu } from './menu';
|
||||||
|
import location from '../util/location';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块sql
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolModuleImport {
|
||||||
|
@Config('typeorm.dataSource')
|
||||||
|
ormConfig;
|
||||||
|
|
||||||
|
@InjectDataSource('default')
|
||||||
|
defaultDataSource: DataSource;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Config('cool')
|
||||||
|
coolConfig;
|
||||||
|
|
||||||
|
@Logger()
|
||||||
|
coreLogger: ILogger;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolModuleConfig: CoolModuleConfig;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolModuleMenu: CoolModuleMenu;
|
||||||
|
|
||||||
|
initJudge: 'file' | 'db';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
this.initJudge = this.coolConfig.initJudge;
|
||||||
|
if (!this.initJudge) {
|
||||||
|
this.initJudge = 'file';
|
||||||
|
}
|
||||||
|
// 是否需要导入
|
||||||
|
if (this.coolConfig.initDB) {
|
||||||
|
const modules = this.coolModuleConfig.modules;
|
||||||
|
if (!modules || modules.length === 0) return;
|
||||||
|
const metadatas = await this.getDbMetadatas();
|
||||||
|
setTimeout(async () => {
|
||||||
|
for (const module of modules) {
|
||||||
|
if (this.initJudge == 'file') {
|
||||||
|
const { exist, lockPath } = this.checkFileExist(module);
|
||||||
|
if (!exist) {
|
||||||
|
await this.initDataBase(module, metadatas, lockPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.initJudge == 'db') {
|
||||||
|
const exist = await this.checkDbExist(module, metadatas);
|
||||||
|
if (!exist) {
|
||||||
|
await this.initDataBase(module, metadatas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.coolEventManager.emit('onDBInit', {});
|
||||||
|
this.coolModuleMenu.init();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库元数据
|
||||||
|
*/
|
||||||
|
async getDbMetadatas() {
|
||||||
|
// 获得所有的实体
|
||||||
|
const entityMetadatas = this.defaultDataSource.entityMetadatas;
|
||||||
|
const metadatas = _.mapValues(
|
||||||
|
_.keyBy(entityMetadatas, 'tableName'),
|
||||||
|
'target'
|
||||||
|
);
|
||||||
|
return metadatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据是否存在
|
||||||
|
* @param module
|
||||||
|
* @param metadatas
|
||||||
|
*/
|
||||||
|
async checkDbExist(module: string, metadatas) {
|
||||||
|
const cKey = `init_db_${module}`;
|
||||||
|
const repository = this.defaultDataSource.getRepository(
|
||||||
|
metadatas['base_sys_conf']
|
||||||
|
);
|
||||||
|
const data = await repository.findOneBy({ cKey: Equal(cKey) });
|
||||||
|
return !!data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件是否存在
|
||||||
|
* @param module
|
||||||
|
*/
|
||||||
|
checkFileExist(module: string) {
|
||||||
|
const importLockPath = path.join(
|
||||||
|
`${location.getRootPath()}`,
|
||||||
|
'..',
|
||||||
|
'lock',
|
||||||
|
'db'
|
||||||
|
);
|
||||||
|
if (!fs.existsSync(importLockPath)) {
|
||||||
|
fs.mkdirSync(importLockPath, { recursive: true });
|
||||||
|
}
|
||||||
|
const lockPath = path.join(importLockPath, module + '.db.lock');
|
||||||
|
return {
|
||||||
|
exist: fs.existsSync(lockPath),
|
||||||
|
lockPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据库
|
||||||
|
* @param module
|
||||||
|
* @param lockPath 锁定导入
|
||||||
|
*/
|
||||||
|
async initDataBase(module: string, metadatas, lockPath?: string) {
|
||||||
|
// 计算耗时
|
||||||
|
const startTime = new Date().getTime();
|
||||||
|
// 模块路径
|
||||||
|
const modulePath = `${location.getRootPath()}/modules/${module}`;
|
||||||
|
// 数据路径
|
||||||
|
const dataPath = `${modulePath}/db.json`;
|
||||||
|
// 判断文件是否存在
|
||||||
|
if (fs.existsSync(dataPath)) {
|
||||||
|
// 读取数据
|
||||||
|
const data = JSON.parse(fs.readFileSync(dataPath).toString() || '{}');
|
||||||
|
// 导入数据
|
||||||
|
for (const key in data) {
|
||||||
|
try {
|
||||||
|
for (const item of data[key]) {
|
||||||
|
await this.importData(metadatas, item, key);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.coreLogger.error(
|
||||||
|
'\x1B[36m [cool:core] midwayjs cool core init ' +
|
||||||
|
module +
|
||||||
|
' database err \x1B[0m'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const endTime = new Date().getTime();
|
||||||
|
await this.lockImportData(
|
||||||
|
module,
|
||||||
|
metadatas,
|
||||||
|
lockPath,
|
||||||
|
endTime - startTime
|
||||||
|
);
|
||||||
|
this.coreLogger.info(
|
||||||
|
'\x1B[36m [cool:core] midwayjs cool core init ' +
|
||||||
|
module +
|
||||||
|
' database complete \x1B[0m'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定导入
|
||||||
|
* @param module
|
||||||
|
* @param metadatas
|
||||||
|
* @param lockPath
|
||||||
|
* @param time
|
||||||
|
*/
|
||||||
|
async lockImportData(
|
||||||
|
module: string,
|
||||||
|
metadatas,
|
||||||
|
lockPath: string,
|
||||||
|
time: number
|
||||||
|
) {
|
||||||
|
if (this.initJudge == 'file') {
|
||||||
|
fs.writeFileSync(lockPath, `time consuming:${time}ms`);
|
||||||
|
}
|
||||||
|
if (this.initJudge == 'db') {
|
||||||
|
const repository = this.defaultDataSource.getRepository(
|
||||||
|
metadatas['base_sys_conf']
|
||||||
|
);
|
||||||
|
if (this.ormConfig.default.type == 'postgres') {
|
||||||
|
await repository.save(
|
||||||
|
repository.create({
|
||||||
|
cKey: `init_db_${module}`,
|
||||||
|
cValue: `time consuming:${time}ms`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await repository.insert({
|
||||||
|
cKey: `init_db_${module}`,
|
||||||
|
cValue: `time consuming:${time}ms`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
* @param metadatas
|
||||||
|
* @param datas
|
||||||
|
* @param tableName
|
||||||
|
*/
|
||||||
|
async importData(
|
||||||
|
metadatas: any[],
|
||||||
|
item: any,
|
||||||
|
tableName: string,
|
||||||
|
parentItem: any = null
|
||||||
|
) {
|
||||||
|
const repository = this.defaultDataSource.getRepository(
|
||||||
|
metadatas[tableName]
|
||||||
|
);
|
||||||
|
// 处理当前项中的引用
|
||||||
|
if (parentItem) {
|
||||||
|
for (const key in item) {
|
||||||
|
if (typeof item[key] === 'string' && item[key].startsWith('@')) {
|
||||||
|
const parentKey = item[key].substring(1); // 移除"@"符号
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parentItem, parentKey)) {
|
||||||
|
item[key] = parentItem[parentKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 插入当前项到数据库
|
||||||
|
let insertedItem;
|
||||||
|
if (this.ormConfig.default.type == 'postgres') {
|
||||||
|
insertedItem = await repository.save(repository.create(item));
|
||||||
|
if (item.id) {
|
||||||
|
await repository.update(insertedItem.id, { id: item.id });
|
||||||
|
await this.defaultDataSource.query(
|
||||||
|
`SELECT setval('${tableName}_id_seq', (SELECT MAX(id) FROM ${tableName}));`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insertedItem = await repository.insert(item);
|
||||||
|
}
|
||||||
|
// 递归处理@childDatas
|
||||||
|
if (!_.isEmpty(item['@childDatas'])) {
|
||||||
|
const childDatas = item['@childDatas'];
|
||||||
|
delete item['@childDatas'];
|
||||||
|
for (const childKey in childDatas) {
|
||||||
|
for (const childItem of childDatas[childKey]) {
|
||||||
|
await this.importData(metadatas, childItem, childKey, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
core/src/module/menu.ts
Normal file
187
core/src/module/menu.ts
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import {
|
||||||
|
App,
|
||||||
|
Config,
|
||||||
|
ILogger,
|
||||||
|
IMidwayApplication,
|
||||||
|
Inject,
|
||||||
|
Logger,
|
||||||
|
Provide,
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { InjectDataSource, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { DataSource, Equal } from 'typeorm';
|
||||||
|
import { CoolEventManager } from '../event';
|
||||||
|
import { CoolConfig } from '../interface';
|
||||||
|
import { CoolModuleConfig } from './config';
|
||||||
|
import location from '../util/location';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolModuleMenu {
|
||||||
|
@Inject()
|
||||||
|
coolModuleConfig: CoolModuleConfig;
|
||||||
|
|
||||||
|
@Config('cool')
|
||||||
|
coolConfig: CoolConfig;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Logger()
|
||||||
|
coreLogger: ILogger;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
initJudge: 'file' | 'db';
|
||||||
|
|
||||||
|
@Config('typeorm.dataSource')
|
||||||
|
ormConfig;
|
||||||
|
|
||||||
|
@InjectDataSource('default')
|
||||||
|
defaultDataSource: DataSource;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
datas = {};
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.initJudge = this.coolConfig.initJudge;
|
||||||
|
if (!this.initJudge) {
|
||||||
|
this.initJudge = 'file';
|
||||||
|
}
|
||||||
|
// 是否需要导入
|
||||||
|
if (this.coolConfig.initMenu) {
|
||||||
|
const modules = this.coolModuleConfig.modules;
|
||||||
|
const metadatas = await this.getDbMetadatas();
|
||||||
|
for (const module of modules) {
|
||||||
|
if (this.initJudge == 'file') {
|
||||||
|
const { exist, lockPath } = this.checkFileExist(module);
|
||||||
|
if (!exist) {
|
||||||
|
await this.importMenu(module, metadatas, lockPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.initJudge == 'db') {
|
||||||
|
const exist = await this.checkDbExist(module, metadatas);
|
||||||
|
if (!exist) {
|
||||||
|
await this.importMenu(module, metadatas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.coolEventManager.emit('onMenuImport', this.datas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入菜单
|
||||||
|
* @param module
|
||||||
|
* @param lockPath
|
||||||
|
*/
|
||||||
|
async importMenu(module: string, metadatas, lockPath?: string) {
|
||||||
|
// 模块路径
|
||||||
|
const modulePath = `${location.getRootPath()}/modules/${module}`;
|
||||||
|
// json 路径
|
||||||
|
const menuPath = `${modulePath}/menu.json`;
|
||||||
|
// 导入
|
||||||
|
if (fs.existsSync(menuPath)) {
|
||||||
|
const data = fs.readFileSync(menuPath);
|
||||||
|
try {
|
||||||
|
// this.coolEventManager.emit("onMenuImport", module, JSON.parse(data.toString()));
|
||||||
|
this.datas[module] = JSON.parse(data.toString());
|
||||||
|
await this.lockImportData(module, metadatas, lockPath);
|
||||||
|
} catch (error) {
|
||||||
|
this.coreLogger.error(error);
|
||||||
|
this.coreLogger.error(
|
||||||
|
`自动初始化模块[${module}]菜单失败,请检查对应的数据结构是否正确`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库元数据
|
||||||
|
*/
|
||||||
|
async getDbMetadatas() {
|
||||||
|
// 获得所有的实体
|
||||||
|
const entityMetadatas = this.defaultDataSource.entityMetadatas;
|
||||||
|
const metadatas = _.mapValues(
|
||||||
|
_.keyBy(entityMetadatas, 'tableName'),
|
||||||
|
'target'
|
||||||
|
);
|
||||||
|
return metadatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据是否存在
|
||||||
|
* @param module
|
||||||
|
* @param metadatas
|
||||||
|
*/
|
||||||
|
async checkDbExist(module: string, metadatas) {
|
||||||
|
const cKey = `init_menu_${module}`;
|
||||||
|
const repository = this.defaultDataSource.getRepository(
|
||||||
|
metadatas['base_sys_conf']
|
||||||
|
);
|
||||||
|
const data = await repository.findOneBy({ cKey: Equal(cKey) });
|
||||||
|
return !!data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件是否存在
|
||||||
|
* @param module
|
||||||
|
*/
|
||||||
|
checkFileExist(module: string) {
|
||||||
|
const importLockPath = path.join(
|
||||||
|
`${location.getRootPath()}`,
|
||||||
|
'..',
|
||||||
|
'lock',
|
||||||
|
'menu'
|
||||||
|
);
|
||||||
|
if (!fs.existsSync(importLockPath)) {
|
||||||
|
fs.mkdirSync(importLockPath, { recursive: true });
|
||||||
|
}
|
||||||
|
const lockPath = path.join(importLockPath, module + '.menu.lock');
|
||||||
|
return {
|
||||||
|
exist: fs.existsSync(lockPath),
|
||||||
|
lockPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定导入
|
||||||
|
* @param module
|
||||||
|
* @param metadatas
|
||||||
|
* @param lockPath
|
||||||
|
* @param time
|
||||||
|
*/
|
||||||
|
async lockImportData(module: string, metadatas, lockPath: string) {
|
||||||
|
if (this.initJudge == 'file') {
|
||||||
|
fs.writeFileSync(lockPath, 'success');
|
||||||
|
}
|
||||||
|
if (this.initJudge == 'db') {
|
||||||
|
const repository = this.defaultDataSource.getRepository(
|
||||||
|
metadatas['base_sys_conf']
|
||||||
|
);
|
||||||
|
if (this.ormConfig.default.type == 'postgres') {
|
||||||
|
await repository.save(
|
||||||
|
repository.create({
|
||||||
|
cKey: `init_menu_${module}`,
|
||||||
|
cValue: 'success',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await repository.insert({
|
||||||
|
cKey: `init_menu_${module}`,
|
||||||
|
cValue: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
167
core/src/rest/eps.ts
Normal file
167
core/src/rest/eps.ts
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import {
|
||||||
|
CONTROLLER_KEY,
|
||||||
|
getClassMetadata,
|
||||||
|
listModule,
|
||||||
|
Provide,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import {
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
Config,
|
||||||
|
Inject,
|
||||||
|
MidwayWebRouterService,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import { CoolUrlTagData } from '../tag/data';
|
||||||
|
import { TagTypes } from '../decorator/tag';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体路径
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolEps {
|
||||||
|
admin = {};
|
||||||
|
|
||||||
|
app = {};
|
||||||
|
|
||||||
|
module = {};
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
midwayWebRouterService: MidwayWebRouterService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Config('cool.eps')
|
||||||
|
epsConfig: boolean;
|
||||||
|
|
||||||
|
@Config('module')
|
||||||
|
moduleConfig: any;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolUrlTagData: CoolUrlTagData;
|
||||||
|
|
||||||
|
// @Init()
|
||||||
|
async init() {
|
||||||
|
if (!this.epsConfig) return;
|
||||||
|
const entitys = await this.entity();
|
||||||
|
const controllers = await this.controller();
|
||||||
|
const routers = await this.router();
|
||||||
|
await this.modules();
|
||||||
|
const adminArr = [];
|
||||||
|
const appArr = [];
|
||||||
|
for (const controller of controllers) {
|
||||||
|
const { prefix, module, curdOption, routerOptions } = controller;
|
||||||
|
const name = curdOption?.entity?.name;
|
||||||
|
(_.startsWith(prefix, '/admin/') ? adminArr : appArr).push({
|
||||||
|
module,
|
||||||
|
info: {
|
||||||
|
type: {
|
||||||
|
name: prefix.split('/').pop(),
|
||||||
|
description: routerOptions?.description || '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api: routers[prefix],
|
||||||
|
name,
|
||||||
|
columns: entitys[name] || [],
|
||||||
|
prefix,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.admin = _.groupBy(adminArr, 'module');
|
||||||
|
this.app = _.groupBy(appArr, 'module');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块信息
|
||||||
|
* @param module
|
||||||
|
*/
|
||||||
|
async modules(module?: string) {
|
||||||
|
for (const key in this.moduleConfig) {
|
||||||
|
const config = this.moduleConfig[key];
|
||||||
|
this.module[key] = {
|
||||||
|
name: config.name,
|
||||||
|
description: config.description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return module ? this.module[module] : this.module;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有controller
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async controller() {
|
||||||
|
const result = [];
|
||||||
|
const controllers = listModule(CONTROLLER_KEY);
|
||||||
|
for (const controller of controllers) {
|
||||||
|
result.push(getClassMetadata(CONTROLLER_KEY, controller));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有路由
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async router() {
|
||||||
|
let ignoreUrls: string[] = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN);
|
||||||
|
if (_.isEmpty(ignoreUrls)) {
|
||||||
|
ignoreUrls = [];
|
||||||
|
}
|
||||||
|
return _.groupBy(
|
||||||
|
(await this.midwayWebRouterService.getFlattenRouterTable()).map(item => {
|
||||||
|
return {
|
||||||
|
method: item.requestMethod,
|
||||||
|
path: item.url,
|
||||||
|
summary: item.summary,
|
||||||
|
dts: {},
|
||||||
|
tag: '',
|
||||||
|
prefix: item.prefix,
|
||||||
|
ignoreToken: ignoreUrls.includes(item.prefix + item.url),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
'prefix'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有实体
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async entity() {
|
||||||
|
const result = {};
|
||||||
|
const dataSourceNames = this.typeORMDataSourceManager.getDataSourceNames();
|
||||||
|
for (const dataSourceName of dataSourceNames) {
|
||||||
|
const entityMetadatas = await this.typeORMDataSourceManager.getDataSource(
|
||||||
|
dataSourceName
|
||||||
|
).entityMetadatas;
|
||||||
|
for (const entityMetadata of entityMetadatas) {
|
||||||
|
const commColums = [];
|
||||||
|
let columns = entityMetadata.columns;
|
||||||
|
if (entityMetadata.tableType != 'regular') continue;
|
||||||
|
columns = _.filter(
|
||||||
|
columns.map(e => {
|
||||||
|
return {
|
||||||
|
propertyName: e.propertyName,
|
||||||
|
type:
|
||||||
|
typeof e.type === 'string' ? e.type : e.type.name.toLowerCase(),
|
||||||
|
length: e.length,
|
||||||
|
comment: e.comment,
|
||||||
|
nullable: e.isNullable,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
o => {
|
||||||
|
if (['createTime', 'updateTime'].includes(o.propertyName)) {
|
||||||
|
commColums.push(o);
|
||||||
|
}
|
||||||
|
return o && !['createTime', 'updateTime'].includes(o.propertyName);
|
||||||
|
}
|
||||||
|
).concat(commColums);
|
||||||
|
result[entityMetadata.name] = columns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
287
core/src/service/base.ts
Normal file
287
core/src/service/base.ts
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
import { App, Config, Init, Inject, Provide } from '@midwayjs/core';
|
||||||
|
import { Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { BaseMysqlService } from './mysql';
|
||||||
|
import { BasePgService } from './postgres';
|
||||||
|
import { CoolValidateException } from '../exception/validate';
|
||||||
|
import { ERRINFO } from '../constant/global';
|
||||||
|
import { Application, Context } from '@midwayjs/koa';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import { Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { QueryOp } from '../decorator/controller';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { CoolEventManager } from '../event';
|
||||||
|
import { CoolCoreException } from '../exception/core';
|
||||||
|
import { BaseSqliteService } from './sqlite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务基类
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export abstract class BaseService {
|
||||||
|
// mysql的基类
|
||||||
|
@Inject()
|
||||||
|
baseMysqlService: BaseMysqlService;
|
||||||
|
|
||||||
|
// postgres的基类
|
||||||
|
@Inject()
|
||||||
|
basePgService: BasePgService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
baseSqliteService: BaseSqliteService;
|
||||||
|
|
||||||
|
// 数据库类型
|
||||||
|
@Config('typeorm.dataSource.default.type')
|
||||||
|
ormType;
|
||||||
|
|
||||||
|
// 当前服务名称
|
||||||
|
service: BaseMysqlService | BasePgService | BaseSqliteService;
|
||||||
|
|
||||||
|
// 模型
|
||||||
|
protected entity: Repository<any>;
|
||||||
|
|
||||||
|
protected sqlParams;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
@Inject('ctx')
|
||||||
|
baseCtx: Context;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
baseApp: Application;
|
||||||
|
|
||||||
|
@Init()
|
||||||
|
async init() {
|
||||||
|
const services = {
|
||||||
|
mysql: this.baseMysqlService,
|
||||||
|
mariadb: this.baseMysqlService,
|
||||||
|
postgres: this.basePgService,
|
||||||
|
sqlite: this.baseSqliteService,
|
||||||
|
};
|
||||||
|
this.service = services[this.ormType];
|
||||||
|
if (!this.service) throw new CoolCoreException('暂不支持当前数据库类型');
|
||||||
|
this.sqlParams = this.service.sqlParams;
|
||||||
|
await this.service.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置模型
|
||||||
|
setEntity(entity: any) {
|
||||||
|
this.entity = entity;
|
||||||
|
this.service.setEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求上下文
|
||||||
|
setCtx(ctx: Context) {
|
||||||
|
this.baseCtx = ctx;
|
||||||
|
this.service.setCtx(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置应用对象
|
||||||
|
setApp(app: Application) {
|
||||||
|
this.baseApp = app;
|
||||||
|
this.service.setApp(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置sql
|
||||||
|
* @param condition 条件是否成立
|
||||||
|
* @param sql sql语句
|
||||||
|
* @param params 参数
|
||||||
|
*/
|
||||||
|
setSql(condition, sql, params) {
|
||||||
|
return this.service.setSql(condition, sql, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得查询个数的SQL
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
getCountSql(sql) {
|
||||||
|
return this.service.getCountSql(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数安全性检查
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
async paramSafetyCheck(params) {
|
||||||
|
return await this.service.paramSafetyCheck(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生查询
|
||||||
|
* @param sql
|
||||||
|
* @param params
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async nativeQuery(sql, params?, connectionName?) {
|
||||||
|
return await this.service.nativeQuery(sql, params, connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得ORM管理
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
getOrmManager(connectionName = 'default') {
|
||||||
|
return this.service.getOrmManager(connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作entity获得分页数据,不用写sql
|
||||||
|
* @param find QueryBuilder
|
||||||
|
* @param query
|
||||||
|
* @param autoSort
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async entityRenderPage(
|
||||||
|
find: SelectQueryBuilder<any>,
|
||||||
|
query,
|
||||||
|
autoSort = true
|
||||||
|
) {
|
||||||
|
return await this.service.entityRenderPage(find, query, autoSort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行SQL并获得分页数据
|
||||||
|
* @param sql 执行的sql语句
|
||||||
|
* @param query 分页查询条件
|
||||||
|
* @param autoSort 是否自动排序
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||||
|
return await this.service.sqlRenderPage(
|
||||||
|
sql,
|
||||||
|
query,
|
||||||
|
autoSort,
|
||||||
|
connectionName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得单个ID
|
||||||
|
* @param id ID
|
||||||
|
* @param infoIgnoreProperty 忽略返回属性
|
||||||
|
*/
|
||||||
|
async info(id: any, infoIgnoreProperty?: string[]) {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
return await this.service.info(id, infoIgnoreProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||||
|
*/
|
||||||
|
async delete(ids: any) {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
await this.modifyBefore(ids, 'delete');
|
||||||
|
await this.service.delete(ids);
|
||||||
|
await this.modifyAfter(ids, 'delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软删除
|
||||||
|
* @param ids 删除的ID数组
|
||||||
|
* @param entity 实体
|
||||||
|
*/
|
||||||
|
async softDelete(ids: number[], entity?: Repository<any>) {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
await this.service.softDelete(ids, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async update(param: any) {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (!param.id && !(param instanceof Array))
|
||||||
|
throw new CoolValidateException(ERRINFO.NOID);
|
||||||
|
await this.addOrUpdate(param, 'update');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async add(param: any | any[]): Promise<Object> {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
delete param.id;
|
||||||
|
await this.addOrUpdate(param, 'add');
|
||||||
|
return {
|
||||||
|
id:
|
||||||
|
param instanceof Array
|
||||||
|
? param.map(e => {
|
||||||
|
return e.id ? e.id : e._id;
|
||||||
|
})
|
||||||
|
: param.id
|
||||||
|
? param.id
|
||||||
|
: param._id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增|修改
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
await this.modifyBefore(param, type);
|
||||||
|
await this.service.addOrUpdate(param, type);
|
||||||
|
await this.modifyAfter(param, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async list(query, option, connectionName?): Promise<any> {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
return await this.service.list(query, option, connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async page(query, option, connectionName?) {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
return await this.service.page(query, option, connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建查询配置
|
||||||
|
* @param query 前端查询
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
async getOptionFind(query, option: QueryOp) {
|
||||||
|
this.service.setEntity(this.entity);
|
||||||
|
return await this.service.getOptionFind(query, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增|修改|删除 之后的操作
|
||||||
|
* @param data 对应数据
|
||||||
|
*/
|
||||||
|
async modifyAfter(
|
||||||
|
data: any,
|
||||||
|
type: 'delete' | 'update' | 'add'
|
||||||
|
): Promise<void> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增|修改|删除 之前的操作
|
||||||
|
* @param data 对应数据
|
||||||
|
*/
|
||||||
|
async modifyBefore(
|
||||||
|
data: any,
|
||||||
|
type: 'delete' | 'update' | 'add'
|
||||||
|
): Promise<void> {}
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Provide } from '@midwayjs/core';
|
|
||||||
|
|
||||||
@Provide()
|
|
||||||
export class BookService {
|
|
||||||
async getBookById() {
|
|
||||||
return 'hello world';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
499
core/src/service/mysql.ts
Normal file
499
core/src/service/mysql.ts
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
import { Init, Provide, Inject, App, Config } from '@midwayjs/core';
|
||||||
|
import { Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { CoolValidateException } from '../exception/validate';
|
||||||
|
import { ERRINFO, EVENT } from '../constant/global';
|
||||||
|
import { Application, Context } from '@midwayjs/koa';
|
||||||
|
import * as SqlString from 'sqlstring';
|
||||||
|
import { CoolConfig } from '../interface';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import { Brackets, Equal, In, Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { QueryOp } from '../decorator/controller';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { CoolEventManager } from '../event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务基类
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export abstract class BaseMysqlService {
|
||||||
|
// 分页配置
|
||||||
|
@Config('cool')
|
||||||
|
private _coolConfig: CoolConfig;
|
||||||
|
|
||||||
|
// 模型
|
||||||
|
entity: Repository<any>;
|
||||||
|
|
||||||
|
sqlParams;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
// 设置模型
|
||||||
|
setEntity(entity: any) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求上下文
|
||||||
|
setCtx(ctx: Context) {
|
||||||
|
this.baseCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@App()
|
||||||
|
baseApp: Application;
|
||||||
|
|
||||||
|
// 设置应用对象
|
||||||
|
setApp(app: Application) {
|
||||||
|
this.baseApp = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject('ctx')
|
||||||
|
baseCtx: Context;
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
@Init()
|
||||||
|
init() {
|
||||||
|
this.sqlParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置sql
|
||||||
|
* @param condition 条件是否成立
|
||||||
|
* @param sql sql语句
|
||||||
|
* @param params 参数
|
||||||
|
*/
|
||||||
|
setSql(condition, sql, params) {
|
||||||
|
let rSql = false;
|
||||||
|
if (condition || condition === 0) {
|
||||||
|
rSql = true;
|
||||||
|
this.sqlParams = this.sqlParams.concat(params);
|
||||||
|
}
|
||||||
|
return rSql ? sql : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得查询个数的SQL
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
getCountSql(sql) {
|
||||||
|
sql = sql
|
||||||
|
.replace(new RegExp('LIMIT', 'gm'), 'limit ')
|
||||||
|
.replace(new RegExp('\n', 'gm'), ' ');
|
||||||
|
if (sql.includes('limit')) {
|
||||||
|
const sqlArr = sql.split('limit ');
|
||||||
|
sqlArr.pop();
|
||||||
|
sql = sqlArr.join('limit ');
|
||||||
|
}
|
||||||
|
return `select count(*) as count from (${sql}) a`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数安全性检查
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
async paramSafetyCheck(params) {
|
||||||
|
const lp = params.toLowerCase();
|
||||||
|
return !(
|
||||||
|
lp.indexOf('update ') > -1 ||
|
||||||
|
lp.indexOf('select ') > -1 ||
|
||||||
|
lp.indexOf('delete ') > -1 ||
|
||||||
|
lp.indexOf('insert ') > -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生查询
|
||||||
|
* @param sql
|
||||||
|
* @param params
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async nativeQuery(sql, params?, connectionName?) {
|
||||||
|
if (_.isEmpty(params)) {
|
||||||
|
params = this.sqlParams;
|
||||||
|
}
|
||||||
|
let newParams = [];
|
||||||
|
newParams = newParams.concat(params);
|
||||||
|
this.sqlParams = [];
|
||||||
|
for (const param of newParams) {
|
||||||
|
SqlString.escape(param);
|
||||||
|
}
|
||||||
|
return await this.getOrmManager(connectionName).query(sql, newParams || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得ORM管理
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
getOrmManager(connectionName = 'default') {
|
||||||
|
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作entity获得分页数据,不用写sql
|
||||||
|
* @param find QueryBuilder
|
||||||
|
* @param query
|
||||||
|
* @param autoSort
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async entityRenderPage(
|
||||||
|
find: SelectQueryBuilder<any>,
|
||||||
|
query,
|
||||||
|
autoSort = true
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
size = this._coolConfig.crud.pageSize,
|
||||||
|
page = 1,
|
||||||
|
order = 'createTime',
|
||||||
|
sort = 'desc',
|
||||||
|
isExport = false,
|
||||||
|
maxExportLimit,
|
||||||
|
} = query;
|
||||||
|
const count = await find.getCount();
|
||||||
|
let dataFind: SelectQueryBuilder<any>;
|
||||||
|
if (isExport && maxExportLimit > 0) {
|
||||||
|
dataFind = find.limit(maxExportLimit);
|
||||||
|
} else {
|
||||||
|
dataFind = find.offset((page - 1) * size).limit(size);
|
||||||
|
}
|
||||||
|
if (autoSort) {
|
||||||
|
find.addOrderBy(order, sort.toUpperCase());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: await dataFind.getMany(),
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
size: parseInt(size),
|
||||||
|
total: count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行SQL并获得分页数据
|
||||||
|
* @param sql 执行的sql语句
|
||||||
|
* @param query 分页查询条件
|
||||||
|
* @param autoSort 是否自动排序
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||||
|
const {
|
||||||
|
size = this._coolConfig.crud.pageSize,
|
||||||
|
page = 1,
|
||||||
|
order = 'createTime',
|
||||||
|
sort = 'desc',
|
||||||
|
isExport = false,
|
||||||
|
maxExportLimit,
|
||||||
|
} = query;
|
||||||
|
if (order && sort && autoSort) {
|
||||||
|
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||||
|
throw new CoolValidateException('非法传参~');
|
||||||
|
}
|
||||||
|
sql += ` ORDER BY ${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
|
||||||
|
}
|
||||||
|
if (isExport && maxExportLimit > 0) {
|
||||||
|
this.sqlParams.push(parseInt(maxExportLimit));
|
||||||
|
sql += ' LIMIT ? ';
|
||||||
|
}
|
||||||
|
if (!isExport) {
|
||||||
|
this.sqlParams.push((page - 1) * size);
|
||||||
|
this.sqlParams.push(parseInt(size));
|
||||||
|
sql += ' LIMIT ?,? ';
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = [];
|
||||||
|
params = params.concat(this.sqlParams);
|
||||||
|
const result = await this.nativeQuery(sql, params, connectionName);
|
||||||
|
const countResult = await this.nativeQuery(
|
||||||
|
this.getCountSql(sql),
|
||||||
|
params,
|
||||||
|
connectionName
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
list: result,
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
size: parseInt(size),
|
||||||
|
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查排序
|
||||||
|
* @param sort 排序
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
checkSort(sort) {
|
||||||
|
if (!['desc', 'asc'].includes(sort.toLowerCase())) {
|
||||||
|
throw new CoolValidateException('sort 非法传参~');
|
||||||
|
}
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得单个ID
|
||||||
|
* @param id ID
|
||||||
|
* @param infoIgnoreProperty 忽略返回属性
|
||||||
|
*/
|
||||||
|
async info(id: any, infoIgnoreProperty?: string[]) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (!id) {
|
||||||
|
throw new CoolValidateException(ERRINFO.NOID);
|
||||||
|
}
|
||||||
|
const info = await this.entity.findOneBy({ id: Equal(id) });
|
||||||
|
if (info && infoIgnoreProperty) {
|
||||||
|
for (const property of infoIgnoreProperty) {
|
||||||
|
delete info[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||||
|
*/
|
||||||
|
async delete(ids: any) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (ids instanceof String) {
|
||||||
|
ids = ids.split(',');
|
||||||
|
}
|
||||||
|
// 启动软删除发送事件
|
||||||
|
if (this._coolConfig.crud?.softDelete) {
|
||||||
|
this.softDelete(ids);
|
||||||
|
}
|
||||||
|
await this.entity.delete(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软删除
|
||||||
|
* @param ids 删除的ID数组
|
||||||
|
* @param entity 实体
|
||||||
|
*/
|
||||||
|
async softDelete(ids: number[], entity?: Repository<any>) {
|
||||||
|
const data = await this.entity.find({
|
||||||
|
where: {
|
||||||
|
id: In(ids),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (_.isEmpty(data)) return;
|
||||||
|
const _entity = entity ? entity : this.entity;
|
||||||
|
const params = {
|
||||||
|
data,
|
||||||
|
ctx: this.baseCtx,
|
||||||
|
entity: _entity,
|
||||||
|
};
|
||||||
|
if (data.length > 0) {
|
||||||
|
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增|修改
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
delete param.createTime;
|
||||||
|
// 判断是否是批量操作
|
||||||
|
if (param instanceof Array) {
|
||||||
|
param.forEach(item => {
|
||||||
|
item.updateTime = new Date();
|
||||||
|
item.createTime = new Date();
|
||||||
|
});
|
||||||
|
await this.entity.save(param);
|
||||||
|
} else {
|
||||||
|
const upsert = this._coolConfig.crud?.upsert || 'normal';
|
||||||
|
if (type == 'update') {
|
||||||
|
if (upsert == 'save') {
|
||||||
|
const info = await this.entity.findOneBy({ id: param.id });
|
||||||
|
param = {
|
||||||
|
...info,
|
||||||
|
...param,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
param.updateTime = new Date();
|
||||||
|
upsert == 'normal'
|
||||||
|
? await this.entity.update(param.id, param)
|
||||||
|
: await this.entity.save(param);
|
||||||
|
}
|
||||||
|
if (type == 'add') {
|
||||||
|
param.createTime = new Date();
|
||||||
|
param.updateTime = new Date();
|
||||||
|
upsert == 'normal'
|
||||||
|
? await this.entity.insert(param)
|
||||||
|
: await this.entity.save(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async list(query, option, connectionName?): Promise<any> {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
const sql = await this.getOptionFind(query, option);
|
||||||
|
return this.nativeQuery(sql, [], connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async page(query, option, connectionName?) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
const sql = await this.getOptionFind(query, option);
|
||||||
|
return this.sqlRenderPage(sql, query, false, connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建查询配置
|
||||||
|
* @param query 前端查询
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
async getOptionFind(query, option: QueryOp) {
|
||||||
|
let { order = 'createTime', sort = 'desc', keyWord = '' } = query;
|
||||||
|
const sqlArr = ['SELECT'];
|
||||||
|
const selects = ['a.*'];
|
||||||
|
const find = this.entity.createQueryBuilder('a');
|
||||||
|
if (option) {
|
||||||
|
if (typeof option === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
option = await option(this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
// 判断是否有关联查询,有的话取个别名
|
||||||
|
if (!_.isEmpty(option.join)) {
|
||||||
|
for (const item of option.join) {
|
||||||
|
selects.push(`${item.alias}.*`);
|
||||||
|
find[item.type || 'leftJoin'](
|
||||||
|
item.entity,
|
||||||
|
item.alias,
|
||||||
|
item.condition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 默认条件
|
||||||
|
if (option.where) {
|
||||||
|
const wheres =
|
||||||
|
typeof option.where === 'function'
|
||||||
|
? await option.where(this.baseCtx, this.baseApp)
|
||||||
|
: option.where;
|
||||||
|
if (!_.isEmpty(wheres)) {
|
||||||
|
for (const item of wheres) {
|
||||||
|
if (
|
||||||
|
item.length == 2 ||
|
||||||
|
(item.length == 3 && (item[2] || item[2] === 0))
|
||||||
|
) {
|
||||||
|
for (const key in item[1]) {
|
||||||
|
this.sqlParams.push(item[1][key]);
|
||||||
|
}
|
||||||
|
find.andWhere(item[0], item[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 附加排序
|
||||||
|
if (!_.isEmpty(option.addOrderBy)) {
|
||||||
|
for (const key in option.addOrderBy) {
|
||||||
|
if (order && order == key) {
|
||||||
|
sort = option.addOrderBy[key].toUpperCase();
|
||||||
|
}
|
||||||
|
find.addOrderBy(
|
||||||
|
SqlString.escapeId(key),
|
||||||
|
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 关键字模糊搜索
|
||||||
|
if (keyWord || keyWord === 0) {
|
||||||
|
keyWord = `%${keyWord}%`;
|
||||||
|
find.andWhere(
|
||||||
|
new Brackets(qb => {
|
||||||
|
const keyWordLikeFields = option.keyWordLikeFields || [];
|
||||||
|
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||||
|
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
|
||||||
|
keyWord,
|
||||||
|
});
|
||||||
|
this.sqlParams.push(keyWord);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 筛选字段
|
||||||
|
if (!_.isEmpty(option.select)) {
|
||||||
|
sqlArr.push(option.select.join(','));
|
||||||
|
find.select(option.select);
|
||||||
|
} else {
|
||||||
|
sqlArr.push(selects.join(','));
|
||||||
|
}
|
||||||
|
// 字段全匹配
|
||||||
|
if (!_.isEmpty(option.fieldEq)) {
|
||||||
|
for (let key of option.fieldEq) {
|
||||||
|
const c = {};
|
||||||
|
// 如果key有包含.的情况下操作
|
||||||
|
if (typeof key === 'string' && key.includes('.')) {
|
||||||
|
const keys = key.split('.');
|
||||||
|
const lastKey = keys.pop();
|
||||||
|
key = { requestParam: lastKey, column: key };
|
||||||
|
}
|
||||||
|
// 单表字段无别名的情况下操作
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
if (query[key] || query[key] === 0) {
|
||||||
|
c[key] = query[key];
|
||||||
|
const eq = query[key] instanceof Array ? 'in' : '=';
|
||||||
|
if (eq === 'in') {
|
||||||
|
find.andWhere(`${key} ${eq} (:${key})`, c);
|
||||||
|
} else {
|
||||||
|
find.andWhere(`${key} ${eq} :${key}`, c);
|
||||||
|
}
|
||||||
|
this.sqlParams.push(query[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (query[key.requestParam] || query[key.requestParam] === 0) {
|
||||||
|
c[key.column] = query[key.requestParam];
|
||||||
|
const eq = query[key.requestParam] instanceof Array ? 'in' : '=';
|
||||||
|
if (eq === 'in') {
|
||||||
|
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||||
|
} else {
|
||||||
|
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||||
|
}
|
||||||
|
this.sqlParams.push(query[key.requestParam]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sqlArr.push(selects.join(','));
|
||||||
|
}
|
||||||
|
// 接口请求的排序
|
||||||
|
if (sort && order) {
|
||||||
|
const sorts = sort.toUpperCase().split(',');
|
||||||
|
const orders = order.split(',');
|
||||||
|
if (sorts.length != orders.length) {
|
||||||
|
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||||
|
}
|
||||||
|
for (const i in sorts) {
|
||||||
|
find.addOrderBy(
|
||||||
|
SqlString.escapeId(orders[i]),
|
||||||
|
this.checkSort(sorts[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option?.extend) {
|
||||||
|
await option?.extend(find, this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
const sqls = find.getSql().split('FROM');
|
||||||
|
sqlArr.push('FROM');
|
||||||
|
// 取sqls的最后一个
|
||||||
|
sqlArr.push(sqls[sqls.length - 1]);
|
||||||
|
return sqlArr.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
639
core/src/service/postgres.ts
Normal file
639
core/src/service/postgres.ts
Normal file
@ -0,0 +1,639 @@
|
|||||||
|
import { Init, Provide, Inject, App, Config } from '@midwayjs/core';
|
||||||
|
import { CoolValidateException } from '../exception/validate';
|
||||||
|
import { ERRINFO, EVENT } from '../constant/global';
|
||||||
|
import { Application, Context } from '@midwayjs/koa';
|
||||||
|
import { Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { CoolConfig } from '../interface';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { QueryOp } from '../decorator/controller';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { CoolEventManager } from '../event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务基类
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export abstract class BasePgService {
|
||||||
|
// 分页配置
|
||||||
|
@Config('cool')
|
||||||
|
private _coolConfig: CoolConfig;
|
||||||
|
|
||||||
|
// 模型
|
||||||
|
entity: Repository<any>;
|
||||||
|
|
||||||
|
sqlParams;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
// 设置模型
|
||||||
|
setEntity(entity: any) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求上下文
|
||||||
|
setCtx(ctx: Context) {
|
||||||
|
this.baseCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@App()
|
||||||
|
baseApp: Application;
|
||||||
|
|
||||||
|
// 设置应用对象
|
||||||
|
setApp(app: Application) {
|
||||||
|
this.baseApp = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject('ctx')
|
||||||
|
baseCtx: Context;
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
@Init()
|
||||||
|
init() {
|
||||||
|
this.sqlParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置sql
|
||||||
|
* @param condition 条件是否成立
|
||||||
|
* @param sql sql语句
|
||||||
|
* @param params 参数
|
||||||
|
*/
|
||||||
|
setSql(condition, sql, params) {
|
||||||
|
let rSql = false;
|
||||||
|
if (condition || condition === 0) {
|
||||||
|
rSql = true;
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
const param = params[i];
|
||||||
|
if (param instanceof Array) {
|
||||||
|
// 将这个? 替换成 $1,$2,$3
|
||||||
|
const replaceStr = [];
|
||||||
|
for (let j = 0; j < param.length; j++) {
|
||||||
|
replaceStr.push('$' + (this.sqlParams.length + j + 1));
|
||||||
|
}
|
||||||
|
this.sqlParams = this.sqlParams.concat(...params);
|
||||||
|
sql = sql.replace('?', replaceStr.join(','));
|
||||||
|
} else {
|
||||||
|
sql = sql.replace('?', '$' + (this.sqlParams.length + 1));
|
||||||
|
this.sqlParams.push(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rSql ? sql : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得查询个数的SQL
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
getCountSql(sql) {
|
||||||
|
sql = sql
|
||||||
|
.replace(new RegExp('LIMIT', 'gm'), 'limit ')
|
||||||
|
.replace(new RegExp('\n', 'gm'), ' ');
|
||||||
|
if (sql.includes('limit')) {
|
||||||
|
const sqlArr = sql.split('limit ');
|
||||||
|
sqlArr.pop();
|
||||||
|
sql = sqlArr.join('limit ');
|
||||||
|
}
|
||||||
|
return `select count(*) as count from (${sql}) a`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数安全性检查
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
async paramSafetyCheck(params) {
|
||||||
|
const lp = params.toLowerCase();
|
||||||
|
return !(
|
||||||
|
lp.indexOf('update ') > -1 ||
|
||||||
|
lp.indexOf('select ') > -1 ||
|
||||||
|
lp.indexOf('delete ') > -1 ||
|
||||||
|
lp.indexOf('insert ') > -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生查询
|
||||||
|
* @param sql
|
||||||
|
* @param params
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async nativeQuery(sql, params?, connectionName?) {
|
||||||
|
sql = this.convertToPostgres(sql);
|
||||||
|
if (_.isEmpty(params)) {
|
||||||
|
params = this.sqlParams;
|
||||||
|
}
|
||||||
|
let newParams = [];
|
||||||
|
// sql没处理过?的情况下
|
||||||
|
if (sql.includes('?')) {
|
||||||
|
for (const item of params) {
|
||||||
|
// 如果是数组,将这个? 替换成 $1,$2,$3
|
||||||
|
if (item instanceof Array) {
|
||||||
|
const replaceStr = [];
|
||||||
|
for (let i = 0; i < item.length; i++) {
|
||||||
|
replaceStr.push('$' + (newParams.length + i + 1));
|
||||||
|
}
|
||||||
|
newParams.push(...item);
|
||||||
|
sql = sql.replace('?', replaceStr.join(','));
|
||||||
|
} else {
|
||||||
|
sql = sql.replace('?', '$' + (newParams.length + 1));
|
||||||
|
newParams.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newParams = params;
|
||||||
|
}
|
||||||
|
this.sqlParams = [];
|
||||||
|
return await this.getOrmManager(connectionName).query(sql, newParams || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得ORM管理
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
getOrmManager(connectionName = 'default') {
|
||||||
|
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作entity获得分页数据,不用写sql
|
||||||
|
* @param find QueryBuilder
|
||||||
|
* @param query
|
||||||
|
* @param autoSort
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async entityRenderPage(
|
||||||
|
find: SelectQueryBuilder<any>,
|
||||||
|
query,
|
||||||
|
autoSort = true
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
size = this._coolConfig.crud.pageSize,
|
||||||
|
page = 1,
|
||||||
|
order = 'createTime',
|
||||||
|
sort = 'desc',
|
||||||
|
isExport = false,
|
||||||
|
maxExportLimit,
|
||||||
|
} = query;
|
||||||
|
const count = await find.getCount();
|
||||||
|
let dataFind: SelectQueryBuilder<any>;
|
||||||
|
if (isExport && maxExportLimit > 0) {
|
||||||
|
dataFind = find.limit(maxExportLimit);
|
||||||
|
} else {
|
||||||
|
dataFind = find.offset((page - 1) * size).limit(size);
|
||||||
|
}
|
||||||
|
if (autoSort) {
|
||||||
|
find.addOrderBy(order, sort.toUpperCase());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: await dataFind.getMany(),
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
size: parseInt(size),
|
||||||
|
total: count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将mysql语句转换为postgres语句
|
||||||
|
* @param sql
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected convertToPostgres(sql) {
|
||||||
|
// 首先确保表名被正确引用
|
||||||
|
sql = sql.replace(/(?<!")(\b\w+\b)\.(?!\w+")/g, '"$1".');
|
||||||
|
// 然后确保字段名被正确引用
|
||||||
|
return sql.replace(/\.(\w+)(?!\w)/g, '."$1"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询sql中的参数个数
|
||||||
|
* @param sql
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected countDollarSigns(sql) {
|
||||||
|
const matches = sql.match(/\$\d+/g);
|
||||||
|
return matches ? matches.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行SQL并获得分页数据
|
||||||
|
* @param sql 执行的sql语句
|
||||||
|
* @param query 分页查询条件
|
||||||
|
* @param autoSort 是否自动排序
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||||
|
const {
|
||||||
|
size = this._coolConfig.crud.pageSize,
|
||||||
|
page = 1,
|
||||||
|
order = 'createTime',
|
||||||
|
sort = 'desc',
|
||||||
|
isExport = false,
|
||||||
|
maxExportLimit,
|
||||||
|
} = query;
|
||||||
|
sql = `SELECT * FROM (${sql}) a `;
|
||||||
|
if (order && sort && autoSort) {
|
||||||
|
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||||
|
throw new CoolValidateException('非法传参~');
|
||||||
|
}
|
||||||
|
sql += `ORDER BY a."${order}" ${this.checkSort(sort)}`;
|
||||||
|
}
|
||||||
|
let cutParams = 0;
|
||||||
|
const paramCount = this.countDollarSigns(sql);
|
||||||
|
if (isExport && maxExportLimit > 0) {
|
||||||
|
this.sqlParams.push(parseInt(maxExportLimit));
|
||||||
|
cutParams = 1;
|
||||||
|
sql += ` LIMIT $${paramCount + 1}`;
|
||||||
|
}
|
||||||
|
if (!isExport) {
|
||||||
|
this.sqlParams.push(parseInt(size));
|
||||||
|
this.sqlParams.push((page - 1) * size);
|
||||||
|
cutParams = 2;
|
||||||
|
sql += ` LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`;
|
||||||
|
}
|
||||||
|
let params = [];
|
||||||
|
params = params.concat(this.sqlParams);
|
||||||
|
const result = await this.nativeQuery(sql, params, connectionName);
|
||||||
|
params = params.slice(0, -cutParams);
|
||||||
|
const countResult = await this.nativeQuery(
|
||||||
|
this.getCountSql(sql),
|
||||||
|
params,
|
||||||
|
connectionName
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
list: result,
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
size: parseInt(size),
|
||||||
|
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查排序
|
||||||
|
* @param sort 排序
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
checkSort(sort) {
|
||||||
|
if (!['desc', 'asc'].includes(sort.toLowerCase())) {
|
||||||
|
throw new CoolValidateException('sort 非法传参~');
|
||||||
|
}
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得单个ID
|
||||||
|
* @param id ID
|
||||||
|
* @param infoIgnoreProperty 忽略返回属性
|
||||||
|
*/
|
||||||
|
async info(id: any, infoIgnoreProperty?: string[]) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (!id) {
|
||||||
|
throw new CoolValidateException(ERRINFO.NOID);
|
||||||
|
}
|
||||||
|
const info = await this.entity.findOneBy({ id });
|
||||||
|
if (info && infoIgnoreProperty) {
|
||||||
|
for (const property of infoIgnoreProperty) {
|
||||||
|
delete info[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||||
|
*/
|
||||||
|
async delete(ids: any) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (ids instanceof String) {
|
||||||
|
ids = ids.split(',');
|
||||||
|
}
|
||||||
|
// 启动软删除发送事件
|
||||||
|
if (this._coolConfig.crud?.softDelete) {
|
||||||
|
this.softDelete(ids);
|
||||||
|
}
|
||||||
|
await this.entity.delete(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软删除
|
||||||
|
* @param ids 删除的ID数组
|
||||||
|
* @param entity 实体
|
||||||
|
*/
|
||||||
|
async softDelete(ids: number[], entity?: Repository<any>) {
|
||||||
|
const data = await this.entity.find({
|
||||||
|
where: {
|
||||||
|
id: In(ids),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (_.isEmpty(data)) return;
|
||||||
|
const _entity = entity ? entity : this.entity;
|
||||||
|
const params = {
|
||||||
|
data,
|
||||||
|
ctx: this.baseCtx,
|
||||||
|
entity: _entity,
|
||||||
|
};
|
||||||
|
if (data.length > 0) {
|
||||||
|
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增|修改
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
delete param.createTime;
|
||||||
|
// 判断是否是批量操作
|
||||||
|
if (param instanceof Array) {
|
||||||
|
param.forEach(item => {
|
||||||
|
item.updateTime = new Date();
|
||||||
|
item.createTime = new Date();
|
||||||
|
});
|
||||||
|
await this.entity.save(param);
|
||||||
|
} else {
|
||||||
|
const upsert = this._coolConfig.crud?.upsert || 'normal';
|
||||||
|
if (type == 'update') {
|
||||||
|
if (upsert == 'save') {
|
||||||
|
const info = await this.entity.findOneBy({ id: param.id });
|
||||||
|
param = {
|
||||||
|
...info,
|
||||||
|
...param,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
param.updateTime = new Date();
|
||||||
|
upsert == 'normal'
|
||||||
|
? await this.entity.update(param.id, param)
|
||||||
|
: await this.entity.save(param);
|
||||||
|
}
|
||||||
|
if (type == 'add') {
|
||||||
|
param.createTime = new Date();
|
||||||
|
param.updateTime = new Date();
|
||||||
|
upsert == 'normal'
|
||||||
|
? await this.entity.insert(param)
|
||||||
|
: await this.entity.save(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async list(query, option, connectionName?): Promise<any> {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
const sql = await this.getOptionFind(query, option);
|
||||||
|
return this.nativeQuery(sql, [], connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async page(query, option, connectionName?) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
const sql = await this.getOptionFind(query, option);
|
||||||
|
return this.sqlRenderPage(sql, query, false, connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建查询配置
|
||||||
|
* @param query 前端查询
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
async getOptionFind(query, option: QueryOp) {
|
||||||
|
let { order = 'createTime', sort = 'desc', keyWord = '' } = query;
|
||||||
|
const sqlArr = ['SELECT'];
|
||||||
|
const selects = ['a.*'];
|
||||||
|
const find = this.entity.createQueryBuilder('a');
|
||||||
|
if (option) {
|
||||||
|
if (typeof option === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
option = await option(this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
// 判断是否有关联查询,有的话取个别名
|
||||||
|
if (!_.isEmpty(option.join)) {
|
||||||
|
for (const item of option.join) {
|
||||||
|
selects.push(`${item.alias}.*`);
|
||||||
|
find[item.type || 'leftJoin'](
|
||||||
|
item.entity,
|
||||||
|
item.alias,
|
||||||
|
item.condition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 默认条件
|
||||||
|
if (option.where) {
|
||||||
|
const wheres =
|
||||||
|
typeof option.where === 'function'
|
||||||
|
? await option.where(this.baseCtx, this.baseApp)
|
||||||
|
: option.where;
|
||||||
|
if (!_.isEmpty(wheres)) {
|
||||||
|
for (const item of wheres) {
|
||||||
|
if (
|
||||||
|
item.length == 2 ||
|
||||||
|
(item.length == 3 && (item[2] || item[2] === 0))
|
||||||
|
) {
|
||||||
|
for (const key in item[1]) {
|
||||||
|
this.sqlParams.push(item[1][key]);
|
||||||
|
}
|
||||||
|
find.andWhere(item[0], item[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 附加排序
|
||||||
|
if (!_.isEmpty(option.addOrderBy)) {
|
||||||
|
for (const key in option.addOrderBy) {
|
||||||
|
if (order && order == key) {
|
||||||
|
sort = option.addOrderBy[key].toUpperCase();
|
||||||
|
}
|
||||||
|
find.addOrderBy(
|
||||||
|
`${this.matchColumn(option?.select, key)}.${key}`,
|
||||||
|
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 关键字模糊搜索
|
||||||
|
if (keyWord || keyWord == 0) {
|
||||||
|
keyWord = `%${keyWord}%`;
|
||||||
|
find.andWhere(
|
||||||
|
new Brackets(qb => {
|
||||||
|
const keyWordLikeFields = option.keyWordLikeFields || [];
|
||||||
|
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||||
|
let column = keyWordLikeFields[i];
|
||||||
|
column = column.includes('.') ? column : `a.${column}`;
|
||||||
|
const values = {};
|
||||||
|
values[`keyWord${i}`] = keyWord;
|
||||||
|
qb.orWhere(`${column} like :keyWord${i}`, values);
|
||||||
|
this.sqlParams.push(keyWord);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 筛选字段
|
||||||
|
if (!_.isEmpty(option.select)) {
|
||||||
|
sqlArr.push(option.select.join(','));
|
||||||
|
find.select(option.select);
|
||||||
|
} else {
|
||||||
|
sqlArr.push(selects.join(','));
|
||||||
|
}
|
||||||
|
// 字段全匹配
|
||||||
|
if (!_.isEmpty(option.fieldEq)) {
|
||||||
|
for (let key of option.fieldEq) {
|
||||||
|
const c = {};
|
||||||
|
let column;
|
||||||
|
// 如果key有包含.的情况下操作
|
||||||
|
if (typeof key === 'string' && key.includes('.')) {
|
||||||
|
const keys = key.split('.');
|
||||||
|
const lastKey = keys.pop();
|
||||||
|
key = { requestParam: lastKey, column: key };
|
||||||
|
column = key;
|
||||||
|
} else {
|
||||||
|
column = `a.${key}`;
|
||||||
|
}
|
||||||
|
// 单表字段无别名的情况下操作
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
if (query[key] || query[key] == 0) {
|
||||||
|
c[key] = query[key];
|
||||||
|
const eq = query[key] instanceof Array ? 'in' : '=';
|
||||||
|
if (eq === 'in') {
|
||||||
|
find.andWhere(`${column} ${eq} (:...${key})`, c);
|
||||||
|
} else {
|
||||||
|
find.andWhere(`${column} ${eq} :${key}`, c);
|
||||||
|
}
|
||||||
|
this.sqlParams.push(query[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (query[key.requestParam] || query[key.requestParam] == 0) {
|
||||||
|
c[key.column] = query[key.requestParam];
|
||||||
|
const eq = query[key.requestParam] instanceof Array ? 'in' : '=';
|
||||||
|
if (eq === 'in') {
|
||||||
|
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||||
|
} else {
|
||||||
|
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||||
|
}
|
||||||
|
this.sqlParams.push(query[key.requestParam]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sqlArr.push(selects.join(','));
|
||||||
|
}
|
||||||
|
// 接口请求的排序
|
||||||
|
if (sort && order) {
|
||||||
|
const sorts = sort.toUpperCase().split(',');
|
||||||
|
const orders = order.split(',');
|
||||||
|
if (sorts.length != orders.length) {
|
||||||
|
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||||
|
}
|
||||||
|
for (const i in sorts) {
|
||||||
|
find.addOrderBy(
|
||||||
|
`${this.matchColumn(option?.select, orders[i])}.${orders[i]}`,
|
||||||
|
this.checkSort(sorts[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option?.extend) {
|
||||||
|
await option?.extend(find, this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
const sqls = find.getSql().split('FROM');
|
||||||
|
sqlArr.push('FROM');
|
||||||
|
// 取sqls的最后一个
|
||||||
|
sqlArr.push(sqls[sqls.length - 1]);
|
||||||
|
sqlArr.forEach((item, index) => {
|
||||||
|
if (item.includes('ORDER BY')) {
|
||||||
|
sqlArr[index] = this.replaceOrderByPrefix(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sqlArr.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换sql中的表别名
|
||||||
|
* @param sql
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
replaceOrderByPrefix(sql) {
|
||||||
|
// 使用正则表达式匹配 ORDER BY 后面的部分
|
||||||
|
// 这里假设 ORDER BY 后面跟着的是由空格分隔的字段名,且字段名由双引号包围
|
||||||
|
const orderByRegex =
|
||||||
|
/ORDER BY\s+("[^"]+_[^"]+")(\s*(ASC|DESC)?\s*(,\s*"[^"]+_[^"]+")*)/gi;
|
||||||
|
|
||||||
|
// 定义替换函数
|
||||||
|
// @ts-ignore
|
||||||
|
function replaceMatch(match, p1, p2) {
|
||||||
|
// 将 p1 中的 "a_" 替换为 "a."
|
||||||
|
const replacedField = p1.replace(/a_([^"]+)/g, 'a.$1');
|
||||||
|
// 如果有其他字段,递归调用替换函数
|
||||||
|
const replacedRest = p2.replace(/("[^"]+_)/g, (m, p) =>
|
||||||
|
p.replace('a_', 'a.')
|
||||||
|
);
|
||||||
|
// 组合替换后的字段和其他部分
|
||||||
|
return `ORDER BY ${replacedField.replace(/"/g, '')}${replacedRest.replace(
|
||||||
|
/"/g,
|
||||||
|
''
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用替换函数替换匹配到的内容
|
||||||
|
const replacedOrderBySql = sql.replace(orderByRegex, replaceMatch);
|
||||||
|
|
||||||
|
// 移除所有双引号
|
||||||
|
const sqlWithoutQuotes = replacedOrderBySql.replace(/"/g, '');
|
||||||
|
|
||||||
|
return sqlWithoutQuotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 筛选的字段匹配
|
||||||
|
* @param select 筛选的字段
|
||||||
|
* @param field 字段
|
||||||
|
* @returns 字段在哪个表中
|
||||||
|
*/
|
||||||
|
protected matchColumn(select: string[] = [], field: string) {
|
||||||
|
for (const column of select) {
|
||||||
|
// 检查字段是否有别名,考虑 'AS' 关键字的不同大小写形式
|
||||||
|
const aliasPattern = new RegExp(`\\b\\w+\\s+as\\s+${field}\\b`, 'i');
|
||||||
|
const aliasMatch = column.match(aliasPattern);
|
||||||
|
if (aliasMatch) {
|
||||||
|
// 提取别名前的字段和表名
|
||||||
|
const fieldPattern = new RegExp(
|
||||||
|
`(\\w+)\\.(\\w+)\\s+as\\s+${field}`,
|
||||||
|
'i'
|
||||||
|
);
|
||||||
|
const fieldMatch = column.match(fieldPattern);
|
||||||
|
if (fieldMatch) {
|
||||||
|
// 返回匹配到的表名
|
||||||
|
return fieldMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查字段是否直接在选择列表中
|
||||||
|
const fieldPattern = new RegExp(`\\b(\\w+)\\.${field}\\b`, 'i');
|
||||||
|
const fieldMatch = column.match(fieldPattern);
|
||||||
|
if (fieldMatch) {
|
||||||
|
// 如果直接匹配到字段,返回字段所属的表名
|
||||||
|
return fieldMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有匹配到任何特定的表或别名,返回默认的 'a' 表
|
||||||
|
return 'a';
|
||||||
|
}
|
||||||
|
}
|
||||||
532
core/src/service/sqlite.ts
Normal file
532
core/src/service/sqlite.ts
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
import { Init, Provide, Inject, App, Config } from '@midwayjs/core';
|
||||||
|
import { Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { CoolValidateException } from '../exception/validate';
|
||||||
|
import { ERRINFO, EVENT } from '../constant/global';
|
||||||
|
import { Application, Context } from '@midwayjs/koa';
|
||||||
|
import * as SqlString from 'sqlstring';
|
||||||
|
import { CoolConfig } from '../interface';
|
||||||
|
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { QueryOp } from '../decorator/controller';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { CoolEventManager } from '../event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务基类
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export abstract class BaseSqliteService {
|
||||||
|
// 分页配置
|
||||||
|
@Config('cool')
|
||||||
|
private _coolConfig: CoolConfig;
|
||||||
|
|
||||||
|
// 模型
|
||||||
|
entity: Repository<any>;
|
||||||
|
|
||||||
|
sqlParams;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
// 设置模型
|
||||||
|
setEntity(entity: any) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求上下文
|
||||||
|
setCtx(ctx: Context) {
|
||||||
|
this.baseCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@App()
|
||||||
|
baseApp: Application;
|
||||||
|
|
||||||
|
// 设置应用对象
|
||||||
|
setApp(app: Application) {
|
||||||
|
this.baseApp = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject('ctx')
|
||||||
|
baseCtx: Context;
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
@Init()
|
||||||
|
init() {
|
||||||
|
this.sqlParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置sql
|
||||||
|
* @param condition 条件是否成立
|
||||||
|
* @param sql sql语句
|
||||||
|
* @param params 参数
|
||||||
|
*/
|
||||||
|
setSql(condition, sql, params) {
|
||||||
|
let rSql = false;
|
||||||
|
if (condition || condition === 0) {
|
||||||
|
rSql = true;
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
const param = params[i];
|
||||||
|
if (param instanceof Array) {
|
||||||
|
// 将这个? 替换成 $1,$2,$3
|
||||||
|
const replaceStr = [];
|
||||||
|
for (let j = 0; j < param.length; j++) {
|
||||||
|
replaceStr.push('$' + (this.sqlParams.length + j + 1));
|
||||||
|
}
|
||||||
|
this.sqlParams = this.sqlParams.concat(...params);
|
||||||
|
sql = sql.replace('?', replaceStr.join(','));
|
||||||
|
} else {
|
||||||
|
sql = sql.replace('?', '$' + (this.sqlParams.length + 1));
|
||||||
|
this.sqlParams.push(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (rSql ? sql : '').replace(/\$\d+/g, '?');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得查询个数的SQL
|
||||||
|
* @param sql
|
||||||
|
*/
|
||||||
|
getCountSql(sql) {
|
||||||
|
sql = sql
|
||||||
|
.replace(new RegExp('LIMIT', 'gm'), 'limit ')
|
||||||
|
.replace(new RegExp('\n', 'gm'), ' ');
|
||||||
|
if (sql.includes('limit')) {
|
||||||
|
const sqlArr = sql.split('limit ');
|
||||||
|
sqlArr.pop();
|
||||||
|
sql = sqlArr.join('limit ');
|
||||||
|
}
|
||||||
|
return `select count(*) as count from (${sql}) a`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数安全性检查
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
async paramSafetyCheck(params) {
|
||||||
|
const lp = params.toLowerCase();
|
||||||
|
return !(
|
||||||
|
lp.indexOf('update ') > -1 ||
|
||||||
|
lp.indexOf('select ') > -1 ||
|
||||||
|
lp.indexOf('delete ') > -1 ||
|
||||||
|
lp.indexOf('insert ') > -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生查询
|
||||||
|
* @param sql
|
||||||
|
* @param params
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async nativeQuery(sql, params?, connectionName?) {
|
||||||
|
if (_.isEmpty(params)) {
|
||||||
|
params = this.sqlParams;
|
||||||
|
}
|
||||||
|
const newParams = [];
|
||||||
|
// sql没处理过?的情况下
|
||||||
|
for (const item of params) {
|
||||||
|
// 如果是数组,将这个? 替换成 $1,$2,$3
|
||||||
|
if (item instanceof Array) {
|
||||||
|
const replaceStr = [];
|
||||||
|
for (let i = 0; i < item.length; i++) {
|
||||||
|
replaceStr.push('$' + (newParams.length + i + 1));
|
||||||
|
}
|
||||||
|
newParams.push(...item);
|
||||||
|
sql = sql.replace('?', replaceStr.join(','));
|
||||||
|
} else {
|
||||||
|
sql = sql.replace('?', '$' + (newParams.length + 1));
|
||||||
|
newParams.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sqlParams = [];
|
||||||
|
return await this.getOrmManager(connectionName).query(
|
||||||
|
sql.replace(/\$\d+/g, '?'),
|
||||||
|
newParams || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得ORM管理
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
getOrmManager(connectionName = 'default') {
|
||||||
|
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作entity获得分页数据,不用写sql
|
||||||
|
* @param find QueryBuilder
|
||||||
|
* @param query
|
||||||
|
* @param autoSort
|
||||||
|
* @param connectionName
|
||||||
|
*/
|
||||||
|
async entityRenderPage(
|
||||||
|
find: SelectQueryBuilder<any>,
|
||||||
|
query,
|
||||||
|
autoSort = true
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
size = this._coolConfig.crud.pageSize,
|
||||||
|
page = 1,
|
||||||
|
order = 'createTime',
|
||||||
|
sort = 'desc',
|
||||||
|
isExport = false,
|
||||||
|
maxExportLimit,
|
||||||
|
} = query;
|
||||||
|
const count = await find.getCount();
|
||||||
|
let dataFind: SelectQueryBuilder<any>;
|
||||||
|
if (isExport && maxExportLimit > 0) {
|
||||||
|
dataFind = find.limit(maxExportLimit);
|
||||||
|
} else {
|
||||||
|
dataFind = find.offset((page - 1) * size).limit(size);
|
||||||
|
}
|
||||||
|
if (autoSort) {
|
||||||
|
find.addOrderBy(order, sort.toUpperCase());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: await dataFind.getMany(),
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
size: parseInt(size),
|
||||||
|
total: count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行SQL并获得分页数据
|
||||||
|
* @param sql 执行的sql语句
|
||||||
|
* @param query 分页查询条件
|
||||||
|
* @param autoSort 是否自动排序
|
||||||
|
* @param connectionName 连接名称
|
||||||
|
*/
|
||||||
|
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||||
|
const {
|
||||||
|
size = this._coolConfig.crud.pageSize,
|
||||||
|
page = 1,
|
||||||
|
order = 'createTime',
|
||||||
|
sort = 'desc',
|
||||||
|
isExport = false,
|
||||||
|
maxExportLimit,
|
||||||
|
} = query;
|
||||||
|
sql = `SELECT * FROM (${sql}) a`;
|
||||||
|
if (order && sort && autoSort) {
|
||||||
|
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||||
|
throw new CoolValidateException('非法传参~');
|
||||||
|
}
|
||||||
|
sql += ` ORDER BY a.${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
|
||||||
|
}
|
||||||
|
let cutParams = 0;
|
||||||
|
if (isExport && maxExportLimit > 0) {
|
||||||
|
this.sqlParams.push(parseInt(maxExportLimit));
|
||||||
|
cutParams = 1;
|
||||||
|
sql += ' LIMIT ? ';
|
||||||
|
}
|
||||||
|
if (!isExport) {
|
||||||
|
this.sqlParams.push((page - 1) * size);
|
||||||
|
this.sqlParams.push(parseInt(size));
|
||||||
|
cutParams = 2;
|
||||||
|
sql += ' LIMIT ?,? ';
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = [];
|
||||||
|
params = params.concat(this.sqlParams);
|
||||||
|
const result = await this.nativeQuery(sql, params, connectionName);
|
||||||
|
params = params.slice(0, -cutParams);
|
||||||
|
const countResult = await this.nativeQuery(
|
||||||
|
this.getCountSql(sql),
|
||||||
|
params,
|
||||||
|
connectionName
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
list: result,
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
size: parseInt(size),
|
||||||
|
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查排序
|
||||||
|
* @param sort 排序
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
checkSort(sort) {
|
||||||
|
if (!['desc', 'asc'].includes(sort.toLowerCase())) {
|
||||||
|
throw new CoolValidateException('sort 非法传参~');
|
||||||
|
}
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得单个ID
|
||||||
|
* @param id ID
|
||||||
|
* @param infoIgnoreProperty 忽略返回属性
|
||||||
|
*/
|
||||||
|
async info(id: any, infoIgnoreProperty?: string[]) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (!id) {
|
||||||
|
throw new CoolValidateException(ERRINFO.NOID);
|
||||||
|
}
|
||||||
|
const info = await this.entity.findOneBy({ id });
|
||||||
|
if (info && infoIgnoreProperty) {
|
||||||
|
for (const property of infoIgnoreProperty) {
|
||||||
|
delete info[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||||
|
*/
|
||||||
|
async delete(ids: any) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
if (ids instanceof String) {
|
||||||
|
ids = ids.split(',');
|
||||||
|
}
|
||||||
|
// 启动软删除发送事件
|
||||||
|
if (this._coolConfig.crud?.softDelete) {
|
||||||
|
this.softDelete(ids);
|
||||||
|
}
|
||||||
|
await this.entity.delete(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软删除
|
||||||
|
* @param ids 删除的ID数组
|
||||||
|
* @param entity 实体
|
||||||
|
*/
|
||||||
|
async softDelete(ids: number[], entity?: Repository<any>) {
|
||||||
|
const data = await this.entity.find({
|
||||||
|
where: {
|
||||||
|
id: In(ids),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (_.isEmpty(data)) return;
|
||||||
|
const _entity = entity ? entity : this.entity;
|
||||||
|
const params = {
|
||||||
|
data,
|
||||||
|
ctx: this.baseCtx,
|
||||||
|
entity: _entity,
|
||||||
|
};
|
||||||
|
if (data.length > 0) {
|
||||||
|
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增|修改
|
||||||
|
* @param param 数据
|
||||||
|
*/
|
||||||
|
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
delete param.createTime;
|
||||||
|
// 判断是否是批量操作
|
||||||
|
if (param instanceof Array) {
|
||||||
|
param.forEach(item => {
|
||||||
|
item.updateTime = new Date();
|
||||||
|
item.createTime = new Date();
|
||||||
|
});
|
||||||
|
await this.entity.save(param);
|
||||||
|
} else {
|
||||||
|
const upsert = this._coolConfig.crud?.upsert || 'normal';
|
||||||
|
if (type == 'update') {
|
||||||
|
if (upsert == 'save') {
|
||||||
|
const info = await this.entity.findOneBy({ id: param.id });
|
||||||
|
param = {
|
||||||
|
...info,
|
||||||
|
...param,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
param.updateTime = new Date();
|
||||||
|
upsert == 'normal'
|
||||||
|
? await this.entity.update(param.id, param)
|
||||||
|
: await this.entity.save(param);
|
||||||
|
}
|
||||||
|
if (type == 'add') {
|
||||||
|
param.createTime = new Date();
|
||||||
|
param.updateTime = new Date();
|
||||||
|
upsert == 'normal'
|
||||||
|
? await this.entity.insert(param)
|
||||||
|
: await this.entity.save(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async list(query, option, connectionName?): Promise<any> {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
const sql = await this.getOptionFind(query, option);
|
||||||
|
return this.nativeQuery(sql, [], connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
* @param query 查询条件
|
||||||
|
* @param option 查询配置
|
||||||
|
* @param connectionName 连接名
|
||||||
|
*/
|
||||||
|
async page(query, option, connectionName?) {
|
||||||
|
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||||
|
const sql = await this.getOptionFind(query, option);
|
||||||
|
return this.sqlRenderPage(sql, query, false, connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建查询配置
|
||||||
|
* @param query 前端查询
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
async getOptionFind(query, option: QueryOp) {
|
||||||
|
let { order = 'createTime', sort = 'desc', keyWord = '' } = query;
|
||||||
|
const sqlArr = ['SELECT'];
|
||||||
|
const selects = ['a.*'];
|
||||||
|
const find = this.entity.createQueryBuilder('a');
|
||||||
|
if (option) {
|
||||||
|
if (typeof option === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
option = await option(this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
// 判断是否有关联查询,有的话取个别名
|
||||||
|
if (!_.isEmpty(option.join)) {
|
||||||
|
for (const item of option.join) {
|
||||||
|
selects.push(`${item.alias}.*`);
|
||||||
|
find[item.type || 'leftJoin'](
|
||||||
|
item.entity,
|
||||||
|
item.alias,
|
||||||
|
item.condition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 默认条件
|
||||||
|
if (option.where) {
|
||||||
|
const wheres =
|
||||||
|
typeof option.where === 'function'
|
||||||
|
? await option.where(this.baseCtx, this.baseApp)
|
||||||
|
: option.where;
|
||||||
|
if (!_.isEmpty(wheres)) {
|
||||||
|
for (const item of wheres) {
|
||||||
|
if (
|
||||||
|
item.length == 2 ||
|
||||||
|
(item.length == 3 && (item[2] || item[2] === 0))
|
||||||
|
) {
|
||||||
|
for (const key in item[1]) {
|
||||||
|
this.sqlParams.push(item[1][key]);
|
||||||
|
}
|
||||||
|
find.andWhere(item[0], item[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 附加排序
|
||||||
|
if (!_.isEmpty(option.addOrderBy)) {
|
||||||
|
for (const key in option.addOrderBy) {
|
||||||
|
if (order && order == key) {
|
||||||
|
sort = option.addOrderBy[key].toUpperCase();
|
||||||
|
}
|
||||||
|
find.addOrderBy(
|
||||||
|
SqlString.escapeId(key),
|
||||||
|
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 关键字模糊搜索
|
||||||
|
if (keyWord || keyWord === 0) {
|
||||||
|
keyWord = `%${keyWord}%`;
|
||||||
|
find.andWhere(
|
||||||
|
new Brackets(qb => {
|
||||||
|
const keyWordLikeFields = option.keyWordLikeFields || [];
|
||||||
|
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||||
|
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
|
||||||
|
keyWord,
|
||||||
|
});
|
||||||
|
this.sqlParams.push(keyWord);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 筛选字段
|
||||||
|
if (!_.isEmpty(option.select)) {
|
||||||
|
sqlArr.push(option.select.join(','));
|
||||||
|
find.select(option.select);
|
||||||
|
} else {
|
||||||
|
sqlArr.push(selects.join(','));
|
||||||
|
}
|
||||||
|
// 字段全匹配
|
||||||
|
if (!_.isEmpty(option.fieldEq)) {
|
||||||
|
for (let key of option.fieldEq) {
|
||||||
|
const c = {};
|
||||||
|
// 如果key有包含.的情况下操作
|
||||||
|
if (typeof key === 'string' && key.includes('.')) {
|
||||||
|
const keys = key.split('.');
|
||||||
|
const lastKey = keys.pop();
|
||||||
|
key = { requestParam: lastKey, column: key };
|
||||||
|
}
|
||||||
|
// 单表字段无别名的情况下操作
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
if (query[key] || query[key] == 0) {
|
||||||
|
c[key] = query[key];
|
||||||
|
const eq = query[key] instanceof Array ? 'in' : '=';
|
||||||
|
if (eq === 'in') {
|
||||||
|
find.andWhere(`${key} ${eq} (:${key})`, c);
|
||||||
|
} else {
|
||||||
|
find.andWhere(`${key} ${eq} :${key}`, c);
|
||||||
|
}
|
||||||
|
// this.sqlParams.push(query[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (query[key.requestParam] || query[key.requestParam] == 0) {
|
||||||
|
c[key.column] = query[key.requestParam];
|
||||||
|
const eq = query[key.requestParam] instanceof Array ? 'in' : '=';
|
||||||
|
if (eq === 'in') {
|
||||||
|
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||||
|
} else {
|
||||||
|
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||||
|
}
|
||||||
|
// this.sqlParams.push(query[key.requestParam]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sqlArr.push(selects.join(','));
|
||||||
|
}
|
||||||
|
// 接口请求的排序
|
||||||
|
if (sort && order) {
|
||||||
|
const sorts = sort.toUpperCase().split(',');
|
||||||
|
const orders = order.split(',');
|
||||||
|
if (sorts.length != orders.length) {
|
||||||
|
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||||
|
}
|
||||||
|
for (const i in sorts) {
|
||||||
|
find.addOrderBy(
|
||||||
|
SqlString.escapeId(orders[i]),
|
||||||
|
this.checkSort(sorts[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (option?.extend) {
|
||||||
|
await option?.extend(find, this.baseCtx, this.baseApp);
|
||||||
|
}
|
||||||
|
const sqls = find.getSql().split('FROM');
|
||||||
|
sqlArr.push('FROM');
|
||||||
|
// 取sqls的最后一个
|
||||||
|
sqlArr.push(sqls[sqls.length - 1]);
|
||||||
|
return sqlArr.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
94
core/src/tag/data.ts
Normal file
94
core/src/tag/data.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { COOL_METHOD_TAG_KEY, CoolUrlTagConfig } from './../decorator/tag';
|
||||||
|
import {
|
||||||
|
CONTROLLER_KEY,
|
||||||
|
getClassMetadata,
|
||||||
|
listPropertyDataFromClass,
|
||||||
|
listModule,
|
||||||
|
Provide,
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
WEB_ROUTER_KEY,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import { COOL_URL_TAG_KEY } from '../decorator/tag';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL标签
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class CoolUrlTagData {
|
||||||
|
data = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
// 类标记
|
||||||
|
await this.classTag();
|
||||||
|
// 方法标记
|
||||||
|
await this.methodTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类标记
|
||||||
|
*/
|
||||||
|
async classTag() {
|
||||||
|
const tags = listModule(COOL_URL_TAG_KEY);
|
||||||
|
for (const controller of tags) {
|
||||||
|
// class的标记
|
||||||
|
const controllerOption = getClassMetadata(CONTROLLER_KEY, controller);
|
||||||
|
const tagOption: CoolUrlTagConfig = getClassMetadata(
|
||||||
|
COOL_URL_TAG_KEY,
|
||||||
|
controller
|
||||||
|
);
|
||||||
|
if (tagOption?.key) {
|
||||||
|
const data: string[] = this.data[tagOption.key] || [];
|
||||||
|
this.data[tagOption.key] = _.uniq(
|
||||||
|
data.concat(
|
||||||
|
(tagOption?.value || []).map(e => {
|
||||||
|
return controllerOption.prefix + '/' + e;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法标记
|
||||||
|
*/
|
||||||
|
async methodTag() {
|
||||||
|
const controllers = listModule(CONTROLLER_KEY);
|
||||||
|
for (const controller of controllers) {
|
||||||
|
const controllerOption = getClassMetadata(CONTROLLER_KEY, controller);
|
||||||
|
// 方法标记
|
||||||
|
const listPropertyMetas = listPropertyDataFromClass(
|
||||||
|
COOL_METHOD_TAG_KEY,
|
||||||
|
controller
|
||||||
|
);
|
||||||
|
const requestMetas = getClassMetadata(WEB_ROUTER_KEY, controller);
|
||||||
|
for (const propertyMeta of listPropertyMetas) {
|
||||||
|
const _data = this.data[propertyMeta.tag] || [];
|
||||||
|
const requestMeta = _.find(requestMetas, { method: propertyMeta.key });
|
||||||
|
if (requestMeta && controllerOption) {
|
||||||
|
this.data[propertyMeta.tag] = _.uniq(
|
||||||
|
_data.concat(controllerOption.prefix + requestMeta.path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据键获得
|
||||||
|
* @param key
|
||||||
|
* @param type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
byKey(key: string, type?: 'app' | 'admin'): string[] {
|
||||||
|
return this.data[key]?.filter(e => {
|
||||||
|
return type ? _.startsWith(e, `/${type}/`) : true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
27
core/src/util/func.ts
Normal file
27
core/src/util/func.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ILogger } from '@midwayjs/core';
|
||||||
|
import { Init, Logger, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
core/src/util/location.ts
Normal file
68
core/src/util/location.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location 工具类
|
||||||
|
*/
|
||||||
|
class LocationUtil {
|
||||||
|
private locationCache = new Map<string, any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标类的定义位置
|
||||||
|
* @param target 目标类
|
||||||
|
* @returns 目标类的定义位置
|
||||||
|
*/
|
||||||
|
async scriptPath(target: any) {
|
||||||
|
const targetName = target.name;
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
if (this.locationCache.has(targetName)) {
|
||||||
|
return this.locationCache.get(targetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalPrepareStackTrace = Error.prepareStackTrace;
|
||||||
|
let targetFile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Error.prepareStackTrace = (error, stack) => stack;
|
||||||
|
const stack = new Error().stack as any;
|
||||||
|
|
||||||
|
for (const site of stack) {
|
||||||
|
const fileName = site.getFileName();
|
||||||
|
// 如果文件名不存在,则跳过
|
||||||
|
if (!fileName || !fs.existsSync(fileName)) continue;
|
||||||
|
if (!fileName.includes('/modules/')) continue;
|
||||||
|
targetFile = {
|
||||||
|
path: fileName,
|
||||||
|
line: site.getLineNumber(),
|
||||||
|
column: site.getColumnNumber(),
|
||||||
|
source: `file://${fileName}`,
|
||||||
|
};
|
||||||
|
this.locationCache.set(targetName, targetFile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Error.prepareStackTrace = originalPrepareStackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取使用此包的项目的真实根目录路径
|
||||||
|
* @returns 项目根目录的绝对路径
|
||||||
|
*/
|
||||||
|
getRootPath(): string {
|
||||||
|
const err = new Error();
|
||||||
|
const callerfile = err.stack.split('\n')[2].match(/\(([^)]+)\)/)[1];
|
||||||
|
const dirPath = path.dirname(callerfile);
|
||||||
|
const nodeModulesIndex = dirPath.indexOf('/node_modules/');
|
||||||
|
if (nodeModulesIndex !== -1) {
|
||||||
|
return dirPath.substring(0, nodeModulesIndex) + '/dist';
|
||||||
|
}
|
||||||
|
return dirPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不再需要单例模式,直接导出实例即可
|
||||||
|
export default new LocationUtil();
|
||||||
Loading…
x
Reference in New Issue
Block a user