本目录是 NEXUS 的后端服务(Python + Robyn + PostgreSQL)。项目整体说明见仓库根目录的 README.md。
依赖:Python(见 pyproject.toml 的 requires-python)、uv、PostgreSQL。
- 安装依赖(首次运行):
cd BACKEND-NEXUS
uv sync-
准备环境变量:复制 .env.example 为
.env并按实际填写。 -
初始化数据库(首次搭建):
- 先在 PostgreSQL 中创建用户/数据库(示例以
.env.example默认值为准,用户/库名都是cam):
CREATE USER cam WITH PASSWORD 'YOUR_PASSWORD';
CREATE DATABASE cam OWNER cam;
GRANT ALL PRIVILEGES ON DATABASE cam TO cam;-
然后在
.env里填写DATABASE_*/DATABASE_URI -
创建表结构(首次建表建议用
create_all):
cd BACKEND-NEXUS
uv run python -m database.init_db- 启动开发环境(热重载):
cd BACKEND-NEXUS
./run-dev.ps1后端默认监听 http://127.0.0.1:1024(根路径 / 返回 OK)。
启动后可访问交互式接口文档:
- Swagger UI:
http://127.0.0.1:1024/docs - OpenAPI JSON:
http://127.0.0.1:1024/openapi.json
本后端采用 RPC 风格 设计:查询类操作用 GET,其余写操作用 POST;路径以动词命名(如 /getXxx、/deleteXxx),不是 RESTful 资源路径。
| 项 | 说明 |
|---|---|
| Base URL | http://127.0.0.1:1024(端口由 .env 中 PORT 控制) |
| 鉴权 | 除登录、注册外,需在请求头携带 Authorization: Bearer <access_token> |
| 参数缺失 / 格式错误 | HTTP 400,description 为错误说明 |
| 业务逻辑错误 | HTTP 200,响应体中 status 为负数,message 为错误描述 |
| 查询成功 | HTTP 200,直接返回数据对象或列表 |
| 写操作成功 | HTTP 200,返回 { "status": 200, "message": "...", ... } |
特殊权限:/v1/user/getUserById 仅 L0 用户可访问(见 authentication.py 中 API_PERMISSION_MAP)。
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | / |
否 | 健康检查,返回 OK |
| 方法 | 路径 | 鉴权 | 参数 | 功能 |
|---|---|---|---|---|
| GET | /v1/user/getUserById |
是(L0) | Query: id |
按用户 ID 获取用户详情 |
| GET | /v1/user/getMyInfo |
是 | — | 获取当前登录用户详情 |
| GET | /v1/user/getUserByUsernameOrNicknameOrEmail |
是 | Query: username_or_nickname_or_email |
按用户名 / 昵称 / 邮箱搜索用户 |
| POST | /v1/user/login |
否 | Body: username, password |
登录,返回 access_token |
| POST | /v1/user/register |
否 | Body: username, password, nickname, email, role |
注册新用户 |
| POST | /v1/user/modifyPassword |
是 | Body: old_password, new_password |
修改当前用户密码 |
| 方法 | 路径 | 鉴权 | 参数 | 功能 |
|---|---|---|---|---|
| GET | /v1/service/getServiceById |
是 | Query: id |
按服务 ID 获取服务详情 |
| GET | /v1/service/getAllServices |
是 | Query: page_size(默认 10), current_page(默认 1) |
分页获取全部服务 |
| GET | /v1/service/getHisNewestServicesByOwnerId |
是 | Query: page_size, current_page, is_my_services(默认 true), owner_id(is_my_services=false 时必填) |
获取某用户拥有的最新版本服务列表 |
| GET | /v1/service/getHisMaintainedServicesByUserId |
是 | Query: page_size, current_page, user_id(默认当前用户) |
获取某用户维护的服务列表 |
| GET | /v1/service/getServiceByUuidAndVersion |
是 | Query: service_uuid, version(可为 latest) |
按 UUID 与版本获取服务详情(含分类与 API 聚合数据) |
| GET | /v1/service/getAllVersionsByUuid |
是 | Query: service_uuid |
获取某服务的全部历史版本号 |
| GET | /v1/service/getAllDeletedServicesByUserId |
是 | Query: page_size, current_page |
分页获取当前用户已软删除的服务 |
| GET | /v1/service/isServiceMaintainer |
是 | Query: service_id, candidate_id |
判断候选用户是否为该服务的维护者 |
| GET | /v1/service/getIterationById |
是 | Query: id |
获取指定迭代(ServiceIteration)详情 |
| GET | /v1/service/compareVersionsByUuid |
是 | Query: service_uuid, base_version, compare_version |
对比两个已发布版本的 Service/API/参数树差异 |
| GET | /v1/service/exportOpenapiByUuidAndVersion |
是 | Query: service_uuid, version(可为 latest) |
导出指定服务版本的 OpenAPI 3.1 JSON |
| POST | /v1/service/createNewService |
是 | Body: service_uuid, description |
创建新服务(初始版本 0.0.1) |
| POST | /v1/service/addOrRemoveServiceMaintainerById |
是 | Body: service_id, candidate_id |
添加或移除服务维护者 |
| POST | /v1/service/deleteServiceById |
是 | Body: id |
软删除服务(仅最新版本,历史版本保留) |
| POST | /v1/service/restoreServiceById |
是 | Body: id |
还原已软删除的服务 |
| POST | /v1/service/deleteIterationById |
是 | Body: service_iteration_id |
删除服务的历史迭代版本 |
| POST | /v1/service/startIteration |
是 | Body: service_id |
发起迭代,从当前最新版本复制草稿,返回 service_iteration_id |
| POST | /v1/service/commitIteration |
是 | Body: service_iteration_id, new_version |
提交迭代(直接发布)。服务已开启审批时,非 Owner/L0 不可用;pending 状态不可提交 |
| POST | /v1/service/submitIterationForApproval |
是 | Body: service_iteration_id, new_version |
提交审批(服务须开启 requires_iteration_approval),状态变为 pending |
| POST | /v1/service/approveIteration |
是 | Body: service_iteration_id, review_comment(可选) |
Owner/L0 通过待审迭代,按 proposed_version 发布 |
| POST | /v1/service/rejectIteration |
是 | Body: service_iteration_id, review_comment(必填) |
Owner/L0 驳回,状态变为 rejected,可继续编辑 |
| GET | /v1/service/getPendingIterations |
是 | Query: page_size, current_page |
Owner 分页获取本人服务下待审迭代 |
| GET | /v1/service/getIterationAuditLog |
是 | Query: service_iteration_id, page_size, current_page |
获取迭代变更审计时间线 |
| GET | /v1/service/getIterationChangePreview |
是 | Query: service_iteration_id |
获取相对 base_version 的变更预览(diff) |
| POST | /v1/service/updateServiceApprovalSetting |
是 | Body: service_id, requires_iteration_approval |
Owner 开启/关闭服务的「迭代需审批」 |
| POST | /v1/service/updateDescription |
是 | Body: service_iteration_id, description |
在迭代中修改服务描述 |
| POST | /v1/service/importOpenapiToNewIteration |
是 | Body: service_id, openapi_object |
从 OpenAPI 文档创建新迭代并写入草稿 |
| POST | /v1/service/importOpenapiToIteration |
是 | Body: service_iteration_id, openapi_object |
将 OpenAPI 文档导入当前迭代(覆盖草稿) |
| 方法 | 路径 | 鉴权 | 参数 | 功能 |
|---|---|---|---|---|
| GET | /v1/api/getAllCategoriesByServiceId |
是 | Query: service_id |
获取服务的全部分类 |
| GET | /v1/api/getAllApisByServiceId |
是 | Query: service_id, category_id |
获取某分类下的 API 列表 |
| GET | /v1/api/getApiById |
是 | Query: api_id, is_latest(默认 true) |
获取 API 详情(正式版或草稿版) |
| POST | /v1/api/addCategoryByServiceId |
是 | Body: service_id, category_name, description |
新增 API 分类 |
| POST | /v1/api/deleteCategoryById |
是 | Body: category_id, service_iteration_id(可选,迭代中删除时传入) |
删除分类 |
| POST | /v1/api/updateCategoryById |
是 | Body: category_id, category_name, description |
更新分类名称与描述 |
| POST | /v1/api/updateApiCategoryById |
是 | Body: api_id, category_id |
修改已发布 API 所属分类(非迭代操作) |
| POST | /v1/api/addApi |
是 | Body: service_iteration_id, name, method, path, description, level, category_id(可选) |
在迭代中新增 API 草稿 |
| POST | /v1/api/copyApiByApiDraftId |
是 | Body: service_iteration_id, api_draft_id |
在同一迭代内复制 API 草稿 |
| POST | /v1/api/deleteApiByApiDraftId |
是 | Body: service_iteration_id, api_draft_id |
在迭代中删除 API 草稿 |
| POST | /v1/api/updateApiByApiDraftId |
是 | Body: service_iteration_id, api_draft_id, name, method, path, description, level, req_params, resp_params(后两者为 JSON 字符串) |
在迭代中更新 API 草稿及其请求 / 响应参数树 |
说明:前端通常通过
getServiceByUuidAndVersion一次性获取分类与 API 聚合数据,因此getAllCategoriesByServiceId、getAllApisByServiceId在前端较少直接调用。
环境变量文件不纳入 git,请从 .env.example 复制后修改。字段如下:
# App
PYTHONPATH=<YOUR-PROJECT-PATH>
PORT=1024
# Auth
ALGORITHM=HS256
LOGIN_SECRET=<YOUR-LOGIN-SECRET>
# Database (PostgreSQL)
DATABASE_ENGINE=postgresql+psycopg2
DATABASE_USERNAME=<YOUR-DATABASE-USERNAME>
DATABASE_PASSWORD=<YOUR-DATABASE-PASSWORD>
DATABASE_HOST=<YOUR-DATABASE-HOST>
DATABASE_PORT=<YOUR-DATABASE-PORT>
DATABASE_NAME=<YOUR-DATABASE-NAME>
DATABASE_URI=postgresql+psycopg2://<YOUR-DATABASE-USERNAME>:<YOUR-DATABASE-PASSWORD>@<YOUR-DATABASE-HOST>:<YOUR-DATABASE-PORT>/<YOUR-DATABASE-NAME>
# Mail (optional; used when committing iteration)
MAIL_SERVER=smtp.example.com
MAIL_PORT=465
MAIL_USERNAME=your_email@example.com
MAIL_PASSWORD=your_smtp_password
MAIL_DEFAULT_SENDER=your_email@example.com项目已配置 Alembic(alembic.ini、alembic/env.py),连接串从 .env 的 DATABASE_URI 读取,元数据来自 database/models.py。
首次接入迁移、且尚未执行过迭代审批相关 DDL 时,在 BACKEND-NEXUS 目录:
# 仅应用已提交的 revision(推荐)
.\database\db-migrate.ps1bash database/db-migrate.sh若在 Linux 上出现 $'\r': command not found 或 set: invalid option,说明脚本是 Windows 换行符,先执行:
sed -i 's/\r$//' database/db-migrate.sh或直接:uv run alembic upgrade head。
等价于 uv run alembic upgrade head,会执行 alembic/versions/20250604_001_iteration_approval.py(审批字段 + iteration_audit_log)。
若你已经手工执行过 docs/migrations/20250604_iteration_approval.sql,不要再 upgrade,只需标记版本:
uv run alembic stamp 20250604_001-
生成新 revision(需数据库可连、用于 autogenerate 对比):
- Windows:
.\database\db-migrate.ps1 -Generate -Message "describe change" - Linux/macOS:
bash database/db-migrate.sh --generate "describe change"
- Windows:
-
检查
alembic/versions/下新生成的脚本,确认无误后再upgrade head(不带-Generate的db-migrate脚本只会升级)。
仍可用 uv run python -m database.init_db 建表;若要走 Alembic 线,可在建表后 uv run alembic stamp head 与线上一致。
deploy/scripts/deploy.sh 在 RUN_DB_MIGRATE=true 且存在 alembic.ini 时会执行 alembic upgrade head(不 autogenerate)。
- 后端技术选型采用
Python Robyn框架,数据库采用PostgreSQL - 安装依赖使用
uv sync;开发热重载可使用uv run robyn -m app --dev或脚本run-dev.ps1/run-dev.sh - 项目默认监听
1024端口,可通过修改.env中的环境变量PORT修改
以下内容为项目内部逻辑与数据结构说明。
-
使用
SQLAlchemy进行数据库ORM映射,全部数据库相关放到database目录中,包含:models.py:数据库表ORM类enums.py:枚举类,即自定义类型,例如ApiLevel、UserLevel等database.py:数据库连接与配置,定义session工厂db-migrate.sh:【见下条】
-
数据库首次建表建议使用 database/init_db.py 的
create_all:uv run python -m database.init_db
-
database/models.py中数据库表修改后可使用迁移脚本(前提:你本地已配置好 Alembic):- macOS/Linux:
bash database/db-migrate.sh - Windows PowerShell:
./database/db-migrate.ps1(或.\\database\\db-migrate.ps1)
- macOS/Linux:
-
为方便每个表的记录的
json化,让所有表继承自SerializableMixin基类,包含序列化toJson()方法,可选择保留属性、排除属性以及是否包含关系表字段;为避免循环引用,toJson()实现时内部toJson()方法不得设定include_relations=True。 -
service-maintainer为多对多关系,通过中间表user_service_link关联 -
ApiLevel枚举类从P0到P4重要性递减 -
UserLevel枚举类从L0到L4权限递减。暂时只考虑L0和L4两类用户:L0为超级管理员,有权限访问全部API;L4为普通用户,只可访问自己的service、api等资源。未确定中间类别的用户权限
-
将全部服务分为
user、service、api三类,分别对应三个子路由 -
每个路由实现内部逻辑都交由
service层处理。路由层仅负责接收请求参数、调用service层方法、返回响应。service层再调用对应的model层方法进行数据库的CRUD -
本项目中
service层的方法规范:-
方法命名为
<service_name><operation_name>,例如userLogin()、serviceGetAllCategoriesByServiceId()等。避免和路由及路由函数函数重名 -
传入
SQLAlchemy Session实例,命名为db,以及其他所需参数; -
请求成功
200时,返回区分get操作与其他操作,均返回对象,对象值为:get操作:单个对象或对象列表- 其他操作:成功
message与其他必要数据
-
请求失败
4xx或5xx时,返回Robyn Response对象:return Response( status_code=<Fail status code>, headers={}, description="<Fail message>", )
-
-
鉴权
-
通过
Robyn内置的AuthenticationHandler实现,具体逻辑在authentication.py中 -
登录生成
access token并在后续请求Header中Authorization字段携带,格式为Bearer <access_token> -
接口鉴权通过
Robyn内置的BearerGetter()方法获取access token并进行验证;另外,在user相关service中另实现了userGetUserIdByAccessToken()方法,可传入Robyn Request或access token解析出user_id。但注意:二者只能二选一传入 -
在
authentication.py中定义API_PERMISSION_MAP,用于存储每个API允许访问的最低UserLevel的映射。若API不在该map中,默认允许所有用户访问 -
每个子路由中添加鉴权中间件
<subRouter>.configure_authentication(AuthHandler(token_getter=BearerGetter()))
在每个路由中设定
auth_required=True开启鉴权,即只有登录用户有权限访问
-
-
错误处理:对于一个 API,若缺少必要参数或参数格式错误,返回
400 Bad Request错误;而其他逻辑错误(例如密码错误),则响应正常返回200,附带status为负,message为错误描述。
-
每个
service有一个owner,多个maintainer,但计划MVP版本不引入maintainer。因此除了L0用户外,只有owner才能操作其service -
每个
service中包含一个唯一的service_uuid,用于标识该service。service_uuid命名格式为a/b/c,a、b、c均为小写字母或数字,三者均由用户自定义version命名格式为X.Y.Z,其中X、Y、Z均为非负整数。所有服务初始版本均为0.0.1
二者在前端做正则校验
-
api的category切换只支持在service最新版本中进行,不属于service迭代周期内的行为
-
一次
service迭代周期内包含以下几种行为:- 修改
service description - 新增
API - 删除
API - 编辑
API(包含API自有属性、请求参数以及响应参数)
- 修改
-
Service表存储每个service的最新版本,而ServiceIteration表存储每个service迭代周期内的所有变更。Service表中的version与ServiceIteration表中当前service的最新version对齐 -
ServiceIteration被标记is_committed=False时,代表正在当前service的迭代周期,每个service每个用户只能有一个迭代周期在进行中;ServiceIteration被标记is_committed=True时,代表该迭代周期已完成,作为当前service的历史版本记录
-
用户发起
service迭代流程/startIteration,创建一个新迭代周期ServiceIteration记录,标记is_committed=False,并将当前服务最新版本全部信息备份到ServiceIteration,返回一个service_iteration_id,存在客户端,作为本迭代周期的唯一标识 -
用户在本迭代周期内进行上述四种行为,每次行为均在
ServiceIteration中进行记录。每个行为发生需要通过service_iteration_id标识当前迭代周期:-
修改
service description:将修改后的description存储到ServiceIteration -
新增
API:新增一条ApiDraft记录,只记录新增的API自有信息(name、method、path、description、level、category_id(可选)) -
删除
API:通过api_draft_id删除ApiDraft记录,同时利用CASCADE删除其关联的请求参数和响应参数 -
编辑
API:【⚠️ 复杂】通过api_draft_id定位到ApiDraft记录,更新其自有属性(name、method、path、description、level、category_id(可选))。之后,删除其关联的全部请求参数和响应参数,并根据传入的请求参数和响应参数,更新其关联的请求参数和响应参数。传入
req_params格式约定:[ { "name": "user", "location": "body", "type": "object", "required": true, "default_value": null, "description": "用户信息", "example": "{}", "array_child_type": null, "children": [ { "name": "name", "type": "string", "required": true, "default_value": null, "description": "用户姓名", "example": "张三", "array_child_type": null, "children": null }, { "name": "profile", "type": "object", "required": false, "default_value": null, "description": "用户档案", "example": "{}", "array_child_type": null, "children": [ { "name": "age", "type": "int", "required": true, "default_value": null, "description": "年龄", "example": "25", "array_child_type": null, "children": null } ] } ] }, { "name": "tags", "location": "query", "type": "array", "required": false, "default_value": null, "description": "标签列表", "example": "[\"tag1\", \"tag2\"]", "array_child_type": "string", "children": null } ]resp_params类似,只是location换为status_code
-
-
用户在本迭代周期内完成所有行为后发布版本:
- 未开启审批(默认):
/commitIteration,输入new_version后直接写回正式表 - 已开启审批:提交人
/submitIterationForApproval→ Owner/approveIteration发布;或 Owner/L0/commitIteration直接发布。approval_status=pending时禁止一切草稿编辑(见services/utils.py)
发布时将
ServiceIteration及其ApiDraft/参数草稿同步到Api/RequestParam/ResponseParam,更新service.version,is_committed=True,approval_status=committed。 - 未开启审批(默认):
- 产品说明见 docs/PRD.md §6.7
- 实现:
services/iteration_approval.py、services/iteration_audit.py、services/iteration_commit.py;路由见上文「迭代审批」相关接口 approval_status:draft|pending|rejected|committed(与is_committed配合)iteration_audit_log:记录 API 增删改及提交/通过/驳回/提交发布等事件(database/enums.py→IterationAuditAction)- 数据库迁移:
alembic/versions/20250604_001_iteration_approval.py或 docs/migrations/20250604_iteration_approval.sql
