管理类软件通用高级查询组件(一)---升鲜宝生鲜配送供应链管理软件重构方案
解决方案(Spring Boot + Vue3 + MyBatis-Plus 落地版)
|----------|------------------------------------------------------------|
| 适用系统 | 升鲜宝供应链管理系统 / 多门店收银系统 / WMS / OMS / PMS / 财务 / CRM |
| 适用对象 | 后端研发、前端研发、产品经理、测试、架构负责人 |
| 技术栈 | Spring Boot、MyBatis-Plus、MySQL 8.0、Redis、Vue3、Element Plus |
| 文档版本 | V1.0 |
| 生成日期 | 2025-03-24 |
文档摘要:
本文档将截图中类似"高级搜索"的查询框,抽象为升鲜宝系统级通用能力:前端通过字段配置动态渲染查询表单,后端通过字段白名单、操作符解析、排序白名单、数据权限和字段权限统一生成安全查询条件,并支持用户保存常用查询方案。方案可覆盖商品、订单、采购、库存、财务、CRM、供应商等多个业务域,避免每个页面重复开发查询条件。
目录
-
建设目标与设计原则
-
业务场景与组件定位
-
总体架构设计
-
前端高级查询组件设计
-
后端通用查询模型设计
-
数据库表结构与数据字典
-
MyBatis-Plus 查询构建方案
-
权限、安全与防 SQL 注入设计
-
查询方案保存与默认条件
-
模块接入规范与示例
-
接口清单与请求响应示例
-
性能优化与缓存策略
-
测试验收清单
-
实施路线图
-
附录:DDL 与代码骨架
1. 建设目标与设计原则
升鲜宝系统中,商品、订单、库存、采购、销售、财务、CRM 等模块都存在复杂筛选需求。如果每个页面单独开发高级搜索弹窗,会产生字段重复、权限难统一、排序不可控、SQL 注入风险高、查询方案无法复用等问题。因此,本方案将高级查询抽象为平台级组件。
- 前端统一:一个 AdvancedSearchDialog 组件支持 input、select、treeSelect、dateRange、numberRange、remoteSelect 等多种控件。
- 后端统一:一个 QueryRequestDTO 接收查询条件、排序、分页和模块编码。
- 安全统一:所有字段必须进入字段白名单,前端只传业务字段编码,不传数据库字段名。
- 权限统一:查询条件与结果字段同时接入数据权限、字段权限、组织/门店权限。
- 体验统一:支持用户保存"我的查询方案"、默认查询方案、一键重置和常用条件回显。
2. 业务场景与组件定位
|----------|----------------------------------|-----------------------------------|
| 业务模块 | 典型查询条件 | 接入价值 |
| 商品/PMS | 机构、门店、类别、品牌、SKU编码、SKU名称、条码、上下架状态 | 商品档案与 SKU 选择场景复用,减少订单、采购、库存页面重复开发 |
| 订单/OMS | 客户、业务员、订单状态、配送方式、下单日期、审核状态 | 统一订单列表、订单选择弹窗、出库生成订单查询 |
| 采购/PUR | 供应商、采购员、采购状态、到货日期、付款状态 | 采购订单、采购入库、供应商对账统一过滤 |
| 仓库/WMS | 仓库、库区、库位、批次、效期、库存状态 | 库存查询、拣货、盘点、批次追踪统一条件 |
| 门店/HWMS | 门店、门店库位、商品、批次、盘点状态 | 门店仓储式超市库存、POS 出库、线上核销复用 |
| 财务/FIN | 客户/供应商、账期、结算状态、金额区间、单据日期 | 客户对账、供应商对账、收付款流水统一筛选 |
| CRM | 客户等级、客户状态、业务人员、提醒日期、授信状态 | 客户池、跟进记录、账期提醒统一查询 |
3. 总体架构设计
通用高级查询组件由前端渲染层、查询配置中心、查询解析引擎、权限增强层、业务查询 Facade 五部分组成。
Vue3 页面
├─ 普通搜索区
├─ 高级搜索按钮
└─ AdvancedSearchDialog.vue
↓ 提交 QueryRequestDTO
Spring Boot API
├─ QueryFieldController 查询字段配置
├─ QuerySchemeController 查询方案保存
├─ QueryFacade 业务查询门面
├─ QueryConditionParser 条件解析
├─ QueryFieldWhitelist 字段白名单
├─ QueryOperatorHandler 操作符处理
├─ FieldPermissionEngine 字段权限过滤
└─ DataScopeEngine 数据权限增强
↓
MyBatis-Plus / Custom SQL / Mapper
↓
MySQL 8.0
|--------|-------------------------------------------|----------------------------|
| 层级 | 组件 | 职责 |
| 前端组件层 | AdvancedSearchDialog / QueryFieldRenderer | 根据配置渲染高级查询弹窗,收集查询条件并回显 |
| 配置中心层 | sys_query_field / sys_query_scheme | 维护模块字段、组件类型、操作符、排序、用户方案 |
| 解析引擎层 | QueryConditionParser / OperatorHandler | 把前端字段编码转换成安全 SQL 条件 |
| 权限增强层 | DataScopeEngine / FieldPermissionEngine | 补充组织、门店、角色、字段级权限 |
| 业务查询层 | 各模块 QueryFacade | 承接业务列表接口,组合分页、排序、统计与 VO 转换 |
4. 前端高级查询组件设计
4.1 组件组成
|--------------------------|----------|---------------------------------------------------------|
| 组件 | 用途 | 说明 |
| AdvancedSearchDialog.vue | 高级搜索弹窗容器 | 控制弹窗标题、宽度、列布局、确定/取消/重置按钮 |
| QueryFieldRenderer.vue | 字段渲染器 | 根据 componentType 渲染 Input、Select、TreeSelect、DateRange 等 |
| QuerySchemeManager.vue | 查询方案管理 | 保存、加载、删除、设为默认查询方案 |
| useAdvancedSearch.ts | 组合式状态管理 | 处理表单数据、条件转换、字段显隐、默认值 |
| queryFieldApi.ts | 字段配置 API | 拉取 moduleCode 对应字段配置 |
4.2 字段配置模型
export interface QueryFieldConfig {
fieldCode: string
fieldName: string
componentType: 'input' | 'select' | 'treeSelect' | 'dateRange' | 'numberRange' | 'remoteSelect'
dictType?: string
defaultOperator: 'eq' | 'like' | 'likeRight' | 'between' | 'in'
requiredFlag: boolean
visibleFlag: boolean
span: number
placeholder?: string
remoteApi?: string
defaultValue?: any
}
4.3 商品 SKU 高级查询示例
const skuAdvancedFields = [
{ fieldCode: 'orgId', fieldName: '机构', componentType: 'remoteSelect', defaultOperator: 'eq', span: 12 },
{ fieldCode: 'shopId', fieldName: '门店', componentType: 'remoteSelect', defaultOperator: 'eq', span: 12 },
{ fieldCode: 'deptCode', fieldName: '部门编码', componentType: 'remoteSelect', defaultOperator: 'eq', span: 12 },
{ fieldCode: 'categoryId', fieldName: '类别编码', componentType: 'treeSelect', defaultOperator: 'eq', span: 12 },
{ fieldCode: 'skuCode', fieldName: 'SKU编码', componentType: 'input', defaultOperator: 'likeRight', span: 12 },
{ fieldCode: 'skuName', fieldName: 'SKU名称', componentType: 'input', defaultOperator: 'like', span: 12 },
{ fieldCode: 'barcode', fieldName: '国际条码', componentType: 'input', defaultOperator: 'eq', span: 12 },
{ fieldCode: 'processStatus', fieldName: '处理状态', componentType: 'select', dictType: 'process_status', defaultOperator: 'eq', span: 12 },
{ fieldCode: 'deliveryMode', fieldName: '配送方式', componentType: 'select', dictType: 'delivery_mode', defaultOperator: 'eq', span: 12 }
]
5. 后端通用查询模型设计
5.1 请求 DTO
@Data
public class QueryRequestDTO {
private String moduleCode;
private Integer pageNo = 1;
private Integer pageSize = 20;
private List<QueryConditionDTO> conditions;
private List<QueryOrderDTO> orders;
private Boolean includeSummary = false;
}
@Data
public class QueryConditionDTO {
private String field;
private String operator;
private Object value;
private Object valueEnd;
}
@Data
public class QueryOrderDTO {
private String field;
private String direction;
}
5.2 操作符枚举
|----------------|-----------|---------------------------------|-------------------------------|
| 操作符 | 含义 | SQL 语义 | 适用控件 |
| eq | 等于 | column = ? | select / input / remoteSelect |
| ne | 不等于 | column <> ? | select / input |
| like | 包含 | column LIKE concat('%', ?, '%') | input |
| likeLeft | 左模糊 | column LIKE concat('%', ?) | input |
| likeRight | 右模糊 | column LIKE concat(?, '%') | input |
| gt / ge | 大于 / 大于等于 | column > ? / column >= ? | number / date |
| lt / le | 小于 / 小于等于 | column < ? / column <= ? | number / date |
| between | 区间 | column BETWEEN ? AND ? | dateRange / numberRange |
| in | 包含任一 | column IN (...) | multiSelect |
| null / notNull | 为空 / 不为空 | IS NULL / IS NOT NULL | checkbox / special |
6. 数据库表结构与数据字典
数据库层采用"字段配置表 + 查询方案表 + 查询日志表"的三表模型。字段配置表解决不同模块可查询字段、字段映射、控件类型、操作符和排序白名单;查询方案表解决用户自定义常用条件;查询日志表用于审计与慢查询分析。
6.1 sys_query_field:通用查询字段配置表
|-------------------|--------------|--------|------------------------------------------------------|
| 字段名 | 类型 | 必填 | 说明 |
| id | BIGINT | 是 | 主键 ID |
| module_code | VARCHAR(64) | 是 | 模块编码,如 pms_sku、oms_order |
| field_code | VARCHAR(64) | 是 | 前端字段编码,如 skuName |
| field_name | VARCHAR(100) | 是 | 字段显示名称 |
| table_alias | VARCHAR(32) | 否 | SQL 表别名,如 g、o、c |
| column_name | VARCHAR(64) | 是 | 真实数据库字段名,如 sku_name |
| java_type | VARCHAR(32) | 是 | Java 类型 String、Long、Integer、BigDecimal、LocalDateTime |
| component_type | VARCHAR(32) | 是 | 控件类型 input、select、treeSelect、dateRange |
| dict_type | VARCHAR(64) | 否 | 字典类型 |
| support_operators | VARCHAR(255) | 是 | 支持操作符列表 |
| default_operator | VARCHAR(32) | 是 | 默认操作符 |
| sortable_flag | TINYINT | 是 | 是否允许排序 |
| display_order | INT | 是 | 显示顺序 |
| required_flag | TINYINT | 是 | 是否必填 |
| visible_flag | TINYINT | 是 | 是否显示 |
| enabled_flag | TINYINT | 是 | 是否启用 |
| remark | VARCHAR(500) | 否 | 备注 |
| create_time | DATETIME | 是 | 创建时间 |
| update_time | DATETIME | 是 | 更新时间 |
CREATE TABLE sys_query_field (
id BIGINT NOT NULL PRIMARY KEY COMMENT '主键ID',
module_code VARCHAR(64) NOT NULL COMMENT '模块编码',
field_code VARCHAR(64) NOT NULL COMMENT '前端字段编码',
field_name VARCHAR(100) NOT NULL COMMENT '字段名称',
table_alias VARCHAR(32) DEFAULT NULL COMMENT 'SQL表别名',
column_name VARCHAR(64) NOT NULL COMMENT '数据库字段名',
java_type VARCHAR(32) NOT NULL COMMENT 'Java类型',
component_type VARCHAR(32) NOT NULL COMMENT '组件类型',
dict_type VARCHAR(64) DEFAULT NULL COMMENT '字典类型',
support_operators VARCHAR(255) NOT NULL COMMENT '支持操作符',
default_operator VARCHAR(32) NOT NULL COMMENT '默认操作符',
sortable_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否允许排序:0否,1是',
display_order INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
required_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否必填:0否,1是',
visible_flag TINYINT NOT NULL DEFAULT 1 COMMENT '是否显示:0否,1是',
enabled_flag TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用:0否,1是',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_module_field (module_code, field_code),
KEY idx_module_enabled (module_code, enabled_flag, display_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用查询字段配置表';
6.2 sys_query_scheme:用户查询方案表
CREATE TABLE sys_query_scheme (
id BIGINT NOT NULL PRIMARY KEY COMMENT '主键ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
module_code VARCHAR(64) NOT NULL COMMENT '模块编码',
scheme_name VARCHAR(100) NOT NULL COMMENT '查询方案名称',
condition_json JSON NOT NULL COMMENT '查询条件JSON',
default_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认:0否,1是',
enabled_flag TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用:0否,1是',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_user_module_name (user_id, module_code, scheme_name),
KEY idx_user_module_default (user_id, module_code, default_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户查询方案表';
6.3 sys_query_log:查询审计日志表
CREATE TABLE sys_query_log (
id BIGINT NOT NULL PRIMARY KEY COMMENT '主键ID',
module_code VARCHAR(64) NOT NULL COMMENT '模块编码',
user_id BIGINT NOT NULL COMMENT '用户ID',
request_json JSON NOT NULL COMMENT '请求条件JSON',
cost_ms INT NOT NULL DEFAULT 0 COMMENT '耗时毫秒',
result_count INT NOT NULL DEFAULT 0 COMMENT '结果数量',
success_flag TINYINT NOT NULL DEFAULT 1 COMMENT '是否成功:0否,1是',
error_message VARCHAR(1000) DEFAULT NULL COMMENT '异常信息',
trace_id VARCHAR(64) DEFAULT NULL COMMENT '链路ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
KEY idx_module_time (module_code, create_time),
KEY idx_user_time (user_id, create_time),
KEY idx_trace_id (trace_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用查询日志表';
7. MyBatis-Plus 查询构建方案
7.1 核心流程
-
校验 moduleCode 是否存在并启用。
-
加载 sys_query_field 字段白名单,按 fieldCode 建立 Map。
-
过滤空值条件,校验字段是否可查询。
-
校验 operator 是否在 support_operators 中。
-
将 fieldCode 映射为 tableAlias.columnName。
-
按 javaType 做值类型转换。
-
通过 OperatorHandler 写入 LambdaQueryWrapper 或 Custom SQL 参数。
-
追加数据权限条件、组织/门店条件。
-
校验排序字段白名单,追加 order by。
-
执行分页查询并记录 sys_query_log。
7.2 查询解析器代码骨架
@Service
public class QueryConditionParser {
@Resource
private QueryFieldService queryFieldService;
@Resource
private DataScopeEngine dataScopeEngine;
public QueryBuildResult parse(QueryRequestDTO request, LoginUser loginUser) {
Map<String, QueryFieldConfig> fieldMap = queryFieldService.getFieldMap(request.getModuleCode());
QueryBuildResult result = new QueryBuildResult();
for (QueryConditionDTO condition : request.getConditions()) {
QueryFieldConfig field = fieldMap.get(condition.getField());
if (field == null) {
throw new SxbException("非法查询字段:" + condition.getField());
}
if (!field.supportOperator(condition.getOperator())) {
throw new SxbException("字段不支持该查询操作符");
}
OperatorHandler handler = OperatorHandlerFactory.get(condition.getOperator());
handler.apply(result, field, condition);
}
dataScopeEngine.apply(request.getModuleCode(), result, loginUser);
return result;
}
}
8. 权限、安全与防 SQL 注入设计
|---------|-----------------------------|----------------------------------------------|
| 风险点 | 错误做法 | 正确做法 |
| 字段注入 | 前端直接传 sku_name 或 g.sku_name | 前端只传 skuName,后端白名单映射 |
| 操作符注入 | 前端传自定义 SQL 片段 | operator 必须是系统枚举,并且字段支持该操作符 |
| 排序注入 | orderBy 直接拼接请求值 | 排序字段必须 sortableFlag=1,direction 只允许 asc/desc |
| 越权查询 | 前端传 orgId/shopId 任意查 | 后端 DataScopeEngine 强制追加当前用户可访问组织/门店 |
| 字段越权 | 返回所有字段 | FieldPermissionEngine 对 VO 字段做脱敏或移除 |
| 慢查询 | 复杂 like + 大分页 | 限制 pageSize、建立索引、记录查询日志、启用缓存 |
8.1 字段白名单规则
- 禁止前端传数据库列名。
- 禁止前端传 SQL 片段。
- 禁止未配置字段参与查询或排序。
- 禁止未启用字段被展示或参与查询。
- 跨表字段必须配置 table_alias,避免 SQL 歧义。
8.2 数据权限接入
通用查询组件必须与升鲜宝数据权限体系打通。对于有组织、门店、仓库、客户、供应商归属的数据,业务查询 Facade 不应该依赖前端传入权限条件,而应由后端按当前登录用户自动追加。
public interface DataScopeEngine {
void apply(String moduleCode, QueryBuildResult result, LoginUser loginUser);
}
// 示例:商品 SKU 查询自动追加组织和门店权限
// g.org_id IN (当前用户可访问机构)
// g.shop_id IN (当前用户可访问门店)
9. 查询方案保存与默认条件
查询方案用于解决用户经常重复设置同一组高级查询条件的问题。每个用户、每个模块可以保存多个查询方案,并允许设置一个默认方案。
|--------|----------------------------------------------------|
| 功能 | 说明 |
| 保存当前条件 | 将当前 conditions、orders、pageSize 等保存为 condition_json |
| 设为默认 | 用户进入模块页面时自动加载默认查询方案 |
| 共享方案 | 后续可扩展为角色级或公司级共享查询方案 |
| 重置条件 | 恢复系统默认条件,不影响已保存方案 |
| 删除方案 | 删除个人方案,不影响模块字段配置 |
{
"pageNo": 1,
"pageSize": 20,
"conditions": [
{ "field": "shopId", "operator": "eq", "value": 10001 },
{ "field": "skuName", "operator": "like", "value": "龙虾" },
{ "field": "processStatus", "operator": "eq", "value": 0 }
],
"orders": [
{ "field": "createTime", "direction": "desc" }
]
}
10. 模块接入规范与示例
|--------|------------------------|------------------------|
| 模块 | moduleCode | 建议接入页面 |
| 商品档案 | pms_goods | 商品列表、商品选择弹窗、商品导入校验 |
| SKU 档案 | pms_sku | SKU 列表、订单选品、采购选品、出入库选品 |
| 销售订单 | oms_order | 订单列表、订单审核、销售出库生成 |
| 采购订单 | pur_order | 采购列表、采购入库、供应商对账 |
| 库存查询 | wms_inventory | 实时库存、批次库存、效期预警 |
| 门店库存 | hwms_inventory | 门店库存、POS 出库、线上核销 |
| 客户档案 | crm_customer | 客户列表、账期提醒、授信查询 |
| 供应商档案 | sup_supplier | 供应商列表、采购对账、结算状态 |
| 客户对账 | fin_customer_statement | 客户账单、应收、收款流水 |
| 供应商对账 | fin_supplier_statement | 供应商账单、应付、付款流水 |
10.1 pms_sku 字段初始化 SQL 示例
INSERT INTO sys_query_field
(id, module_code, field_code, field_name, table_alias, column_name, java_type, component_type, dict_type, support_operators, default_operator, sortable_flag, display_order)
VALUES
(1001, 'pms_sku', 'orgId', '机构', 'g', 'org_id', 'Long', 'remoteSelect', NULL, 'eq,in', 'eq', 0, 10),
(1002, 'pms_sku', 'shopId', '门店', 'g', 'shop_id', 'Long', 'remoteSelect', NULL, 'eq,in', 'eq', 0, 20),
(1003, 'pms_sku', 'categoryId', '类别编码', 'g', 'category_id', 'Long', 'treeSelect', NULL, 'eq,in', 'eq', 0, 30),
(1004, 'pms_sku', 'skuCode', 'SKU编码', 's', 'sku_code', 'String', 'input', NULL, 'eq,likeRight,in', 'likeRight', 1, 40),
(1005, 'pms_sku', 'skuName', 'SKU名称', 's', 'sku_name', 'String', 'input', NULL, 'eq,like,likeRight', 'like', 1, 50),
(1006, 'pms_sku', 'barcode', '国际条码', 's', 'barcode', 'String', 'input', NULL, 'eq,likeRight', 'eq', 1, 60),
(1007, 'pms_sku', 'processStatus', '处理状态', 's', 'process_status', 'Integer', 'select', 'process_status', 'eq,in', 'eq', 0, 70),
(1008, 'pms_sku', 'deliveryMode', '配送方式', 's', 'delivery_mode', 'Integer', 'select', 'delivery_mode', 'eq,in', 'eq', 0, 80),
(1009, 'pms_sku', 'createTime', '创建时间', 's', 'create_time', 'LocalDateTime', 'dateRange', NULL, 'between,ge,le', 'between', 1, 90);
11. 接口清单与请求响应示例
|---------------------------------|--------|-----------------------------|
| 接口 | 方法 | 说明 |
| /api/query/fields/{moduleCode} | GET | 获取指定模块的高级查询字段配置 |
| /api/query/schemes/{moduleCode} | GET | 获取当前用户在指定模块下的查询方案 |
| /api/query/schemes | POST | 保存查询方案 |
| /api/query/schemes/{id} | PUT | 修改查询方案 |
| /api/query/schemes/{id} | DELETE | 删除查询方案 |
| /api/pms/sku/page | POST | 业务模块分页查询,接收 QueryRequestDTO |
11.1 获取查询字段响应示例
{
"code": 0,
"message": "success",
"data": [
{ "fieldCode": "orgId", "fieldName": "机构", "componentType": "remoteSelect", "defaultOperator": "eq", "span": 12 },
{ "fieldCode": "shopId", "fieldName": "门店", "componentType": "remoteSelect", "defaultOperator": "eq", "span": 12 },
{ "fieldCode": "skuName", "fieldName": "SKU名称", "componentType": "input", "defaultOperator": "like", "span": 12 }
]
}
11.2 分页查询请求示例
{
"moduleCode": "pms_sku",
"pageNo": 1,
"pageSize": 20,
"conditions": [
{ "field": "shopId", "operator": "eq", "value": 2001 },
{ "field": "skuName", "operator": "like", "value": "波士顿龙虾" },
{ "field": "processStatus", "operator": "eq", "value": 0 }
],
"orders": [
{ "field": "createTime", "direction": "desc" }
]
}
12. 性能优化与缓存策略
|---------|-----------------------------------------------------------------|
| 优化点 | 方案 |
| 字段配置缓存 | 按 moduleCode 缓存 sys_query_field,可使用 Caffeine 本地缓存 + Redis 分布式缓存 |
| 字典缓存 | DictSelect 所需字典按 dictType + langCode 缓存,避免每次打开弹窗查询数据库 |
| 远程选择器 | 机构、门店、客户、供应商等 remoteSelect 支持关键字搜索和分页 |
| 索引设计 | 高频条件字段建立组合索引,如 shop_id + sku_code、customer_id + bill_date |
| 分页限制 | pageSize 默认 20,最大建议 200;大数据导出走异步导出任务 |
| 慢查询日志 | sys_query_log 记录 costMs,超过阈值写入告警或运维日志 |
| 条件裁剪 | 空值、空数组、空字符串不进入 SQL 条件 |
13. 测试验收清单
|----------|------------------------------------------------------|
| 测试类别 | 验收点 |
| 功能测试 | 字段能正确展示、回显、重置、提交,查询结果符合预期 |
| 权限测试 | 无权限组织、门店、仓库、客户数据不可被查询 |
| 字段安全测试 | 传入未配置字段、数据库字段名、非法 operator 必须报错 |
| 排序安全测试 | 未配置 sortableFlag 的字段不允许排序 |
| 兼容测试 | input、select、treeSelect、dateRange、numberRange 均可正常工作 |
| 性能测试 | 常用查询 1 秒内返回;复杂查询有慢查询记录 |
| 回归测试 | 接入通用组件后,原有列表查询和分页行为不被破坏 |
| 审计测试 | 查询失败、慢查询、越权尝试有日志记录 |
14. 实施路线图
|-----------|--------|-----------------------------------------------------|
| 阶段 | 周期 | 交付内容 |
| 第一阶段:基础组件 | 1 周 | AdvancedSearchDialog、字段配置接口、QueryRequestDTO、字段白名单校验 |
| 第二阶段:业务试点 | 1 周 | 先接入 pms_sku、oms_order、wms_inventory 三个高频模块 |
| 第三阶段:查询方案 | 1 周 | 实现用户查询方案保存、默认方案、删除、回显 |
| 第四阶段:权限增强 | 1-2 周 | 接入 DataScopeEngine、FieldPermissionEngine、查询审计日志 |
| 第五阶段:全域推广 | 2-4 周 | 推广到采购、供应商、CRM、财务、门店库存等模块 |
| 第六阶段:高级能力 | 持续迭代 | 共享查询方案、复杂条件组 OR/AND、异步导出联动、查询分析报表 |
15. 附录:Controller / Service 代码骨架
@RestController
@RequestMapping("/api/query/fields")
public class QueryFieldController {
@Resource
private QueryFieldService queryFieldService;
@GetMapping("/{moduleCode}")
public List<QueryFieldVO> listByModule(@PathVariable String moduleCode) {
return queryFieldService.listEnabledFields(moduleCode);
}
}
@RestController
@RequestMapping("/api/query/schemes")
public class QuerySchemeController {
@Resource
private QuerySchemeService querySchemeService;
@GetMapping("/{moduleCode}")
public List<QuerySchemeVO> list(@PathVariable String moduleCode) {
return querySchemeService.listMySchemes(moduleCode);
}
@PostMapping
public Long save(@RequestBody QuerySchemeSaveDTO dto) {
return querySchemeService.saveScheme(dto);
}
}
@RestController
@RequestMapping("/api/pms/sku")
public class PmsSkuController {
@Resource
private PmsSkuQueryFacade pmsSkuQueryFacade;
@PostMapping("/page")
public PageResult<PmsSkuVO> page(@RequestBody QueryRequestDTO request) {
request.setModuleCode("pms_sku");
return pmsSkuQueryFacade.page(request);
}
}
16. 最终落地建议
- 不要把高级查询只做成单个页面功能,应作为升鲜宝平台级基础组件建设。
- 第一批建议接入 pms_sku、oms_order、wms_inventory,因为这三个模块最能验证商品、订单、库存三类典型场景。
- 后端必须坚持"字段白名单 + 操作符枚举 + 排序白名单",禁止任何前端 SQL 片段进入后端。
- 查询组件应与数据权限、字段权限、操作日志统一接入,避免后续补权限成本过高。
- 后续可继续扩展为"通用列表中心":查询条件、列表列配置、导入导出方案、打印模板一起配置化。