17 KiB
17 KiB
FMS 货代物流管理系统需求规范
1. 项目定位
- FMS 是一个前后端分离、以模块为中心的多机构货代物流管理系统。
- 一个机构对应一个独立数据库,机构码统一命名为
orgid。 - 登录时必须提交机构码、账号和密码。后端根据机构码连接对应数据库并验证用户。
- 登录成功后,登录凭证与机构码绑定。后续请求只操作当前机构的数据库,切换机构必须重新登录。
- 后端负责登录、机构数据库路由、通用数据接口、事务控制以及少量确有必要的特殊业务接口。
- 普通业务优先复用通用查询和保存能力,避免为每张表单独创建 Controller、DTO 和实体类。
- 模块负责组织查询数据源、保存对象、字段展示、查询条件、编辑方式和权限。
2. 现有系统过渡期约定
以下约定用于当前系统的开发过渡阶段,不作为新数据库重构后的最终安全模型。
- 页面、查询条件、SQL、数据组织、校验和普通业务流程目前主要由前端完成。
- 当前阶段暂不重点考虑客户端直接提交查询条件和 SQL 带来的安全风险。
- 后端通用接口完成后应尽量保持稳定,普通业务优先修改前端配置和数据库配置。
- 文件、报表、导入、导出以及通用接口无法覆盖的功能,可以增加特殊接口。
- 新数据库启用后,前端应逐步改为只提交
module_id和结构化数据,不再直接指定任意表名或 SQL。
3. 现有通用接口
| 方法 | 路径 | 用途 |
|---|---|---|
POST |
/api/auth/login |
机构登录 |
POST |
/api/data/loaddata |
按表或视图加载数据 |
POST |
/api/data/loaddatabysql |
执行完整查询 SQL |
POST |
/api/data/page |
执行分页查询 |
POST |
/api/data/saveobjt |
多表事务保存 |
3.1 登录
{
"orgid": "G3HD",
"username": "admin",
"password": "123456"
}
登录成功后返回登录凭证和用户信息。后续请求携带登录凭证,不再重复提交 orgid。
3.2 loaddata
用于表或视图的普通查询,不执行分页。
{
"source": "v_bs_business",
"fields": ["b_id", "b_state", "customer_name"],
"where": "b_state = '草拟'",
"order_by": "b_id desc"
}
source必填,表示表名或视图名。fields可省略,省略时返回全部字段。where可省略,由前端直接拼接,不包含WHERE关键字。order_by可省略,不包含ORDER BY关键字。
成功响应:
{
"success": true,
"code": 0,
"message": "ok",
"data": {
"rows": [],
"row_count": 0
}
}
3.3 loaddatabysql
用于关联、聚合、统计等完整 SQL 查询,不执行分页。过渡期内 SQL 由前端直接拼接。
{
"sql": "select customer_id, sum(amount) as total from v_fee where fee_date >= '2026-01-01' group by customer_id"
}
成功响应与 loaddata 相同。
3.4 page
所有分页查询统一使用本接口。
{
"sql": "select * from v_bs_business where b_state = '草拟'",
"order_by": "b_id desc",
"page_no": 1,
"page_size": 20
}
sql必填,不包含分页语句。order_by必填,用于保证分页顺序稳定。page_no从 1 开始,默认值为1。page_size默认值为20。- 后端负责生成分页 SQL 和计算总记录数。
成功响应:
{
"success": true,
"code": 0,
"message": "ok",
"data": {
"rows": [],
"row_count": 0,
"total": 0,
"page_no": 1,
"page_size": 20
}
}
3.5 saveobjt
用于一个或多个表的新增、修改和删除。
[
{
"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对应的主键值。 - 数字字段的空字符串不做特殊转换,按原值提交给数据库。
- 每张表按照删除、修改、新增的顺序执行,表与表之间按照请求数组顺序执行。
- 一次请求中的所有表和所有操作使用同一个数据库连接和事务。
- 全部操作成功后提交,任意操作失败时全部回滚。
- 保存成功只返回成功状态,不返回每张表的操作数量。
成功响应:
{
"success": true,
"code": 0,
"message": "保存成功",
"data": null
}
保存失败时可以返回失败位置:
{
"success": false,
"code": 2001,
"message": "保存失败",
"data": {
"table": "bs_business_container",
"action": "update",
"index": 2
}
}
4. 统一约定
- JavaScript 和 Java 内部函数、方法及变量使用小驼峰命名法。
- JSON 请求与响应字段使用
snake_case,在 API 边界与数据库字段显式映射。 - 数据库字段保留现有命名风格:主表普通字段使用
b_前缀,主键固定为b_id;子表普通字段使用mx_前缀,主键固定为subid。 - API 路径使用全小写名称,JSON 多单词字段使用
snake_case。 code = 0表示成功,非零值表示失败。- 前端根据
code生成success字段,code = 0时success = true。 - 查询结果统一放在
data.rows中,没有数据时返回空数组。 - 过渡期查询条件和 SQL 由前端拼接,不使用参数占位符。
- 不增加功能相同但名称不同的接口。
- 不使用
V2、V3、new、old、test或bak等临时名称。 - 接口发生不兼容升级时,统一通过
/api/v2等版本路径升级。
5. 新数据库重构方案
5.1 重构目标
- 保持以模块为中心的开发方式。
- 将业务数据约束与页面配置分离。
- 所有业务页面、页面布局、按钮、标签页、弹窗和业务流程仍由前端手工开发。
- 模块配置只服务于通用 Table 和 Form 组件,不根据配置自动生成完整页面、路由或业务流程。
- Table 根据模块配置渲染列、格式、查询项和排序;Form 根据模块配置渲染字段、控件、校验和分组。
- 模块定义查询对象、保存对象、主键、字段配置和保存策略。
- 前端只提交
module_id、结构化查询条件和待保存数据。 - 后端根据模块配置解析允许访问的对象和字段,并使用参数化 SQL。
- 通用保存负责简单单表模块,复杂业务由明确的业务处理器完成。
- 不将全部业务逻辑塞入配置表或动态 SQL。
5.2 数据库版本与基础规范
- 新数据库建议使用 SQL Server 2019 或 SQL Server 2022,不再使用 SQL Server 2008 R2。
- 继续采用一个机构对应一个独立业务数据库的隔离方式,业务表不重复保存
orgid。 - 技术主键统一使用雪花 ID,数据库类型为
bigint。主表主键固定为b_id,子表主键固定为subid。 - 中文和多语言文本使用
nvarchar,长文本使用nvarchar(max),不使用text。 - 布尔值使用
bit,不再使用字符串形式的0、1、空字符串或NULL表示多种状态。 - 时间字段使用
datetime2。 - 金额、单价、数量、汇率和税率不强制统一精度,根据每个字段的业务含义分别定义
decimal的精度和小数位数。 - 迁移旧字段时保留原有精度;新增字段必须在需求或表结构中明确精度,不使用
float代替精确金额。 - 字段命名延续现有风格:主表字段使用
b_前缀,子表字段使用mx_前缀。 - 主表与子表的外键字段也遵循对应前缀,例如子表引用主表时使用
mx_<业务对象>_id。 - 数字字段的空字符串不自动转换成
0,由调用方提交正确的数据类型。
5.3 dbo 与表名前缀
- 新数据库的所有业务表、配置表和视图统一创建在
dbo下,不创建其他 Schema。 - 领域划分继续使用现有表名前缀,避免增加对象名称和通用接口的复杂度。
- 模块配置保存数据库对象名称时统一按
dbo.<对象名>处理,例如dbo.bs_business、dbo.cw_bill和dbo.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_ 前缀 |
菜单、角色和权限仍可使用独立系统表,但不属于 Table/Form 渲染配置。主子页面关系由手写页面负责组织,第一阶段不需要通过配置表生成。
5.5 模块定义
dbo.s_module 建议包含以下核心字段:
| 字段 | 用途 |
|---|---|
b_id |
模块唯一标识,主键 |
b_name |
模块名称 |
b_query_object |
查询表或稳定查询视图 |
b_save_object |
保存目标表,只读模块允许为空 |
b_key_field |
保存对象主键字段 |
b_key_strategy |
snowflake、identity 或 manual |
b_save_mode |
generic、custom 或 readonly |
b_handler_code |
自定义处理器标识,通用模块为空 |
b_default_order |
默认排序 |
b_enabled |
是否启用 |
b_sort_order |
模块排序 |
查询对象与保存对象必须分开:查询视图负责组合展示数据,保存表负责持久化可写字段。
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_search_operator |
| 表单编辑 | 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对应保存表字段,计算字段和只读字段可以为空。mx_search_operator使用受控枚举,例如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指定允许查询的对象、值字段、显示字段和排序字段。 - 复杂选项通过
b_handler_code交给后端处理,不在配置表保存任意可执行 SQL。
5.8 通用组件与手写页面的边界
| 层级 | 负责内容 |
|---|---|
| 手写页面 | 页面布局、工具栏、按钮、标签页、弹窗、主子区域、业务流程和接口调用时机 |
ModuleTable |
加载模块字段配置,渲染列、格式化内容、查询条件、排序、分页和选择状态 |
ModuleForm |
加载模块字段配置,渲染输入控件、字段校验、表单分组和编辑状态 |
| 模块配置 | 描述数据源和字段元数据,不描述完整页面 |
推荐的组件使用方式:
<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.9 查询与保存边界
模块化后的推荐调用关系:
module_id
-> 加载模块定义
-> 检查用户模块权限
-> 确定查询对象、保存对象和主键
-> 加载模块字段配置
-> 生成参数化查询或保存语句
-> 返回模块定义和业务数据
- 前端不再提交任意
view_name、table、key_field或完整 SQL。 - 查询条件使用结构化格式提交,字段和操作符必须来自模块配置白名单。
- 雪花 ID 由后端保存流程生成,不依赖前端提前申请。
b_save_mode = generic只适用于基础资料等简单单表模块。b_save_mode = readonly用于报表、统计和日志查询。b_save_mode = custom用于业务单、费用、账单、发票等涉及主子表、状态流转和业务校验的模块。- 自定义处理器仍通过统一模块接口调用,不为每个页面随意增加不同形式的接口。
5.10 业务表设计原则
- 每张业务表必须定义真实主键:主表固定使用
b_id,子表固定使用subid。 - 主表普通字段使用
b_前缀,子表普通字段使用mx_前缀。 - 有明确关系的主子表必须建立外键,外键字段同时建立普通索引。
- 业务编号与技术主键分离,例如主表的
b_business_no建立唯一索引但不作为主键。 - 唯一性、非空、默认值和可校验的状态范围优先使用数据库约束保证。
- 不使用空字符串同时表达未知、未填写和数值零。
- 重要且允许多人编辑的业务表可以增加
b_row_version或mx_row_version,用于乐观锁控制。 - 创建人、修改人和时间字段按业务审计要求添加,不由通用保存接口无条件补充。
- 删除策略根据业务确定;财务和审批数据通常使用作废或状态流转,不直接物理删除。
5.11 权限原则
- 权限至少控制模块的查看、新增、修改、删除和导出操作。
- 菜单与模块分离,同一个模块可以出现在不同菜单位置,也可以作为隐藏子模块使用。
- 后端必须根据登录用户和
module_id校验权限,不能只依赖前端隐藏按钮。 - 字段级权限仅在确有业务需求时增加,第一阶段不提前复杂化。
5.12 重构实施顺序
- 确定数据库版本、字段命名、表名前缀和主键策略,所有对象固定使用
dbo。 - 建立模块、模块字段、表单分组和选项来源等最小配置表。
- 先实现一个简单基础资料页面,验证
ModuleTable、ModuleForm、通用查询和单表保存。 - 手工实现一个业务主子页面,验证组件组合、事务和自定义保存。
- 实现一个只读报表模块,验证查询、分页和导出边界。
- 建立配置校验工具,检查查询对象、保存对象、字段、主键、重复排序和选项来源。
- 三类模块验证通过后,再迁移现有模块及字段配置。
- 迁移期间保留现有通用接口,按模块逐步切换,避免一次性替换全部功能。