Files
workspace/code/fms/软件需求.md
T
2026-07-19 21:57:40 +08:00

453 lines
21 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.
# FMS 货代物流管理系统需求规范
## 1. 项目定位
- FMS 是一个前后端分离、以模块为中心的多机构货代物流管理系统。
- 一个机构对应一个独立数据库,机构码统一命名为 `orgid`。
- 登录时必须提交机构码、账号和密码。后端根据机构码连接对应数据库并验证用户。
- 登录成功后,登录凭证与机构码绑定。后续请求只操作当前机构的数据库,切换机构必须重新登录。
- 后端负责登录、机构数据库路由、通用数据接口、事务控制以及少量确有必要的特殊业务接口。
- 普通业务优先复用通用查询和保存能力,避免为每张表单独创建 Controller、DTO 和实体类。
- 模块负责组织查询数据源、保存对象、字段展示、查询条件、编辑方式和权限。
## 2. 现有系统过渡期约定
以下约定用于当前系统的开发过渡阶段,不作为新数据库重构后的最终安全模型。
- 页面、查询条件、SQL、数据组织、校验和普通业务流程目前主要由前端完成。
- 当前阶段暂不重点考虑客户端直接提交查询条件和 SQL 带来的安全风险。
- 后端通用接口完成后应尽量保持稳定,普通业务优先修改前端配置和数据库配置。
- 文件、报表、导入和导出等能力也应建设为稳定的平台级通用接口,后续不得为具体业务模块增加特殊接口或后端代码。
- 新数据库启用后,前端应逐步改为只提交 `module_id` 和结构化数据,不再直接指定任意表名或 SQL。
- `G3HY2025` 旧数据库仅用于参考表结构和业务含义,新代码只面向 `FMS` 新数据库,不保留旧表结构的运行时兼容分支。
## 3. 现有通用接口
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `POST` | `/api/auth/login` | 机构登录 |
| `POST` | `/api/data/loaddata` | 按表或视图加载数据 |
| `POST` | `/api/data/loaddatabysql` | 执行完整查询 SQL |
| `POST` | `/api/data/page` | 执行分页查询 |
| `POST` | `/api/data/saveobjt` | 多表事务保存 |
| `GET` | `/api/data/nextid` | 获取下一个雪花 ID |
### 3.1 登录
```json
{
"orgid": "G3HD",
"userid": "admin",
"password": "123456"
}
```
登录成功后返回登录凭证和用户信息。后续请求携带登录凭证,不再重复提交 `orgid`。
```json
{
"code": 0,
"message": "操作成功",
"data": {
"token": "<jwt>",
"orgid": "G3HD",
"user": {
"id": "admin",
"name": "管理员"
}
}
}
```
### 3.2 loaddata
用于表或视图的普通查询,不执行分页。
```json
{
"view_name": "v_bs_business",
"search_columns": ["b_id", "b_state", "v_customer_company"],
"search_condition": "b_state = '草拟'",
"order_by": "b_id desc"
}
```
- `view_name` 必填,表示表名或视图名。
- `search_columns` 可省略,省略时返回全部字段。
- `search_condition` 可省略,由前端直接拼接,不包含 `WHERE` 关键字。
- `order_by` 可省略,只支持一个排序字段及可选的 `ASC` 或 `DESC`。
成功响应:
```json
{
"code": 0,
"message": "操作成功",
"data": []
}
```
### 3.3 loaddatabysql
用于关联、聚合、统计等完整 SQL 查询,不执行分页。过渡期内 SQL 由前端直接拼接。
```json
{
"sql": "select customer_id, sum(amount) as total from v_fee where fee_date >= '2026-01-01' group by customer_id"
}
```
成功响应与 `loaddata` 相同,`data` 直接为查询结果数组。
### 3.4 page
所有分页查询统一使用本接口。
```json
{
"view_name": "v_bs_business",
"search_columns": ["b_id", "b_state", "v_customer_company"],
"search_condition": "b_state = '草拟'",
"order_by": "b_id desc",
"page_no": 1,
"page_size": 20
}
```
- `view_name` 必填,表示分页查询使用的表或视图名称。
- `search_columns` 可省略,省略时返回全部字段。
- `search_condition` 可省略,由前端直接拼接,不包含 `WHERE` 关键字。
- `order_by` 必填,用于保证分页顺序稳定;支持使用逗号分隔多个查询结果字段。
- `page_no` 从 1 开始,默认值为 `1`。
- `page_size` 默认值为 `20`。
- SQL Server 2022 使用 `COUNT_BIG(*) OVER()` 与 `OFFSET/FETCH` 在一次查询中返回当前页和总记录数;仅页码越界且当前页为空时补充执行总数查询。
成功响应:
```json
{
"code": 0,
"message": "操作成功",
"data": {
"rows": [],
"row_count": 0,
"total": 0,
"page_no": 1,
"page_size": 20
}
}
```
### 3.5 saveobjt
用于一个或多个表的新增、修改和删除。
```json
[
{
"table": "bs_business",
"key_field": "b_id",
"inserts": [],
"updates": [
{
"b_id": "10001",
"b_state": "草拟"
}
],
"deletes": []
},
{
"table": "bs_business_container",
"key_field": "subid",
"inserts": [],
"updates": [],
"deletes": [
{
"subid": "20001"
}
]
}
]
```
- 请求体必须是非空数组。
- `table` 是目标表名,`key_field` 是该表的单个主键字段。
- `inserts`、`updates` 和 `deletes` 均为可选数组,省略时按空数组处理。
- 修改和删除的数据必须包含 `key_field` 对应的主键值。
- 数字字段的空字符串不做特殊转换,按原值提交给数据库。
- 每张表按照删除、修改、新增的顺序执行,表与表之间按照请求数组顺序执行。
- 一次请求中的所有表和所有操作使用同一个数据库连接和事务。
- 全部操作成功后提交,任意操作失败时全部回滚。
- 保存成功只返回成功状态,不返回每张表的操作数量。
成功响应:
```json
{
"code": 0,
"message": "保存成功",
"data": null
}
```
保存失败时可以返回失败位置:
```json
{
"code": 2001,
"message": "保存失败",
"data": {
"table": "bs_business_container",
"action": "update",
"index": 2
}
}
```
### 3.6 nextid
无请求体。成功时返回一个雪花 ID:
```json
{
"code": 0,
"message": "操作成功",
"data": 1946123456789012345
}
```
## 4. 统一约定
- JavaScript 和 Java 内部函数、方法及变量使用小驼峰命名法。
- JSON 请求与响应字段使用 `snake_case`,在 API 边界与数据库字段显式映射。
- 数据库字段保留现有命名风格:主表普通字段使用 `b_` 前缀,主键固定为 `b_id`;子表普通字段使用 `mx_` 前缀,主键固定为 `subid`。
- API 路径使用全小写名称,JSON 多单词字段使用 `snake_case`。
- `code = 0` 表示成功,非零值表示失败。
- 前端根据 `code` 生成 `success` 字段,`code = 0` 时 `success = true`。
- `loaddata` 和 `loaddatabysql` 的查询结果直接放在 `data` 数组中;`page` 的分页记录放在 `data.rows` 中。
- 过渡期查询条件和 SQL 由前端拼接,不使用参数占位符。
- 不增加功能相同但名称不同的接口。
- 不使用 `V2`、`V3`、`new`、`old`、`test` 或 `bak` 等临时名称。
- 接口发生不兼容升级时,统一通过 `/api/v2` 等版本路径升级。
- 前端和后端代码修改完成后默认不执行构建或测试命令,只进行静态检查;仅在用户明确要求时运行构建或测试。
## 5. 新数据库重构方案
### 5.1 重构目标
- 保持以模块为中心的开发方式。
- 将业务数据约束与页面配置分离。
- 所有业务页面、页面布局、按钮、标签页、弹窗和业务流程仍由前端手工开发。
- 模块配置只服务于通用 Table 和 Form 组件,不根据配置自动生成完整页面、路由或业务流程。
- Table 根据模块配置渲染列、格式、查询项和排序;Form 根据模块配置渲染字段、控件、校验和分组。
- 模块定义查询对象、保存对象、可选业务主键和字段配置。
- 前端只提交 `module_id`、结构化查询条件和待保存数据。
- 后端根据模块配置解析允许访问的对象和字段,并使用参数化 SQL。
- 单表和主子表保存都复用 `saveobjt`;手写页面负责组织多张表的变更,后端在一个事务中统一提交或回滚。
- 后端只保留稳定的通用接口,不为模块分别新增查询、新增、修改、删除接口或处理器代码。
- 复杂业务优先通过手写页面、数据库约束和通用事务保存组合实现,不建立模块处理器注册与分派机制。
- 不将全部业务逻辑塞入配置表或动态 SQL。
### 5.2 数据库版本与基础规范
- 新数据库建议使用 SQL Server 2019 或 SQL Server 2022,不再使用 SQL Server 2008 R2。
- 继续采用一个机构对应一个独立业务数据库的隔离方式,业务表不重复保存 `orgid`。
- 技术主键统一使用雪花 ID,数据库类型为 `bigint`。主表主键固定为 `b_id`,子表主键固定为 `subid`。
- 中文和多语言文本使用 `nvarchar`,长文本使用 `nvarchar(max)`,不使用 `text`。
- 标志和状态字段使用 `tinyint` 等整数类型,不使用 `bit` 或字符串类型。二态字段仍以数字 `0`、`1` 表示,业务需要时允许扩展为 `2`、`3`、`4` 等状态;Java 端按数字处理,不映射为 `Boolean`。
- 时间字段使用 `datetime2`。
- 金额、单价、数量、汇率和税率不强制统一精度,根据每个字段的业务含义分别定义 `decimal` 的精度和小数位数。
- 迁移旧字段时保留原有精度;新增字段必须在需求或表结构中明确精度,不使用 `float` 代替精确金额。
- 字段命名延续现有风格:主表字段使用 `b_` 前缀,子表字段使用 `mx_` 前缀。
- 主表与子表的外键字段也遵循对应前缀,例如子表引用主表时使用 `mx_<业务对象>_id`。
- 数字字段的空字符串不自动转换成 `0`,由调用方提交正确的数据类型。
### 5.3 dbo 与表名前缀
- 新数据库的所有业务表、配置表和视图统一创建在 `dbo` 下,不创建其他 Schema。
- 领域划分继续使用现有表名前缀,避免增加对象名称和通用接口的复杂度。
- 模块配置中的数据库对象只保存裸对象名,不保存 `dbo.` 或其他架构前缀,例如 `bs_business`、`cw_bill` 和 `v_bs_business`。
| 表名前缀 | 用途 |
| --- | --- |
| `s_` | 模块、字段、菜单、权限和其他系统配置 |
| `b_` | 用户、客户、联系人、港口、币种等基础资料 |
| `bs_` | 业务单、箱信息、提单和运输业务 |
| `cw_` | 费用、账单、发票和收付款 |
| `bc_` | 合同、价目表和价格明细 |
| `bf_` | 文件和附件 |
| `oa_` | 通知、提醒等办公功能 |
| `sh_` | 审批流程 |
| `v_` | 面向查询和模块展示的视图 |
### 5.4 核心配置表
第一阶段只建立必要的配置表,避免过早建设复杂的低代码平台。
| 表 | 用途 |
| --- | --- |
| `dbo.s_module` | 模块定义,主表字段使用 `b_` 前缀 |
| `dbo.s_module_field` | 模块字段子表,字段使用 `mx_` 前缀 |
| `dbo.s_module_group` | Form 分组子表,字段使用 `mx_` 前缀 |
| `dbo.s_option_source` | 下拉框和字典数据源主表 |
| `dbo.s_option_item` | 固定选项子表,字段使用 `mx_` 前缀 |
| `dbo.s_function_node` | 统一功能树,组织目录、页面、分组和模块位置 |
角色和权限仍使用独立系统表,不属于 Table/Form 渲染配置。菜单是功能树中 `b_show_in_menu = 1` 的目录和页面节点投影,页面节点可以直接绑定主模块;隐藏子模块可以挂在页面或其他模块下。主子页面关系仍由手写页面负责组织,功能树不自动生成页面或 Tab。
### 5.5 模块定义
`dbo.s_module` 建议包含以下核心字段:
| 字段 | 用途 |
| --- | --- |
| `b_id` | 模块唯一标识,主键 |
| `b_name` | 模块名称 |
| `b_query_object` | 查询表或稳定查询视图 |
| `b_save_object` | 保存目标表,只读模块允许为空 |
| `b_key_field` | 系统主键字段,平台内部使用,普通主表固定为 `b_id` |
| `b_business_key_field` | 可选业务主键字段,例如业务单号 |
| `b_default_order` | 默认排序 |
| `b_enabled` | 是否启用 |
| `b_sort_order` | 模块排序 |
查询对象与保存对象必须分开:查询视图负责组合展示数据,保存表负责持久化可写字段。系统主键固定由后端生成雪花 ID,不在配置页面重复设置;业务主键是带唯一约束的业务字段,例如 `HY20270601`,通过 `b_business_key_field` 标识但不代替系统主键。
### 5.6 模块字段配置
`dbo.s_module_field` 直接归属于模块,使用 `subid` 作为主键,并为 `(mx_module_id, mx_field_code)` 建立唯一索引。同一个查询视图被多个模块复用时,各模块可以拥有独立的显示和编辑配置。
| 配置分组 | 建议字段 |
| --- | --- |
| 主键与归属 | `subid`、`mx_module_id` |
| 字段映射 | `mx_field_code`、`mx_query_field`、`mx_save_field`、`mx_caption` |
| 列表显示 | `mx_list_visible`、`mx_list_order`、`mx_list_width`、`mx_sortable`、`mx_display_format` |
| 查询设置 | `mx_searchable`、`mx_search_order` |
| 表单编辑 | `mx_form_visible`、`mx_form_group_code`、`mx_form_order`、`mx_form_span`、`mx_editable`、`mx_required`、`mx_component_type` |
| 数据来源 | `mx_option_source_id`、`mx_default_rule`、`mx_validation_rule` |
- `mx_field_code` 是模块对前端暴露的稳定字段名。
- `mx_query_field` 对应查询对象返回的字段。
- `mx_save_field` 对应保存表字段,计算字段和只读字段可以为空。
- 字段配置不保存固定查询操作符。高级查询运行时根据字段类型提供 `eq`、`like`、`between`、`in` 等合法操作符,由用户为每条条件选择。
- 主要配置使用明确字段保存,只有少量不稳定的组件扩展属性才考虑使用 JSON。
### 5.7 表单分组与选项来源
- `dbo.s_module_group` 使用 `subid` 作为主键,其余字段使用 `mx_` 前缀。
- 分组配置包含 `mx_module_id`、`mx_group_code`、`mx_group_name`、`mx_sort_order`、`mx_column_count`、`mx_collapsible` 和 `mx_default_collapsed`。
- `dbo.s_module_field.mx_form_group_code` 指向对应分组,Form 按分组顺序和字段顺序渲染。
- 没有分组的字段放入默认分组,简单表单不强制配置分组数据。
- 固定下拉选项保存在 `dbo.s_option_item`。
- 数据库字典选项通过 `dbo.s_option_source` 指定允许查询的对象、值字段、显示字段和排序字段。
- 非固定、非模块查询的复杂选项由手写页面组件负责,不在配置表保存任意可执行 SQL,也不增加后端选项处理器。
### 5.8 功能树、菜单与模块
`dbo.s_function_node` 是技术管理和业务菜单共用的功能结构,不是页面生成配置。节点类型固定为 `directory`、`page`、`group` 和 `module`:
| 节点类型 | 用途 |
| --- | --- |
| `directory` | 业务域或功能目录,可以显示在菜单中 |
| `page` | 已注册的手写页面,可以配置路由并显示在菜单中 |
| `group` | 页面内部的技术分组,只在配置管理中显示 |
| `module` | 指向一个 `s_module`,只在配置管理中显示 |
- 功能节点使用 `b_parent_id` 形成树,并包含 `b_name`、`b_page_code`、`b_module_id`、`b_route`、`b_icon`、`b_show_in_menu`、`b_enabled` 和 `b_sort_order`。
- `module` 节点必须指定 `b_module_id`;`page` 节点也可以直接绑定主模块,同一个模块允许被多个功能节点引用。
- `b_show_in_menu` 只允许用于 `directory` 和 `page` 节点。菜单隐藏不代表模块停用,也不代替权限判断。
- 功能树中的模块父子关系只用于技术人员定位和分类,不定义保存事务、主子表外键或业务流程。
- 页面和 Tab 继续手写。功能树只帮助定位模块和投影菜单,不根据树节点自动生成组件。
- 迁移期间,功能树中的可见目录和页面按节点编码或路由与静态菜单合并;未登记菜单继续保留,完成全部路由核对后再移除静态回退。
模块管理页面左侧按功能树展示完整路径,并提供“未归档模块”虚拟节点。搜索至少匹配功能节点名称、模块编码、模块名称、查询对象和保存对象,结果必须保留祖先路径。左侧可以分别显示或隐藏模块节点和菜单节点;页面支持直接维护目录、页面、模块绑定和隐藏子模块位置。
### 5.9 通用组件与手写页面的边界
| 层级 | 负责内容 |
| --- | --- |
| 手写页面 | 页面布局、工具栏、按钮、标签页、弹窗、主子区域、业务流程和接口调用时机 |
| `ModuleTable` | 加载模块字段配置,渲染列、格式化内容、查询条件、排序、分页和选择状态 |
| `ModuleForm` | 加载模块字段配置,渲染输入控件、字段校验、表单分组和编辑状态 |
| 模块配置 | 描述数据源和字段元数据,不描述完整页面 |
推荐的组件使用方式:
```vue
<ModuleTable module-id="bs_business">
<template #toolbar>
<!-- 页面自己实现按钮和业务操作 -->
</template>
<template #column-b_state="{ record }">
<!-- 页面可以覆盖特殊列的渲染 -->
</template>
</ModuleTable>
<ModuleForm
v-model="formData"
module-id="bs_business"
mode="edit"
@validate="handleValidate"
/>
```
- `ModuleTable` 可以接收 `module-id` 或 `view-name`,优先使用 `module-id`。
- 只有一个视图唯一对应一个模块时才允许只传 `view-name`;同一视图被多个模块复用时必须传 `module-id`。
- `ModuleForm` 应以 `module-id` 为入口,以便确定保存字段、表单分组和校验规则。
- Table 和 Form 必须提供 props、slots 和事件,允许手写页面覆盖默认列、字段、工具栏和交互。
- `ModuleForm` 负责渲染、收集数据和校验,但不自动决定保存时机;页面验证业务条件后再调用保存。
- 特殊控件和复杂区域可以通过插槽由页面自行实现,不要求全部配置化。
### 5.10 查询与保存边界
模块化后的推荐调用关系:
```text
module_id
-> 加载模块定义
-> 检查用户模块权限
-> 确定查询对象、保存对象和主键
-> 加载模块字段配置
-> 生成参数化查询或保存语句
-> 返回模块定义和业务数据
```
- 前端不再提交任意 `view_name`、`table`、`key_field` 或完整 SQL。
- 查询条件使用结构化格式提交,字段和操作符必须来自模块配置白名单。
- 雪花 ID 由后端保存流程生成,不依赖前端提前申请。
- 配置 `b_save_object` 的模块可以参与通用保存,未配置保存对象的模块只读。
- 手写主子页面可以将多张表的新增、修改和删除一次提交给 `saveobjt`,整个请求使用同一事务。
- 后端不按模块编码分派自定义处理器,也不为具体页面增加特殊保存接口。
### 5.11 业务表设计原则
- 每张业务表必须定义真实主键:主表固定使用 `b_id`,子表固定使用 `subid`。
- 主表普通字段使用 `b_` 前缀,子表普通字段使用 `mx_` 前缀。
- 有明确关系的主子表必须建立外键,外键字段同时建立普通索引。
- 业务编号与技术主键分离,例如主表的 `b_business_no` 建立唯一索引但不作为主键。
- 唯一性、非空、默认值和可校验的状态范围优先使用数据库约束保证。
- 不使用空字符串同时表达未知、未填写和数值零。
- 重要且允许多人编辑的业务表可以增加 `b_row_version` 或 `mx_row_version`,用于乐观锁控制。
- 创建人、创建时间、修改人和修改时间字段仅在业务确有审计需求时添加,不作为所有表的固定字段;例如 `b_user` 不添加这些字段。
- 删除策略根据业务确定;财务和审批数据通常使用作废或状态流转,不直接物理删除。
### 5.12 权限原则
- 权限至少控制模块的查看、新增、修改、删除和导出操作。
- 菜单是功能树可见目录和页面节点的投影;页面菜单可以绑定模块,后续权限和多语言都以菜单节点为入口扩展。
- 后端必须根据登录用户和 `module_id` 校验权限,不能只依赖前端隐藏按钮。
- 字段级权限仅在确有业务需求时增加,第一阶段不提前复杂化。
- `g3soft` 是内置超级管理员账号,初始密码为空。超级管理员身份由固定账号名识别,不在 `b_user` 中增加超级管理员或隐藏标志字段。
- 普通 SQL 允许直接查询物理表 `dbo.b_user`,但数据库行级安全策略必须自动排除 `g3soft`;只有认证流程可以在当前数据库连接内临时读取该账号,且查询结束后必须立即清除临时权限。
### 5.13 重构实施顺序
1. 确定数据库版本、字段命名和表名前缀;系统主键固定使用雪花 ID,业务主键使用独立唯一业务字段。
2. 建立模块、功能树、模块字段、表单分组和选项来源等最小配置表。
3. 先实现一个简单基础资料页面,验证 `ModuleTable`、`ModuleForm`、通用查询和单表保存。
4. 手工实现一个业务主子页面,验证组件组合、事务和自定义保存。
5. 实现一个只读报表模块,验证查询、分页和导出边界。
6. 建立配置校验工具,检查查询对象、保存对象、字段、主键、重复排序和选项来源。
7. 三类模块验证通过后,再迁移现有模块及字段配置。
8. 迁移期间保留现有通用接口,按模块逐步切换,避免一次性替换全部功能。