mirror of
https://github.com/cool-team-official/cool-admin-midway-packages.git
synced 2025-12-11 13:52:50 +00:00
兼容mysql、postgresql、sqlite
This commit is contained in:
parent
fcf34dc35e
commit
01c8577507
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cool-midway/core",
|
||||
"version": "7.0.8",
|
||||
"version": "7.1.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"typings": "index.d.ts",
|
||||
@ -41,7 +41,7 @@
|
||||
"jest": "^29.3.1",
|
||||
"mwts": "^1.3.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typeorm": "^0.3.11",
|
||||
"typeorm": "^0.3.19",
|
||||
"typescript": "~4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -13,7 +13,6 @@ export abstract class BaseEntity extends CoolBaseEntity {
|
||||
// 默认自增
|
||||
@PrimaryGeneratedColumn("increment", {
|
||||
comment: "ID",
|
||||
// type: "bigint",
|
||||
})
|
||||
id: number;
|
||||
|
||||
|
||||
@ -14,6 +14,9 @@ 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";
|
||||
|
||||
@ -9,8 +9,7 @@ import {
|
||||
Scope,
|
||||
ScopeEnum,
|
||||
} from "@midwayjs/decorator";
|
||||
import { CoolCoreException } from "../exception/core";
|
||||
import * as Importer from "mysql2-import";
|
||||
// import * as Importer from "mysql2-import";
|
||||
import * as fs from "fs";
|
||||
import { CoolModuleConfig } from "./config";
|
||||
import * as path from "path";
|
||||
@ -18,6 +17,7 @@ import { InjectDataSource, TypeORMDataSourceManager } from "@midwayjs/typeorm";
|
||||
import { DataSource } from "typeorm";
|
||||
import { CoolEventManager } from "../event";
|
||||
import { CoolModuleMenu } from "./menu";
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* 模块sql
|
||||
@ -56,21 +56,21 @@ export class CoolModuleImport {
|
||||
async init() {
|
||||
// 是否需要导入
|
||||
if (this.coolConfig.initDB) {
|
||||
await this.checkDbVersion();
|
||||
const modules = this.coolModuleConfig.modules;
|
||||
const importLockPath = path.join(
|
||||
`${this.app.getBaseDir()}`,
|
||||
"..",
|
||||
"lock"
|
||||
"lock",
|
||||
'db'
|
||||
);
|
||||
if (!fs.existsSync(importLockPath)) {
|
||||
fs.mkdirSync(importLockPath);
|
||||
fs.mkdirSync(importLockPath, { recursive: true });
|
||||
}
|
||||
setTimeout(async () => {
|
||||
for (const module of modules) {
|
||||
const lockPath = path.join(importLockPath, module + ".sql.lock");
|
||||
const lockPath = path.join(importLockPath, module + ".db.lock");
|
||||
if (!fs.existsSync(lockPath)) {
|
||||
await this.initDataBase(module, lockPath);
|
||||
await this.initDataBase(module, lockPath);
|
||||
}
|
||||
}
|
||||
this.coolEventManager.emit("onDBInit", {});
|
||||
@ -85,71 +85,115 @@ export class CoolModuleImport {
|
||||
* @param lockPath 锁定导入
|
||||
*/
|
||||
async initDataBase(module: string, lockPath: string) {
|
||||
// 计算耗时
|
||||
const startTime = new Date().getTime();
|
||||
// 模块路径
|
||||
const modulePath = `${this.app.getBaseDir()}/modules/${module}`;
|
||||
// sql 路径
|
||||
const sqlPath = `${modulePath}/init.sql`;
|
||||
// 延迟2秒再导入数据库
|
||||
if (fs.existsSync(sqlPath)) {
|
||||
let second = 0;
|
||||
const t = setInterval(() => {
|
||||
this.coreLogger.info(
|
||||
"\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
module +
|
||||
" database... \x1B[0m"
|
||||
);
|
||||
second++;
|
||||
}, 1000);
|
||||
const { host, username, password, database, charset, port } = this
|
||||
.ormConfig?.default
|
||||
? this.ormConfig.default
|
||||
: this.ormConfig;
|
||||
const importer = new Importer({
|
||||
host,
|
||||
password,
|
||||
database,
|
||||
charset,
|
||||
port,
|
||||
user: username,
|
||||
});
|
||||
await importer
|
||||
.import(sqlPath)
|
||||
.then(async () => {
|
||||
clearInterval(t);
|
||||
this.coreLogger.info(
|
||||
"\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
module +
|
||||
" database complete \x1B[0m"
|
||||
);
|
||||
fs.writeFileSync(lockPath, `time consuming:${second}s`);
|
||||
})
|
||||
.catch((err) => {
|
||||
clearTimeout(t);
|
||||
// 数据路径
|
||||
const dataPath = `${modulePath}/db.json`;
|
||||
// 判断文件是否存在
|
||||
if(fs.existsSync(dataPath)) {
|
||||
// 获得所有的实体
|
||||
const entityMetadatas = this.defaultDataSource.entityMetadatas;
|
||||
const metadatas = _.mapValues(_.keyBy(entityMetadatas, 'tableName'), 'target');
|
||||
// 读取数据
|
||||
const data = JSON.parse(fs.readFileSync(dataPath).toString() || '{}');
|
||||
// 导入数据
|
||||
for(const key in data) {
|
||||
try{
|
||||
const repository = this.defaultDataSource.getRepository(metadatas[key]);
|
||||
if(this.ormConfig.default.type == 'postgres') {
|
||||
for(const item of data[key]) {
|
||||
const result: any = await repository.save(repository.create(item));
|
||||
if(item.id) {
|
||||
await repository.update(result.id, { id: item.id });
|
||||
// 更新pgsql序列
|
||||
await this.defaultDataSource.query(`SELECT setval('${key}_id_seq', (SELECT MAX(id) FROM ${key}));`);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
await repository.insert(data[key]);
|
||||
}
|
||||
}catch(e){
|
||||
this.coreLogger.error(
|
||||
"\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
module +
|
||||
" database err please manual import \x1B[0m"
|
||||
" database err \x1B[0m"
|
||||
);
|
||||
fs.writeFileSync(lockPath, `time consuming:${second}s`);
|
||||
this.coreLogger.error(err);
|
||||
this.coreLogger.error(
|
||||
`自动初始化模块[${module}]数据库失败,尝试手动导入数据库`
|
||||
);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const endTime = new Date().getTime();
|
||||
fs.writeFileSync(lockPath, `time consuming:${endTime-startTime}ms`);
|
||||
this.coreLogger.info(
|
||||
"\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
module +
|
||||
" database complete \x1B[0m"
|
||||
);
|
||||
}
|
||||
// // sql 路径
|
||||
// const sqlPath = `${modulePath}/init.sql`;
|
||||
// // 延迟2秒再导入数据库
|
||||
// if (fs.existsSync(sqlPath)) {
|
||||
// let second = 0;
|
||||
// const t = setInterval(() => {
|
||||
// this.coreLogger.info(
|
||||
// "\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
// module +
|
||||
// " database... \x1B[0m"
|
||||
// );
|
||||
// second++;
|
||||
// }, 1000);
|
||||
// const { host, username, password, database, charset, port } = this
|
||||
// .ormConfig?.default
|
||||
// ? this.ormConfig.default
|
||||
// : this.ormConfig;
|
||||
// const importer = new Importer({
|
||||
// host,
|
||||
// password,
|
||||
// database,
|
||||
// charset,
|
||||
// port,
|
||||
// user: username,
|
||||
// });
|
||||
// await importer
|
||||
// .import(sqlPath)
|
||||
// .then(async () => {
|
||||
// clearInterval(t);
|
||||
// this.coreLogger.info(
|
||||
// "\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
// module +
|
||||
// " database complete \x1B[0m"
|
||||
// );
|
||||
// fs.writeFileSync(lockPath, `time consuming:${second}s`);
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// clearTimeout(t);
|
||||
// this.coreLogger.error(
|
||||
// "\x1B[36m [cool:core] midwayjs cool core init " +
|
||||
// module +
|
||||
// " database err please manual import \x1B[0m"
|
||||
// );
|
||||
// fs.writeFileSync(lockPath, `time consuming:${second}s`);
|
||||
// this.coreLogger.error(err);
|
||||
// this.coreLogger.error(
|
||||
// `自动初始化模块[${module}]数据库失败,尝试手动导入数据库`
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据库版本
|
||||
*/
|
||||
async checkDbVersion() {
|
||||
const versions = (
|
||||
await this.defaultDataSource.query("SELECT VERSION() AS version")
|
||||
)[0].version.split(".");
|
||||
if ((versions[0] == 5 && versions[1] < 7) || versions[0] < 5) {
|
||||
throw new CoolCoreException(
|
||||
"数据库不满足要求:mysql>=5.7,请升级数据库版本"
|
||||
);
|
||||
}
|
||||
}
|
||||
// async checkDbVersion() {
|
||||
// const versions = (
|
||||
// await this.defaultDataSource.query("SELECT VERSION() AS version")
|
||||
// )[0].version.split(".");
|
||||
// if ((versions[0] == 5 && versions[1] < 7) || versions[0] < 5) {
|
||||
// throw new CoolCoreException(
|
||||
// "数据库不满足要求:mysql>=5.7,请升级数据库版本"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@ export class CoolModuleMenu {
|
||||
@Inject()
|
||||
coolEventManager: CoolEventManager;
|
||||
|
||||
datas = {};
|
||||
|
||||
async init() {
|
||||
// 是否需要导入
|
||||
if (this.coolConfig.initMenu) {
|
||||
@ -34,17 +36,19 @@ export class CoolModuleMenu {
|
||||
const importLockPath = path.join(
|
||||
`${this.app.getBaseDir()}`,
|
||||
"..",
|
||||
"lock"
|
||||
"lock",
|
||||
'menu'
|
||||
);
|
||||
if (!fs.existsSync(importLockPath)) {
|
||||
fs.mkdirSync(importLockPath);
|
||||
fs.mkdirSync(importLockPath, { recursive: true });
|
||||
}
|
||||
for (const module of modules) {
|
||||
const lockPath = path.join(importLockPath, module + ".menu.lock");
|
||||
if (!fs.existsSync(lockPath)) {
|
||||
await this.importMenu(module, lockPath);
|
||||
await this.importMenu(module, lockPath);
|
||||
}
|
||||
}
|
||||
this.coolEventManager.emit("onMenuImport", this.datas);
|
||||
this.coolEventManager.emit("onMenuInit", {});
|
||||
}
|
||||
}
|
||||
@ -63,7 +67,8 @@ export class CoolModuleMenu {
|
||||
if (fs.existsSync(menuPath)) {
|
||||
const data = fs.readFileSync(menuPath);
|
||||
try {
|
||||
this.coolEventManager.emit("onMenuImport", module, JSON.parse(data.toString()));
|
||||
// this.coolEventManager.emit("onMenuImport", module, JSON.parse(data.toString()));
|
||||
this.datas[module] = JSON.parse(data.toString());
|
||||
fs.writeFileSync(lockPath, data);
|
||||
} catch (error) {
|
||||
this.coreLogger.error(error);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cool-midway/core",
|
||||
"version": "7.0.8",
|
||||
"version": "7.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"typings": "index.d.ts",
|
||||
@ -40,7 +40,7 @@
|
||||
"jest": "^29.3.1",
|
||||
"mwts": "^1.3.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typeorm": "^0.3.11",
|
||||
"typeorm": "^0.3.19",
|
||||
"typescript": "~4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -1,23 +1,41 @@
|
||||
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
|
||||
import { App, Config, Init, Inject, Provide } from "@midwayjs/decorator";
|
||||
import { Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseMysqlService } from "./mysql";
|
||||
import { BasePgService } from "./postgres";
|
||||
import { CoolValidateException } from "../exception/validate";
|
||||
import { ERRINFO, EVENT } from "../constant/global";
|
||||
import { ERRINFO } 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 { 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 {
|
||||
// 分页配置
|
||||
@Config("cool")
|
||||
private _coolConfig: CoolConfig;
|
||||
// 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>;
|
||||
@ -30,31 +48,42 @@ export abstract class BaseService {
|
||||
@Inject()
|
||||
coolEventManager: CoolEventManager;
|
||||
|
||||
@Inject("ctx")
|
||||
baseCtx: Context;
|
||||
|
||||
@App()
|
||||
baseApp: Application;
|
||||
|
||||
@Init()
|
||||
async init(){
|
||||
const services = {
|
||||
mysql: 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);
|
||||
}
|
||||
|
||||
@App()
|
||||
baseApp: Application;
|
||||
|
||||
|
||||
// 设置应用对象
|
||||
setApp(app: Application) {
|
||||
this.baseApp = app;
|
||||
}
|
||||
|
||||
@Inject("ctx")
|
||||
baseCtx: Context;
|
||||
|
||||
// 初始化
|
||||
@Init()
|
||||
init() {
|
||||
this.sqlParams = [];
|
||||
this.service.setApp(app);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,12 +93,7 @@ export abstract class BaseService {
|
||||
* @param params 参数
|
||||
*/
|
||||
setSql(condition, sql, params) {
|
||||
let rSql = false;
|
||||
if (condition || (condition === 0 && condition !== "")) {
|
||||
rSql = true;
|
||||
this.sqlParams = this.sqlParams.concat(params);
|
||||
}
|
||||
return rSql ? sql : "";
|
||||
return this.service.setSql(condition, sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,29 +101,15 @@ export abstract class BaseService {
|
||||
* @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`;
|
||||
return this.service.getCountSql(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 参数安全性检查
|
||||
* @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
|
||||
);
|
||||
async paramSafetyCheck(params) {
|
||||
return await this.service.paramSafetyCheck(params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,16 +119,7 @@ export abstract class BaseService {
|
||||
* @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 || []);
|
||||
return await this.service.nativeQuery(sql, params, connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +127,7 @@ export abstract class BaseService {
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
getOrmManager(connectionName = "default") {
|
||||
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||
return this.service.getOrmManager(connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,32 +142,7 @@ export abstract class BaseService {
|
||||
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,
|
||||
},
|
||||
};
|
||||
return await this.service.entityRenderPage(find, query, autoSort);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,58 +153,7 @@ export abstract class BaseService {
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
if (order && sort && autoSort) {
|
||||
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||
throw new CoolValidateException("非法传参~");
|
||||
}
|
||||
sql += ` ORDER BY ${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
|
||||
}
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
this.sqlParams.push(parseInt(maxExportLimit));
|
||||
sql += " LIMIT ? ";
|
||||
}
|
||||
if (!isExport) {
|
||||
this.sqlParams.push((page - 1) * size);
|
||||
this.sqlParams.push(parseInt(size));
|
||||
sql += " LIMIT ?,? ";
|
||||
}
|
||||
|
||||
let params = [];
|
||||
params = params.concat(this.sqlParams);
|
||||
const result = await this.nativeQuery(sql, params, connectionName);
|
||||
const countResult = await this.nativeQuery(
|
||||
this.getCountSql(sql),
|
||||
params,
|
||||
connectionName
|
||||
);
|
||||
return {
|
||||
list: result,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查排序
|
||||
* @param sort 排序
|
||||
* @returns
|
||||
*/
|
||||
private checkSort(sort) {
|
||||
if (!["desc", "asc"].includes(sort.toLowerCase())) {
|
||||
throw new CoolValidateException("sort 非法传参~");
|
||||
}
|
||||
return sort;
|
||||
return await this.service.sqlRenderPage(sql, query, autoSort, connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,17 +162,7 @@ export abstract class BaseService {
|
||||
* @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;
|
||||
return await this.service.info(id, infoIgnoreProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,16 +170,8 @@ export abstract class BaseService {
|
||||
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||
*/
|
||||
async delete(ids: any) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
await this.modifyBefore(ids, "delete");
|
||||
if (ids instanceof String) {
|
||||
ids = ids.split(",");
|
||||
}
|
||||
// 启动软删除发送事件
|
||||
if (this._coolConfig.crud?.softDelete) {
|
||||
this.softDelete(ids);
|
||||
}
|
||||
await this.entity.delete(ids);
|
||||
await this.service.delete(ids);
|
||||
await this.modifyAfter(ids, "delete");
|
||||
}
|
||||
|
||||
@ -274,21 +181,7 @@ export abstract class BaseService {
|
||||
* @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);
|
||||
}
|
||||
await this.service.softDelete(ids, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -297,10 +190,9 @@ export abstract class BaseService {
|
||||
*/
|
||||
async update(param: any) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
await this.modifyBefore(param, "update");
|
||||
if (!param.id && !(param instanceof Array))
|
||||
throw new CoolValidateException(ERRINFO.NOID);
|
||||
await this.addOrUpdate(param, 'update');
|
||||
await this.addOrUpdate(param,'update')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,7 +201,6 @@ export abstract class BaseService {
|
||||
*/
|
||||
async add(param: any | any[]): Promise<Object> {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
await this.modifyBefore(param, "add");
|
||||
await this.addOrUpdate(param, 'add');
|
||||
return {
|
||||
id:
|
||||
@ -328,34 +219,8 @@ export abstract class BaseService {
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
await this.modifyBefore(param, type);
|
||||
await this.service.addOrUpdate(param, type);
|
||||
await this.modifyAfter(param, type);
|
||||
}
|
||||
|
||||
@ -366,9 +231,7 @@ export abstract class BaseService {
|
||||
* @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);
|
||||
return await this.service.list(query, option, connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -378,9 +241,7 @@ export abstract class BaseService {
|
||||
* @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);
|
||||
return await this.service.page(query, option, connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -388,146 +249,8 @@ export abstract class BaseService {
|
||||
* @param query 前端查询
|
||||
* @param option
|
||||
*/
|
||||
private async getOptionFind(query, option: QueryOp) {
|
||||
let { order = "createTime", sort = "desc", keyWord = "" } = query;
|
||||
const sqlArr = ["SELECT"];
|
||||
const selects = ["a.*"];
|
||||
const find = this.entity.createQueryBuilder("a");
|
||||
if (option) {
|
||||
if (typeof option == "function") {
|
||||
// @ts-ignore
|
||||
option = await option(this.baseCtx, this.baseApp);
|
||||
}
|
||||
// 判断是否有关联查询,有的话取个别名
|
||||
if (!_.isEmpty(option.join)) {
|
||||
for (const item of option.join) {
|
||||
selects.push(`${item.alias}.*`);
|
||||
find[item.type || "leftJoin"](
|
||||
item.entity,
|
||||
item.alias,
|
||||
item.condition
|
||||
);
|
||||
}
|
||||
}
|
||||
// 默认条件
|
||||
if (option.where) {
|
||||
const wheres =
|
||||
typeof option.where == "function"
|
||||
? await option.where(this.baseCtx, this.baseApp)
|
||||
: option.where;
|
||||
if (!_.isEmpty(wheres)) {
|
||||
for (const item of wheres) {
|
||||
if (
|
||||
item.length == 2 ||
|
||||
(item.length == 3 &&
|
||||
(item[2] || (item[2] === 0 && item[2] != "")))
|
||||
) {
|
||||
for (const key in item[1]) {
|
||||
this.sqlParams.push(item[1][key]);
|
||||
}
|
||||
find.andWhere(item[0], item[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 附加排序
|
||||
if (!_.isEmpty(option.addOrderBy)) {
|
||||
for (const key in option.addOrderBy) {
|
||||
if (order && order == key) {
|
||||
sort = option.addOrderBy[key].toUpperCase();
|
||||
}
|
||||
find.addOrderBy(
|
||||
SqlString.escapeId(key),
|
||||
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||
);
|
||||
}
|
||||
}
|
||||
// 关键字模糊搜索
|
||||
if (keyWord || (keyWord == 0 && keyWord != "")) {
|
||||
keyWord = `%${keyWord}%`;
|
||||
find.andWhere(
|
||||
new Brackets((qb) => {
|
||||
const keyWordLikeFields = option.keyWordLikeFields;
|
||||
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
|
||||
keyWord,
|
||||
});
|
||||
this.sqlParams.push(keyWord);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
// 筛选字段
|
||||
if (!_.isEmpty(option.select)) {
|
||||
sqlArr.push(option.select.join(","));
|
||||
find.select(option.select);
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 字段全匹配
|
||||
if (!_.isEmpty(option.fieldEq)) {
|
||||
for (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 && query[key] == "")) {
|
||||
c[key] = query[key];
|
||||
const eq = query[key] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key} ${eq} (:${key})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key} ${eq} :${key}`, c);
|
||||
}
|
||||
this.sqlParams.push(query[key]);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
query[key.requestParam] ||
|
||||
(query[key.requestParam] == 0 && query[key.requestParam] !== "")
|
||||
) {
|
||||
c[key.column] = query[key.requestParam];
|
||||
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||
}
|
||||
this.sqlParams.push(query[key.requestParam]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 接口请求的排序
|
||||
if (sort && order) {
|
||||
const sorts = sort.toUpperCase().split(",");
|
||||
const orders = order.split(",");
|
||||
if (sorts.length != orders.length) {
|
||||
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||
}
|
||||
for (const i in sorts) {
|
||||
find.addOrderBy(
|
||||
SqlString.escapeId(orders[i]),
|
||||
this.checkSort(sorts[i])
|
||||
);
|
||||
}
|
||||
}
|
||||
if (option?.extend) {
|
||||
await option?.extend(find, this.baseCtx, this.baseApp);
|
||||
}
|
||||
const sqls = find.getSql().split("FROM");
|
||||
sqlArr.push("FROM");
|
||||
// 取sqls的最后一个
|
||||
sqlArr.push(sqls[sqls.length - 1]);
|
||||
return sqlArr.join(" ");
|
||||
async getOptionFind(query, option: QueryOp) {
|
||||
return await this.service.getOptionFind(query, option);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -547,4 +270,5 @@ export abstract class BaseService {
|
||||
data: any,
|
||||
type: "delete" | "update" | "add"
|
||||
): Promise<void> {}
|
||||
|
||||
}
|
||||
|
||||
500
core/src/service/mysql.ts
Normal file
500
core/src/service/mysql.ts
Normal file
@ -0,0 +1,500 @@
|
||||
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
|
||||
import { Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { CoolValidateException } from "../exception/validate";
|
||||
import { ERRINFO, EVENT } from "../constant/global";
|
||||
import { Application, Context } from "@midwayjs/koa";
|
||||
import * as SqlString from "sqlstring";
|
||||
import { CoolConfig } from "../interface";
|
||||
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
|
||||
import { Brackets, In, Repository, SelectQueryBuilder } from "typeorm";
|
||||
import { QueryOp } from "../decorator/controller";
|
||||
import * as _ from "lodash";
|
||||
import { CoolEventManager } from "../event";
|
||||
|
||||
/**
|
||||
* 服务基类
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export abstract class 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 && condition !== "")) {
|
||||
rSql = true;
|
||||
this.sqlParams = this.sqlParams.concat(params);
|
||||
}
|
||||
return rSql ? sql : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得查询个数的SQL
|
||||
* @param sql
|
||||
*/
|
||||
getCountSql(sql) {
|
||||
sql = sql
|
||||
.replace(new RegExp("LIMIT", "gm"), "limit ")
|
||||
.replace(new RegExp("\n", "gm"), " ");
|
||||
if (sql.includes("limit")) {
|
||||
const sqlArr = sql.split("limit ");
|
||||
sqlArr.pop();
|
||||
sql = sqlArr.join("limit ");
|
||||
}
|
||||
return `select count(*) as count from (${sql}) a`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数安全性检查
|
||||
* @param params
|
||||
*/
|
||||
async paramSafetyCheck(params) {
|
||||
const lp = params.toLowerCase();
|
||||
return !(
|
||||
lp.indexOf("update ") > -1 ||
|
||||
lp.indexOf("select ") > -1 ||
|
||||
lp.indexOf("delete ") > -1 ||
|
||||
lp.indexOf("insert ") > -1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生查询
|
||||
* @param sql
|
||||
* @param params
|
||||
* @param connectionName
|
||||
*/
|
||||
async nativeQuery(sql, params?, connectionName?) {
|
||||
if (_.isEmpty(params)) {
|
||||
params = this.sqlParams;
|
||||
}
|
||||
let newParams = [];
|
||||
newParams = newParams.concat(params);
|
||||
this.sqlParams = [];
|
||||
for (const param of newParams) {
|
||||
SqlString.escape(param);
|
||||
}
|
||||
return await this.getOrmManager(connectionName).query(sql, newParams || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得ORM管理
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
getOrmManager(connectionName = "default") {
|
||||
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作entity获得分页数据,不用写sql
|
||||
* @param find QueryBuilder
|
||||
* @param query
|
||||
* @param autoSort
|
||||
* @param connectionName
|
||||
*/
|
||||
async entityRenderPage(
|
||||
find: SelectQueryBuilder<any>,
|
||||
query,
|
||||
autoSort = true
|
||||
) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
const count = await find.getCount();
|
||||
let dataFind: SelectQueryBuilder<any>;
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
dataFind = find.limit(maxExportLimit);
|
||||
} else {
|
||||
dataFind = find.offset((page - 1) * size).limit(size);
|
||||
}
|
||||
if (autoSort) {
|
||||
find.addOrderBy(order, sort.toUpperCase());
|
||||
}
|
||||
return {
|
||||
list: await dataFind.getMany(),
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: count,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL并获得分页数据
|
||||
* @param sql 执行的sql语句
|
||||
* @param query 分页查询条件
|
||||
* @param autoSort 是否自动排序
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
if (order && sort && autoSort) {
|
||||
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||
throw new CoolValidateException("非法传参~");
|
||||
}
|
||||
sql += ` ORDER BY ${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
|
||||
}
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
this.sqlParams.push(parseInt(maxExportLimit));
|
||||
sql += " LIMIT ? ";
|
||||
}
|
||||
if (!isExport) {
|
||||
this.sqlParams.push((page - 1) * size);
|
||||
this.sqlParams.push(parseInt(size));
|
||||
sql += " LIMIT ?,? ";
|
||||
}
|
||||
|
||||
let params = [];
|
||||
params = params.concat(this.sqlParams);
|
||||
const result = await this.nativeQuery(sql, params, connectionName);
|
||||
const countResult = await this.nativeQuery(
|
||||
this.getCountSql(sql),
|
||||
params,
|
||||
connectionName
|
||||
);
|
||||
return {
|
||||
list: result,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查排序
|
||||
* @param sort 排序
|
||||
* @returns
|
||||
*/
|
||||
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 && item[2] != "")))
|
||||
) {
|
||||
for (const key in item[1]) {
|
||||
this.sqlParams.push(item[1][key]);
|
||||
}
|
||||
find.andWhere(item[0], item[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 附加排序
|
||||
if (!_.isEmpty(option.addOrderBy)) {
|
||||
for (const key in option.addOrderBy) {
|
||||
if (order && order == key) {
|
||||
sort = option.addOrderBy[key].toUpperCase();
|
||||
}
|
||||
find.addOrderBy(
|
||||
SqlString.escapeId(key),
|
||||
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||
);
|
||||
}
|
||||
}
|
||||
// 关键字模糊搜索
|
||||
if (keyWord || (keyWord == 0 && keyWord != "")) {
|
||||
keyWord = `%${keyWord}%`;
|
||||
find.andWhere(
|
||||
new Brackets((qb) => {
|
||||
const keyWordLikeFields = option.keyWordLikeFields || [];
|
||||
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
|
||||
keyWord,
|
||||
});
|
||||
this.sqlParams.push(keyWord);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
// 筛选字段
|
||||
if (!_.isEmpty(option.select)) {
|
||||
sqlArr.push(option.select.join(","));
|
||||
find.select(option.select);
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 字段全匹配
|
||||
if (!_.isEmpty(option.fieldEq)) {
|
||||
for (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 && query[key] == "")) {
|
||||
c[key] = query[key];
|
||||
const eq = query[key] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key} ${eq} (:${key})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key} ${eq} :${key}`, c);
|
||||
}
|
||||
this.sqlParams.push(query[key]);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
query[key.requestParam] ||
|
||||
(query[key.requestParam] == 0 && query[key.requestParam] !== "")
|
||||
) {
|
||||
c[key.column] = query[key.requestParam];
|
||||
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||
}
|
||||
this.sqlParams.push(query[key.requestParam]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 接口请求的排序
|
||||
if (sort && order) {
|
||||
const sorts = sort.toUpperCase().split(",");
|
||||
const orders = order.split(",");
|
||||
if (sorts.length != orders.length) {
|
||||
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||
}
|
||||
for (const i in sorts) {
|
||||
find.addOrderBy(
|
||||
SqlString.escapeId(orders[i]),
|
||||
this.checkSort(sorts[i])
|
||||
);
|
||||
}
|
||||
}
|
||||
if (option?.extend) {
|
||||
await option?.extend(find, this.baseCtx, this.baseApp);
|
||||
}
|
||||
const sqls = find.getSql().split("FROM");
|
||||
sqlArr.push("FROM");
|
||||
// 取sqls的最后一个
|
||||
sqlArr.push(sqls[sqls.length - 1]);
|
||||
return sqlArr.join(" ");
|
||||
}
|
||||
|
||||
}
|
||||
596
core/src/service/postgres.ts
Normal file
596
core/src/service/postgres.ts
Normal file
@ -0,0 +1,596 @@
|
||||
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
|
||||
import { CoolValidateException } from "../exception/validate";
|
||||
import { ERRINFO, EVENT } from "../constant/global";
|
||||
import { Application, Context } from "@midwayjs/koa";
|
||||
import { Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { CoolConfig } from "../interface";
|
||||
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
|
||||
import { Brackets, In, Repository, SelectQueryBuilder } from "typeorm";
|
||||
import { QueryOp } from "../decorator/controller";
|
||||
import * as _ from "lodash";
|
||||
import { CoolEventManager } from "../event";
|
||||
|
||||
/**
|
||||
* 服务基类
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export abstract class BasePgService {
|
||||
// 分页配置
|
||||
@Config("cool")
|
||||
private _coolConfig: CoolConfig;
|
||||
|
||||
// 模型
|
||||
entity: Repository<any>;
|
||||
|
||||
sqlParams;
|
||||
|
||||
@Inject()
|
||||
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
@Inject()
|
||||
coolEventManager: CoolEventManager;
|
||||
|
||||
// 设置模型
|
||||
setEntity(entity: any) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
// 设置请求上下文
|
||||
setCtx(ctx: Context) {
|
||||
this.baseCtx = ctx;
|
||||
}
|
||||
|
||||
@App()
|
||||
baseApp: Application;
|
||||
|
||||
// 设置应用对象
|
||||
setApp(app: Application) {
|
||||
this.baseApp = app;
|
||||
}
|
||||
|
||||
@Inject("ctx")
|
||||
baseCtx: Context;
|
||||
|
||||
// 初始化
|
||||
@Init()
|
||||
init() {
|
||||
this.sqlParams = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置sql
|
||||
* @param condition 条件是否成立
|
||||
* @param sql sql语句
|
||||
* @param params 参数
|
||||
*/
|
||||
setSql(condition, sql, params) {
|
||||
let rSql = false;
|
||||
if (condition || (condition === 0 && condition !== "")) {
|
||||
rSql = true;
|
||||
for(let i = 0; i < params.length; i++) {
|
||||
const param = params[i];
|
||||
if (param instanceof Array) {
|
||||
// 将这个? 替换成 $1,$2,$3
|
||||
const replaceStr = [];
|
||||
for(let j = 0; j < param.length; j++) {
|
||||
replaceStr.push('$' + (this.sqlParams.length + j + 1));
|
||||
}
|
||||
this.sqlParams = this.sqlParams.concat(...params);
|
||||
sql = sql.replace('?', replaceStr.join(','));
|
||||
} else {
|
||||
sql = sql.replace('?', '$' + (this.sqlParams.length + 1));
|
||||
this.sqlParams.push(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rSql ? sql : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得查询个数的SQL
|
||||
* @param sql
|
||||
*/
|
||||
getCountSql(sql) {
|
||||
sql = sql
|
||||
.replace(new RegExp("LIMIT", "gm"), "limit ")
|
||||
.replace(new RegExp("\n", "gm"), " ");
|
||||
if (sql.includes("limit")) {
|
||||
const sqlArr = sql.split("limit ");
|
||||
sqlArr.pop();
|
||||
sql = sqlArr.join("limit ");
|
||||
}
|
||||
return `select count(*) as count from (${sql}) a`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数安全性检查
|
||||
* @param params
|
||||
*/
|
||||
async paramSafetyCheck(params) {
|
||||
const lp = params.toLowerCase();
|
||||
return !(
|
||||
lp.indexOf("update ") > -1 ||
|
||||
lp.indexOf("select ") > -1 ||
|
||||
lp.indexOf("delete ") > -1 ||
|
||||
lp.indexOf("insert ") > -1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生查询
|
||||
* @param sql
|
||||
* @param params
|
||||
* @param connectionName
|
||||
*/
|
||||
async nativeQuery(sql, params?, connectionName?) {
|
||||
sql = this.convertToPostgres(sql);
|
||||
if (_.isEmpty(params)) {
|
||||
params = this.sqlParams;
|
||||
}
|
||||
let newParams = [];
|
||||
// sql没处理过?的情况下
|
||||
if(sql.includes('?')){
|
||||
for (const item of params) {
|
||||
// 如果是数组,将这个? 替换成 $1,$2,$3
|
||||
if (item instanceof Array) {
|
||||
const replaceStr = [];
|
||||
for(let i = 0; i < item.length; i++) {
|
||||
replaceStr.push('$' + (newParams.length + i + 1));
|
||||
}
|
||||
newParams.push(...item)
|
||||
sql = sql.replace('?', replaceStr.join(','));
|
||||
} else {
|
||||
sql = sql.replace('?', '$' + (newParams.length + 1));
|
||||
newParams.push(item);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
newParams = params;
|
||||
}
|
||||
this.sqlParams = [];
|
||||
return await this.getOrmManager(connectionName).query(sql, newParams || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得ORM管理
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
getOrmManager(connectionName = "default") {
|
||||
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作entity获得分页数据,不用写sql
|
||||
* @param find QueryBuilder
|
||||
* @param query
|
||||
* @param autoSort
|
||||
* @param connectionName
|
||||
*/
|
||||
async entityRenderPage(
|
||||
find: SelectQueryBuilder<any>,
|
||||
query,
|
||||
autoSort = true
|
||||
) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
const count = await find.getCount();
|
||||
let dataFind: SelectQueryBuilder<any>;
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
dataFind = find.limit(maxExportLimit);
|
||||
} else {
|
||||
dataFind = find.offset((page - 1) * size).limit(size);
|
||||
}
|
||||
if (autoSort) {
|
||||
find.addOrderBy(order, sort.toUpperCase());
|
||||
}
|
||||
return {
|
||||
list: await dataFind.getMany(),
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: count,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将mysql语句转换为postgres语句
|
||||
* @param sql
|
||||
* @returns
|
||||
*/
|
||||
protected convertToPostgres(sql) {
|
||||
// 首先确保表名被正确引用
|
||||
sql = sql.replace(/(?<!")(\b\w+\b)\.(?!\w+")/g, '"$1".');
|
||||
// 然后确保字段名被正确引用
|
||||
return sql.replace(/\.(\w+)(?!\w)/g, '."$1"');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询sql中的参数个数
|
||||
* @param sql
|
||||
* @returns
|
||||
*/
|
||||
protected countDollarSigns(sql) {
|
||||
const matches = sql.match(/\$\d+/g);
|
||||
return matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL并获得分页数据
|
||||
* @param sql 执行的sql语句
|
||||
* @param query 分页查询条件
|
||||
* @param autoSort 是否自动排序
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
sql = `SELECT * FROM (${sql}) a `
|
||||
if (order && sort && autoSort) {
|
||||
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||
throw new CoolValidateException("非法传参~");
|
||||
}
|
||||
sql += `ORDER BY a."${order}" ${this.checkSort(sort)}`
|
||||
}
|
||||
let cutParams = 0;
|
||||
let paramCount = this.countDollarSigns(sql);
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
this.sqlParams.push(parseInt(maxExportLimit));
|
||||
cutParams = 1;
|
||||
sql += ` LIMIT $${paramCount+1}`;
|
||||
}
|
||||
if (!isExport) {
|
||||
this.sqlParams.push(parseInt(size));
|
||||
this.sqlParams.push((page - 1) * size);
|
||||
cutParams = 2;
|
||||
sql += ` LIMIT $${ paramCount + 1} OFFSET $${ paramCount+ 2 }`;
|
||||
}
|
||||
let params = [];
|
||||
params = params.concat(this.sqlParams);
|
||||
const result = await this.nativeQuery(sql, params, connectionName);
|
||||
params = params.slice(0, -cutParams);
|
||||
const countResult = await this.nativeQuery(
|
||||
this.getCountSql(sql),
|
||||
params,
|
||||
connectionName
|
||||
);
|
||||
return {
|
||||
list: result,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查排序
|
||||
* @param sort 排序
|
||||
* @returns
|
||||
*/
|
||||
checkSort(sort) {
|
||||
if (!["desc", "asc"].includes(sort.toLowerCase())) {
|
||||
throw new CoolValidateException("sort 非法传参~");
|
||||
}
|
||||
return sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得单个ID
|
||||
* @param id ID
|
||||
* @param infoIgnoreProperty 忽略返回属性
|
||||
*/
|
||||
async info(id: any, infoIgnoreProperty?: string[]) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
if (!id) {
|
||||
throw new CoolValidateException(ERRINFO.NOID);
|
||||
}
|
||||
const info = await this.entity.findOneBy({ id });
|
||||
if (info && infoIgnoreProperty) {
|
||||
for (const property of infoIgnoreProperty) {
|
||||
delete info[property];
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||
*/
|
||||
async delete(ids: any) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
if (ids instanceof String) {
|
||||
ids = ids.split(",");
|
||||
}
|
||||
// 启动软删除发送事件
|
||||
if (this._coolConfig.crud?.softDelete) {
|
||||
this.softDelete(ids);
|
||||
}
|
||||
await this.entity.delete(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 软删除
|
||||
* @param ids 删除的ID数组
|
||||
* @param entity 实体
|
||||
*/
|
||||
async softDelete(ids: number[], entity?: Repository<any>) {
|
||||
const data = await this.entity.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
},
|
||||
});
|
||||
if (_.isEmpty(data)) return;
|
||||
const _entity = entity ? entity : this.entity;
|
||||
const params = {
|
||||
data,
|
||||
ctx: this.baseCtx,
|
||||
entity: _entity,
|
||||
};
|
||||
if (data.length > 0) {
|
||||
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增|修改
|
||||
* @param param 数据
|
||||
*/
|
||||
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
delete param.createTime;
|
||||
// 判断是否是批量操作
|
||||
if (param instanceof Array) {
|
||||
param.forEach((item) => {
|
||||
item.updateTime = new Date();
|
||||
item.createTime = new Date();
|
||||
});
|
||||
await this.entity.save(param);
|
||||
} else{
|
||||
const upsert = this._coolConfig.crud?.upsert || 'normal';
|
||||
if (type == 'update') {
|
||||
if(upsert == 'save') {
|
||||
const info = await this.entity.findOneBy({id: param.id})
|
||||
param = {
|
||||
...info,
|
||||
...param
|
||||
}
|
||||
}
|
||||
param.updateTime = new Date();
|
||||
upsert == 'normal'? await this.entity.update(param.id, param): await this.entity.save(param);
|
||||
}
|
||||
if(type =='add'){
|
||||
param.createTime = new Date();
|
||||
param.updateTime = new Date();
|
||||
upsert == 'normal'? await this.entity.insert(param): await this.entity.save(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 非分页查询
|
||||
* @param query 查询条件
|
||||
* @param option 查询配置
|
||||
* @param connectionName 连接名
|
||||
*/
|
||||
async list(query, option, connectionName?): Promise<any> {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
const sql = await this.getOptionFind(query, option);
|
||||
return this.nativeQuery(sql, [], connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param query 查询条件
|
||||
* @param option 查询配置
|
||||
* @param connectionName 连接名
|
||||
*/
|
||||
async page(query, option, connectionName?) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
const sql = await this.getOptionFind(query, option);
|
||||
return this.sqlRenderPage(sql, query, false, connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询配置
|
||||
* @param query 前端查询
|
||||
* @param option
|
||||
*/
|
||||
async getOptionFind(query, option: QueryOp) {
|
||||
let { order = "createTime", sort = "desc", keyWord = "" } = query;
|
||||
const sqlArr = ["SELECT"];
|
||||
const selects = ["a.*"];
|
||||
const find = this.entity.createQueryBuilder("a");
|
||||
if (option) {
|
||||
if (typeof option == "function") {
|
||||
// @ts-ignore
|
||||
option = await option(this.baseCtx, this.baseApp);
|
||||
}
|
||||
// 判断是否有关联查询,有的话取个别名
|
||||
if (!_.isEmpty(option.join)) {
|
||||
for (const item of option.join) {
|
||||
selects.push(`${item.alias}.*`);
|
||||
find[item.type || "leftJoin"](
|
||||
item.entity,
|
||||
item.alias,
|
||||
item.condition
|
||||
);
|
||||
}
|
||||
}
|
||||
// 默认条件
|
||||
if (option.where) {
|
||||
const wheres =
|
||||
typeof option.where == "function"
|
||||
? await option.where(this.baseCtx, this.baseApp)
|
||||
: option.where;
|
||||
if (!_.isEmpty(wheres)) {
|
||||
for (const item of wheres) {
|
||||
if (
|
||||
item.length == 2 ||
|
||||
(item.length == 3 &&
|
||||
(item[2] || (item[2] === 0 && item[2] != "")))
|
||||
) {
|
||||
for (const key in item[1]) {
|
||||
this.sqlParams.push(item[1][key]);
|
||||
}
|
||||
find.andWhere(item[0], item[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 附加排序
|
||||
if (!_.isEmpty(option.addOrderBy)) {
|
||||
for (const key in option.addOrderBy) {
|
||||
if (order && order == key) {
|
||||
sort = option.addOrderBy[key].toUpperCase();
|
||||
}
|
||||
find.addOrderBy(
|
||||
`${this.matchColumn(option?.select, key)}.${key}`,
|
||||
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||
);
|
||||
}
|
||||
}
|
||||
// 关键字模糊搜索
|
||||
if (keyWord || (keyWord == 0 && keyWord != "")) {
|
||||
keyWord = `%${keyWord}%`;
|
||||
find.andWhere(
|
||||
new Brackets((qb) => {
|
||||
const keyWordLikeFields = option.keyWordLikeFields || [];
|
||||
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||
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 && query[key] == "")) {
|
||||
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 && query[key.requestParam] !== "")
|
||||
) {
|
||||
c[key.column] = query[key.requestParam];
|
||||
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||
}
|
||||
this.sqlParams.push(query[key.requestParam]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 接口请求的排序
|
||||
if (sort && order) {
|
||||
const sorts = sort.toUpperCase().split(",");
|
||||
const orders = order.split(",");
|
||||
if (sorts.length != orders.length) {
|
||||
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||
}
|
||||
for (const i in sorts) {
|
||||
find.addOrderBy(
|
||||
`${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]);
|
||||
return sqlArr.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选的字段匹配
|
||||
* @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';
|
||||
}
|
||||
|
||||
}
|
||||
530
core/src/service/sqlite.ts
Normal file
530
core/src/service/sqlite.ts
Normal file
@ -0,0 +1,530 @@
|
||||
import { Init, Provide, Inject, App, Config } from "@midwayjs/decorator";
|
||||
import { Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { CoolValidateException } from "../exception/validate";
|
||||
import { ERRINFO, EVENT } from "../constant/global";
|
||||
import { Application, Context } from "@midwayjs/koa";
|
||||
import * as SqlString from "sqlstring";
|
||||
import { CoolConfig } from "../interface";
|
||||
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
|
||||
import { Brackets, In, Repository, SelectQueryBuilder } from "typeorm";
|
||||
import { QueryOp } from "../decorator/controller";
|
||||
import * as _ from "lodash";
|
||||
import { CoolEventManager } from "../event";
|
||||
|
||||
/**
|
||||
* 服务基类
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export abstract class BaseSqliteService {
|
||||
// 分页配置
|
||||
@Config("cool")
|
||||
private _coolConfig: CoolConfig;
|
||||
|
||||
// 模型
|
||||
entity: Repository<any>;
|
||||
|
||||
sqlParams;
|
||||
|
||||
@Inject()
|
||||
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
@Inject()
|
||||
coolEventManager: CoolEventManager;
|
||||
|
||||
// 设置模型
|
||||
setEntity(entity: any) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
// 设置请求上下文
|
||||
setCtx(ctx: Context) {
|
||||
this.baseCtx = ctx;
|
||||
}
|
||||
|
||||
@App()
|
||||
baseApp: Application;
|
||||
|
||||
// 设置应用对象
|
||||
setApp(app: Application) {
|
||||
this.baseApp = app;
|
||||
}
|
||||
|
||||
@Inject("ctx")
|
||||
baseCtx: Context;
|
||||
|
||||
// 初始化
|
||||
@Init()
|
||||
init() {
|
||||
this.sqlParams = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置sql
|
||||
* @param condition 条件是否成立
|
||||
* @param sql sql语句
|
||||
* @param params 参数
|
||||
*/
|
||||
setSql(condition, sql, params) {
|
||||
let rSql = false;
|
||||
if (condition || (condition === 0 && condition !== "")) {
|
||||
rSql = true;
|
||||
for(let i = 0; i < params.length; i++) {
|
||||
const param = params[i];
|
||||
if (param instanceof Array) {
|
||||
// 将这个? 替换成 $1,$2,$3
|
||||
const replaceStr = [];
|
||||
for(let j = 0; j < param.length; j++) {
|
||||
replaceStr.push('$' + (this.sqlParams.length + j + 1));
|
||||
}
|
||||
this.sqlParams = this.sqlParams.concat(...params);
|
||||
sql = sql.replace('?', replaceStr.join(','));
|
||||
} else {
|
||||
sql = sql.replace('?', '$' + (this.sqlParams.length + 1));
|
||||
this.sqlParams.push(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (rSql ? sql : "").replace(/\$\d+/g, '?');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得查询个数的SQL
|
||||
* @param sql
|
||||
*/
|
||||
getCountSql(sql) {
|
||||
sql = sql
|
||||
.replace(new RegExp("LIMIT", "gm"), "limit ")
|
||||
.replace(new RegExp("\n", "gm"), " ");
|
||||
if (sql.includes("limit")) {
|
||||
const sqlArr = sql.split("limit ");
|
||||
sqlArr.pop();
|
||||
sql = sqlArr.join("limit ");
|
||||
}
|
||||
return `select count(*) as count from (${sql}) a`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数安全性检查
|
||||
* @param params
|
||||
*/
|
||||
async paramSafetyCheck(params) {
|
||||
const lp = params.toLowerCase();
|
||||
return !(
|
||||
lp.indexOf("update ") > -1 ||
|
||||
lp.indexOf("select ") > -1 ||
|
||||
lp.indexOf("delete ") > -1 ||
|
||||
lp.indexOf("insert ") > -1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生查询
|
||||
* @param sql
|
||||
* @param params
|
||||
* @param connectionName
|
||||
*/
|
||||
async nativeQuery(sql, params?, connectionName?) {
|
||||
if (_.isEmpty(params)) {
|
||||
params = this.sqlParams;
|
||||
}
|
||||
let newParams = [];
|
||||
// sql没处理过?的情况下
|
||||
for (const item of params) {
|
||||
// 如果是数组,将这个? 替换成 $1,$2,$3
|
||||
if (item instanceof Array) {
|
||||
const replaceStr = [];
|
||||
for(let i = 0; i < item.length; i++) {
|
||||
replaceStr.push('$' + (newParams.length + i + 1));
|
||||
}
|
||||
newParams.push(...item)
|
||||
sql = sql.replace('?', replaceStr.join(','));
|
||||
} else {
|
||||
sql = sql.replace('?', '$' + (newParams.length + 1));
|
||||
newParams.push(item);
|
||||
}
|
||||
}
|
||||
this.sqlParams = [];
|
||||
return await this.getOrmManager(connectionName).query(sql.replace(/\$\d+/g, '?'), newParams || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得ORM管理
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
getOrmManager(connectionName = "default") {
|
||||
return this.typeORMDataSourceManager.getDataSource(connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作entity获得分页数据,不用写sql
|
||||
* @param find QueryBuilder
|
||||
* @param query
|
||||
* @param autoSort
|
||||
* @param connectionName
|
||||
*/
|
||||
async entityRenderPage(
|
||||
find: SelectQueryBuilder<any>,
|
||||
query,
|
||||
autoSort = true
|
||||
) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
const count = await find.getCount();
|
||||
let dataFind: SelectQueryBuilder<any>;
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
dataFind = find.limit(maxExportLimit);
|
||||
} else {
|
||||
dataFind = find.offset((page - 1) * size).limit(size);
|
||||
}
|
||||
if (autoSort) {
|
||||
find.addOrderBy(order, sort.toUpperCase());
|
||||
}
|
||||
return {
|
||||
list: await dataFind.getMany(),
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: count,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL并获得分页数据
|
||||
* @param sql 执行的sql语句
|
||||
* @param query 分页查询条件
|
||||
* @param autoSort 是否自动排序
|
||||
* @param connectionName 连接名称
|
||||
*/
|
||||
async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
|
||||
const {
|
||||
size = this._coolConfig.crud.pageSize,
|
||||
page = 1,
|
||||
order = "createTime",
|
||||
sort = "desc",
|
||||
isExport = false,
|
||||
maxExportLimit,
|
||||
} = query;
|
||||
sql = `SELECT * FROM (${sql}) a`;
|
||||
if (order && sort && autoSort) {
|
||||
if (!(await this.paramSafetyCheck(order + sort))) {
|
||||
throw new CoolValidateException("非法传参~");
|
||||
}
|
||||
sql += ` ORDER BY a.${SqlString.escapeId(order)} ${this.checkSort(sort)}`;
|
||||
}
|
||||
let cutParams = 0;
|
||||
if (isExport && maxExportLimit > 0) {
|
||||
this.sqlParams.push(parseInt(maxExportLimit));
|
||||
cutParams = 1;
|
||||
sql += " LIMIT ? ";
|
||||
}
|
||||
if (!isExport) {
|
||||
this.sqlParams.push((page - 1) * size);
|
||||
this.sqlParams.push(parseInt(size));
|
||||
cutParams = 2;
|
||||
sql += " LIMIT ?,? ";
|
||||
}
|
||||
|
||||
let params = [];
|
||||
params = params.concat(this.sqlParams);
|
||||
const result = await this.nativeQuery(sql, params, connectionName);
|
||||
params = params.slice(0, -cutParams);
|
||||
const countResult = await this.nativeQuery(
|
||||
this.getCountSql(sql),
|
||||
params,
|
||||
connectionName
|
||||
);
|
||||
return {
|
||||
list: result,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
total: parseInt(countResult[0] ? countResult[0].count : 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查排序
|
||||
* @param sort 排序
|
||||
* @returns
|
||||
*/
|
||||
checkSort(sort) {
|
||||
if (!["desc", "asc"].includes(sort.toLowerCase())) {
|
||||
throw new CoolValidateException("sort 非法传参~");
|
||||
}
|
||||
return sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得单个ID
|
||||
* @param id ID
|
||||
* @param infoIgnoreProperty 忽略返回属性
|
||||
*/
|
||||
async info(id: any, infoIgnoreProperty?: string[]) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
if (!id) {
|
||||
throw new CoolValidateException(ERRINFO.NOID);
|
||||
}
|
||||
const info = await this.entity.findOneBy({ id });
|
||||
if (info && infoIgnoreProperty) {
|
||||
for (const property of infoIgnoreProperty) {
|
||||
delete info[property];
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
|
||||
*/
|
||||
async delete(ids: any) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
if (ids instanceof String) {
|
||||
ids = ids.split(",");
|
||||
}
|
||||
// 启动软删除发送事件
|
||||
if (this._coolConfig.crud?.softDelete) {
|
||||
this.softDelete(ids);
|
||||
}
|
||||
await this.entity.delete(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 软删除
|
||||
* @param ids 删除的ID数组
|
||||
* @param entity 实体
|
||||
*/
|
||||
async softDelete(ids: number[], entity?: Repository<any>) {
|
||||
const data = await this.entity.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
},
|
||||
});
|
||||
if (_.isEmpty(data)) return;
|
||||
const _entity = entity ? entity : this.entity;
|
||||
const params = {
|
||||
data,
|
||||
ctx: this.baseCtx,
|
||||
entity: _entity,
|
||||
};
|
||||
if (data.length > 0) {
|
||||
this.coolEventManager.emit(EVENT.SOFT_DELETE, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增|修改
|
||||
* @param param 数据
|
||||
*/
|
||||
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
delete param.createTime;
|
||||
// 判断是否是批量操作
|
||||
if (param instanceof Array) {
|
||||
param.forEach((item) => {
|
||||
item.updateTime = new Date();
|
||||
item.createTime = new Date();
|
||||
});
|
||||
await this.entity.save(param);
|
||||
} else{
|
||||
const upsert = this._coolConfig.crud?.upsert || 'normal';
|
||||
if (type == 'update') {
|
||||
if(upsert == 'save') {
|
||||
const info = await this.entity.findOneBy({id: param.id})
|
||||
param = {
|
||||
...info,
|
||||
...param
|
||||
}
|
||||
}
|
||||
param.updateTime = new Date();
|
||||
upsert == 'normal'? await this.entity.update(param.id, param): await this.entity.save(param);
|
||||
}
|
||||
if(type =='add'){
|
||||
param.createTime = new Date();
|
||||
param.updateTime = new Date();
|
||||
upsert == 'normal'? await this.entity.insert(param): await this.entity.save(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 非分页查询
|
||||
* @param query 查询条件
|
||||
* @param option 查询配置
|
||||
* @param connectionName 连接名
|
||||
*/
|
||||
async list(query, option, connectionName?): Promise<any> {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
const sql = await this.getOptionFind(query, option);
|
||||
return this.nativeQuery(sql, [], connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param query 查询条件
|
||||
* @param option 查询配置
|
||||
* @param connectionName 连接名
|
||||
*/
|
||||
async page(query, option, connectionName?) {
|
||||
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
|
||||
const sql = await this.getOptionFind(query, option);
|
||||
return this.sqlRenderPage(sql, query, false, connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询配置
|
||||
* @param query 前端查询
|
||||
* @param option
|
||||
*/
|
||||
async getOptionFind(query, option: QueryOp) {
|
||||
let { order = "createTime", sort = "desc", keyWord = "" } = query;
|
||||
const sqlArr = ["SELECT"];
|
||||
const selects = ["a.*"];
|
||||
const find = this.entity.createQueryBuilder("a");
|
||||
if (option) {
|
||||
if (typeof option == "function") {
|
||||
// @ts-ignore
|
||||
option = await option(this.baseCtx, this.baseApp);
|
||||
}
|
||||
// 判断是否有关联查询,有的话取个别名
|
||||
if (!_.isEmpty(option.join)) {
|
||||
for (const item of option.join) {
|
||||
selects.push(`${item.alias}.*`);
|
||||
find[item.type || "leftJoin"](
|
||||
item.entity,
|
||||
item.alias,
|
||||
item.condition
|
||||
);
|
||||
}
|
||||
}
|
||||
// 默认条件
|
||||
if (option.where) {
|
||||
const wheres =
|
||||
typeof option.where == "function"
|
||||
? await option.where(this.baseCtx, this.baseApp)
|
||||
: option.where;
|
||||
if (!_.isEmpty(wheres)) {
|
||||
for (const item of wheres) {
|
||||
if (
|
||||
item.length == 2 ||
|
||||
(item.length == 3 &&
|
||||
(item[2] || (item[2] === 0 && item[2] != "")))
|
||||
) {
|
||||
for (const key in item[1]) {
|
||||
this.sqlParams.push(item[1][key]);
|
||||
}
|
||||
find.andWhere(item[0], item[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 附加排序
|
||||
if (!_.isEmpty(option.addOrderBy)) {
|
||||
for (const key in option.addOrderBy) {
|
||||
if (order && order == key) {
|
||||
sort = option.addOrderBy[key].toUpperCase();
|
||||
}
|
||||
find.addOrderBy(
|
||||
SqlString.escapeId(key),
|
||||
this.checkSort(option.addOrderBy[key].toUpperCase())
|
||||
);
|
||||
}
|
||||
}
|
||||
// 关键字模糊搜索
|
||||
if (keyWord || (keyWord == 0 && keyWord != "")) {
|
||||
keyWord = `%${keyWord}%`;
|
||||
find.andWhere(
|
||||
new Brackets((qb) => {
|
||||
const keyWordLikeFields = option.keyWordLikeFields || [];
|
||||
for (let i = 0; i < option.keyWordLikeFields?.length || 0; i++) {
|
||||
qb.orWhere(`${keyWordLikeFields[i]} like :keyWord`, {
|
||||
keyWord,
|
||||
});
|
||||
this.sqlParams.push(keyWord);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
// 筛选字段
|
||||
if (!_.isEmpty(option.select)) {
|
||||
sqlArr.push(option.select.join(","));
|
||||
find.select(option.select);
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 字段全匹配
|
||||
if (!_.isEmpty(option.fieldEq)) {
|
||||
for (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 && query[key] == "")) {
|
||||
c[key] = query[key];
|
||||
const eq = query[key] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key} ${eq} (:${key})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key} ${eq} :${key}`, c);
|
||||
}
|
||||
// this.sqlParams.push(query[key]);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
query[key.requestParam] ||
|
||||
(query[key.requestParam] == 0 && query[key.requestParam] !== "")
|
||||
) {
|
||||
c[key.column] = query[key.requestParam];
|
||||
const eq = query[key.requestParam] instanceof Array ? "in" : "=";
|
||||
if (eq === "in") {
|
||||
find.andWhere(`${key.column} ${eq} (:${key.column})`, c);
|
||||
} else {
|
||||
find.andWhere(`${key.column} ${eq} :${key.column}`, c);
|
||||
}
|
||||
// this.sqlParams.push(query[key.requestParam]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sqlArr.push(selects.join(","));
|
||||
}
|
||||
// 接口请求的排序
|
||||
if (sort && order) {
|
||||
const sorts = sort.toUpperCase().split(",");
|
||||
const orders = order.split(",");
|
||||
if (sorts.length != orders.length) {
|
||||
throw new CoolValidateException(ERRINFO.SORTFIELD);
|
||||
}
|
||||
for (const i in sorts) {
|
||||
find.addOrderBy(
|
||||
SqlString.escapeId(orders[i]),
|
||||
this.checkSort(sorts[i])
|
||||
);
|
||||
}
|
||||
}
|
||||
if (option?.extend) {
|
||||
await option?.extend(find, this.baseCtx, this.baseApp);
|
||||
}
|
||||
const sqls = find.getSql().split("FROM");
|
||||
sqlArr.push("FROM");
|
||||
// 取sqls的最后一个
|
||||
sqlArr.push(sqls[sqls.length - 1]);
|
||||
return sqlArr.join(" ");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user