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

+89 -7
View File
@@ -17,7 +17,7 @@
--
-- 内容:
-- 1. 初始角色(ADMIN / SALESMAN / WORKER / CUSTOMER)
-- 2. 初始菜单(顶级 6 + 系统管理子菜单 5)
-- 2. 初始菜单(顶级 6 + 系统管理子菜单 6 + 运营派单子菜单 13 等,共 50 条)
-- 3. 初始字典(客户/设备/派单/支付等业务字典)
-- 4. 管理员账户(admin / admin123456)
-- 5. 用户-角色绑定 / 角色-菜单绑定
@@ -43,12 +43,12 @@ ON CONFLICT (id) DO UPDATE SET
SELECT setval('sys_role_id_seq', 4, true);
-- ------------------------------------------------------------
-- 2. 初始菜单(顶级菜单,显式指定 id:1-6,系统管理为目录类型 menu_type=1,其余为 menu_type=2)
-- 2. 初始菜单(顶级菜单,显式指定 id:1-6,系统管理/运营派单为目录类型 menu_type=1,其余为 menu_type=2)
-- ------------------------------------------------------------
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(1, NULL, '客户管理', '/customer', NULL, 'TeamOutlined', 1, 1, 'customer:list', 1),
(2, NULL, '项目管理', '/project', NULL, 'ProjectOutlined', 2, 1, 'project:list', 1),
(3, NULL, '运营派单', '/dispatch', NULL, 'ScheduleOutlined', 3, 2, 'dispatch:list', 1),
(3, NULL, '运营派单', '/dispatch', NULL, 'ScheduleOutlined', 3, 1, 'dispatch:list', 1),
(4, NULL, '设备监控', '/device', NULL, 'MonitorOutlined', 4, 2, 'device:list', 1),
(5, NULL, '统计报表', '/report', NULL, 'BarChartOutlined', 5, 2, 'report:list', 1),
(6, NULL, '系统管理', '/system', NULL, 'SettingOutlined', 6, 1, 'system:list', 1)
@@ -132,8 +132,34 @@ ON CONFLICT (id) DO UPDATE SET
permission = EXCLUDED.permission,
visible = EXCLUDED.visible;
-- 运营派单子菜单(parent_id=3,id:34-36 为菜单项,37-46 为按钮权限)
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(34, 3, '运维费定价', '/dispatch/price', 'dispatch/price/index', 'MoneyCollectOutlined', 1, 2, 'dispatch:price', 1),
(35, 3, '电工派单成本价', '/dispatch/cost', 'dispatch/cost/index', 'AccountBookOutlined', 2, 2, 'dispatch:cost', 1),
(36, 3, '电工登记管理', '/dispatch/worker', 'dispatch/worker/index', 'ToolOutlined', 3, 2, 'dispatch:worker', 1),
(37, 3, '新增价目', NULL, NULL, NULL, 1, 3, 'price:create', 1),
(38, 3, '编辑价目', NULL, NULL, NULL, 2, 3, 'price:update', 1),
(39, 3, '删除价目', NULL, NULL, NULL, 3, 3, 'price:delete', 1),
(40, 3, '新增成本价', NULL, NULL, NULL, 4, 3, 'cost:create', 1),
(41, 3, '编辑成本价', NULL, NULL, NULL, 5, 3, 'cost:update', 1),
(42, 3, '删除成本价', NULL, NULL, NULL, 6, 3, 'cost:delete', 1),
(43, 3, '新增电工', NULL, NULL, NULL, 7, 3, 'worker:create', 1),
(44, 3, '编辑电工', NULL, NULL, NULL, 8, 3, 'worker:update', 1),
(45, 3, '删除电工', NULL, NULL, NULL, 9, 3, 'worker:delete', 1),
(46, 3, '审核电工', NULL, NULL, NULL, 10, 3, 'worker:audit', 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;
-- 重置 sys_menu 自增序列
SELECT setval('sys_menu_id_seq', 33, true);
SELECT setval('sys_menu_id_seq', 50, true);
-- ------------------------------------------------------------
-- 3. 初始字典(sys_dict 无业务唯一键,清空重建以支持幂等执行)
@@ -162,7 +188,28 @@ INSERT INTO sys_dict (dict_type, dict_label, dict_value, sort_order) VALUES
('dispatch_type', '安装派单', 'install', 2),
('dispatch_type', '报警派单', 'alarm', 3),
('dispatch_type', '维修派单', 'repair', 4),
('dispatch_type', '维保派单', 'maintenance', 5);
('dispatch_type', '维保派单', 'maintenance', 5),
('om_service_type', '维修', '维修', 1),
('om_service_type', '维保', '维保', 2),
('om_service_type', '报警处理', '报警处理', 3),
('om_service_type', '巡检', '巡检', 4),
('om_service_type', '其他', '其他', 5),
('om_price_mode', '按次', '1', 1),
('om_price_mode', '按工时', '2', 2),
('om_price_mode', '材料', '3', 3),
('om_price_unit', '次', '次', 1),
('om_price_unit', '小时', '小时', 2),
('om_price_unit', '个', '个', 3),
('om_price_unit', '米', '米', 4),
('om_price_unit', '根', '根', 5),
('worker_cert_type', '低压电工证', '低压电工证', 1),
('worker_cert_type', '高压电工证', '高压电工证', 2),
('worker_cert_type', '特种作业证', '特种作业证', 3),
('worker_cert_type', '登高证', '登高证', 4),
('worker_cert_type', '其他', '其他', 5),
('worker_audit_status', '待审核', '0', 1),
('worker_audit_status', '已通过', '1', 2),
('worker_audit_status', '已驳回', '2', 3);
-- ------------------------------------------------------------
-- 4. 管理员账户(显式指定 id=1,密码哈希为 bcrypt('admin123456'))
@@ -188,13 +235,14 @@ INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1)
ON CONFLICT (user_id, role_id) DO NOTHING;
-- ------------------------------------------------------------
-- 6. 角色-菜单绑定:ADMIN 拥有全部 32 条菜单,SALESMAN 拥有客户域+项目+统计报表
-- 6. 角色-菜单绑定:ADMIN 拥有全部 46 条菜单,SALESMAN 拥有客户域+项目+统计报表
-- WORKER、CUSTOMER 不绑定任何菜单
-- ------------------------------------------------------------
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10), (1, 11),
(1, 12), (1, 13), (1, 14), (1, 15), (1, 16), (1, 17), (1, 18), (1, 19), (1, 20), (1, 21), (1, 22), (1, 23),
(1, 24), (1, 27), (1, 28), (1, 29), (1, 30), (1, 31), (1, 32), (1, 33),
(1, 34), (1, 35), (1, 36), (1, 37), (1, 38), (1, 39), (1, 40), (1, 41), (1, 42), (1, 43), (1, 44), (1, 45), (1, 46),
(2, 1), (2, 2), (2, 5),
(2, 12), (2, 13), (2, 14), (2, 15), (2, 16), (2, 17), (2, 18), (2, 19), (2, 20), (2, 21), (2, 22), (2, 23),
(2, 24), (2, 27), (2, 28), (2, 29), (2, 30), (2, 31), (2, 32), (2, 33)
@@ -204,8 +252,42 @@ ON CONFLICT (role_id, menu_id) DO NOTHING;
DELETE FROM sys_role_menu WHERE menu_id IN (25, 26);
DELETE FROM sys_menu WHERE id IN (25, 26);
-- ------------------------------------------------------------
-- 7. 定时任务域菜单(系统管理 parent_id=6,id:47 为菜单项,48-50 为按钮权限)
-- ------------------------------------------------------------
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;
-- ------------------------------------------------------------
-- 8. 定时任务种子(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;
-- ============================================================
-- 完 · 7 张系统表种子数据(4 角色 + 32 菜单 + 22 字典 + 1 管理员 + 绑定关系)
-- 完 · 系统表种子数据(4 角色 + 50 菜单 + 43 字典 + 1 管理员 + 2 定时任务 + 绑定关系)
-- ============================================================
@@ -0,0 +1,221 @@
-- 运维费定价 + 电工登记管理功能扩展
-- 背景:运营派单域由「派单执行」扩展为「客户运维服务价目 + 电工派单成本价 + 电工登记审核」,
-- 对应新增 service_price(客户运维服务价目)、worker_cert(电工资质证照)、
-- sys_notice(证照到期站内通知)三张表,并为 worker / dispatch_price 补齐
-- 登录、审核、计价方式等业务字段,同时挂载运营派单子菜单与按钮权限。
-- 幂等:新增列一律带 DEFAULT + IF NOT EXISTS;建表/索引用 IF NOT EXISTS;
-- 菜单/角色绑定用 ON CONFLICT;字典用「先 DELETE 再 INSERT」收敛。
-- 注意:本文件不 TRUNCATE sys_dict(避免清空其它字典及重置 id 影响 db_seed.sql)。
BEGIN;
-- ------------------------------------------------------------
-- 1) worker 表扩展:电工 APP 登录与入驻审核
-- ------------------------------------------------------------
ALTER TABLE worker ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255); -- 电工APP登录密码bcrypt哈希
ALTER TABLE worker ADD COLUMN IF NOT EXISTS audit_status SMALLINT NOT NULL DEFAULT 0; -- 审核状态:0待审核 1已通过 2已驳回
ALTER TABLE worker ADD COLUMN IF NOT EXISTS audit_remark VARCHAR(255); -- 驳回理由/审核备注
ALTER TABLE worker ADD COLUMN IF NOT EXISTS audit_by BIGINT; -- 审核人(→ sys_user.id)
ALTER TABLE worker ADD COLUMN IF NOT EXISTS audit_at TIMESTAMPTZ; -- 审核时间
ALTER TABLE worker ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ; -- 最后登录时间
COMMENT ON COLUMN worker.password_hash IS '电工APP登录密码bcrypt哈希';
COMMENT ON COLUMN worker.audit_status IS '审核状态:0待审核 1已通过 2已驳回';
COMMENT ON COLUMN worker.audit_remark IS '驳回理由/审核备注';
COMMENT ON COLUMN worker.audit_by IS '审核人(→ sys_user.id)';
COMMENT ON COLUMN worker.audit_at IS '审核时间';
COMMENT ON COLUMN worker.last_login_at IS '最后登录时间';
-- 电工登记台账按审核状态过滤/统计
CREATE INDEX IF NOT EXISTS idx_worker_audit_status ON worker (audit_status);
-- ------------------------------------------------------------
-- 2) 电工资质证照表(电工登记管理的资质附件与到期提醒依据)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS worker_cert (
id BIGSERIAL PRIMARY KEY,
worker_id BIGINT NOT NULL REFERENCES worker(id) ON DELETE CASCADE,
cert_type VARCHAR(64), -- 证书类型(字典 worker_cert_type)
cert_no VARCHAR(64), -- 证书编号
issue_authority VARCHAR(128), -- 发证机关
issue_date DATE, -- 发证日期
expire_date DATE, -- 有效期至(到期提醒依据;NULL=长期有效)
cert_image_url VARCHAR(512), -- 证照图片对象key
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE worker_cert IS '电工资质证照表';
COMMENT ON COLUMN worker_cert.id IS '自增主键';
COMMENT ON COLUMN worker_cert.worker_id IS '共享电工ID(→ worker)';
COMMENT ON COLUMN worker_cert.cert_type IS '证书类型(字典 worker_cert_type)';
COMMENT ON COLUMN worker_cert.cert_no IS '证书编号';
COMMENT ON COLUMN worker_cert.issue_authority IS '发证机关';
COMMENT ON COLUMN worker_cert.issue_date IS '发证日期';
COMMENT ON COLUMN worker_cert.expire_date IS '有效期至(到期提醒依据;NULL=长期有效)';
COMMENT ON COLUMN worker_cert.cert_image_url IS '证照图片对象key';
COMMENT ON COLUMN worker_cert.created_at IS '创建时间';
COMMENT ON COLUMN worker_cert.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_worker_cert_worker ON worker_cert (worker_id);
CREATE INDEX IF NOT EXISTS idx_worker_cert_expire ON worker_cert (expire_date);
CREATE UNIQUE INDEX IF NOT EXISTS uk_worker_cert_type_no ON worker_cert (worker_id, cert_type, cert_no);
-- ------------------------------------------------------------
-- 3) 客户运维服务价目表(运维费定价)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS service_price (
id BIGSERIAL PRIMARY KEY,
price_code VARCHAR(64) NOT NULL UNIQUE,
price_name VARCHAR(64) NOT NULL,
service_type VARCHAR(32),
price_mode SMALLINT NOT NULL DEFAULT 1,
unit_price NUMERIC(10,2) NOT NULL DEFAULT 0,
unit VARCHAR(16),
status SMALLINT NOT NULL DEFAULT 1,
remark VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE service_price IS '客户运维服务价目表';
COMMENT ON COLUMN service_price.id IS '自增主键';
COMMENT ON COLUMN service_price.price_code IS '价目编码(唯一,FW+YYYYMMDD+3位)';
COMMENT ON COLUMN service_price.price_name IS '价目名称';
COMMENT ON COLUMN service_price.service_type IS '服务类型:维修/维保/报警处理/巡检/其他';
COMMENT ON COLUMN service_price.price_mode IS '计价方式:1按次 2按工时 3材料';
COMMENT ON COLUMN service_price.unit_price IS '单价';
COMMENT ON COLUMN service_price.unit IS '计价单位:次/小时/个/米/根';
COMMENT ON COLUMN service_price.status IS '状态:1启用 0停用';
COMMENT ON COLUMN service_price.remark IS '备注';
COMMENT ON COLUMN service_price.created_at IS '创建时间';
COMMENT ON COLUMN service_price.updated_at IS '更新时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_service_price_code ON service_price (price_code);
CREATE INDEX IF NOT EXISTS idx_service_price_mode_status ON service_price (price_mode, status);
-- ------------------------------------------------------------
-- 4) dispatch_price 表扩展:电工派单成本价
-- ------------------------------------------------------------
ALTER TABLE dispatch_price ADD COLUMN IF NOT EXISTS price_mode SMALLINT NOT NULL DEFAULT 1; -- 计价方式:1按次 2按工时
ALTER TABLE dispatch_price ADD COLUMN IF NOT EXISTS unit VARCHAR(16) DEFAULT '次'; -- 计价单位
ALTER TABLE dispatch_price ADD COLUMN IF NOT EXISTS status SMALLINT NOT NULL DEFAULT 1; -- 状态:1启用 0停用
ALTER TABLE dispatch_price ADD COLUMN IF NOT EXISTS remark VARCHAR(255); -- 备注
ALTER TABLE dispatch_price ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
COMMENT ON COLUMN dispatch_price.price_mode IS '计价方式:1按次 2按工时';
COMMENT ON COLUMN dispatch_price.unit IS '计价单位';
COMMENT ON COLUMN dispatch_price.status IS '状态:1启用 0停用';
COMMENT ON COLUMN dispatch_price.remark IS '备注';
COMMENT ON COLUMN dispatch_price.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_dispatch_price_status ON dispatch_price (status);
-- ------------------------------------------------------------
-- 5) 站内通知表(当前用途:电工资质证照到期提醒)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_notice (
id BIGSERIAL PRIMARY KEY,
notice_type VARCHAR(32) NOT NULL,
title VARCHAR(128) NOT NULL,
content VARCHAR(512),
biz_type VARCHAR(32),
biz_id BIGINT,
receiver_id BIGINT NOT NULL,
is_read SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_notice IS '站内通知表';
COMMENT ON COLUMN sys_notice.id IS '自增主键';
COMMENT ON COLUMN sys_notice.notice_type IS '通知类型:cert_expire证照到期';
COMMENT ON COLUMN sys_notice.title IS '通知标题';
COMMENT ON COLUMN sys_notice.content IS '通知内容';
COMMENT ON COLUMN sys_notice.biz_type IS '业务类型:worker_cert电工资质证照';
COMMENT ON COLUMN sys_notice.biz_id IS '业务主键(→ worker_cert.id)';
COMMENT ON COLUMN sys_notice.receiver_id IS '接收人(→ sys_user.id)';
COMMENT ON COLUMN sys_notice.is_read IS '是否已读:0未读 1已读';
COMMENT ON COLUMN sys_notice.created_at IS '创建时间';
CREATE INDEX IF NOT EXISTS idx_sys_notice_receiver ON sys_notice (receiver_id, is_read);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_notice_biz ON sys_notice (notice_type, biz_id, receiver_id);
-- ------------------------------------------------------------
-- 6) 菜单与权限
-- 「运营派单」由顶级菜单项改为目录,运维费定价/成本价/电工登记作为其子菜单
-- ------------------------------------------------------------
UPDATE sys_menu SET menu_type = 1 WHERE id = 3;
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(34, 3, '运维费定价', '/dispatch/price', 'dispatch/price/index', 'MoneyCollectOutlined', 1, 2, 'dispatch:price', 1),
(35, 3, '电工派单成本价', '/dispatch/cost', 'dispatch/cost/index', 'AccountBookOutlined', 2, 2, 'dispatch:cost', 1),
(36, 3, '电工登记管理', '/dispatch/worker', 'dispatch/worker/index', 'ToolOutlined', 3, 2, 'dispatch:worker', 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;
-- 按钮权限(menu_type=3,无路由/组件/图标)
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(37, 3, '新增价目', NULL, NULL, NULL, 1, 3, 'price:create', 1),
(38, 3, '编辑价目', NULL, NULL, NULL, 2, 3, 'price:update', 1),
(39, 3, '删除价目', NULL, NULL, NULL, 3, 3, 'price:delete', 1),
(40, 3, '新增成本价', NULL, NULL, NULL, 4, 3, 'cost:create', 1),
(41, 3, '编辑成本价', NULL, NULL, NULL, 5, 3, 'cost:update', 1),
(42, 3, '删除成本价', NULL, NULL, NULL, 6, 3, 'cost:delete', 1),
(43, 3, '新增电工', NULL, NULL, NULL, 7, 3, 'worker:create', 1),
(44, 3, '编辑电工', NULL, NULL, NULL, 8, 3, 'worker:update', 1),
(45, 3, '删除电工', NULL, NULL, NULL, 9, 3, 'worker:delete', 1),
(46, 3, '审核电工', NULL, NULL, NULL, 10, 3, 'worker:audit', 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)拥有 34~46
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
(1, 34), (1, 35), (1, 36), (1, 37), (1, 38), (1, 39), (1, 40), (1, 41), (1, 42), (1, 43), (1, 44), (1, 45), (1, 46)
ON CONFLICT (role_id, menu_id) DO NOTHING;
-- 重置 sys_menu 自增序列,避免后续插入 id 冲突
SELECT setval('sys_menu_id_seq', 46, true);
-- ------------------------------------------------------------
-- 7) 字典(sys_dict 无业务唯一键,先 DELETE 目标 dict_type 再 INSERT,保证幂等)
-- ------------------------------------------------------------
DELETE FROM sys_dict WHERE dict_type IN (
'om_service_type', 'om_price_mode', 'om_price_unit', 'worker_cert_type', 'worker_audit_status'
);
INSERT INTO sys_dict (dict_type, dict_label, dict_value, sort_order) VALUES
('om_service_type', '维修', '维修', 1),
('om_service_type', '维保', '维保', 2),
('om_service_type', '报警处理', '报警处理', 3),
('om_service_type', '巡检', '巡检', 4),
('om_service_type', '其他', '其他', 5),
('om_price_mode', '按次', '1', 1),
('om_price_mode', '按工时', '2', 2),
('om_price_mode', '材料', '3', 3),
('om_price_unit', '次', '次', 1),
('om_price_unit', '小时', '小时', 2),
('om_price_unit', '个', '个', 3),
('om_price_unit', '米', '米', 4),
('om_price_unit', '根', '根', 5),
('worker_cert_type', '低压电工证', '低压电工证', 1),
('worker_cert_type', '高压电工证', '高压电工证', 2),
('worker_cert_type', '特种作业证', '特种作业证', 3),
('worker_cert_type', '登高证', '登高证', 4),
('worker_cert_type', '其他', '其他', 5),
('worker_audit_status', '待审核', '0', 1),
('worker_audit_status', '已通过', '1', 2),
('worker_audit_status', '已驳回', '2', 3);
COMMIT;
@@ -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;