feat: 新增电工APP端与定时任务管理功能

- 新增电工APP独立鉴权中间件与登录注册接口
- 新增定时任务管理模块,支持任务配置、执行日志与手动触发
- 新增运维费定价与电工派单成本价目管理
- 新增电工资质证照管理与到期提醒定时任务
- 完善数据库实体与DTO校验,优化权限控制
This commit is contained in:
andyesfly committed 2026-10-02 16:08:00 +08:00
1 parent 1803ad679c
commit f4454ad014
57 files changed
+9388 -200

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
import { Inject } from "@midwayjs/core";
import { Job, IJob } from "@midwayjs/cron";
import { NoticeService } from "../service/notice.service";
/**
* 电工资质证照到期提醒定时任务
* 每天 08:00 扫描 worker_cert 中 30 天内到期或已过期的证照,
* 为每个 ADMIN 生成一条站内通知(幂等,已存在的提醒不会重复生成)。
*/
@Job({ cronTime: "0 0 8 * * *", start: true, timeZone: "Asia/Shanghai" })
export class CertExpireTask implements IJob {
@Inject()
noticeService!: NoticeService;
async onTick() {
const result = await this.noticeService.scanCertExpireAndNotify();
console.log(`[cert-expire] 证照到期扫描完成:扫描 ${result.scanned} 条,新增通知 ${result.created} 条`);
}
}
+24
View File
@@ -0,0 +1,24 @@
import dayjs from "dayjs";
import type { BuiltinJob } from "./job_registry";
/**
* 示例心跳任务:用于验证调度链路是否打通。
* 默认每 5 分钟执行一次,写一条 info 日志并返回本次心跳时间。
*/
export const demoHeartbeatJob: BuiltinJob = {
jobKey: "demo_heartbeat",
jobName: "示例心跳",
description: "用于验证调度链路的示例任务,按周期写入一条心跳日志",
defaultCron: "*/5 * * * *",
defaultParams: { message: "heartbeat" },
paramFields: [
{ key: "message", label: "心跳内容", type: "string", placeholder: "默认 heartbeat" }
],
handler: (params, ctx) => {
throw new Error("E2E 重试验证");
const now = dayjs().format("YYYY-MM-DD HH:mm:ss");
const message = params?.message ?? "heartbeat";
ctx.runtime.logger.info(`[${ctx.jobKey}] 心跳 ${now}(message=${message})`);
return `心跳正常:${now}(message=${message})`;
}
};
+59
View File
@@ -0,0 +1,59 @@
import { demoHeartbeatJob } from "./demo_heartbeat.task";
import { logCleanJob } from "./log_clean.task";
export type JobParamType = "number" | "string";
/** 可编辑参数白名单字段(前端表单由它驱动渲染) */
export interface JobParamField {
key: string;
label: string;
type: JobParamType;
min?: number; // type=number 时的最小值
max?: number; // type=number 时的最大值
placeholder?: string;
}
export interface JobLogger {
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string, ...args: any[]): void;
}
/** 调度器注入给任务的运行时能力(避免任务模块直接依赖 DI 与 Sequelize) */
export interface JobRuntime {
logger: JobLogger;
/** 清理 sys_operation_log 中早于 keepDays 天的记录,返回删除条数 */
cleanOperationLogs(keepDays: number): Promise<number>;
/** 清理 sys_job_log 中早于 keepDays 天的记录,返回删除条数 */
cleanJobLogs(keepDays: number): Promise<number>;
}
export interface JobHandlerContext {
jobKey: string;
jobName: string;
triggerType: number; // 1定时自动 2手动触发 3失败重试
attempt: number; // 第几次尝试,从 1 开始
runtime: JobRuntime;
}
/** 返回值作为本次执行的结果摘要(message);抛异常视为执行失败 */
export type JobHandler = (
params: Record<string, any>,
ctx: JobHandlerContext
) => Promise<string | void> | string | void;
export interface BuiltinJob {
jobKey: string;
jobName: string;
description: string;
defaultCron: string;
defaultParams: Record<string, any>;
paramFields: JobParamField[];
handler: JobHandler;
}
export const BUILTIN_JOBS: BuiltinJob[] = [demoHeartbeatJob, logCleanJob];
export function getBuiltinJob(jobKey: string): BuiltinJob | undefined {
return BUILTIN_JOBS.find(job => job.jobKey === jobKey);
}
+22
View File
@@ -0,0 +1,22 @@
import type { BuiltinJob } from "./job_registry";
/**
* 日志清理任务:清理早于 keepDays 天的操作日志与任务日志。
* 默认保留 90 天,每日 03:00 执行。
*/
export const logCleanJob: BuiltinJob = {
jobKey: "log_clean",
jobName: "日志清理",
description: "清理早于 keepDays 天的操作日志与任务日志",
defaultCron: "0 3 * * *",
defaultParams: { keepDays: 90 },
paramFields: [
{ key: "keepDays", label: "保留天数", type: "number", min: 1, max: 3650 }
],
handler: async (params, ctx) => {
const keepDays = Number(params?.keepDays ?? 90);
const opCount = await ctx.runtime.cleanOperationLogs(keepDays);
const jobCount = await ctx.runtime.cleanJobLogs(keepDays);
return `清理操作日志 ${opCount} 条、任务日志 ${jobCount} 条`;
}
};