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

17 KiB
Raw Blame History

开发文档:代理商消息通知页面

需求:在「账户管理」菜单下新增「消息通知」页面(代理商账户余额提醒列表)。 环境: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:兄弟菜单授权数 812 不等(导自线上)→ 取并集后预计 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. 上线执行顺序

  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 不需要替换硬编码)
    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 不补