作为 xiaoqu-agent-service(MCP 服务) 的开发参考。新系统与旧系统零代码耦合, 仅照搬其行级数据权限规则与数据源结构。设计方案见 xiaoqu-franchisee/doc/agent-mcp-service-design.md
17 KiB
开发文档:代理商消息通知页面
需求:在「账户管理」菜单下新增「消息通知」页面(代理商账户余额提醒列表)。 环境:dev
192.168.1.181:3306(root/kaixinjiuhao);prd 上线需替换 parent_id 后重跑。 涉及库:xiaoqu_comples_d(菜单/字段/权限)+xiaoqu_franchisee_d(业务数据)。
1. 数据库变更
1.1 业务表 — xiaoqu_franchisee_d(dataSource2 / m2)
USE xiaoqu_franchisee_d;
CREATE TABLE `agent_message_notice` (
`id` bigint(8) NOT NULL AUTO_INCREMENT,
`create_id` bigint(8) DEFAULT '0' COMMENT '创建id',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_id` bigint(8) DEFAULT '0' COMMENT '修改id',
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`soft_delete` int(4) DEFAULT '0' COMMENT '数据状态',
`agent_id` bigint(8) DEFAULT '0' COMMENT '代理商ID',
`agent_name` varchar(64) DEFAULT '' COMMENT '代理商名称',
`notify_user_name` varchar(32) DEFAULT '' COMMENT '消息通知人员',
`notify_mobile` varchar(20) DEFAULT '' COMMENT '消息通知人员手机号',
`notify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息通知时间',
`content` varchar(512) DEFAULT '' COMMENT '消息通知内容',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代理商消息通知';
1.2 菜单 + 字段 + 权限(权限库)
包含 4 张表:webpage / webpage_authority / page_field / field_webpage_authority,在一个事务里完成。
环境对应的权限 schema:
- dev →
xiaoqu_comples_d- test →
test- prod →
xiaoqu_comples_p切环境只需改第一行
USE,其余语句通用。
关键改动(2026-05-14):
webpage_authority和field_webpage_authority的 INSERT 从硬编码(1, 102)改为动态复用同 parent 下兄弟菜单的授权范围。这样 dev/test/prod 跑同一份 SQL,新菜单的可见性自动跟同分类下的兄弟菜单一致,避免授权遗漏。
USE xiaoqu_comples_d; -- dev;test 改 USE test; prod 改 USE xiaoqu_comples_p;
START TRANSACTION;
-- ─────────────────────────────────────────────────────────────
-- ① webpage:菜单本体(挂到「账户管理」id=131 之下)
-- ─────────────────────────────────────────────────────────────
INSERT INTO webpage
(title, param, icon, description, order_by, parent_id, `type`, `status`, `path`, is_table_show)
VALUES
('消息通知', '', '', '', 6, 131, 0, 1, 'messageNotice', 1);
SET @new_web_id := LAST_INSERT_ID();
SELECT @new_web_id AS new_webpage_id;
-- ─────────────────────────────────────────────────────────────
-- ② webpage_authority:菜单授权(动态复用同 parent 下兄弟菜单的授权 authority 并集)
-- ─────────────────────────────────────────────────────────────
INSERT INTO webpage_authority
(web_id, authority_id, title, `path`, parent_id, check_status, `type`, is_table_show, order_by)
SELECT
@new_web_id, sa.authority_id, '消息通知', 'messageNotice', 131, 1, 0, 1, 6
FROM (
SELECT DISTINCT authority_id
FROM webpage_authority
WHERE web_id IN (SELECT id FROM webpage WHERE parent_id = 131 AND id <> @new_web_id)
AND soft_delete = 0
) sa;
-- ─────────────────────────────────────────────────────────────
-- ③ page_field:页面字段定义(对应 AgentMessageNoticeOutput 的字段)
-- is_table_show=1 → 列表列;nav_select=1 → 顶部搜索区;is_select=1 → 详情/编辑面板可选
-- ─────────────────────────────────────────────────────────────
INSERT INTO page_field
(web_id, cn_name, en_name, class_name, field_type, order_by,
is_select, nav_select, is_table_show, param)
VALUES
-- 列表显示字段(按截图列顺序排)
(@new_web_id, '代理商名称', 'agentName', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 2, 1, 0, 1, ''),
(@new_web_id, '消息通知人员', 'notifyUserName', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 3, 1, 1, 1, ''),
(@new_web_id, '消息通知人员手机号', 'notifyMobile', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 4, 1, 1, 1, ''),
(@new_web_id, '消息通知时间', 'notifyTime', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'date', 5, 1, 0, 1, ''),
(@new_web_id, '消息通知内容', 'content', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 6, 1, 0, 1, ''),
-- 搜索区隐藏字段(用于「渠道商」下拉传 agentId)
(@new_web_id, '渠道商', 'agentId', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'inputSelect', 7, 1, 1, 0, ''),
-- 公共审计字段(不展示,仅用于权限/数据元配置完整)
(@new_web_id, 'id', 'id', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 32, 1, 0, 0, ''),
(@new_web_id, '创建id', 'createId', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'inputSelect',33, 1, 0, 0, ''),
(@new_web_id, '创建时间', 'createTime', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'date', 34, 1, 0, 0, ''),
(@new_web_id, '修改id', 'modifyId', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'inputSelect',35, 1, 0, 0, ''),
(@new_web_id, '修改时间', 'modifyTime', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'date', 36, 1, 0, 0, ''),
(@new_web_id, '数据状态', 'softDelete', 'me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput', 'string', 37, 1, 0, 0, '');
-- ─────────────────────────────────────────────────────────────
-- ④ field_webpage_authority:字段级权限
-- 跟随 ② 步骤里授予的 authority 范围 CROSS JOIN page_field 批量生成
-- ─────────────────────────────────────────────────────────────
INSERT INTO field_webpage_authority
(web_id, authority_id, cn_name, en_name, is_select, nav_select,
field_type, field_id, order_by, check_status, param, is_table_show)
SELECT
pf.web_id, wa.authority_id, pf.cn_name, pf.en_name, pf.is_select, pf.nav_select,
pf.field_type, pf.id, pf.order_by, 1, pf.param, pf.is_table_show
FROM page_field pf
CROSS JOIN (
SELECT DISTINCT authority_id FROM webpage_authority
WHERE web_id = @new_web_id AND soft_delete = 0
) wa
WHERE pf.web_id = @new_web_id;
-- ─────────────────────────────────────────────────────────────
-- ⑤ 校验
-- ─────────────────────────────────────────────────────────────
SELECT id, parent_id, title, path, order_by FROM webpage WHERE id = @new_web_id;
SELECT COUNT(*) AS authority_grants FROM webpage_authority WHERE web_id = @new_web_id;
SELECT COUNT(*) AS field_count FROM page_field WHERE web_id = @new_web_id;
SELECT COUNT(*) AS field_grants_total FROM field_webpage_authority WHERE web_id = @new_web_id;
-- 期待 field_grants_total = field_count × authority_grants
-- dev: 12 × 2 = 24 / test: 12 × N(N=兄弟菜单 authority 并集大小) / prod 类似
COMMIT;
-- 失败请改 ROLLBACK
各环境实测授权范围(2026-05-14 核对兄弟菜单授权数):
- dev:账户管理下兄弟菜单授权数都是 2(仅 1+102) → 消息通知也是 2
- test:兄弟菜单授权数 8
12 不等(导自线上)→ 取并集后预计 1315 个 authority- prod:上线前用 §4 校验脚本现查
已执行情况:
- dev (
xiaoqu_comples_d):webpage.id=386,webpage_authority=2 条(1, 102),field_webpage_authority=24 条(12 字段 × 2 authority)- test (
test, 2026-05-14):webpage.id=388,webpage_authority=12 条(authority_id: 1, 22, 23, 32, 41, 49, 52, 65, 68, 70, 75, 80),field_webpage_authority=144 条(12 × 12)
1.3 字段配置说明
| 字段 (en_name) | order_by | nav_select 搜索区 | is_table_show 列表 | field_type 控件 | 截图对应 |
|---|---|---|---|---|---|
| agentName | 2 | ✗ | ✓ | string | 列表「代理商名称」 |
| notifyUserName | 3 | ✓ | ✓ | string | 搜索「人员名称」+ 列表 |
| notifyMobile | 4 | ✓ | ✓ | string | 搜索「手机号码」+ 列表 |
| notifyTime | 5 | ✗ | ✓ | date | 列表「消息通知时间」 |
| content | 6 | ✗ | ✓ | string | 列表「消息通知内容」 |
| agentId | 7 | ✓ | ✗ | inputSelect | 搜索「渠道商」下拉(传 ID,cn_name 显示为"渠道商") |
| id / createId / createTime / modifyId / modifyTime / softDelete | 32-37 | ✗ | ✗ | — | DTO 公共字段,仅元数据完整性,不展示 |
agentId用inputSelect控件:参考账户列表 (web_id=138) 同样手法 —— 搜索区显示"渠道商"下拉,实际传agentId;列表不显示这个字段,只显示agentName。
2. 后端改动 — xiaoqu-franchisee
2.1 包结构与文件
xiaoqu-publicbeanandmap/src/main/java/me/iiv/iivframework/
├── beans/b2/AgentMessageNotice.java # MyBatis-Plus @TableName("agent_message_notice")
├── mapper/m2/AgentMessageNoticeMapper.java # extends BaseMapper<AgentMessageNotice>
└── service/s2/
├── AgentMessageNoticeService.java # interface IService<AgentMessageNotice>
└── impl/AgentMessageNoticeServiceImpl.java # extends ServiceImpl
xiaoqu-franchisee/src/main/java/me/iiv/iivframework/controller/agentMessage/
├── AgentMessageNoticeController.java # @RequestMapping("/agentMessageNotice")
├── input/AgentMessageNoticeQueryInput.java
└── output/AgentMessageNoticeOutput.java # ⚠️ 全限定类名必须与 page_field.class_name 一致
⚠️
page_field.class_name我填了me.iiv.iivframework.controller.agentMessage.output.AgentMessageNoticeOutput,后端 DTO 必须放这个路径,否则后台「字段权限配置」用反射找类会失败。
2.2 API
| Method | URL | 说明 |
|---|---|---|
| POST | /agentMessageNotice/list |
分页列表(含搜索条件) |
Input
public class AgentMessageNoticeQueryInput {
private Long agentId; // 渠道商下拉
private String notifyUserName; // 人员名称 模糊
private String notifyMobile; // 手机号码 模糊
private Integer page = 1;
private Integer limit = 10;
}
Output
public class AgentMessageNoticeOutput {
private Long id;
private Long createId;
private String createTime;
private Long modifyId;
private String modifyTime;
private Integer softDelete;
private Long agentId;
private String agentName;
private String notifyUserName;
private String notifyMobile;
private String notifyTime; // 格式化 yyyy-MM-dd HH:mm:ss
private String content;
}
查询逻辑
Page<AgentMessageNotice> p = new Page<>(input.getPage(), input.getLimit());
LambdaQueryWrapper<AgentMessageNotice> qw = Wrappers.<AgentMessageNotice>lambdaQuery()
.eq(AgentMessageNotice::getSoftDelete, 0)
.eq(input.getAgentId() != null, AgentMessageNotice::getAgentId, input.getAgentId())
.like(StringUtils.isNotBlank(input.getNotifyUserName()),
AgentMessageNotice::getNotifyUserName, input.getNotifyUserName())
.like(StringUtils.isNotBlank(input.getNotifyMobile()),
AgentMessageNotice::getNotifyMobile, input.getNotifyMobile())
.orderByDesc(AgentMessageNotice::getNotifyTime);
2.3 数据来源(待 PM 澄清)
页面只读。记录写入方需明确:
- A. XXL-Job 定时扫描代理商余额,触发阈值就写一条(推荐)
- B. 余额变更/提现流程内嵌写入
→ 确认后补一个 AgentMessageNoticeService.send(agentId, content) 方法 + 触发点 / 定时任务。
3. 前端改动 — vue-manage-system
3.1 路由
src/router/module/accoutManageRouter.js(注意原拼写 accout)追加:
{
path: '/messageNotice',
name: 'messageNotice',
component: () => import('@/components/page/accountManage/messageNotice.vue'),
meta: { title: '消息通知' }
}
3.2 API
src/api/api.js:
agentMessageNotice: {
list: '/agentMessageNotice/list'
}
src/api/accountService.js(或新建 agentMessageNoticeService.js):
export const agentMessageNoticeService = {
list(obj) {
return request.post($api.agentMessageNotice.list, obj);
}
};
3.3 页面
src/components/page/accountManage/messageNotice.vue,骨架参考 outAllDoc.vue:
- 搜索区:渠道商(el-select,options 用
dropDownService拿代理列表)/ 人员名称(el-input)/ 手机号码(el-input)/ 清空 + 搜索 - 列表:序号 / 代理商名称 / 消息通知人员 / 消息通知人员手机号 / 消息通知时间 / 消息通知内容
- 分页:el-pagination
- 可继承
standTableMixin
4. 上线执行顺序
- DEV (
xiaoqu_franchisee_d+xiaoqu_comples_d)- 业务库执行 §1.1(建表
agent_message_notice) - 权限库
USE xiaoqu_comples_d;执行 §1.2(事务) - 后端编码 →
mvn package→ 部署 - 前端编码 →
yarn build-dev→ 部署
- 业务库执行 §1.1(建表
- DEV 冒烟
- 管理员账号登录,左侧菜单出现「账户管理 > 消息通知」
- 进入页面:列表接口返回(空数据也算成功)
- 后台「权限组管理 → 配置页面内容权限」能看到这个页面的所有字段
- 手工 INSERT 几条
agent_message_notice数据,验证列表渲染 / 三个搜索条件 / 排序
- TEST (
xiaoqu_franchisee_d+test)- 业务表
agent_message_notice共享 dev 业务库,已存在 — 无需建表 - 权限库
USE test;执行 §1.2 SQL(动态 SELECT 自动复用兄弟菜单授权,SQL 完全不改) - 部署后端/前端到 test 环境
- 业务表
- PRD 上线前必查(只做 sanity check,§1.2 SQL 不需要替换硬编码)
USE xiaoqu_comples_p; -- ① 「账户管理」parent_id 是否仍是 131 SELECT id FROM webpage WHERE title='账户管理' AND parent_id=0; -- ② 「账户管理」下兄弟菜单 + 当前授权 authority 集合大小 SELECT w.id, w.title, COUNT(DISTINCT wa.authority_id) AS auth_cnt FROM webpage w LEFT JOIN webpage_authority wa ON wa.web_id=w.id AND wa.soft_delete=0 WHERE w.parent_id=131 GROUP BY w.id, w.title; -- ③ 防重复执行:确认 messageNotice 尚未存在 SELECT id, title, path FROM webpage WHERE path='messageNotice'; - PRD 上线:§1.2 SQL 把
USE改成USE xiaoqu_comples_p后整段照跑;如 prod「账户管理」parent_id 与 dev 不同(131),把 §1.2 里所有131替换成 prod 实际值即可,其余无需改
5. 风险与注意
| 项 | 风险 | 处理 |
|---|---|---|
parent_id=131 硬编码 |
PRD 不一定一致 | §4 ① sanity check 取真实 id |
authority_id=1/102 硬编码 |
已用动态 SELECT 替换 | §1.2 webpage_authority/field_webpage_authority INSERT 改为子查询自动复用同 parent 下兄弟菜单授权 |
page_field.class_name 全限定类名 |
后端 DTO 路径必须严格一致 | 改包名必同步改 SQL 或反向更新 |
LAST_INSERT_ID() 在 CROSS JOIN 之后 |
自增 id 会消耗但不回退 | 业务无影响 |
| 字段中文名歧义 | "渠道商"≈"代理商",截图列名是"代理商名称",搜索区下拉是"渠道商" | 按截图分别用,与现有 138 页面风格一致 |
| 数据来源未明确 | 表建好但永远空 | 触发逻辑确认后补开发 |
| dev 兄弟菜单授权集合极简(仅 1+102) | dev SQL 执行时仅给了 2 个 authority;test/prod 上同一份动态 SQL 会拿到更大集合(test 上 13~15 个) | 设计上接受(按"跟兄弟菜单可见性一致"原则),dev 不补 |