Files
andyesfly 6cf6a3d5b9 feat: 新增可配置通知中心,支持扫描式/埋点式通知规则与手动群发
新增通知规则实体、扫描任务、API与管理界面,重构站内通知支持用户/电工双类型,移除硬编码的证照到期提醒,新增工单派单、电工审核结果等通知触发场景,调整菜单与数据库结构适配新通知体系。
2026-10-06 11:53:02 +08:00

162 lines
11 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 可配置消息通知中心
-- 背景:现有站内通知为硬编码单点(仅「证照到期」一种、标题模板写死、接收人写死为 ADMIN),
-- 且仅 Web 管理端可见;两个移动端(管理端 App / 电工端 App)没有消息中心。
-- 本期建立「可配置 + 两端统一」的站内消息中心:
-- - 改造 sys_notice:新增 receiver_type(user|worker),两端共用一套消息数据;
-- - 新增 sys_notice_rule(通知规则模板 + 接收对象配置);
-- - 新增 sys_notice_batch(手动群发批次/发送记录);
-- - 新增内置任务 notice_scan(扫描式规则统一入口)与首批规则种子;
-- - 挂载系统管理下的「通知管理」菜单与按钮权限。
-- 幂等:新增列带 DEFAULT + IF NOT EXISTS;建表/索引用 IF NOT EXISTS;菜单/绑定用 ON CONFLICT;
-- 任务与规则种子用 ON CONFLICT DO NOTHING。
-- 注意:菜单 id 51-54,避开 20261001(34-46)与 20261002(47-50)已占用区间。
BEGIN;
-- ------------------------------------------------------------
-- 1) 改造 sys_notice:增加接收人类型与来源引用
-- ------------------------------------------------------------
ALTER TABLE sys_notice ADD COLUMN IF NOT EXISTS receiver_type VARCHAR(16) NOT NULL DEFAULT 'user'; -- 接收人类型:user-管理端用户 worker-电工
ALTER TABLE sys_notice ADD COLUMN IF NOT EXISTS rule_id BIGINT; -- 来源通知规则(→ sys_notice_rule.id)
ALTER TABLE sys_notice ADD COLUMN IF NOT EXISTS batch_id BIGINT; -- 来源群发批次(→ sys_notice_batch.id)
COMMENT ON COLUMN sys_notice.receiver_type IS '接收人类型:user-管理端用户 worker-电工';
COMMENT ON COLUMN sys_notice.rule_id IS '来源通知规则(→ sys_notice_rule.id)';
COMMENT ON COLUMN sys_notice.batch_id IS '来源群发批次(→ sys_notice_batch.id)';
-- 幂等键升级:加入 receiver_type(user 与 worker 的 id 空间不同,必须区分)
DROP INDEX IF EXISTS uk_sys_notice_biz;
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_notice_deliver
ON sys_notice (notice_type, biz_type, biz_id, receiver_type, receiver_id);
CREATE INDEX IF NOT EXISTS idx_sys_notice_receiver_type ON sys_notice (receiver_type, receiver_id, is_read);
-- ------------------------------------------------------------
-- 2) 通知规则表(规则模板 + 接收对象配置)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_notice_rule (
id BIGSERIAL PRIMARY KEY,
rule_key VARCHAR(64) NOT NULL, -- 规则键(唯一)
rule_name VARCHAR(64) NOT NULL, -- 规则名称
event_key VARCHAR(64) NOT NULL, -- 事件键(对应代码事件注册表)
trigger_mode SMALLINT NOT NULL DEFAULT 1, -- 触发方式:1扫描式 2埋点式
enabled SMALLINT NOT NULL DEFAULT 1, -- 状态:1启用 0停用
advance_days INT, -- 提前天数(扫描式)
title_tpl VARCHAR(128) NOT NULL, -- 标题模板(支持 {变量})
content_tpl VARCHAR(512) NOT NULL, -- 正文模板(支持 {变量})
receiver_config JSONB NOT NULL DEFAULT '{"users":{"roles":[],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb, -- 接收对象配置
remark VARCHAR(255), -- 备注
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE sys_notice_rule IS '通知规则表(模板 + 接收对象)';
COMMENT ON COLUMN sys_notice_rule.id IS '自增主键';
COMMENT ON COLUMN sys_notice_rule.rule_key IS '规则键(唯一)';
COMMENT ON COLUMN sys_notice_rule.rule_name IS '规则名称';
COMMENT ON COLUMN sys_notice_rule.event_key IS '事件键(对应代码事件注册表)';
COMMENT ON COLUMN sys_notice_rule.trigger_mode IS '触发方式:1扫描式 2埋点式';
COMMENT ON COLUMN sys_notice_rule.enabled IS '状态:1启用 0停用';
COMMENT ON COLUMN sys_notice_rule.advance_days IS '提前天数(扫描式)';
COMMENT ON COLUMN sys_notice_rule.title_tpl IS '标题模板(支持 {变量})';
COMMENT ON COLUMN sys_notice_rule.content_tpl IS '正文模板(支持 {变量})';
COMMENT ON COLUMN sys_notice_rule.receiver_config IS '接收对象配置:users{roles,ids} + workers{mode:none|event|all|ids|status,ids,auditStatus,status}';
COMMENT ON COLUMN sys_notice_rule.remark IS '备注';
COMMENT ON COLUMN sys_notice_rule.created_at IS '创建时间';
COMMENT ON COLUMN sys_notice_rule.updated_at IS '更新时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_notice_rule_key ON sys_notice_rule (rule_key);
CREATE INDEX IF NOT EXISTS idx_sys_notice_rule_event ON sys_notice_rule (event_key, trigger_mode, enabled);
-- ------------------------------------------------------------
-- 3) 手动群发批次表(发送记录)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_notice_batch (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(128) NOT NULL, -- 消息标题
content VARCHAR(512) NOT NULL, -- 消息正文
receiver_config JSONB NOT NULL DEFAULT '{"users":{"roles":[],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb, -- 接收对象快照
receiver_count INT NOT NULL DEFAULT 0, -- 实际接收人数
sent_by BIGINT, -- 发送人(→ sys_user.id)
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 发送时间
);
COMMENT ON TABLE sys_notice_batch IS '手动群发批次表(发送记录)';
COMMENT ON COLUMN sys_notice_batch.id IS '自增主键';
COMMENT ON COLUMN sys_notice_batch.title IS '消息标题';
COMMENT ON COLUMN sys_notice_batch.content IS '消息正文';
COMMENT ON COLUMN sys_notice_batch.receiver_config IS '接收对象快照';
COMMENT ON COLUMN sys_notice_batch.receiver_count IS '实际接收人数';
COMMENT ON COLUMN sys_notice_batch.sent_by IS '发送人(→ sys_user.id)';
COMMENT ON COLUMN sys_notice_batch.sent_at IS '发送时间';
-- ------------------------------------------------------------
-- 4) 菜单与权限(系统管理 parent_id=6 下新增「通知管理」+ 3 个按钮权限)
-- ------------------------------------------------------------
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
(51, 6, '通知管理', '/system/notice', 'system/notice/index', 'BellOutlined', 7, 2, 'notice:rule', 1),
(52, 51, '保存规则', NULL, NULL, NULL, 1, 3, 'notice:rule:update', 1),
(53, 51, '删除规则', NULL, NULL, NULL, 2, 3, 'notice:rule:delete', 1),
(54, 51, '群发消息', NULL, NULL, NULL, 3, 3, 'notice:batch', 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)拥有 51~54
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
(1, 51), (1, 52), (1, 53), (1, 54)
ON CONFLICT (role_id, menu_id) DO NOTHING;
-- 重置 sys_menu 自增序列,避免后续插入 id 冲突
SELECT setval('sys_menu_id_seq', 54, true);
-- ------------------------------------------------------------
-- 5) 内置任务种子:通知规则扫描(扫描式规则的统一入口)
-- ------------------------------------------------------------
INSERT INTO sys_job (job_key, job_name, cron_expression, params, retry_times, retry_interval, status, remark) VALUES
('notice_scan', '通知规则扫描', '0 0 8 * * *', '{}'::jsonb, 1, 300, 1,
'遍历启用的扫描式通知规则,按模板与接收对象生成站内通知(幂等,默认每日 08:00)')
ON CONFLICT (job_key) DO NOTHING;
-- ------------------------------------------------------------
-- 6) 首批通知规则种子(按 rule_key 幂等;接收对象均可在「通知管理」页调整)
-- ------------------------------------------------------------
INSERT INTO sys_notice_rule (rule_key, rule_name, event_key, trigger_mode, enabled, advance_days, title_tpl, content_tpl, receiver_config, remark) VALUES
('cert_expire', '电工资质证照到期提醒', 'cert_expire', 1, 1, 30,
'电工【{workerName}】的{certType}{expireState}',
'证书编号:{certNo};发证机关:{issueAuthority};有效期至:{expireDate};{remainDaysText},请及时提醒电工办理续期',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"event","ids":[]}}'::jsonb,
'扫描 worker_cert 中 advance_days 天内到期或已过期的证照,通知 ADMIN 与证照所属电工'),
('worker_audit_result', '电工入驻审核结果通知', 'worker_audit_result', 2, 1, NULL,
'您的入驻审核{auditState}',
'审核{auditState};{auditRemarkText}如资料有误,请修改后重新提交。',
'{"users":{"roles":[],"ids":[]},"workers":{"mode":"event","ids":[]}}'::jsonb,
'审核通过/驳回时实时通知电工本人(业务埋点触发)'),
('dispatch_assign', '工单派单通知', 'dispatch_assign', 2, 1, NULL,
'您有新的{orderTypeName}工单',
'工单编号:{orderNo};客户:{customerName};{detailText}请及时在电工端查看并接单。',
'{"users":{"roles":[],"ids":[]},"workers":{"mode":"event","ids":[]}}'::jsonb,
'勘察/安装/维保/报修派单时实时通知被指派电工(业务埋点触发)'),
('contract_expire', '客户合同到期提醒', 'contract_expire', 1, 1, 30,
'客户【{customerName}】的合同{expireState}',
'合同编号:{contractNo};合同名称:{contractName};到期日:{endDate};{remainDaysText},请及时跟进续签。',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb,
'扫描 contract 中 advance_days 天内到期或已过期的合同,通知 ADMIN'),
('visit_plan', '客户拜访计划提醒', 'visit_plan', 1, 1, 0,
'客户拜访计划待执行:{customerName}',
'计划拜访日期:{planDate};{remainDaysText}。请及时完成拜访。',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb,
'扫描 customer_visit 中未执行且计划日期已到/超期的拜访计划,通知 ADMIN'),
('order_timeout', '工单超时未接单提醒', 'order_timeout', 1, 1, 1,
'工单超时未接单:{orderTypeName}',
'工单编号:{orderNo};客户:{customerName};已派单 {staleDays} 天仍未接单,请及时跟进。',
'{"users":{"roles":["ADMIN"],"ids":[]},"workers":{"mode":"none","ids":[]}}'::jsonb,
'扫描派单后超过 advance_days 天仍未接单的工单,通知 ADMIN')
ON CONFLICT (rule_key) DO NOTHING;
COMMIT;