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

@@ -0,0 +1,123 @@
-- 定时任务管理功能扩展
-- 背景:系统周期性后台作业(物化视图刷新、日志清理等)分散在 pg_cron 与应用 @midwayjs/cron 注解中,
-- 运维无法在管理端查看/启停/调频/追溯执行记录。本期新增「自研轻量调度器 + 内置任务注册表」,
-- 新增 sys_job(任务定义与调度配置)、sys_job_log(执行日志)两张表,
-- 并挂载系统管理下的「定时任务」菜单与按钮权限。
-- 幂等:建表/索引用 IF NOT EXISTS;菜单/角色绑定用 ON CONFLICT;任务种子用 ON CONFLICT (job_key) DO NOTHING。
-- 注意:菜单 id 47-50,避开并行规格 add-om-pricing-and-worker-registry 已占用的 34-46。
BEGIN;
-- ------------------------------------------------------------
-- 1) 任务定义与调度配置表
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_job (
id BIGSERIAL PRIMARY KEY,
job_key VARCHAR(64) NOT NULL UNIQUE, -- 任务键,与代码注册表一一对应
job_name VARCHAR(64) NOT NULL, -- 任务名称
cron_expression VARCHAR(64) NOT NULL, -- cron 表达式
params JSONB NOT NULL DEFAULT '{}'::jsonb, -- 任务参数(受白名单约束)
retry_times SMALLINT NOT NULL DEFAULT 0, -- 失败后最大重试次数 0-5
retry_interval INT NOT NULL DEFAULT 60, -- 失败后重试间隔(秒 10-3600)
status SMALLINT NOT NULL DEFAULT 1, -- 1启用 0停用
remark VARCHAR(255), -- 备注
last_run_at TIMESTAMPTZ, -- 上次执行时间
last_run_status SMALLINT, -- 上次执行状态:1成功 2失败 3跳过
last_run_cost_ms INT, -- 上次执行耗时(毫秒)
last_error TEXT, -- 上次失败原因(成功时清空)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_job IS '定时任务定义与调度配置表';
COMMENT ON COLUMN sys_job.id IS '自增主键';
COMMENT ON COLUMN sys_job.job_key IS '任务键,与代码注册表一一对应';
COMMENT ON COLUMN sys_job.job_name IS '任务名称';
COMMENT ON COLUMN sys_job.cron_expression IS 'cron 表达式(分 时 日 月 周,支持 6 段带秒)';
COMMENT ON COLUMN sys_job.params IS '任务参数(受白名单字段约束)';
COMMENT ON COLUMN sys_job.retry_times IS '失败后最大重试次数 0-5';
COMMENT ON COLUMN sys_job.retry_interval IS '失败后重试间隔(秒 10-3600)';
COMMENT ON COLUMN sys_job.status IS '状态:1启用 0停用';
COMMENT ON COLUMN sys_job.remark IS '备注';
COMMENT ON COLUMN sys_job.last_run_at IS '上次执行时间';
COMMENT ON COLUMN sys_job.last_run_status IS '上次执行状态:1成功 2失败 3跳过';
COMMENT ON COLUMN sys_job.last_run_cost_ms IS '上次执行耗时(毫秒)';
COMMENT ON COLUMN sys_job.last_error IS '上次失败原因(成功时清空)';
COMMENT ON COLUMN sys_job.created_at IS '创建时间';
COMMENT ON COLUMN sys_job.updated_at IS '更新时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_job_key ON sys_job (job_key);
CREATE INDEX IF NOT EXISTS idx_sys_job_status ON sys_job (status);
-- ------------------------------------------------------------
-- 2) 任务执行日志表
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_job_log (
id BIGSERIAL PRIMARY KEY,
job_id BIGINT NOT NULL REFERENCES sys_job(id) ON DELETE CASCADE, -- 任务ID(→ sys_job.id)
job_key VARCHAR(64) NOT NULL, -- 任务键(冗余,便于查询)
job_name VARCHAR(64) NOT NULL, -- 任务名称(冗余)
trigger_type SMALLINT NOT NULL DEFAULT 1, -- 触发方式:1定时自动 2手动触发 3失败重试
status SMALLINT NOT NULL, -- 1成功 2失败 3跳过
attempt SMALLINT NOT NULL DEFAULT 1, -- 第几次尝试
started_at TIMESTAMPTZ, -- 开始时间
finished_at TIMESTAMPTZ, -- 结束时间
cost_ms INT, -- 执行耗时(毫秒)
message TEXT, -- 结果摘要
error_stack TEXT, -- 异常堆栈(截断 4000 字符)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_job_log IS '定时任务执行日志表';
COMMENT ON COLUMN sys_job_log.id IS '自增主键';
COMMENT ON COLUMN sys_job_log.job_id IS '任务ID(→ sys_job.id)';
COMMENT ON COLUMN sys_job_log.job_key IS '任务键(冗余,便于查询)';
COMMENT ON COLUMN sys_job_log.job_name IS '任务名称(冗余)';
COMMENT ON COLUMN sys_job_log.trigger_type IS '触发方式:1定时自动 2手动触发 3失败重试';
COMMENT ON COLUMN sys_job_log.status IS '执行状态:1成功 2失败 3跳过';
COMMENT ON COLUMN sys_job_log.attempt IS '第几次尝试';
COMMENT ON COLUMN sys_job_log.started_at IS '开始时间';
COMMENT ON COLUMN sys_job_log.finished_at IS '结束时间';
COMMENT ON COLUMN sys_job_log.cost_ms IS '执行耗时(毫秒)';
COMMENT ON COLUMN sys_job_log.message IS '结果摘要';
COMMENT ON COLUMN sys_job_log.error_stack IS '异常堆栈(截断 4000 字符)';
COMMENT ON COLUMN sys_job_log.created_at IS '创建时间';
CREATE INDEX IF NOT EXISTS idx_sys_job_log_job_created ON sys_job_log (job_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sys_job_log_created ON sys_job_log (created_at);
-- ------------------------------------------------------------
-- 3) 菜单与权限(系统管理 parent_id=6 下新增「定时任务」菜单 + 3 个按钮权限)
-- ------------------------------------------------------------
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(47, 6, '定时任务', '/system/job', 'system/job/index', 'ClockCircleOutlined', 6, 2, 'system:job', 1),
(48, 47, '编辑定时任务', NULL, NULL, NULL, 1, 3, 'system:job:update', 1),
(49, 47, '立即执行', NULL, NULL, NULL, 2, 3, 'system:job:run', 1),
(50, 47, '清理日志', NULL, NULL, NULL, 3, 3, 'system:job:log:clear', 1)
ON CONFLICT (id) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
menu_name = EXCLUDED.menu_name,
menu_path = EXCLUDED.menu_path,
component = EXCLUDED.component,
icon = EXCLUDED.icon,
sort_order = EXCLUDED.sort_order,
menu_type = EXCLUDED.menu_type,
permission = EXCLUDED.permission,
visible = EXCLUDED.visible;
-- 角色绑定:ADMIN(role_id=1)拥有 47~50
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
(1, 47), (1, 48), (1, 49), (1, 50)
ON CONFLICT (role_id, menu_id) DO NOTHING;
-- 重置 sys_menu 自增序列,避免后续插入 id 冲突
SELECT setval('sys_menu_id_seq', 50, true);
-- ------------------------------------------------------------
-- 4) sys_job 内置任务种子(按 job_key 幂等)
-- ------------------------------------------------------------
INSERT INTO sys_job (job_key, job_name, cron_expression, params, retry_times, retry_interval, status, remark) VALUES
('demo_heartbeat', '示例心跳', '*/5 * * * *', '{"message":"heartbeat"}'::jsonb, 0, 60, 1,
'示例心跳任务:用于验证调度链路,默认每 5 分钟写入一条 info 日志'),
('log_clean', '日志清理', '0 3 * * *', '{"keepDays":90}'::jsonb, 2, 60, 1,
'日志清理任务:每日 03:00 清理早于 keepDays 天的操作日志与任务日志,默认保留 90 天')
ON CONFLICT (job_key) DO NOTHING;
COMMIT;