完善rpc

This commit is contained in:
COOL 2025-01-17 19:49:04 +08:00
parent 33e7e508f2
commit 89e7c3bc3d
32 changed files with 11587 additions and 9 deletions

View File

@ -32,11 +32,10 @@
"url": "https://cool-js.com"
},
"devDependencies": {
"@midwayjs/logger": "^3.4.2",
"mwtsc": "^1.15.1",
"@midwayjs/cli": "2.1.1",
"@midwayjs/core": "^3.20.0",
"@midwayjs/koa": "^3.20.0",
"@midwayjs/logger": "^3.4.2",
"@midwayjs/mock": "^3.20.0",
"@midwayjs/typeorm": "^3.20.0",
"@types/download": "^8.0.5",
@ -46,6 +45,7 @@
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"mwts": "^1.3.0",
"mwtsc": "^1.15.1",
"ts-jest": "^29.2.5",
"typeorm": "^0.3.20",
"typescript": "~5.7.3"

10
core/pnpm-lock.yaml generated
View File

@ -44,6 +44,9 @@ importers:
moment:
specifier: ^2.30.1
version: 2.30.1
moment-timezone:
specifier: ^0.5.46
version: 0.5.46
pm2:
specifier: ^5.4.3
version: 5.4.3
@ -2816,6 +2819,9 @@ packages:
module-details-from-path@1.0.3:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
moment-timezone@0.5.46:
resolution: {integrity: sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==}
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
@ -7452,6 +7458,10 @@ snapshots:
module-details-from-path@1.0.3: {}
moment-timezone@0.5.46:
dependencies:
moment: 2.30.1
moment@2.30.1: {}
mqemitter@6.0.2:

View File

@ -27,6 +27,7 @@ export abstract class BaseService {
@Inject()
basePgService: BasePgService;
// sqlite的基类
@Inject()
baseSqliteService: BaseSqliteService;

View File

@ -1,4 +1,4 @@
import { Init, Provide, Inject, App, Config } from '@midwayjs/core';
import { Init, Provide, Inject, App, Config, ALL } from '@midwayjs/core';
import { Scope, ScopeEnum } from '@midwayjs/core';
import { CoolValidateException } from '../exception/validate';
import { ERRINFO, EVENT } from '../constant/global';
@ -10,7 +10,7 @@ import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm';
import { QueryOp } from '../decorator/controller';
import * as _ from 'lodash';
import { CoolEventManager } from '../event';
import * as moment from 'moment';
/**
*
*/
@ -32,6 +32,9 @@ export abstract class BaseSqliteService {
@Inject()
coolEventManager: CoolEventManager;
@Config(ALL)
allConfig: any;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
@ -324,6 +327,27 @@ export abstract class BaseSqliteService {
}
}
/**
*
* @param timezone '+08:00', '-07:00'
* @returns Date
*/
getDateWithTimezone(): Date {
const timezone =
this.allConfig.typeorm?.dataSource?.default?.timezone || '+08:00';
const match = timezone.match(/^([+-])(\d{2}):(\d{2})$/);
if (!match) {
throw new CoolValidateException('时区格式错误,应为 "+08:00" 这样的格式');
}
const [_, sign, hours, minutes] = match;
const offsetInMinutes =
(parseInt(hours) * 60 + parseInt(minutes)) * (sign === '+' ? 1 : -1);
const date = new Date();
const utc = date.getTime() + date.getTimezoneOffset() * 60000;
return new Date(utc + offsetInMinutes * 60000);
}
/**
* |
* @param param
@ -334,6 +358,7 @@ export abstract class BaseSqliteService {
// 判断是否是批量操作
if (param instanceof Array) {
param.forEach(item => {
// 设置时区+08:00
item.updateTime = new Date();
item.createTime = new Date();
});
@ -354,8 +379,8 @@ export abstract class BaseSqliteService {
: await this.entity.save(param);
}
if (type == 'add') {
param.createTime = new Date();
param.updateTime = new Date();
param.createTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
param.updateTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
upsert == 'normal'
? await this.entity.insert(param)
: await this.entity.save(param);

11
rpc/.editorconfig Normal file
View File

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

22
rpc/.eslintrc.json Normal file
View File

@ -0,0 +1,22 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"env": {
"jest": true
},
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"node/no-extraneous-import": "off",
"@typescript-eslint/no-this-alias": "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"
}
}

4
rpc/.gitattributes vendored Normal file
View File

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

13
rpc/.gitignore vendored Normal file
View File

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

3
rpc/.prettierrc.js Normal file
View File

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

10
rpc/index.d.ts vendored Normal file
View File

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

7
rpc/jest.config.js Normal file
View File

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

1
rpc/jest.setup.js Normal file
View File

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

58
rpc/package.json Normal file
View File

@ -0,0 +1,58 @@
{
"name": "@cool-midway/rpc",
"version": "8.0.0",
"description": "cool-admin midway rpc",
"main": "dist/index.js",
"typings": "index.d.ts",
"scripts": {
"build": "mwtsc --cleanOutDir",
"test": "cross-env NODE_ENV=unittest jest",
"cov": "jest --coverage",
"lint": "mwts check",
"lint:fix": "mwts fix"
},
"keywords": [
"cool",
"cool-admin",
"cooljs"
],
"author": "COOL",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"index.d.ts"
],
"readme": "README.md",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://cool-js.com"
},
"devDependencies": {
"@cool-midway/core": "file:/Users/ap/Documents/src/admin/midway-packages/core",
"@midwayjs/cli": "^2.1.1",
"@midwayjs/core": "^3.20.0",
"@midwayjs/koa": "^3.20.0",
"@midwayjs/logger": "^3.4.2",
"@midwayjs/mock": "^3.20.0",
"@midwayjs/redis": "^3.20.0",
"@midwayjs/typeorm": "^3.20.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.7",
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"mwts": "^1.3.0",
"mwtsc": "^1.15.1",
"sqlstring": "^2.3.3",
"ts-jest": "^29.2.5",
"typeorm": "^0.3.20",
"typescript": "^5.7.3",
"uuid": "^11.0.5"
},
"dependencies": {
"ioredis": "^5.4.2",
"moleculer": "^0.14.35"
}
}

8714
rpc/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

27
rpc/src/README.md Normal file
View File

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

View File

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

26
rpc/src/configuration.ts Normal file
View File

@ -0,0 +1,26 @@
import { Configuration } from '@midwayjs/core';
import * as DefaultConfig from './config/config.default';
import { IMidwayContainer } from '@midwayjs/core';
import { CoolRpc } from './rpc';
import { CoolRpcDecorator } from './decorator';
@Configuration({
namespace: 'cool:rpc',
importConfigs: [
{
default: DefaultConfig,
},
],
})
export class CoolRpcConfiguration {
async onReady(container: IMidwayContainer) {
global['moleculer.transactions'] = {};
(await container.getAsync(CoolRpc)).init();
// 装饰器
await container.getAsync(CoolRpcDecorator);
}
async onStop(container: IMidwayContainer): Promise<void> {
(await container.getAsync(CoolRpc)).stop();
}
}

View File

@ -0,0 +1,19 @@
import {
Scope,
ScopeEnum,
saveClassMetadata,
saveModule,
} from '@midwayjs/core';
export const COOL_RPC_EVENT_KEY = 'decorator:cool:rpc:event';
export function CoolRpcEvent(): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(COOL_RPC_EVENT_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(COOL_RPC_EVENT_KEY, {}, target);
// 指定 IoC 容器创建实例的作用域
Scope(ScopeEnum.Singleton)(target);
};
}

View File

@ -0,0 +1,23 @@
import { attachClassMetadata } from '@midwayjs/core';
export const COOL_RPC_EVENT_HANDLER_KEY = 'decorator:cool:rpc:event:handler';
/**
*
* @param eventName
* @returns
*/
export function CoolRpcEventHandler(eventName?: string): MethodDecorator {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
attachClassMetadata(
COOL_RPC_EVENT_HANDLER_KEY,
{
propertyKey,
descriptor,
eventName,
},
target
);
};
}

101
rpc/src/decorator/index.ts Normal file
View File

@ -0,0 +1,101 @@
import { CoolCommException } from '@cool-midway/core';
import {
Provide,
Scope,
ScopeEnum,
JoinPoint,
Init,
MidwayDecoratorService,
Inject,
} from '@midwayjs/core';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { COOL_RPC_TRANSACTION, TransactionOptions } from './transaction';
import { v1 as uuid } from 'uuid';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolRpcDecorator {
@Inject()
decoratorService: MidwayDecoratorService;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Init()
async init() {
// 事务
await this.transaction();
}
/**
*
*/
async transaction() {
this.decoratorService.registerMethodHandler(
COOL_RPC_TRANSACTION,
options => {
return {
around: async (joinPoint: JoinPoint) => {
const option: TransactionOptions = options.metadata;
let isCaller = false;
let rpcTransactionId;
if (joinPoint.args[0]) {
isCaller = false;
rpcTransactionId = joinPoint.args[0].rpcTransactionId;
}
// 如果没有事务ID手动创建
if (!rpcTransactionId) {
isCaller = true;
rpcTransactionId = uuid();
}
let data;
const dataSource = this.typeORMDataSourceManager.getDataSource(
option?.connectionName || 'default'
);
const queryRunner = dataSource.createQueryRunner();
// 使用我们的新queryRunner建立真正的数据库连
await queryRunner.connect();
if (option && option.isolation) {
await queryRunner.startTransaction(option.isolation);
} else {
await queryRunner.startTransaction();
}
try {
global['moleculer.transactions'][rpcTransactionId] = queryRunner;
// 半小时后清除
setTimeout(() => {
global['moleculer.transactions'][rpcTransactionId].release();
delete global['moleculer.transactions'][rpcTransactionId];
}, 1800 * 1000);
joinPoint.args.push(rpcTransactionId);
joinPoint.args.push(queryRunner);
data = await joinPoint.proceed(...joinPoint.args);
if (isCaller) {
global['moleculer:broker'].broadcast('moleculer.transaction', {
rpcTransactionId,
commit: true,
});
}
//await queryRunner.commitTransaction();
} catch (error) {
//await queryRunner.rollbackTransaction();
if (isCaller) {
global['moleculer:broker'].broadcast('moleculer.transaction', {
rpcTransactionId,
commit: false,
});
}
throw new CoolCommException(error.message);
}
return data;
},
};
}
);
}
}

84
rpc/src/decorator/rpc.ts Normal file
View File

@ -0,0 +1,84 @@
import {
Scope,
ScopeEnum,
saveClassMetadata,
saveModule,
} from '@midwayjs/core';
export const MOLECYLER_KEY = 'decorator:cool:rpc';
export type MethodTypes =
| 'add'
| 'delete'
| 'update'
| 'page'
| 'info'
| 'list';
// 字段匹配
export interface FieldEq {
// 字段
column: string;
// 请求参数
requestParam: string;
}
// 关联查询
export interface LeftJoinOp {
// 实体
entity: any;
// 别名
alias: string;
// 关联条件
condition: string;
}
// Crud配置
export interface CurdOption {
// 路由前缀不配置默认是按Controller下的文件夹路径
prefix?: string;
// curd api接口
method: MethodTypes[];
// 分页查询配置
pageQueryOp?: QueryOp;
// 非分页查询配置
listQueryOp?: QueryOp;
// 插入参数
insertParam?: Function;
// info 忽略返回属性
infoIgnoreProperty?: string[];
// 实体
entity: { entityKey?: any; connectionName?: string } | any;
}
// 查询配置
export interface QueryOp {
// 需要模糊查询的字段
keyWordLikeFields?: string[];
// 查询条件
where?: Function;
// 查询字段
select?: string[];
// 字段相等
fieldEq?: string[] | FieldEq[];
// 添加排序条件
addOrderBy?: {};
// 关联配置
leftJoin?: LeftJoinOp[];
}
/**
* moleculer
* @param option
* @returns
*/
export function CoolRpcService(option?: CurdOption): ClassDecorator {
return (target: any) => {
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(MOLECYLER_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(MOLECYLER_KEY, option, target);
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Request)(target);
};
}

View File

@ -0,0 +1,22 @@
import * as _ from 'lodash';
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_RPC_TRANSACTION = 'decorator:cool_rpc_transaction';
export function CoolRpcTransaction(
option?: TransactionOptions
): MethodDecorator {
return createCustomMethodDecorator(COOL_RPC_TRANSACTION, option);
}

28
rpc/src/index.ts Normal file
View File

@ -0,0 +1,28 @@
export { CoolRpcConfiguration as Configuration } from './configuration';
export * from './test';
export * from './rpc';
export * from './decorator/rpc';
export * from './decorator/event/event';
export * from './decorator/event/handler';
export * from './service/base';
export * from './service/mysql';
export * from './service/postgres';
export * from './service/sqlite';
export * from './decorator/transaction';
export * from './transaction/event';
export * from './decorator/index';
export interface CoolRpcConfig {
// 服务名称
name: string;
// redis
redis: RedisConfig & RedisConfig[] & unknown;
}
export interface RedisConfig {
host: string;
password: string;
port: number;
db: number;
}

275
rpc/src/rpc.ts Normal file
View File

@ -0,0 +1,275 @@
import { ILogger, IMidwayApplication, Inject } from '@midwayjs/core';
import {
App,
Config,
getClassMetadata,
listModule,
Logger,
Provide,
Scope,
ScopeEnum,
} from '@midwayjs/core';
import { ServiceBroker } from 'moleculer';
import { CoolRpcConfig } from '.';
import { CoolCoreException, CoolValidateException } from '@cool-midway/core';
import { v1 as uuid } from 'uuid';
import { BaseRpcService } from './service/base';
import { CurdOption, MOLECYLER_KEY } from './decorator/rpc';
import { COOL_RPC_EVENT_KEY } from './decorator/event/event';
import { COOL_RPC_EVENT_HANDLER_KEY } from './decorator/event/handler';
import * as _ from 'lodash';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { camelCase } from '@midwayjs/core/dist/util/camelCase';
// import { AgentService } from '@moleculer/lab';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CoolRpc {
broker: ServiceBroker;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Logger()
coreLogger: ILogger;
@Config('cool.rpc')
rpcConfig: CoolRpcConfig;
@Config('cool')
coolConfig;
@App()
app: IMidwayApplication;
cruds;
async init() {
if (!this.rpcConfig?.name) {
throw new CoolCoreException(
'cool.rpc.name config is require and every service name must be unique'
);
}
let redisConfig;
if (!this.rpcConfig?.redis && !this.coolConfig?.redis) {
throw new CoolCoreException('cool.rpc.redis or cool.redis is require');
}
redisConfig = this.rpcConfig?.redis
? this.rpcConfig?.redis
: this.coolConfig?.redis;
const transporter = {
type: 'Redis',
options: {},
};
if (redisConfig instanceof Array) {
transporter.options = {
cluster: {
nodes: redisConfig,
},
};
} else {
transporter.options = redisConfig;
}
this.broker = new ServiceBroker({
nodeID: `${this.rpcConfig.name}-${uuid()}`,
transporter,
// metrics: {
// enabled: true,
// reporter: 'Laboratory',
// },
// tracing: {
// enabled: true,
// exporter: 'Laboratory',
// },
...this.rpcConfig,
});
// this.broker.createService({
// name: this.rpcConfig.name,
// mixins: [],
// // settings: {
// // name: 'test',
// // port: 3210,
// // token: '123123',
// // apiKey: '92C18ZR-ERM45EG-HT8GQGQ-4MHCXAT',
// // },
// });
global['moleculer:broker'] = this.broker;
await this.initService();
await this.createService();
}
/**
*
* @returns
*/
async getEvents() {
const allEvents = {};
const modules = listModule(COOL_RPC_EVENT_KEY);
for (const module of modules) {
const moduleInstance = await this.app
.getApplicationContext()
.getAsync(module);
moduleInstance['broker'] = this.broker;
const events = getClassMetadata(COOL_RPC_EVENT_HANDLER_KEY, module);
for (const event of events) {
allEvents[event.eventName ? event.eventName : event.propertyKey] = {
handler(ctx) {
moduleInstance[event.propertyKey](ctx.params);
},
};
}
}
return allEvents;
}
/**
*
*/
async createService() {
const _this = this;
this.broker.createService({
name: this.rpcConfig.name,
events: await this.getEvents(),
actions: {
async call(ctx) {
const { service, method, params } = ctx.params;
const targetName = _.upperFirst(service);
const target = _.find(_this.cruds, { name: targetName });
if (!target) {
throw new CoolValidateException('找不到服务');
}
const curdOption: CurdOption = getClassMetadata(
MOLECYLER_KEY,
target
);
const cls = await _this.app
.getApplicationContext()
.getAsync(camelCase(service));
const serviceInstance: BaseRpcService = new target();
Object.assign(serviceInstance, cls);
serviceInstance.setModel(_this.getModel(curdOption));
serviceInstance.setApp(_this.app);
serviceInstance.init();
// 如果是通用crud方法 注入参数
if (
['add', 'delete', 'update', 'page', 'info', 'list'].includes(method)
) {
if (!curdOption.method.includes(method)) {
throw new CoolValidateException('方法不存在');
}
}
return serviceInstance[method](params);
},
},
});
this.broker.start();
}
/**
* serviceentity
*/
async initService() {
// 获得所有的service
this.cruds = listModule(MOLECYLER_KEY);
for (const crud of this.cruds) {
const curdOption: CurdOption = getClassMetadata(MOLECYLER_KEY, crud);
const serviceInstance: BaseRpcService = await this.app
.getApplicationContext()
.getAsync(crud);
serviceInstance.setModel(this.getModel(curdOption));
serviceInstance.setCurdOption(curdOption);
}
}
/**
* Model
* @param curdOption
*/
getModel(curdOption) {
// 获得到model
let entityModel;
const { entity } = curdOption || {};
if (entity) {
const dataSourceName =
this.typeORMDataSourceManager.getDataSourceNameByModel(entity);
entityModel = this.typeORMDataSourceManager
.getDataSource(dataSourceName)
.getRepository(entity);
}
return entityModel;
}
/**
*
* @param name
* @param controller
* @param method
* @param params
* @returns
*/
async call(name: string, service: string, method: string, params?: {}) {
return this.broker.call(`${name}.call`, { service, method, params });
}
/**
*
* @param name
* @param params
* @param node
*/
async event(name: string, params: any, node?: string | string[]) {
this.broker.emit(name, params);
}
/**
* 广
* @param name
* @param params
* @param node
*/
async broadcastEvent(name: string, params: any, node?: string | string[]) {
this.broker.broadcast(name, params);
}
/**
* 广
* @param name
* @param params
* @param node
*/
async broadcastLocalEvent(
name: string,
params: any,
node?: string | string[]
) {
this.broker.broadcastLocal(name, params);
}
/**
* broker对象
* @returns
*/
getBroker() {
return this.broker;
}
/**
*
*/
stop() {
this.broker.stop();
}
}

300
rpc/src/service/base.ts Normal file
View File

@ -0,0 +1,300 @@
import { Config, Init, Inject, Provide } from '@midwayjs/core';
import { Scope, ScopeEnum } from '@midwayjs/core';
import { BaseMysqlService } from './mysql';
import { BasePgService } from './postgres';
import {
CoolValidateException,
ERRINFO,
QueryOp,
CoolEventManager,
CoolCoreException,
} from '@cool-midway/core';
import { Application, Context } from '@midwayjs/koa';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import * as _ from 'lodash';
import { BaseSqliteService } from './sqlite';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseRpcService {
// mysql的基类
@Inject()
baseMysqlService: BaseMysqlService;
// postgres的基类
@Inject()
basePgService: BasePgService;
// sqlite的基类
@Inject()
baseSqliteService: BaseSqliteService;
// 数据库类型
@Config('typeorm.dataSource.default.type')
ormType;
// 当前服务名称
service: BaseMysqlService | BasePgService | BaseSqliteService;
// 模型
protected entity: Repository<any>;
protected sqlParams;
protected curdOption;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
@Inject('ctx')
baseCtx: Context;
baseApp: Application;
// 设置模型
setModel(entity: any) {
this.entity = entity;
}
setCurdOption(curdOption) {
this.curdOption = curdOption;
}
@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) {
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> {}
}

500
rpc/src/service/mysql.ts Normal file
View File

@ -0,0 +1,500 @@
import { Init, Provide, Inject, Config } from '@midwayjs/core';
import { Scope, ScopeEnum } from '@midwayjs/core';
import {
CoolValidateException,
ERRINFO,
EVENT,
QueryOp,
CoolEventManager,
} from '@cool-midway/core';
import { Application, Context } from '@midwayjs/koa';
import * as SqlString from 'sqlstring';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { Brackets, Equal, In, Repository, SelectQueryBuilder } from 'typeorm';
import * as _ from 'lodash';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseMysqlService {
// 分页配置
@Config('cool')
private _coolConfig;
// 模型
entity: Repository<any>;
sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
}
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.getRawMany(),
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(' ');
}
}

640
rpc/src/service/postgres.ts Normal file
View File

@ -0,0 +1,640 @@
import { Init, Provide, Inject, Config } from '@midwayjs/core';
import {
CoolValidateException,
ERRINFO,
EVENT,
QueryOp,
CoolEventManager,
} from '@cool-midway/core';
import { Application, Context } from '@midwayjs/koa';
import { Scope, ScopeEnum } from '@midwayjs/core';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm';
import * as _ from 'lodash';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BasePgService {
// 分页配置
@Config('cool')
private _coolConfig;
// 模型
entity: Repository<any>;
sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
}
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.getRawMany(),
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';
}
}

558
rpc/src/service/sqlite.ts Normal file
View File

@ -0,0 +1,558 @@
import { Init, Provide, Inject, Config, ALL } from '@midwayjs/core';
import { Scope, ScopeEnum } from '@midwayjs/core';
import {
CoolValidateException,
ERRINFO,
EVENT,
QueryOp,
CoolEventManager,
} from '@cool-midway/core';
import { Application, Context } from '@midwayjs/koa';
import * as SqlString from 'sqlstring';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm';
import * as _ from 'lodash';
import * as moment from 'moment';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseSqliteService {
// 分页配置
@Config('cool')
private _coolConfig;
// 模型
entity: Repository<any>;
sqlParams;
@Inject()
typeORMDataSourceManager: TypeORMDataSourceManager;
@Inject()
coolEventManager: CoolEventManager;
@Config(ALL)
allConfig: any;
// 设置模型
setEntity(entity: any) {
this.entity = entity;
}
// 设置请求上下文
setCtx(ctx: Context) {
this.baseCtx = ctx;
}
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.getRawMany(),
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 timezone '+08:00', '-07:00'
* @returns Date
*/
getDateWithTimezone(): Date {
const timezone =
this.allConfig.typeorm?.dataSource?.default?.timezone || '+08:00';
const match = timezone.match(/^([+-])(\d{2}):(\d{2})$/);
if (!match) {
throw new CoolValidateException('时区格式错误,应为 "+08:00" 这样的格式');
}
const [_, sign, hours, minutes] = match;
const offsetInMinutes =
(parseInt(hours) * 60 + parseInt(minutes)) * (sign === '+' ? 1 : -1);
const date = new Date();
const utc = date.getTime() + date.getTimezoneOffset() * 60000;
return new Date(utc + offsetInMinutes * 60000);
}
/**
* |
* @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 => {
// 设置时区+08:00
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 = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
param.updateTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
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(' ');
}
}

25
rpc/src/test.ts Normal file
View File

@ -0,0 +1,25 @@
import { Controller, Inject, Post, Provide } from '@midwayjs/core';
import { BaseController } from '@cool-midway/core';
import { CoolRpc } from './rpc';
/**
*
*/
@Provide()
@Controller('/rpc')
export class RpcTestController extends BaseController {
@Inject()
rpc: CoolRpc;
@Inject()
ctx;
/**
*
*/
@Post('/test')
async test() {
const { name, service, method, params } = this.ctx.request.body;
return this.rpc.call(name, service, method, params);
}
}

View File

@ -0,0 +1,40 @@
import { Logger, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { CoolRpcEvent, CoolRpcEventHandler } from '..';
import { ILogger } from '@midwayjs/logger';
/**
* moleculer
*/
@Provide()
@Scope(ScopeEnum.Singleton)
@CoolRpcEvent()
export class MoleculerTransactionHandler {
@Logger()
coreLogger: ILogger;
/**
*
* @param params
*/
@CoolRpcEventHandler('moleculer.transaction') // 唯一参数eventName事件名可不填默认为方法名
async handler(params) {
const { rpcTransactionId, commit } = params;
this.coreLogger.info(
`\x1B[36m [cool:core] MoleculerTransaction event params: ${JSON.stringify(
params
)} \x1B[0m`
);
if (global['moleculer.transactions'][rpcTransactionId]) {
this.coreLogger.info(
`\x1B[36m [cool:core] MoleculerTransaction event ${
commit ? 'commitTransaction' : 'rollbackTransaction'
} ID: ${rpcTransactionId} \x1B[0m`
);
await global['moleculer.transactions'][rpcTransactionId][
commit ? 'commitTransaction' : 'rollbackTransaction'
]();
await global['moleculer.transactions'][rpcTransactionId].release();
delete global['moleculer.transactions'][rpcTransactionId];
}
}
}

28
rpc/tsconfig.json Normal file
View File

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

View File

@ -4,9 +4,6 @@
"description": "cool-admin midway task",
"main": "dist/index.js",
"typings": "index.d.ts",
"bin": {
"cool": "dist/bin/index.js"
},
"scripts": {
"build": "mwtsc --cleanOutDir",
"test": "cross-env NODE_ENV=unittest jest",