作为 xiaoqu-agent-service(MCP 服务) 的开发参考。新系统与旧系统零代码耦合, 仅照搬其行级数据权限规则与数据源结构。设计方案见 xiaoqu-franchisee/doc/agent-mcp-service-design.md
330 lines
17 KiB
Markdown
330 lines
17 KiB
Markdown
# 开发文档:代理商消息通知页面
|
||
|
||
> **需求**:在「账户管理」菜单下新增「消息通知」页面(代理商账户余额提醒列表)。
|
||
> **环境**: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)
|
||
|
||
```sql
|
||
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,新菜单的可见性自动跟同分类下的兄弟菜单一致,避免授权遗漏。
|
||
|
||
```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 不等(导自线上)→ 取并集后预计 13~15 个 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**
|
||
|
||
```java
|
||
public class AgentMessageNoticeQueryInput {
|
||
private Long agentId; // 渠道商下拉
|
||
private String notifyUserName; // 人员名称 模糊
|
||
private String notifyMobile; // 手机号码 模糊
|
||
private Integer page = 1;
|
||
private Integer limit = 10;
|
||
}
|
||
```
|
||
|
||
**Output**
|
||
|
||
```java
|
||
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;
|
||
}
|
||
```
|
||
|
||
**查询逻辑**
|
||
|
||
```java
|
||
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`)追加:
|
||
|
||
```js
|
||
{
|
||
path: '/messageNotice',
|
||
name: 'messageNotice',
|
||
component: () => import('@/components/page/accountManage/messageNotice.vue'),
|
||
meta: { title: '消息通知' }
|
||
}
|
||
```
|
||
|
||
### 3.2 API
|
||
|
||
`src/api/api.js`:
|
||
|
||
```js
|
||
agentMessageNotice: {
|
||
list: '/agentMessageNotice/list'
|
||
}
|
||
```
|
||
|
||
`src/api/accountService.js`(或新建 `agentMessageNoticeService.js`):
|
||
|
||
```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. 上线执行顺序
|
||
|
||
1. **DEV (`xiaoqu_franchisee_d` + `xiaoqu_comples_d`)**
|
||
- 业务库执行 §1.1(建表 `agent_message_notice`)
|
||
- 权限库 `USE xiaoqu_comples_d;` 执行 §1.2(事务)
|
||
- 后端编码 → `mvn package` → 部署
|
||
- 前端编码 → `yarn build-dev` → 部署
|
||
2. **DEV 冒烟**
|
||
- 管理员账号登录,左侧菜单出现「账户管理 > 消息通知」
|
||
- 进入页面:列表接口返回(空数据也算成功)
|
||
- 后台「权限组管理 → 配置页面内容权限」能看到这个页面的所有字段
|
||
- 手工 INSERT 几条 `agent_message_notice` 数据,验证列表渲染 / 三个搜索条件 / 排序
|
||
3. **TEST (`xiaoqu_franchisee_d` + `test`)**
|
||
- 业务表 `agent_message_notice` 共享 dev 业务库,已存在 — **无需建表**
|
||
- 权限库 `USE test;` 执行 §1.2 SQL(动态 SELECT 自动复用兄弟菜单授权,**SQL 完全不改**)
|
||
- 部署后端/前端到 test 环境
|
||
4. **PRD 上线前必查**(只做 sanity check,§1.2 SQL 不需要替换硬编码)
|
||
```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';
|
||
```
|
||
5. **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 不补 |
|