兼容mysql、postgresql、sqlite

This commit is contained in:
cool 2024-01-15 14:13:53 +08:00
parent fcf34dc35e
commit 01c8577507
10 changed files with 1817 additions and 416 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@cool-midway/core", "name": "@cool-midway/core",
"version": "7.0.8", "version": "7.1.0",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "index.d.ts", "typings": "index.d.ts",
@ -41,7 +41,7 @@
"jest": "^29.3.1", "jest": "^29.3.1",
"mwts": "^1.3.0", "mwts": "^1.3.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"typeorm": "^0.3.11", "typeorm": "^0.3.19",
"typescript": "~4.9.4" "typescript": "~4.9.4"
}, },
"dependencies": { "dependencies": {

View File

@ -13,7 +13,6 @@ export abstract class BaseEntity extends CoolBaseEntity {
// 默认自增 // 默认自增
@PrimaryGeneratedColumn("increment", { @PrimaryGeneratedColumn("increment", {
comment: "ID", comment: "ID",
// type: "bigint",
}) })
id: number; id: number;

View File

@ -14,6 +14,9 @@ export * from "./entity/mongo";
// service // service
export * from "./service/base"; export * from "./service/base";
export * from "./service/mysql";
export * from "./service/postgres";
export * from "./service/sqlite";
// controller // controller
export * from "./controller/base"; export * from "./controller/base";

View File

@ -9,8 +9,7 @@ import {
Scope, Scope,
ScopeEnum, ScopeEnum,
} from "@midwayjs/decorator"; } 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 * as fs from "fs";
import { CoolModuleConfig } from "./config"; import { CoolModuleConfig } from "./config";
import * as path from "path"; import * as path from "path";
@ -18,6 +17,7 @@ import { InjectDataSource, TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { DataSource } from "typeorm"; import { DataSource } from "typeorm";
import { CoolEventManager } from "../event"; import { CoolEventManager } from "../event";
import { CoolModuleMenu } from "./menu"; import { CoolModuleMenu } from "./menu";
import * as _ from 'lodash';
/** /**
* sql * sql
@ -56,21 +56,21 @@ export class CoolModuleImport {
async init() { async init() {
// 是否需要导入 // 是否需要导入
if (this.coolConfig.initDB) { if (this.coolConfig.initDB) {
await this.checkDbVersion();
const modules = this.coolModuleConfig.modules; const modules = this.coolModuleConfig.modules;
const importLockPath = path.join( const importLockPath = path.join(
`${this.app.getBaseDir()}`, `${this.app.getBaseDir()}`,
"..", "..",
"lock" "lock",
'db'
); );
if (!fs.existsSync(importLockPath)) { if (!fs.existsSync(importLockPath)) {
fs.mkdirSync(importLockPath); fs.mkdirSync(importLockPath, { recursive: true });
} }
setTimeout(async () => { setTimeout(async () => {
for (const module of modules) { 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)) { if (!fs.existsSync(lockPath)) {
await this.initDataBase(module, lockPath); await this.initDataBase(module, lockPath);
} }
} }
this.coolEventManager.emit("onDBInit", {}); this.coolEventManager.emit("onDBInit", {});
@ -85,71 +85,115 @@ export class CoolModuleImport {
* @param lockPath * @param lockPath
*/ */
async initDataBase(module: string, lockPath: string) { async initDataBase(module: string, lockPath: string) {
// 计算耗时
const startTime = new Date().getTime();
// 模块路径 // 模块路径
const modulePath = `${this.app.getBaseDir()}/modules/${module}`; const modulePath = `${this.app.getBaseDir()}/modules/${module}`;
// sql 路径 // 数据路径
const sqlPath = `${modulePath}/init.sql`; const dataPath = `${modulePath}/db.json`;
// 延迟2秒再导入数据库 // 判断文件是否存在
if (fs.existsSync(sqlPath)) { if(fs.existsSync(dataPath)) {
let second = 0; // 获得所有的实体
const t = setInterval(() => { const entityMetadatas = this.defaultDataSource.entityMetadatas;
this.coreLogger.info( const metadatas = _.mapValues(_.keyBy(entityMetadatas, 'tableName'), 'target');
"\x1B[36m [cool:core] midwayjs cool core init " + // 读取数据
module + const data = JSON.parse(fs.readFileSync(dataPath).toString() || '{}');
" database... \x1B[0m" // 导入数据
); for(const key in data) {
second++; try{
}, 1000); const repository = this.defaultDataSource.getRepository(metadatas[key]);
const { host, username, password, database, charset, port } = this if(this.ormConfig.default.type == 'postgres') {
.ormConfig?.default for(const item of data[key]) {
? this.ormConfig.default const result: any = await repository.save(repository.create(item));
: this.ormConfig; if(item.id) {
const importer = new Importer({ await repository.update(result.id, { id: item.id });
host, // 更新pgsql序列
password, await this.defaultDataSource.query(`SELECT setval('${key}_id_seq', (SELECT MAX(id) FROM ${key}));`);
database, }
charset, }
port, }else{
user: username, await repository.insert(data[key]);
}); }
await importer }catch(e){
.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( this.coreLogger.error(
"\x1B[36m [cool:core] midwayjs cool core init " + "\x1B[36m [cool:core] midwayjs cool core init " +
module + module +
" database err please manual import \x1B[0m" " database err \x1B[0m"
); );
fs.writeFileSync(lockPath, `time consuming${second}s`); continue;
this.coreLogger.error(err); }
this.coreLogger.error( }
`自动初始化模块[${module}]数据库失败,尝试手动导入数据库` 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() { // async checkDbVersion() {
const versions = ( // const versions = (
await this.defaultDataSource.query("SELECT VERSION() AS version") // await this.defaultDataSource.query("SELECT VERSION() AS version")
)[0].version.split("."); // )[0].version.split(".");
if ((versions[0] == 5 && versions[1] < 7) || versions[0] < 5) { // if ((versions[0] == 5 && versions[1] < 7) || versions[0] < 5) {
throw new CoolCoreException( // throw new CoolCoreException(
"数据库不满足要求mysql>=5.7,请升级数据库版本" // "数据库不满足要求mysql>=5.7,请升级数据库版本"
); // );
} // }
} // }
} }

View File

@ -27,6 +27,8 @@ export class CoolModuleMenu {
@Inject() @Inject()
coolEventManager: CoolEventManager; coolEventManager: CoolEventManager;
datas = {};
async init() { async init() {
// 是否需要导入 // 是否需要导入
if (this.coolConfig.initMenu) { if (this.coolConfig.initMenu) {
@ -34,17 +36,19 @@ export class CoolModuleMenu {
const importLockPath = path.join( const importLockPath = path.join(
`${this.app.getBaseDir()}`, `${this.app.getBaseDir()}`,
"..", "..",
"lock" "lock",
'menu'
); );
if (!fs.existsSync(importLockPath)) { if (!fs.existsSync(importLockPath)) {
fs.mkdirSync(importLockPath); fs.mkdirSync(importLockPath, { recursive: true });
} }
for (const module of modules) { for (const module of modules) {
const lockPath = path.join(importLockPath, module + ".menu.lock"); const lockPath = path.join(importLockPath, module + ".menu.lock");
if (!fs.existsSync(lockPath)) { if (!fs.existsSync(lockPath)) {
await this.importMenu(module, lockPath); await this.importMenu(module, lockPath);
} }
} }
this.coolEventManager.emit("onMenuImport", this.datas);
this.coolEventManager.emit("onMenuInit", {}); this.coolEventManager.emit("onMenuInit", {});
} }
} }
@ -63,7 +67,8 @@ export class CoolModuleMenu {
if (fs.existsSync(menuPath)) { if (fs.existsSync(menuPath)) {
const data = fs.readFileSync(menuPath); const data = fs.readFileSync(menuPath);
try { 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); fs.writeFileSync(lockPath, data);
} catch (error) { } catch (error) {
this.coreLogger.error(error); this.coreLogger.error(error);

View File

@ -1,6 +1,6 @@
{ {
"name": "@cool-midway/core", "name": "@cool-midway/core",
"version": "7.0.8", "version": "7.1.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"typings": "index.d.ts", "typings": "index.d.ts",
@ -40,7 +40,7 @@
"jest": "^29.3.1", "jest": "^29.3.1",
"mwts": "^1.3.0", "mwts": "^1.3.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"typeorm": "^0.3.11", "typeorm": "^0.3.19",
"typescript": "~4.9.4" "typescript": "~4.9.4"
}, },
"dependencies": { "dependencies": {

View File

@ -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 { CoolValidateException } from "../exception/validate";
import { ERRINFO, EVENT } from "../constant/global"; import { ERRINFO } from "../constant/global";
import { Application, Context } from "@midwayjs/koa"; import { Application, Context } from "@midwayjs/koa";
import * as SqlString from "sqlstring";
import { CoolConfig } from "../interface";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm"; import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { Brackets, In, Repository, SelectQueryBuilder } from "typeorm"; import { Repository, SelectQueryBuilder } from "typeorm";
import { QueryOp } from "../decorator/controller"; import { QueryOp } from "../decorator/controller";
import * as _ from "lodash"; import * as _ from "lodash";
import { CoolEventManager } from "../event"; import { CoolEventManager } from "../event";
import { CoolCoreException } from "../exception/core";
import { BaseSqliteService } from "./sqlite";
/** /**
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export abstract class BaseService { export abstract class BaseService {
// 分页配置 // mysql的基类
@Config("cool") @Inject()
private _coolConfig: CoolConfig; baseMysqlService: BaseMysqlService;
// postgres的基类
@Inject()
basePgService: BasePgService;
@Inject()
baseSqliteService: BaseSqliteService;
// 数据库类型
@Config("typeorm.dataSource.default.type")
ormType;
// 当前服务名称
service: BaseMysqlService | BasePgService | BaseSqliteService;
// 模型 // 模型
protected entity: Repository<any>; protected entity: Repository<any>;
@ -30,31 +48,42 @@ export abstract class BaseService {
@Inject() @Inject()
coolEventManager: CoolEventManager; 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) { setEntity(entity: any) {
this.entity = entity; this.entity = entity;
this.service.setEntity(entity);
} }
// 设置请求上下文 // 设置请求上下文
setCtx(ctx: Context) { setCtx(ctx: Context) {
this.baseCtx = ctx; this.baseCtx = ctx;
this.service.setCtx(ctx);
} }
@App()
baseApp: Application;
// 设置应用对象 // 设置应用对象
setApp(app: Application) { setApp(app: Application) {
this.baseApp = app; this.baseApp = app;
} this.service.setApp(app);
@Inject("ctx")
baseCtx: Context;
// 初始化
@Init()
init() {
this.sqlParams = [];
} }
/** /**
@ -64,12 +93,7 @@ export abstract class BaseService {
* @param params * @param params
*/ */
setSql(condition, sql, params) { setSql(condition, sql, params) {
let rSql = false; return this.service.setSql(condition, sql, params);
if (condition || (condition === 0 && condition !== "")) {
rSql = true;
this.sqlParams = this.sqlParams.concat(params);
}
return rSql ? sql : "";
} }
/** /**
@ -77,29 +101,15 @@ export abstract class BaseService {
* @param sql * @param sql
*/ */
getCountSql(sql) { getCountSql(sql) {
sql = sql return this.service.getCountSql(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 * @param params
*/ */
async paramSafetyCheck(params) { async paramSafetyCheck(params) {
const lp = params.toLowerCase(); return await this.service.paramSafetyCheck(params);
return !(
lp.indexOf("update ") > -1 ||
lp.indexOf("select ") > -1 ||
lp.indexOf("delete ") > -1 ||
lp.indexOf("insert ") > -1
);
} }
/** /**
@ -109,16 +119,7 @@ export abstract class BaseService {
* @param connectionName * @param connectionName
*/ */
async nativeQuery(sql, params?, connectionName?) { async nativeQuery(sql, params?, connectionName?) {
if (_.isEmpty(params)) { return await this.service.nativeQuery(sql, params, connectionName);
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 || []);
} }
/** /**
@ -126,7 +127,7 @@ export abstract class BaseService {
* @param connectionName * @param connectionName
*/ */
getOrmManager(connectionName = "default") { getOrmManager(connectionName = "default") {
return this.typeORMDataSourceManager.getDataSource(connectionName); return this.service.getOrmManager(connectionName);
} }
/** /**
@ -141,32 +142,7 @@ export abstract class BaseService {
query, query,
autoSort = true autoSort = true
) { ) {
const { return await this.service.entityRenderPage(find, query, autoSort);
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,
},
};
} }
/** /**
@ -177,58 +153,7 @@ export abstract class BaseService {
* @param connectionName * @param connectionName
*/ */
async sqlRenderPage(sql, query, autoSort = true, connectionName?) { async sqlRenderPage(sql, query, autoSort = true, connectionName?) {
const { return await this.service.sqlRenderPage(sql, query, autoSort, connectionName);
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;
} }
/** /**
@ -237,17 +162,7 @@ export abstract class BaseService {
* @param infoIgnoreProperty * @param infoIgnoreProperty
*/ */
async info(id: any, infoIgnoreProperty?: string[]) { async info(id: any, infoIgnoreProperty?: string[]) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY); return await this.service.info(id, infoIgnoreProperty);
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;
} }
/** /**
@ -255,16 +170,8 @@ export abstract class BaseService {
* @param ids ID集合 [1,2,3] 1,2,3 * @param ids ID集合 [1,2,3] 1,2,3
*/ */
async delete(ids: any) { async delete(ids: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
await this.modifyBefore(ids, "delete"); await this.modifyBefore(ids, "delete");
if (ids instanceof String) { await this.service.delete(ids);
ids = ids.split(",");
}
// 启动软删除发送事件
if (this._coolConfig.crud?.softDelete) {
this.softDelete(ids);
}
await this.entity.delete(ids);
await this.modifyAfter(ids, "delete"); await this.modifyAfter(ids, "delete");
} }
@ -274,21 +181,7 @@ export abstract class BaseService {
* @param entity * @param entity
*/ */
async softDelete(ids: number[], entity?: Repository<any>) { async softDelete(ids: number[], entity?: Repository<any>) {
const data = await this.entity.find({ await this.service.softDelete(ids, entity);
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);
}
} }
/** /**
@ -297,10 +190,9 @@ export abstract class BaseService {
*/ */
async update(param: any) { async update(param: any) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY); if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
await this.modifyBefore(param, "update");
if (!param.id && !(param instanceof Array)) if (!param.id && !(param instanceof Array))
throw new CoolValidateException(ERRINFO.NOID); 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> { async add(param: any | any[]): Promise<Object> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY); if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY);
await this.modifyBefore(param, "add");
await this.addOrUpdate(param, 'add'); await this.addOrUpdate(param, 'add');
return { return {
id: id:
@ -328,34 +219,8 @@ export abstract class BaseService {
* @param param * @param param
*/ */
async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') { async addOrUpdate(param: any | any[], type: 'add' | 'update' = 'add') {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY); await this.modifyBefore(param, type);
delete param.createTime; await this.service.addOrUpdate(param, type);
// 判断是否是批量操作
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.modifyAfter(param, type); await this.modifyAfter(param, type);
} }
@ -366,9 +231,7 @@ export abstract class BaseService {
* @param connectionName * @param connectionName
*/ */
async list(query, option, connectionName?): Promise<any> { async list(query, option, connectionName?): Promise<any> {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY); return await this.service.list(query, option, connectionName);
const sql = await this.getOptionFind(query, option);
return this.nativeQuery(sql, [], connectionName);
} }
/** /**
@ -378,9 +241,7 @@ export abstract class BaseService {
* @param connectionName * @param connectionName
*/ */
async page(query, option, connectionName?) { async page(query, option, connectionName?) {
if (!this.entity) throw new CoolValidateException(ERRINFO.NOENTITY); return await this.service.page(query, option, connectionName);
const sql = await this.getOptionFind(query, option);
return this.sqlRenderPage(sql, query, false, connectionName);
} }
/** /**
@ -388,146 +249,8 @@ export abstract class BaseService {
* @param query * @param query
* @param option * @param option
*/ */
private async getOptionFind(query, option: QueryOp) { async getOptionFind(query, option: QueryOp) {
let { order = "createTime", sort = "desc", keyWord = "" } = query; return await this.service.getOptionFind(query, option);
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(" ");
} }
/** /**
@ -547,4 +270,5 @@ export abstract class BaseService {
data: any, data: any,
type: "delete" | "update" | "add" type: "delete" | "update" | "add"
): Promise<void> {} ): Promise<void> {}
} }

500
core/src/service/mysql.ts Normal file
View 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(" ");
}
}

View 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
View 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(" ");
}
}