refactor: 确立独立子工程架构 (backend, admin, mpuniapp) 并同步更新 AGENTS.md 规范
这个提交包含在:
@@ -0,0 +1,19 @@
|
||||
import { ModuleConfig } from '@cool-midway/core';
|
||||
|
||||
/**
|
||||
* 模块配置
|
||||
*/
|
||||
export default () => {
|
||||
return {
|
||||
// 模块名称
|
||||
name: '数据回收',
|
||||
// 模块描述
|
||||
description: '收集被删除的数据,管理和恢复',
|
||||
// 中间件,只对本模块有效
|
||||
middlewares: [],
|
||||
// 中间件,全局有效
|
||||
globalMiddlewares: [],
|
||||
// 模块加载顺序,默认为0,值越大越优先加载
|
||||
order: 0,
|
||||
} as ModuleConfig;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { BaseSysUserEntity } from './../../../base/entity/sys/user';
|
||||
import { RecycleDataEntity } from './../../entity/data';
|
||||
import { Body, Inject, Post, Provide } from '@midwayjs/core';
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { RecycleDataService } from '../../service/data';
|
||||
|
||||
/**
|
||||
* 数据回收
|
||||
*/
|
||||
@Provide()
|
||||
@CoolController({
|
||||
api: ['info', 'page'],
|
||||
entity: RecycleDataEntity,
|
||||
pageQueryOp: {
|
||||
keyWordLikeFields: ['b.name', 'a.url'],
|
||||
select: ['a.*', 'b.name as userName'],
|
||||
join: [
|
||||
{
|
||||
entity: BaseSysUserEntity,
|
||||
alias: 'b',
|
||||
condition: 'a.userId = b.id',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
export class AdminRecycleDataController extends BaseController {
|
||||
@Inject()
|
||||
recycleDataService: RecycleDataService;
|
||||
|
||||
@Post('/restore', { summary: '恢复数据' })
|
||||
async restore(@Body('ids') ids: number[]) {
|
||||
await this.recycleDataService.restore(ids);
|
||||
return this.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { BaseEntity, transformerJson } from '../../base/entity/base';
|
||||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 数据回收站 软删除的时候数据会回收到该表
|
||||
*/
|
||||
@Entity('recycle_data')
|
||||
export class RecycleDataEntity extends BaseEntity {
|
||||
@Column({ comment: '表', type: 'json', transformer: transformerJson })
|
||||
entityInfo: {
|
||||
// 数据源名称
|
||||
dataSourceName: string;
|
||||
// entity
|
||||
entity: string;
|
||||
};
|
||||
|
||||
@Index()
|
||||
@Column({ comment: '操作人', nullable: true })
|
||||
userId: number;
|
||||
|
||||
@Column({
|
||||
comment: '被删除的数据',
|
||||
type: 'json',
|
||||
transformer: transformerJson,
|
||||
})
|
||||
data: object[];
|
||||
|
||||
@Column({ comment: '请求的接口', nullable: true })
|
||||
url: string;
|
||||
|
||||
@Column({
|
||||
comment: '请求参数',
|
||||
nullable: true,
|
||||
type: 'json',
|
||||
transformer: transformerJson,
|
||||
})
|
||||
params: string;
|
||||
|
||||
@Column({ comment: '删除数据条数', default: 1 })
|
||||
count: number;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { CoolEvent, EVENT, Event } from '@cool-midway/core';
|
||||
import { Inject } from '@midwayjs/core';
|
||||
import { RecycleDataService } from '../service/data';
|
||||
|
||||
/**
|
||||
* 接受数据事件
|
||||
*/
|
||||
@CoolEvent()
|
||||
export class RecycleDataEvent {
|
||||
@Inject()
|
||||
recycleDataService: RecycleDataService;
|
||||
|
||||
/**
|
||||
* 数据被删除
|
||||
* @param params
|
||||
*/
|
||||
@Event(EVENT.SOFT_DELETE)
|
||||
async softDelete(params) {
|
||||
await this.recycleDataService.record(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
Provide,
|
||||
Inject,
|
||||
CommonSchedule,
|
||||
TaskLocal,
|
||||
FORMAT,
|
||||
} from '@midwayjs/core';
|
||||
import { ILogger } from '@midwayjs/logger';
|
||||
import { RecycleDataService } from '../service/data';
|
||||
|
||||
/**
|
||||
* 数据定时清除定时任务
|
||||
*/
|
||||
@Provide()
|
||||
export class BaseRecycleSchedule implements CommonSchedule {
|
||||
@Inject()
|
||||
recycleDataService: RecycleDataService;
|
||||
|
||||
@Inject()
|
||||
logger: ILogger;
|
||||
|
||||
// 定时执行的具体任务
|
||||
@TaskLocal(FORMAT.CRONTAB.EVERY_DAY)
|
||||
async exec() {
|
||||
this.logger.info('清除回收站数据定时任务开始执行');
|
||||
const startTime = Date.now();
|
||||
await this.recycleDataService.clear();
|
||||
this.logger.info(
|
||||
`清除回收站数据定时任务结束,耗时:${Date.now() - startTime}ms`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { RecycleDataEntity } from './../entity/data';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||
import { LessThan, Repository } from 'typeorm';
|
||||
import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import { BaseSysConfService } from '../../base/service/sys/conf';
|
||||
|
||||
/**
|
||||
* 数据回收
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class RecycleDataService extends BaseService {
|
||||
@InjectEntityModel(RecycleDataEntity)
|
||||
recycleDataEntity: Repository<RecycleDataEntity>;
|
||||
|
||||
@Inject()
|
||||
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
@Inject()
|
||||
baseSysConfService: BaseSysConfService;
|
||||
|
||||
/**
|
||||
* 恢复数据
|
||||
* @param ids
|
||||
*/
|
||||
async restore(ids: number[]) {
|
||||
for (const id of ids) {
|
||||
const info = await this.recycleDataEntity.findOneBy({ id });
|
||||
if (!info) {
|
||||
continue;
|
||||
}
|
||||
let entityModel = this.typeORMDataSourceManager
|
||||
.getDataSource(info.entityInfo.dataSourceName)
|
||||
.getRepository(info.entityInfo.entity);
|
||||
await entityModel.save(info.data);
|
||||
await this.recycleDataEntity.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录数据
|
||||
* @param params
|
||||
*/
|
||||
async record(params) {
|
||||
const { ctx, data, entity } = params;
|
||||
if (!ctx?.req) return;
|
||||
const dataSourceName =
|
||||
this.typeORMDataSourceManager.getDataSourceNameByModel(entity.target);
|
||||
const url = ctx?.url;
|
||||
await this.recycleDataEntity.save({
|
||||
entityInfo: {
|
||||
dataSourceName,
|
||||
entity: entity.target.name,
|
||||
},
|
||||
url,
|
||||
params:
|
||||
ctx?.req.method === 'GET' ? ctx?.request.query : ctx?.request.body,
|
||||
data,
|
||||
count: data.length,
|
||||
userId: _.startsWith(url, '/admin/') ? ctx?.admin.userId : ctx?.user?.id,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志
|
||||
* @param isAll 是否清除全部
|
||||
*/
|
||||
async clear(isAll?) {
|
||||
if (isAll) {
|
||||
await this.recycleDataEntity.clear();
|
||||
return;
|
||||
}
|
||||
const keepDay = await this.baseSysConfService.getValue('recycleKeep');
|
||||
if (keepDay) {
|
||||
const beforeDate = moment().add(-keepDay, 'days').startOf('day').toDate();
|
||||
await this.recycleDataEntity.delete({ createTime: LessThan(beforeDate) });
|
||||
} else {
|
||||
await this.recycleDataEntity.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户