Explorar o código

修改service基类,查询基类.添加事务工具,获取model函数

lrf %!s(int64=2) %!d(string=hai) anos
pai
achega
d6cfdcf468
Modificáronse 8 ficheiros con 260 adicións e 39 borrados
  1. 3 0
      .eslintrc.json
  2. 3 2
      .prettierrc.js
  3. 11 0
      ReadMe.md
  4. 15 1
      src/index.ts
  5. 11 4
      src/interface/SearchBase.ts
  6. 38 32
      src/service/BaseService.ts
  7. 18 0
      src/util/getModel.ts
  8. 161 0
      src/util/transactions.ts

+ 3 - 0
.eslintrc.json

@@ -3,5 +3,8 @@
   "ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
   "env": {
     "jest": true
+  },
+  "rules": {
+    "prettier/prettier": "off"
   }
 }

+ 3 - 2
.prettierrc.js

@@ -1,3 +1,4 @@
 module.exports = {
-  ...require('mwts/.prettierrc.json')
-}
+  ...require('mwts/.prettierrc.json'),
+  printWidth: 200,
+};

+ 11 - 0
ReadMe.md

@@ -3,3 +3,14 @@
 ## publish.bat是npm发布的脚本,使用之前请把修改的东西提交保存
 
 ## 1.所有的东西都要导出来才能用,包括相对地址引用的内容
+
+
+## npm 发布相关
+
+## `npm version patch` 更新版本号
+
+## `npm publish` 发布
+
+## `npm unpublish <包名(@版本)>` 删除指定包的依赖(指定版本)
+
+## `npm i free-midway-component@latest` 项目中升级最新版本

+ 15 - 1
src/index.ts

@@ -1,10 +1,24 @@
+/**框架设置 */
 export { FreeConfiguration as Configuration } from './configuration';
+/**controller基类 */
 export { BaseController } from './controller/BaseController';
+/**service基类 */
 export { BaseService } from './service/BaseService';
+/**自定义错误 */
 export { FrameworkErrorEnum, ServiceError } from './error/service.error';
+/**查询基类 */
 export { SearchBase } from './interface/SearchBase';
-export { VOBase } from './interface/VOBase';
+/**数据库表设置基类 */
 export { BaseModel } from './entity/BaseModel';
+/**数据元信息插件 */
 export * as meta from './entity/meta';
+/**返回结果格式化基类 */
+export { VOBase } from './interface/VOBase';
+/**返回结果格式化插件 */
 export { ResponseMiddleware } from './middleware/response.middleware';
+/**默认错误格式化过滤器 */
 export { DefaultErrorFilter } from './filter/default.filter';
+/**获取model实例的工具函数 */
+export { GetModel } from './util/getModel';
+/**数据库业务服务 */
+export { TransactionService } from './util/transactions';

+ 11 - 4
src/interface/SearchBase.ts

@@ -6,23 +6,30 @@ import _ = require('lodash');
  * @param {Array} like_prop 范围查询字段数组
  */
 export class SearchBase {
-  constructor({ like_prop = [] }) {
+  constructor({ like_prop = [], props = [] }) {
     this.like_prop = like_prop;
+    this.props = props;
   }
   /** 模糊查询字段数组 */
   like_prop: Array<string>;
+  /** class定义字段 */
+  props: Array<string>;
 
   /**
    * 此处将字段转换为使用的方式
    */
   getFilter() {
-    const exclude = ['like_prop'];
-    const keys = Object.keys(this).filter(f => !exclude.includes(f));
+    const others = _.omit(this, ['like_prop', 'props']);
+    let keys = Object.keys(others);
+    keys = keys.filter(f => this.props.includes(f));
     const result = {};
     for (const i of keys) {
       const value = this[i];
       // 值不存在或者为空字符串,过滤掉
-      if (!value || value === '') continue;
+      if (!value || value === '') {
+        delete this[i];
+        continue;
+      }
       if (this.like_prop.includes(i)) {
         // 处理模糊查询
         result[i] = new RegExp(value);

+ 38 - 32
src/service/BaseService.ts

@@ -4,6 +4,8 @@ import { Application, Context } from '@midwayjs/koa';
 import { App, Inject } from '@midwayjs/decorator';
 import _ = require('lodash');
 import { FrameworkErrorEnum, ServiceError } from '../error/service.error';
+import { GetModel } from '../util/getModel';
+import { SearchBase } from '../../dist';
 /**
  * Service基类,实现了一些基础的crud
  */
@@ -18,28 +20,25 @@ export abstract class BaseService<T extends AnyParamConstructor<any>> {
 
   /**
    * 列表查询
-   * @param {object} filter 查询条件
+   * @param {SearchBase} filter 查询条件
    * @param {object} pageOptions 分页条件
    * @param {Boolean} lean 是否使用JavaScript形式数据;false为mongoose的模型实例数据
+   * @param {Boolean} populate 是否进行ref关联数据
    * @returns {Promise<object>} 返回列表
    */
-  async query(
-    filter: object = {},
-    pageOptions: object = {},
-    lean = true
-  ): Promise<object> {
-    const dup = _.cloneDeep(filter);
+  async query(filter: SearchBase, pageOptions: object = {}, lean = true, populate = true): Promise<object> {
+    const dup = _.cloneDeep(filter.getFilter());
     const data = await this.model.find(dup, {}, { ...pageOptions }).lean(lean);
     return data;
   }
 
   /**
    * 数据总数查询
-   * @param {object} filter 查询条件
+   * @param {SearchBase} filter 查询条件
    * @returns {number} 数据总数
    */
-  async count(filter: object = {}): Promise<number> {
-    const dup = _.cloneDeep(filter);
+  async count(filter: SearchBase): Promise<number> {
+    const dup = _.cloneDeep(filter.getFilter());
     const total = await this.model.count(dup);
     return total;
   }
@@ -62,28 +61,23 @@ export abstract class BaseService<T extends AnyParamConstructor<any>> {
    * @returns {object}
    */
   async updateOne(id: string, body: object): Promise<string> {
-    if (!id)
-      throw new ServiceError('缺少查询信息', FrameworkErrorEnum.NEED_PARAMS);
+    if (!id) throw new ServiceError('缺少查询信息', FrameworkErrorEnum.NEED_PARAMS);
     const num = await this.model.count({ _id: id });
-    if (num <= 0)
-      throw new ServiceError(
-        '未找到要修改的数据',
-        FrameworkErrorEnum.NOT_FOUND_DATA
-      );
+    if (num <= 0) throw new ServiceError('未找到要修改的数据', FrameworkErrorEnum.NOT_FOUND_DATA);
     await this.model.updateOne({ _id: id }, body);
     return 'ok';
   }
 
   /**
    * 多修改
-   * @param {object} filter 要修改的对象查询条件
+   * @param {SearchBase} filter 要修改的对象查询条件
    * @param {object} body 要修改的内容
    * @returns {object}
    */
-  async updateMany(filter: object = {}, body: object): Promise<string> {
-    if (!body)
-      throw new ServiceError('缺少修改信息', FrameworkErrorEnum.NEED_BODY);
-    await this.model.updateMany(filter, body);
+  async updateMany(filter: SearchBase, body: object): Promise<string> {
+    if (!body) throw new ServiceError('缺少修改信息', FrameworkErrorEnum.NEED_BODY);
+    const dup = _.cloneDeep(filter.getFilter());
+    await this.model.updateMany(dup, body);
     return 'ok';
   }
 
@@ -93,25 +87,21 @@ export abstract class BaseService<T extends AnyParamConstructor<any>> {
    * @returns {object}
    */
   async delete(id: string): Promise<string> {
-    if (!id)
-      throw new ServiceError('缺少数据信息', FrameworkErrorEnum.NEED_PARAMS);
+    if (!id) throw new ServiceError('缺少数据信息', FrameworkErrorEnum.NEED_PARAMS);
     await this.model.deleteOne({ _id: id });
     return 'ok';
   }
 
   /**
    * 多删除
-   * @param filter 要删除的数据查询条件
+   * @param {SearchBase} filter 要删除的数据查询条件
    * @returns {object}
    */
-  async deleteMany(filter: object = {}): Promise<string> {
+  async deleteMany(filter: SearchBase): Promise<string> {
     const keys = Object.keys(filter);
-    if (keys.length <= 0)
-      throw new ServiceError(
-        '暂不提供清库服务',
-        FrameworkErrorEnum.SERVICE_FAULT
-      );
-    await this.model.deleteMany(filter);
+    if (keys.length <= 0) throw new ServiceError('暂不提供清库服务', FrameworkErrorEnum.SERVICE_FAULT);
+    const dup = _.cloneDeep(filter.getFilter());
+    await this.model.deleteMany(dup);
     return 'ok';
   }
 
@@ -134,4 +124,20 @@ export abstract class BaseService<T extends AnyParamConstructor<any>> {
     const data = await this.model.insertMany(body);
     return data;
   }
+  /**
+   * 获取本服务默认表的ref关系
+   */
+  async getRefs() {
+    const schema = _.get(this.model, 'schema.tree');
+    const refs = [];
+    for (const key in schema) {
+      const f = schema[key];
+      if (_.isObject(f) && _.get(f, 'ref')) {
+        const model = GetModel(key);
+        const path = key;
+        refs.push({ path, model });
+      }
+    }
+    return refs;
+  }
 }

+ 18 - 0
src/util/getModel.ts

@@ -0,0 +1,18 @@
+import { getModelForClass, getClass } from '@typegoose/typegoose';
+import { AnyParamConstructor } from '@typegoose/typegoose/lib/types';
+import { FrameworkErrorEnum, ServiceError } from '../error/service.error';
+import _ = require('lodash');
+export function GetModel(modelName) {
+  let model;
+  try {
+    model = getModelForClass(
+      getClass(_.upperFirst(modelName)) as AnyParamConstructor<any>
+    );
+  } catch (error) {
+    throw new ServiceError('生成模型错误', FrameworkErrorEnum.SERVICE_FAULT);
+  }
+  if (!model)
+    throw new ServiceError('未找到模型', FrameworkErrorEnum.SERVICE_FAULT);
+
+  return model;
+}

+ 161 - 0
src/util/transactions.ts

@@ -0,0 +1,161 @@
+import { Provide } from '@midwayjs/decorator';
+import { FrameworkErrorEnum, ServiceError } from '../error/service.error';
+import { Types } from 'mongoose';
+import _ = require('lodash');
+import { GetModel } from './getModel';
+/**状态码 */
+enum StatusCode {
+  /**待处理 */
+  PADDING = 'padding',
+  /**处理成功 */
+  SUCCESS = 'success',
+  // /**处理失败 */
+  // ERROR = 'error',
+  ROLLBACK = 'rollback',
+}
+/**操作码 */
+enum OperaCode {
+  /**创建 */
+  CREATE = 'create',
+  /**修改 */
+  UPDATE = 'update',
+  /**删除 */
+  DELETE = 'delete',
+}
+/**任务接口 */
+interface IMission {
+  /**表名 */
+  model: string;
+  /**操作 */
+  opera: OperaCode;
+  /**数据 */
+  data?: any;
+  /**任务状态 */
+  status: StatusCode;
+  /**任务操作前的数据(添加不需要) */
+  backUp?: any;
+  /**任务范围(添加不需要) */
+  query?: object;
+}
+
+@Provide()
+export class TransactionService {
+  mission: Array<IMission> = [];
+  /**
+   * 事务添加
+   * @param modelName 表名
+   * @param data 添加的数据
+   */
+  insert(modelName: string, data: object): Types.ObjectId {
+    const _id = new Types.ObjectId();
+    Object.assign(data, { _id });
+    const mission: IMission = {
+      model: modelName,
+      opera: OperaCode.CREATE,
+      data,
+      status: StatusCode.PADDING,
+    };
+    this.mission.push(mission);
+    return _id;
+  }
+
+  /**
+   * 事务修改
+   * @param modelName 表名
+   * @param query 修改的数据范围
+   * @param data 要修改为的数据
+   */
+  update(modelName: string, query: object, data: object) {
+    const mission: IMission = {
+      model: modelName,
+      opera: OperaCode.UPDATE,
+      data,
+      status: StatusCode.PADDING,
+      query,
+    };
+    this.mission.push(mission);
+  }
+  /**
+   * 事务删除
+   * @param modelName 表名
+   * @param query 删除范围
+   */
+  delete(modelName: string, query: object) {
+    const mission: IMission = {
+      model: modelName,
+      opera: OperaCode.DELETE,
+      status: StatusCode.PADDING,
+      query,
+    };
+    this.mission.push(mission);
+  }
+
+  async run() {
+    // 需要备份的操作先备份
+    await this.toRestoreData();
+    for (const m of this.mission) {
+      const { model: modelName, opera, status, data, query } = m;
+      // 判断当前任务状态
+      // 成功:下一步;
+      if (status === StatusCode.SUCCESS) continue;
+      try {
+        // 先找到model
+        const model = GetModel(modelName);
+        // 再看opera,进行操作
+        if (opera === OperaCode.CREATE) {
+          await model.create(data);
+        } else if (opera === OperaCode.UPDATE) {
+          await model.updateMany(query, data);
+        } else if (opera === OperaCode.DELETE) {
+          await model.deleteMany(query);
+        }
+        m.status = StatusCode.SUCCESS;
+      } catch (error) {
+        // 发生错误后,当前步骤状态为padding,之前均为success.将success回滚
+        // 更改标记,中断程序
+        throw new ServiceError(error, FrameworkErrorEnum.SERVICE_FAULT);
+        break;
+      }
+    }
+  }
+
+  /**
+   * 针对每步的操作,进行数据备份,以便回滚还原
+   */
+  private async toRestoreData() {
+    for (const m of this.mission) {
+      const { model: modelName, opera, status, query } = m;
+      // 只进行未处理的备份,处理过的不再备份
+      if (status !== StatusCode.PADDING) continue;
+      // 数据备份;创建不需要备份,没有
+      if (opera === OperaCode.CREATE) continue;
+      const model = GetModel(modelName);
+      const backUp = await model.find(query).lean();
+
+      m.backUp = backUp;
+    }
+  }
+  /**
+   * 回滚,将success的任务全部回滚
+   */
+  async rollback() {
+    const needRollback = this.mission.filter(
+      f => f.status === StatusCode.SUCCESS
+    );
+    for (const m of needRollback) {
+      const { model: modelName, opera, data, backUp } = m;
+      const model = GetModel(modelName);
+      if (opera === OperaCode.CREATE) {
+        const _id = _.get(data, '_id');
+        await model.deleteOne({ _id });
+      } else if (opera === OperaCode.UPDATE) {
+        for (const bd of backUp) {
+          await model.updateOne({ _id: bd._id }, bd);
+        }
+      } else if (opera === OperaCode.DELETE) {
+        await model.insertMany(backUp);
+      }
+      m.status = StatusCode.ROLLBACK;
+    }
+  }
+}