Files
xiaoqu_mcp_service/xiaoqu-franchisee/doc/agent-message-notice-dev.md
T
xiaoqu b6d4bf6126 导入三个参考项目: xiaoqu-franchisee / xiaoqu-publicbeanandmap / elasticsearchpublic
作为 xiaoqu-agent-service(MCP 服务) 的开发参考。新系统与旧系统零代码耦合,
仅照搬其行级数据权限规则与数据源结构。设计方案见 xiaoqu-franchisee/doc/agent-mcp-service-design.md
2026-09-01 23:00:47 +08:00

330 lines
17 KiB
Markdown
Raw 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.
# 开发文档:代理商消息通知页面
> **需求**:在「账户管理」菜单下新增「消息通知」页面(代理商账户余额提醒列表)。
> **环境**: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 不补 |