|
@@ -0,0 +1,330 @@
|
|
|
+import { App, Inject } from '@midwayjs/core';
|
|
|
+import { Application, Context } from '@midwayjs/koa';
|
|
|
+import { get, head, isArray, isNull, isFinite, isString, isUndefined, last } from 'lodash';
|
|
|
+import { Opera } from './dbOpera';
|
|
|
+/**
|
|
|
+ * query默认的查询方式(哪个字段 是 = 还是 IN 还是 LIKE)的设置函数为getQueryColumnsOpera,如果有需要重写即可,返回object
|
|
|
+ * {
|
|
|
+ * column: Opera.xxx
|
|
|
+ * }
|
|
|
+ */
|
|
|
+export abstract class BaseServiceV2 {
|
|
|
+ @App()
|
|
|
+ app: Application;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ ctx: Context;
|
|
|
+
|
|
|
+ /**service的model,数据库操作 */
|
|
|
+ abstract model: any;
|
|
|
+ /**返回{column:查询方式}.特殊的查询需要写入,不写入默认采用 = */
|
|
|
+ getQueryColumnsOpera() {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+ /** */
|
|
|
+ Opera = Opera;
|
|
|
+ /**
|
|
|
+ * 默认查询列表
|
|
|
+ * @param query @Query直接获取的参数
|
|
|
+ * @param param @Query和调用前可组织的参数
|
|
|
+ * @param {object} operas 指定查询方式
|
|
|
+ * @property {number} param.skip 查询数据起始位置
|
|
|
+ * @property {number} param.limit 查询数据的数量
|
|
|
+ * @property {object} param.order 使用的排序 {${column}:'DESC'/'ASC'}
|
|
|
+ * @property {Array<string>} selects 指定显示的字段
|
|
|
+ */
|
|
|
+ async query(query: object = {}, meta: any = {}, operas?) {
|
|
|
+ let skip = get(meta, 'skip', 0);
|
|
|
+ let limit = get(meta, 'limit', 0);
|
|
|
+ const order = get(meta, 'order', {});
|
|
|
+ const selects = get(meta, 'selects', []);
|
|
|
+ const builder = await this.model.createQueryBuilder();
|
|
|
+ if (selects.length > 0) {
|
|
|
+ // 字段是直接传来的,正常限制,需要加上model的name.否则会导致什么字段都没有
|
|
|
+ const modelName = this.model.metadata.name;
|
|
|
+ builder.select(selects.map(i => `${modelName}.${i}`));
|
|
|
+ }
|
|
|
+ // 组织查询顺序
|
|
|
+ let orderObject: any = {};
|
|
|
+ // 如果有自定义顺序,则按照自定义顺序来, 没有自定义顺序,默认按创建时间的desc查询
|
|
|
+ if (Object.keys(order).length > 0) {
|
|
|
+ for (const column in order) orderObject[column] = order[column];
|
|
|
+ } else orderObject = { id: 'DESC' };
|
|
|
+ // 没有传如何查询,就获取query查询设置的默认查询方式
|
|
|
+ if (!operas) operas = this.getQueryColumnsOpera();
|
|
|
+ this.completeBuilderCondition(builder, query, operas);
|
|
|
+ // 分页
|
|
|
+ if (isString(skip)) {
|
|
|
+ skip = parseInt(skip);
|
|
|
+ if (isFinite(skip)) builder.skip(skip);
|
|
|
+ } else if (isFinite(skip)) builder.skip(skip);
|
|
|
+ if (isString(limit)) {
|
|
|
+ limit = parseInt(limit);
|
|
|
+ if (isFinite(limit)) builder.take(limit);
|
|
|
+ } else if (isFinite(limit)) builder.take(limit);
|
|
|
+ // 排序
|
|
|
+ builder.orderBy(orderObject);
|
|
|
+ // 执行
|
|
|
+ if (this.app.getEnv() !== 'prod' && this.app.getEnv() !== 'production') {
|
|
|
+ console.log(builder.getSql());
|
|
|
+ }
|
|
|
+ const data = await builder.getMany();
|
|
|
+ const total = await builder.getCount();
|
|
|
+ return { data, total };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param builder model的createQueryBuilder,只有到最后要查数据的时候才是异步的
|
|
|
+ * @param {object} query 查询条件
|
|
|
+ * @param {object} operas 指定查询方式
|
|
|
+ */
|
|
|
+ completeBuilderCondition(builder, query = {}, operas = {}) {
|
|
|
+ // 组织查询条件
|
|
|
+ if (!query) return;
|
|
|
+ const searchColumns = Object.keys(query);
|
|
|
+ if (searchColumns.length <= 0) return;
|
|
|
+ for (let i = 0; i < searchColumns.length; i++) {
|
|
|
+ const key = searchColumns[i];
|
|
|
+ const value = query[key];
|
|
|
+ if (!value) continue;
|
|
|
+ /**该字段的查询方式 */
|
|
|
+ const opera = get(operas, key);
|
|
|
+ /**builder的使用函数名 */
|
|
|
+ let method = 'where';
|
|
|
+ if (i === 0) method = 'where';
|
|
|
+ else method = 'andWhere';
|
|
|
+ let str;
|
|
|
+ let params;
|
|
|
+ // 需要给变量位置重命名,否则多个条件叠加后,都会使用第一个参数
|
|
|
+ const valueStr = `value${i}`;
|
|
|
+ let valueArr = [];
|
|
|
+ const strArr = [];
|
|
|
+ switch (opera) {
|
|
|
+ case this.Opera.Between:
|
|
|
+ str = `"${key}" Between :${valueStr}_1 AND :${valueStr}_2`;
|
|
|
+ params = { [`${valueStr}_1`]: head(value), [`${valueStr}_2`]: last(value) };
|
|
|
+ break;
|
|
|
+ case this.Opera.Not:
|
|
|
+ str = `"${key}" != :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.Like:
|
|
|
+ str = `"${key}" Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `%${value}%` };
|
|
|
+ break;
|
|
|
+ case this.Opera.LikeLeft:
|
|
|
+ str = `"${key}" Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `%${value}` };
|
|
|
+ break;
|
|
|
+ case this.Opera.LikeRight:
|
|
|
+ str = `"${key}" Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `${value}%` };
|
|
|
+ break;
|
|
|
+ case this.Opera.ILike:
|
|
|
+ str = `"${key}" Not Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `%${value}%` };
|
|
|
+ break;
|
|
|
+ case this.Opera.ILikeLeft:
|
|
|
+ str = `"${key}" Not Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `%${value}` };
|
|
|
+ break;
|
|
|
+ case this.Opera.ILikeRight:
|
|
|
+ str = `"${key}" Not Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `${value}%` };
|
|
|
+ break;
|
|
|
+ case this.Opera.LessThan:
|
|
|
+ str = `"${key}" < :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.LessThanOrEqual:
|
|
|
+ str = `"${key}" <= :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.MoreThan:
|
|
|
+ str = `"${key}" > :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.MoreThanOrEqual:
|
|
|
+ str = `"${key}" >= :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.In:
|
|
|
+ if (!isArray(value)) str = `"${key}" IN (:${valueStr})`;
|
|
|
+ else str = `"${key}" IN (:...${valueStr})`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.IsNull:
|
|
|
+ str = `"${key}" IS NULL`;
|
|
|
+ break;
|
|
|
+ case this.Opera.IsNotNull:
|
|
|
+ str = `"${key}" IS NOT NULL`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.Json:
|
|
|
+ params = {};
|
|
|
+ if (isArray(value)) valueArr = value;
|
|
|
+ else valueArr = [value];
|
|
|
+ for (let vi = 0; vi < valueArr.length; vi++) {
|
|
|
+ const v = valueArr[vi];
|
|
|
+ const mvalKey = `${valueStr}${vi}`;
|
|
|
+ const mstr = `JSONB_EXISTS("${key}", :${mvalKey})`;
|
|
|
+ strArr.push(mstr);
|
|
|
+ params[mvalKey] = v;
|
|
|
+ }
|
|
|
+ str = `(${strArr.join(' OR ')})`;
|
|
|
+ break;
|
|
|
+ case this.Opera.JsonObject:
|
|
|
+ const jokeys = key.split('.');
|
|
|
+ let jorootCol = head(jokeys);
|
|
|
+ let jolastKey = last(jokeys);
|
|
|
+ let jopath = jokeys.filter(f => f !== jorootCol && f !== jolastKey);
|
|
|
+ str = `"${jorootCol}" `;
|
|
|
+ for (const jok of jopath) {
|
|
|
+ str = `${str} -> ${jok}`;
|
|
|
+ }
|
|
|
+ str = `${str} ->> '${jolastKey}' = :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ break;
|
|
|
+ case this.Opera.JsonArrayObject:
|
|
|
+ /**
|
|
|
+ * 1.分割key,过来的属性默认以 x.y.z... 形式
|
|
|
+ * x:根子段;后面,数组中依次往下的属性名
|
|
|
+ */
|
|
|
+ const keys = key.split('.');
|
|
|
+ let numberValue;
|
|
|
+ if (isFinite(parseInt(value))) numberValue = parseInt(value);
|
|
|
+ let rootCol = head(keys);
|
|
|
+ let lastKey = last(keys);
|
|
|
+ let path = keys.filter(f => f !== rootCol && f !== lastKey);
|
|
|
+ const getObject = (path, lastKey, value) => {
|
|
|
+ let obj = {};
|
|
|
+ let mid = obj;
|
|
|
+ for (const k of path) {
|
|
|
+ mid[k] = {};
|
|
|
+ mid = mid[k];
|
|
|
+ }
|
|
|
+ mid[lastKey] = value;
|
|
|
+ return obj;
|
|
|
+ };
|
|
|
+ const obj = getObject(path, lastKey, value);
|
|
|
+ let newVal = JSON.stringify([obj]);
|
|
|
+ str = `"${rootCol}" @> :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: newVal };
|
|
|
+ if (numberValue) {
|
|
|
+ const numObj = getObject(path, lastKey, numberValue);
|
|
|
+ let numVal = JSON.stringify([numObj]);
|
|
|
+ const valueStrNum = `${valueStr}Num`;
|
|
|
+ str = `${str} OR "${rootCol}" @> :${valueStrNum}`;
|
|
|
+ params[valueStrNum] = numVal;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case this.Opera.Equal:
|
|
|
+ default:
|
|
|
+ const isString = this.columnIsString(key);
|
|
|
+ if (isString) {
|
|
|
+ // 字符串默认使用模糊查询
|
|
|
+ str = `"${key}" Like :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: `%${value}%` };
|
|
|
+ } else {
|
|
|
+ str = `"${key}" = :${valueStr}`;
|
|
|
+ params = { [`${valueStr}`]: value };
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!str) continue;
|
|
|
+ builder[method](str, params);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 单查询,不止用id还可以根据别的条件,默认全等,可以在调用时,进行查询方式设置
|
|
|
+ * @param {object} query 查询条件
|
|
|
+ * @param {object} operas 指定查询方式
|
|
|
+ */
|
|
|
+ async fetch(query: object, operas = {}) {
|
|
|
+ const builder = this.model.createQueryBuilder();
|
|
|
+ this.completeBuilderCondition(builder, query, operas);
|
|
|
+ const result = await builder.getOne();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**创建 */
|
|
|
+ async create(data: object) {
|
|
|
+ // 设置 创建数据的人
|
|
|
+ const user = get(this.ctx, 'user');
|
|
|
+ if (user) {
|
|
|
+ // 查询本表是否有data_onwer字段,有再添加.
|
|
|
+ const hasColumn = this.checkModelHaveColumn('data_onwer');
|
|
|
+ if (hasColumn) Object.assign(data, { data_owner: get(user, 'id') });
|
|
|
+ }
|
|
|
+ const result = await this.model.insert(data);
|
|
|
+ const id = get(result, 'identifiers.0.id');
|
|
|
+ // 没有id估计是出错了
|
|
|
+ if (!id) return;
|
|
|
+ const createData = await this.fetch({ id });
|
|
|
+ // 没有查出数据,也是有问题
|
|
|
+ if (!createData) return;
|
|
|
+ return createData;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**修改,单修改/多修改是统一修改为 */
|
|
|
+ async update(query: object = {}, data: object) {
|
|
|
+ // 没有范围的修改不允许执行
|
|
|
+ if (Object.keys(query).length <= 0) return;
|
|
|
+ // 处理数据, 只将是本表的字段拿出来保存
|
|
|
+ const columns = this.model.metadata.columns;
|
|
|
+ /**将array的列设置 转换为object,以便query使用*/
|
|
|
+ const columnsObject = {};
|
|
|
+ // 整理成object
|
|
|
+ for (const c of columns) columnsObject[c.propertyName] = c.type.toString();
|
|
|
+ const updateData = {};
|
|
|
+ const notDealColumn = ['created_time', 'update_time', 'data_owner', '__v'];
|
|
|
+ for (const column in columnsObject) {
|
|
|
+ if (notDealColumn.includes(column)) continue;
|
|
|
+ const val = data[column];
|
|
|
+ if (isNull(val) || isUndefined(val)) continue;
|
|
|
+ updateData[column] = val;
|
|
|
+ }
|
|
|
+ // 找到原数据
|
|
|
+ const originDataBuilder = this.model.createQueryBuilder();
|
|
|
+ this.completeBuilderCondition(originDataBuilder, query);
|
|
|
+ const origin_data = await originDataBuilder.getMany(query);
|
|
|
+ if (origin_data.length <= 0) return;
|
|
|
+ await this.model.update(query, updateData);
|
|
|
+ const new_data = await originDataBuilder.getMany(query);
|
|
|
+ if (new_data.length <= 0) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**删除,单删多删都行 */
|
|
|
+ async delete(query: object) {
|
|
|
+ // 没有范围不能删除,清空表需要特殊处理
|
|
|
+ if (query && Object.keys(query).length <= 0) return;
|
|
|
+ // 删除前,先查出来数据, 找到原数据
|
|
|
+ const originDataBuilder = this.model.createQueryBuilder();
|
|
|
+ this.completeBuilderCondition(originDataBuilder, query);
|
|
|
+ const origin_data = await originDataBuilder.getMany(query);
|
|
|
+ if (origin_data.length <= 0) return;
|
|
|
+ const result = await this.model.delete(query);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**检查有没有指定字段,是从model的映射关系中查询下 */
|
|
|
+ private checkModelHaveColumn(columnName: string) {
|
|
|
+ const columns = this.model.metadata.columns;
|
|
|
+ const has = columns.find(f => get(f, 'propertyName') === columnName);
|
|
|
+ return !isUndefined(has);
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 检查字段是否是字符串字段
|
|
|
+ * @param {string} columnName 字段名
|
|
|
+ */
|
|
|
+ private columnIsString(columnName: string) {
|
|
|
+ const columns = this.model.metadata.columns;
|
|
|
+ const colSetting = columns.find(f => get(f, 'propertyName') === columnName);
|
|
|
+ if (!colSetting) return false;
|
|
|
+ const type = get(colSetting, 'type');
|
|
|
+ if (!type) return false;
|
|
|
+ return type === 'character varying' || type === 'text';
|
|
|
+ }
|
|
|
+}
|