海上工程船舶综合服务平台 源码
# 海上工程船舶综合服务平台 —— 项目分析
—
## 一、项目定位
这是一个面向**海上工程船舶行业**的综合业务平台。行业的典型特征是:船舶造价高、作业窗口受海况制约、单船单项目周期长、船员资质受严格监管、跨境作业涉及多币种结算。因此平台要解决的核心问题是”**船、项目、人、钱**四类资源的匹配与调度”。
平台把业务拆成三条主线:
一是**资源台账**,即把船东、船舶、船员、公司等静态资源登记成可检索、可核验的档案;二是**项目调度**,即把船舶和船员按时间窗口分配到具体工程项目上,并防止档期冲突;三是**运营监控**,即通过位置数据持续掌握船舶在海上的实时动态,并结合维护记录、交易记录形成运营闭环。
从用户角色看,平台预设四类使用者:船东(ship_owner,资源的供给方)、供应商(supplier,设备与服务提供方)、船员(crew_member,被调配的劳动力)、管理员(admin,平台运营方)。平台本身不区分”前台/后台两套账号体系”,而是同一套用户表,通过 `user_type` 字段区分,管理员额外获得后台入口。
—
## 二、技术栈与运行方式
后端使用 Python 的 Flask 框架,配合 Flask-SQLAlchemy 做 ORM、Flask-APScheduler 做定时任务调度,数据库为单文件的 SQLite(`data.sqlite`,位于项目根目录),密码哈希使用 Werkzeug 内置的 `generate_password_hash` / `check_password_hash`。依赖清单极简,仅四个包(Flask、Flask-SQLAlchemy、Flask-APScheduler、Werkzeug),没有前端构建工具链。
好处是零构建、可直接跑;代价是前端资产需要自行 vendor 或走 CDN。
运行入口是根目录的 `run.py`:它把 `app/` 加入 `sys.path`,调用 `create_app()` 建应用,然后从环境变量读取 `HOST`(默认 `0.0.0.0`)和 `PORT`(默认 `5000`)启动,`debug=False` 且开启 `threaded=True`。此外还有一个 `run_dev.py`(用 5001 端口)。
需要注意:本项目实际运行的 Flask 装在**系统 Python 3.14**(`C:\Python314\python.exe`)下,而非 workbuddy 托管的 Python 3.13(那个环境没装 Flask)。
配置文件 `config.py` 里的关键项:数据库 URI 可通过 `DEV_DATABASE_URL` 环境变量覆盖,否则指向项目根目录的 `data.sqlite`;`SECRET_KEY` 默认是硬编码的 `’hard to guess string’`(生产环境必须改);`SQLALCHEMY_TRACK_MODIFICATIONS` 关闭以省内存;`SQLALCHEMY_COMMIT_ON_TEARDOWN` 开启;`SCHEDULER_API_ENABLED` 为真,因此 Flask-APScheduler 会自动暴露 `/scheduler/*` 系列管理接口。
—
## 三、整体架构与目录结构
项目采用 Flask 蓝图(Blueprint)分层,`create_app()` 工厂函数负责装配。
顶层目录说明:根目录放配置(`config.py`)、入口(`run.py` / `run_dev.py`)、数据库文件(`data.sqlite`)、数据初始化脚本(`init_data.py`)、以及若干测试与验证脚本。真正的业务代码全部在 `app/` 包内。
`app/` 内部按职责划分:
`app/__init__.py` 是装配中心。它在这里实例化 `db` 和 `scheduler` 两个全局扩展对象,然后依次注册四个蓝图:`api`(前缀 `/api`)、`main`(无前缀,占根路径)、`admin`(前缀 `/admin`)、`crud`(前缀 `/admin`,与 admin 共用前缀但作为独立蓝图)。调度器只在非测试模式且 `SCHEDULER_ENABLED` 为真时才初始化并启动。
`app/models.py` 是唯一的数据模型定义文件,包含全部 11 个模型类,是整个平台的数据字典。
`app/api/` 是 REST 接口层,每个业务实体一个模块。`app/main/` 是前台页面路由(渲染页面 + 处理登录注册)。`app/admin/` 是后台,分为 `views.py`(固定几页:登录、仪表板、用户、交易)和 `crud.py`(一套**通用 CRUD 框架**,覆盖全部 11 个实体)。`app/templates/` 放 Jinja 模板,`app/static/` 放前端资产。
`app/db_init.py` 与根目录 `init_data.py` 是两个功能重叠的数据初始化脚本,都会清空并重建样例数据。
—
## 四、数据模型(11 张表)
理解这个平台的关键在于理解 `models.py`。11 张表可以分成四组。
### 第一组:主体与档案
**users(用户表)**是整个平台的账号中心。字段包括用户名、邮箱(均唯一且必填)、密码哈希、用户类型、所属公司、创建与更新时间、是否激活。`user_type` 是枚举,只允许 `ship_owner`(船东)、`supplier`(供应商)、`crew_member`(船员)、`admin`(管理员)四个值,默认船员。模型上挂了 `set_password` 和 `check_password` 两个方法封装哈希逻辑,另有三个关联:所属公司、船员档案(一对一)、名下船舶(一对多)。
**companies(公司表)**记录企业主体。字段含名称、唯一注册号、地址、联系人三件套(人/电话/邮箱)、`company_type` 枚举(shipping 航运、oil_gas 油气、wind_power 风电、service_provider 服务商、equipment_supplier 设备供应商)、许可证文件路径、认证等级、成立日期、是否认证通过。
**crew_profiles(船员档案表)**是平台上字段最多、业务含义最密的表。它以 `user_id` 外键挂接用户,个人信息含姓名、国籍、出生日期、性别、地址、电话、紧急联系人。专业资质部分是重点:海员证号与有效期、适任证书号(COC)与有效期、基本安全培训证书(BST)与有效期、体检证书与有效期,另有 `special_training_certificates` 用 JSON 存特殊培训证书列表。工作经历部分含工作年限、偏好职位(JSON)、上次服务船舶、上次离船日期、`availability_status` 枚举(available 可雇佣、on_board 在船、on_leave 休假、suspended 停职)、当前位置、到岗通知期天数。
这套设计的业务意图很明确:**证书到期日和可雇佣状态是船员能否被派工的两个硬约束**。
### 第二组:核心资源
**ships(船舶表)**是平台最核心的资源实体,字段分四类。身份与识别:船名、IMO 编号(唯一)、MMSI 编号(唯一)、呼号。类型与登记:`ship_type` 枚举(wind_installation 风电安装、platform_supply 平台供应、heavy_lift 重吊、anchor_handling 拖轮/起锚、dredger 挖泥、subsea_support 水下支持、tanker 油轮、special_purpose 特种)、船旗国、注册港。主尺度与技术参数:总吨位、载重量、总长、型宽、型深、最大吃水、最大航速、定员、燃油舱容、起重能力、是否具备动态定位能力——其中**起重能力和动态定位能力是海工船作业能力的决定性指标**。营运信息:船东(外键到 users,必填)、营运商(外键到 companies)、`status` 枚举(operational 营运中、maintenance 维护中、offline 离线、decommissioned 退役)、母港、上次/下次坞修日期、证书到期日、保险到期日。
**ship_equipment(船舶设备表)**登记船上的关键设备,含设备类型、制造商、型号、序列号(唯一)、安装日期、上次与下次维护日期、`operational_status` 枚举(operational / maintenance_required / out_of_service)、技术规格(JSON)、安装单位、保修到期日。
### 第三组:业务流转
**projects(工程项目表)**描述一个海上工程任务。字段含项目名、描述、`project_type` 枚举(offshore_wind 海上风电、oil_gas 油气、subsea_construction 水下施工、pipelay 铺管、well_intervention 修井)、位置坐标、水深、作业海域、客户公司(外键)与承包公司(外键)。时间安排有计划开始/结束与实际开始/结束四个日期;`status` 枚举为 planning 规划、tendering 招标、ongoing 进行中、completed 完成、cancelled 取消。另有三个 JSON 字段表达需求:`required_ship_types`(需要的船舶类型)、`required_equipment`(需要的设备)、`crew_requirements`(人员要求)。
这三个 JSON 字段加上 crew 的 `preferred_positions`,就是后续”匹配”逻辑的输入。
**ship_assignments(船舶分配表)**是项目与船舶之间的连接表,也是整个平台**调度逻辑的落点**。它把某条船派给某个项目,字段含 `assignment_type` 枚举(primary 主力、support 辅助、backup 备选)、角色描述、计划起止日期、实际起止日期、`assignment_status` 枚举(planned 计划、ongoing 进行中、completed 完成、delayed 延误、cancelled 取消)、日租金、总费用、币种(默认 CNY)。
**maintenance_records(维护记录表)**记录船舶或具体设备的维保。含船舶外键、设备外键(可空,表示整船级维护)、`maintenance_type` 枚举(routine 例行、corrective 修复、preventive 预防、overhaul 大修)、描述、执行人/公司、起止日期、费用、备件(JSON)、`status` 枚举(planned / in_progress / completed / delayed / cancelled)。
**ship_transactions(船舶交易表)**记录船舶层面的商业行为,`transaction_type` 枚举覆盖 rental 租赁、lease 承租、purchase 购买、sale 出售、charter 船舶租赁、contract 承包。含船舶外键、买方/承租方(外键到 users)、卖方/出租方、起止日期、金额、币种、`status` 枚举(pending / confirmed / in_progress / completed / cancelled / disputed 争议),以及合同文件路径、备注、付款条款、保险详情、违约条款五个文本字段。
### 第四组:监控与资金
**ship_positions(船舶位置表)**是时序数据表,由船载 GPS 或物联网设备写入。字段含船舶外键、纬度、经度、航向、速度、航迹、海拔、时间戳、位置来源(默认 gps)、定位精度。这张表数据量会随时间线性膨胀,是典型的”只增不改”表。
**financial_transactions(财务交易表)**是平台层面的资金流水,与船舶交易区分开。含转出/转入用户外键、金额、币种(默认 USDT)、区块链交易哈希、状态、创建时间。这张表保留了项目早期(似为区块链预约卡类业务)的痕迹——`transaction_hash` 和 USDT 币种与海工船舶业务并不完全自洽。
—
## 五、功能模块划分
### 5.1 API 层(`/api` 前缀,共 56 条路由)
API 蓝图在 `app/api/__init__.py` 中定义,并**逐个 try-import 各业务模块**(每个都打印一行 DEBUG 日志以便定位导入失败)。实际被导入并注册的模块共 8 个:ships、companies、crews、projects、assignments、positions、maintenance、transactions。蓝图自身另提供两个公共接口。
按业务域梳理接口能力:
**船舶域**:列表查询(支持 `ship_type`、`status`、`keyword` 三个筛选参数,keyword 会在船名、IMO、呼号上做模糊匹配,分页返回)、单船详情、新建、更新、删除。此外有四个**下钻接口**:某船的设备清单、位置历史、维护历史、交易历史。
**项目域**:列表与详情、新建、更新、删除,以及一个按条件检索的 `POST /projects/search`。还有 `GET /projects/
**船员域**:列表与详情、新建、更新、删除,以及 `POST /crews/search` 条件检索。
**公司域**:列表与详情、新建、更新、删除,以及 `GET /companies/
**派工域**:列表、详情、新建、更新、删除。另有反查接口:`GET /ships/
**位置域**:位置列表、单条详情、`POST /positions` 写入新位置(供设备上报)、`GET /positions/latest`(全部船舶的最新位置)、`GET /ships/
**维护域**:列表、详情、新建、更新、删除,以及两个调度看板接口 `GET /maintenance/upcoming`(未来 30 天内到期的计划维护)与 `GET /maintenance/scheduled`(所有 planned/in_progress 的维护)。
**交易域**:`/ship-transactions` 的列表、详情、新建,以及 `/financial-transactions` 的列表与新建。
**公共域**:`GET /api/stats` 返回船舶数、船员数、项目数、公司数四个平台公开统计;`GET /api/search?q=` 做跨实体全局搜索,同时在船舶(船名/IMO/呼号)、项目(名称/描述)、船员(用户名/姓/名)上检索,每类最多返回 5 条摘要。
### 5.2 前台页面层(`main` 蓝图)
`app/main/views.py` 提供 9 个路由,全部是无前缀的根路径:
首页 `/` 渲染落地页,会尝试从 session 取当前用户注入模板;仪表板 `/dashboard`、船舶管理 `/ships`、项目管理 `/projects`、船员管理 `/crews`、实时监控 `/monitoring` 这五个业务页**都做了登录校验**——未登录时 302 跳转(仪表板跳首页,其余四个跳登录页)。
`/login` 同时承担页面渲染(GET)与登录提交(POST,接收 JSON)。`/register` 同理,处理注册。`/admin` 只是个便捷跳转,重定向到 `/admin/`。`/logout` 是 GET 请求,清空 session 后回首页。
值得注意的设计特点:**前台页面本身只负责渲染骨架,所有数据都由浏览器端 JavaScript 通过 fetch 调用 `/api/*` 获取**。这正是前端模板(dashboard.html、ships.html 等)可以做得比较厚的原因。
### 5.3 后台管理层(`admin` 蓝图 + `crud` 蓝图)
后台有两套并存的东西,需要分清。
**第一套是 `app/admin/views.py` 的固定页面**,共 8 个路由。核心是 `admin_required` 装饰器:先看 session 有没有 `user_id`,没有就跳 `/admin/login`;再看该用户的 `user_type` 是否等于 `admin`,不是就踢回前台首页。在它的保护下提供了仪表板 `/admin/`(统计用户数、公司数、船舶数、船员数、项目数、交易数,并列出最新的用户/船舶/项目/交易各 5 条)、用户管理 `/admin/users`(分页 10 条)、交易记录 `/admin/transactions`(分页 10 条),以及配套的两个 JSON 接口 `/admin/api/users`、`/admin/api/transactions`。这三个页面使用 Element UI + Vue 2 模板(`*_element.html`),本地 vendor 在 `app/static/templates/unpkg/`。
**第二套是 `app/admin/crud.py` 的通用 CRUD 框架**,这是本项目最有工程价值的设计。它用一个 `ENTITY_REGISTRY` 字典声明式地注册了全部 11 个实体(users、companies、ships、equipment、crews、projects、assignments、positions、maintenance、ship_transactions、financial_transactions),每个实体只需给出模型类和中文名。
框架的核心是 `build_entity_config(slug)`:它会**反射模型的表结构**,自动把每一列分类成 pk / fk / bool / enum / datetime / date / json / int / float / textarea / text 等类型,据此决定前端用什么控件渲染(下拉、开关、日期选择器、数字框、JSON 文本域),并算出该列是否进列表、是否进表单、是否必填、列宽多少。外键列会自动去目标表取标签(`FK_LABEL_FIELDS` 定义了每张表用哪个字段做显示名,如 users 用 username、companies 用 name、crew_profiles 用姓+名),生成下拉选项。
配套的 `populate()` 负责把前端提交的 JSON 写回模型,并按列类型做转换与校验:枚举值必须在允许集合内(否则报错并跳过)、日期按格式解析、JSON 文本尝试 `json.loads`、数值做 int/float 转换。`serialize_row()` 反向把模型序列化成 JSON,其中 `SERIALIZE_HIDDEN` 会过滤掉密码哈希等敏感列。
于是三个路由加上五个 API 就支撑起 11 个实体的完整增删改查:`/admin/entities` 是数据管理入口页(卡片式导航),`/admin/entities/
这套框架还包含若干业务约定:`FORM_HIDDEN` 把 `password_hash`、`created_at`、`updated_at` 排除出表单;针对 users 实体额外注入了一个**虚拟列 `password`**(仅出现在表单、不回显),新建时必填且长度不少于 6 位并调用 `User.set_password()` 哈希存储,编辑时留空表示不修改密码——这是解决 `password_hash` 为 NOT NULL 却不可编辑这一矛盾的方案。
### 5.4 定时任务层
`app/__init__.py` 会在启动时初始化并启动 Flask-APScheduler,Flask-APScheduler 自带的管理 API 会自动暴露在 `/scheduler` 下(含 `/scheduler/jobs`、`/scheduler/pause`、`/scheduler/resume`、`/scheduler/run` 等)。
但需要点明一个现状:**当前没有任何定时任务被真正注册**。代码库里存在 `app/api/schedules.py`,里面定义了 `check_match_deadlines`(检查匹配截止)与 `execute_matching`(执行匹配算法)两个任务函数以及对应的包装函数,可这个模块**没有被任何地方 import**,因此不会执行。整个调度层目前是”空转”状态。
—
## 六、核心业务逻辑要点
平台里真正体现业务规则的地方,集中在以下几处。
**一是派工的时间冲突检测**,这是调度功能的核心。`POST /api/assignments` 在创建派工前,会先校验 project 和 ship 是否存在(不存在返回 404),然后做冲突检测:查询该船所有 `assignment_status` 既不是 cancelled 也不是 completed 的派工记录,再用 `planned_from <= 新计划的 planned_to AND planned_to >= 新计划的 planned_from` 做区间重叠判断。只要存在重叠,就拒绝创建并返回 “该船在此时段已被分配到其他项目”。这个”排除已取消/已完成 + 区间相交”的写法是排期系统的标准做法。
**二是船员的多条件检索**。`POST /api/crews/search` 支持按特殊培训证书、可雇佣状态(可多选)、当前位置、最低工作年限、最长通知期、偏好职位等条件组合筛选,查询以 `CrewProfile` 关联 `User` 为基表,逐条件叠加 filter。代码注释也坦承证书匹配用的是简化的 JSON 包含判断,实际生产需要更复杂的资质比对。
**三是项目条件检索**。`POST /api/projects/search` 与之对称,用于按项目类型、状态、船舶类型需求等条件找项目。
**四是维护预警**。`GET /api/maintenance/upcoming` 以”当前日期 + 30 天”为界,筛出状态为 planned 且开始日期不晚于该界限的记录,按开始日期升序返回——这是维保到期的预警看板;`/api/maintenance/scheduled` 则放宽到 planned 与 in_progress 两种状态,作为整体维护计划视图。
**五是实时位置的最新值查询**。`GET /api/positions/latest` 需要解决”每船只取最新一条”的经典问题,实现方式是先按 ship_id 分组求 `max(timestamp)` 生成子查询,再与主表按 ship_id 和时间戳双条件 join 回原表。这是标准且正确的写法。轨迹接口则按船舶取历史点序列用于地图绘制。
**六是全局搜索**。`GET /api/search` 一个关键字同时打三张表,返回结构化的三类摘要(船舶给 id/name/imo/type,项目给 id/name/type/status,船员给 id/name/user),供前端顶栏的下拉搜索面板使用。
**七是鉴权与会话**。前台登录校验直接落在 `main` 蓝图:`User.query.filter_by(username=…)` 取出用户后调用 `check_password` 校验哈希,通过则把 `session[‘user_id’]` 写入。后台的 `admin_required` 在此基础上叠加 `user_type == ‘admin’` 的角色判断。注册接口 `/register` 会校验用户名与邮箱唯一性,然后建 `User` 并 `set_password` 落库。
**八是通用 CRUD 的类型安全写入**。前面提到的 `populate()` 是防止脏数据入库的唯一关口,它会拒绝非法枚举值、解析日期与 JSON、做数值转换。反过来说,**任何绕过 API 直接改数据库的操作,都可能写入非法枚举值,导致 SQLAlchemy 在加载整行时抛 LookupError,进而拖垮所有相关查询**——这一点在项目历史上确实发生过。
—
## 七、前端实现与交互
前台采用一套自建的设计系统,不走任何前端框架。核心三件套是:`app/static/css/platform.css`(海洋主题、玻璃拟态风格的样式系统)、`app/static/js/platform.js`(共享工具库)、`app/templates/base.html`(统一布局,左侧边栏 + 顶部栏)。
`platform.js` 暴露了一个 `App` 全局对象,封装了 `api/get/post/put/del` 等 fetch 封装、`toast` 轻提示、`confirm` 确认框、`statusBadge` 状态徽章映射、`renderPager` 分页渲染、登出、侧边栏高亮等常用能力。各业务页面继承 `base.html` 后,只需关注自己的数据加载与渲染逻辑,交互风格天然统一。
侧边栏导航共 6 项:首页、仪表板、船舶管理、项目管理、船员管理、实时监控(管理员额外看到”管理后台”入口)。顶部栏提供全局搜索(带分组下拉结果)与用户区(未登录显示登录按钮,已登录显示用户名与登出图标)。
各业务页面的功能:仪表板聚合统计卡片、最近船舶/项目/派工列表、项目状态分布;船舶管理提供列表、搜索、按类型/状态筛选、分页,以及详情弹窗(展示规格、设备、维护、交易四个维度)和增删改表单;项目管理提供列表、筛选、详情弹窗(含该项目的派工列表)与增删改;船员管理提供列表、筛选、资质证书详情(证书过期会高亮)与增删改;实时监控页面用 Leaflet 加载地图,展示全部船舶的最新位置、支持查看某船轨迹回放,并以 15 秒间隔自动刷新。
视觉资产方面,站点 favicon、品牌 logo、落地页英雄主视觉、登录页背景四张图都是通过图像生成能力真实产出的位图,放在 `app/static/images/`;功能性 UI 图标(导航、按钮、状态)则统一使用矢量图标字体。
后台页面使用 Element UI + Vue 2,采用经典的 `el-container` / `el-aside` / `el-main` 布局。这里存在一个贯穿后台模板的**技术约束**:由于模板同时被 Jinja 和 Vue 处理,凡是 Jinja 需要渲染的变量(如 `{{ label }}`、`{{ slug }}`、`{{ config_json|tojson }}`)必须保留在 `{{ }}` 中,而所有 Vue 表达式(如 `{{ e.label }}`、`{{ scope.row.x }}`)必须包进 `{% raw %}…{% endraw %}`。漏包会直接导致 Jinja 求值未定义变量抛 `UndefinedError`(页面 500),或渲染出非法 JavaScript 导致 Vue 挂载失败、界面错位。
—
## 八、总结
这是一套**架构骨架完整、数据模型专业、但业务实现深浅不均**的 Flask 单体应用。
它的长处在于:数据模型对海工船舶行业的刻画相当到位(船型分类、主尺度与技术参数、船员四类证书与到期日、派工的时间窗口与费率、项目对船型/设备/人员的需求表达),说明建模时是照着真实业务来的;通用 CRUD 框架的设计也很聪明,用模型反射把 11 个实体的增删改查压成一份配置加三个路由,显著降低了后续扩展成本;前台自建的设计系统与共享 JS 工具让页面风格统一、维护简单。
它的短板在于:一半的精力沉淀在了与当前业务无关的历史遗留上(预约卡/匹配/区块链交易那套),API 层缺乏鉴权,定时任务层空转,船员、设备、维护、交易四个业务域有接口无数据、有页面无内容,船员与项目的智能匹配这一真正有业务价值的能力尚未落地。
如果要继续推进,优先级最高的三件事是:给 `/api/*` 补上会话或令牌鉴权并收紧注册接口的角色选择;把空转的定时任务层用起来,实现船舶证书/保险到期、设备维保到期、船员证书到期的主动预警;补齐船员、设备、维护、交易四个业务域的样例数据与完整交互,让项目从”骨架”变成”可演示的完整系统”。












































































































