视野 后端服务
野生动物实时识别 App「视野」(Flutter 客户端见 ../flutter_app/)的支付与授权后端。
功能模块
| 模块 | 说明 |
|---|---|
| 账号 | 手机号+密码注册/登录(POST /api/v1/auth/register / login),密码 bcrypt 哈希存储,登录签发自签名 token(auth.secret),授权绑定手机号账号、同账号多设备共享 |
| 订单 | 客户端 GET /api/v1/plans 拉取套餐价格 → POST /api/v1/orders 创建订单并返回支付参数(微信 prepay / 支付宝 orderStr);未支付订单 2 小时惰性关闭,同账号 2 小时内复用 created 订单防重复扣款 |
| 支付回调 | 微信支付 / 支付宝异步回调验签落授权(/api/v1/payment 分组,按渠道应答格式直返,不做统一包装) |
| 订单确认 | 客户端支付成功后 POST /api/v1/orders/{orderId}/confirm 幂等通知,加速授权刷新 |
| 授权查询 | GET /api/v1/license(Bearer token)返回授权状态,App 识别入口强制服务端校验 |
| 套餐 | config.yml plans 节点配置三档套餐(改价 = 改配置重启),价格整数分(1000 / 5600 / 18000) |
| 后台管理端 | server_admin/(Vue3 + Element Plus)管理页面:订单查询、账号/授权管理(手动授权/撤销)、App 版本管理;构建产物由后端 /admin/ 托管,登录页输入 token 后以 X-Admin-Token 头鉴权(config.yml admin.token) |
| 版本管理 | 后台管理端上传 Android APK + 更新说明,APK 存服务器 app.apkDir(默认 ./workspace/,与 ./data 平级、挂载持久化)固定文件名 observer-latest.apk,上传即覆盖,目录永远只保留最新一个文件;版本号从文件名识别:文件须命名为 observer-x.y.z.apk(Flutter 打包产物即此命名,版本号取自 pubspec);客户端启动时 GET /api/v1/app/update 检查更新:服务器版本高于本地版本即弹更新提示(不可跳过)。仅 Android 检查,iOS 不做版本下发(iOS 走 App Store 自行更新)。版本记录可删除:删最新版本联动删除 APK 文件,删历史版本仅删记录 |
| 数据训练(唯一入口) | 后台管理端「数据训练」一个菜单承载数据集全流程:数据集卡片列表(封面图/描述/图片数/已标注数/训练状态徽标),卡片下方直接展示训练任务进度条与状态(无独立训练页);详情页为图片与标注一体视图:分页(每页 20 条)逐行「原图 ‖ 标注图」对照展示;图片入库(手动上传/AI 生成)自动触发 RF-DETR 全图扫描标注,进度条展示在页顶;页顶另有「全量标注」按钮可手动重标全部图片(覆盖各图已有标注);点击原图/标注图弹窗放大,弹窗为审核视图(不做手动画框):点击框选中,列表可确认疑似框/删除误检框/清空并保存——AI 自动标注结果直接作为标注,人工仅审核确认;封面(上传自动转 jpg + UUID 命名)/描述/AI 生成图片(provider 抽象:dashscope 通义万相付费 API / localai 训练机 local-ai qwen-image,config.yml imageGen 节点切换,见配置说明);AI 标注端点与训练机 SSH 为全局配置,直接读 config.yml(localAi / training.ssh 节点,改配置需重启服务);图片落服务器 app.datasetDir/datasets/<数据集名>/,DB 存元数据 + 标注 JSON |
| 模型训练 | 从数据集卡片「开始训练」一键触发(参数 imgsz/epochs/batch/device 默认走 config.yml training 节点,部署级配置):进度/日志/指标监控(每 epoch 粒度)、取消;训练通道 training 节点可配置 subprocess(与 Go 服务同机直接起 python)/ ssh(异机执行,SSH 凭据取 config.yml training.ssh 节点),并发度 1(GPU 独占);训练脚本 server/training/train_server.py(随项目迁移,2026-08-26)参数化,产物(best.tflite/best.pt/曲线)拉回服务器;训练收尾自动做 tflite 产物自检(输入/输出 shape 校验,原 inspect_tflite.py 逻辑内嵌脚本),自检失败任务置失败并带出原因;dump_graph.py 留作训练机人工深度调试 |
| 模型版本与热更新 | 每数据集一个模型:训练完成后一键「发布」(训练任务操作列)——tflite 落 workspace/trainings/<文件名前缀>.tflite(前缀空回退数据集名) + sha256/指标/类别名入 model_version(按数据集独立版本序列 m1.0.0 递增)。管理端无模型管理界面(版本记录仅支撑客户端下发)。App 模型热更新:GET /api/v1/app/update 扩展返回 models 目录数组,客户端独立检查,新模型下载校验替换,失败回退旧模型——模型迭代不再重打包 APK |
| 模型目录与多模型推理 | GET /api/v1/models(登录态)返回全部数据集当前生效模型(数据集/版本/类别/大小/sha256/下载地址),下载 URL /download/trainings/<文件名前缀>.tflite(前缀空回退数据集名);App 模型管理页用户自由下载/删除/启用模型,识别时加载全部已启用模型并行推理 + 跨模型 NMS 合并(按类别名),内置 assets 模型兜底 |
| 标注 | 图片入库自动触发:手动上传/AI 生成成功后,新增图自动调 config.yml localAi 节点配置的 AI 端点做 RF-DETR 全图扫描(label_task 记录进度,页顶进度条展示;localAi 未配置 → 上传/生成接口直接报错;已有标注任务在跑(忙)→ 不报错,当前任务成功完成后自动补标未标注图)→ 扫描结果(重叠去重:NMS 风格按置信度降序保留,重叠比 > localAi.overlapThreshold 默认 0.3 的框剔除——重叠比 = 交叠面积/两框较小面积,RF-DETR 同目标常输出一大一小两框,此判据能命中,同目标只留置信度最高者)直接写 dataset_image.labels_json(覆盖该图已有标注,即重标语义);点击弹窗放大进入审核视图(不做手动画框):点击框选中,列表确认疑似框/删除误检框/清空 → 保存即整体覆写 dataset_image.labels_json(YOLO 归一化 JSON 数组,AI 标注直写、人工仅审核确认);POST /admin/label-tasks 详情页「全量标注」按钮入口(另有自动触发),可发起全量/指定图重标;自动/手动/混合并存,训练前自动整理(prepare_yolo 逻辑在服务端) |
架构与数据流
Flutter App ── POST /auth/register|login ─► 账号注册/登录,签发 token
│
├─► POST /orders (Bearer token) ──► 后端下单(校验 planId/channel)
│ │
│◄───────┘ orderId + payParams
│
├─► 拉起微信/支付宝 SDK 支付
│
├─► 微信/支付宝服务端异步回调 ─► 验签 → 落授权(幂等,绑账号)
│
└─► POST /orders/{id}/confirm (Bearer token) ─► 幂等确认(回调已落授权则直接返回)
│
└─► GET /license (Bearer token) ─► 返回 {active, expiresAt}
- 授权以服务端为准:客户端本地缓存
expiresAt仅用于主页展示,识别入口强制查询服务端 - 有效期语义:自然日(day 当天 24:00 失效;week/month 自生效日起自然日累计),服务端计算
expiresAt,客户端只读不判权
数据表
| 表 | 说明 | 关键字段 |
|---|---|---|
payment_order |
支付订单 | order_id(PK)、phone_num、plan_id、channel(wechat/alipay)、amount_cents、status(created/paid/closed)、wx_trade_no(UNIQUE)、alipay_trade_no(UNIQUE)、created_at、paid_at |
license |
手机号账号与授权 | phone_num(PK)、password(bcrypt)、expires_at(未充值 NULL)、remark(管理端备注)、created_at、updated_at |
app_version |
App 版本管理 | id(PK)、version(x.y.z, UNIQUE)、notes(更新说明)、created_at、updated_at(下载地址不落表:APK 固定文件 app.apkDir/observer-latest.apk,默认 ./workspace/) |
dataset |
训练数据集 | id(PK)、name(UNIQUE)、source(manual/ai)、image_count、labeled_count、status(building/synced/labeled)、cover(封面文件名,UUID 命名 jpg,如 9f2a...-xx.jpg)、description、created_at、updated_at(图片文件在 app.datasetDir/datasets/<name>/,DB 只存元数据;AI 标注/训练机 SSH 配置走 config.yml 的 localAi / training.ssh 节点);生成参数池(创建时 VLM 自动生成,界面不维护,可 POST /datasets/gen-pools 重新生成):gen_species(单值=数据集物种)、gen_tone(单值 轮廓色词 深色/浅色)、gen_heights(数值 站高cm,距离感公式用)、gen_scenes/gen_actions/gen_occlusions(JSON 数组 各≥3条)、gen_classes(单值 第二标注类别名="suspect",第一类别=gen_species,训练 data.yaml names);单物种规则:每数据集只对应一个物种(生成图片固定按数据集名),不同物种拆到不同数据集 |
dataset_image |
数据集图片 | id(PK)、dataset_id、filename、source(manual/ai)、prompt(AI 生成图记录提示词)、labels_json(标注 JSON 数组:YOLO 归一化 xywh+类别+置信度,AI 自动标注与人工标注同存、人工可修改/清理,null/''/'[]'=未标注)、created_at |
model_training |
训练任务 | id(PK)、name、status(running/success/failed)、dataset(训练机数据集名)、imgsz/epochs/batch/device(参数快照)、current_epoch/total_epochs、metrics(JSON)、log_tail、pid、error、started_at/finished_at、created_at |
model_version |
模型版本(每数据集独立序列) | id(PK)、dataset_id、version(m1.0.0 递增, 同数据集 UNIQUE)、training_id、metrics(JSON)、labels(JSON 类别名数组)、sha256、size_bytes、is_latest、notes、created_at(模型文件不落表:发布即写 trainings/<文件名前缀>.tflite(前缀空回退数据集名),客户端固定下载,无存档回退) |
label_task |
标注任务 | id(PK)、dataset_id、filenames(JSON 选中图片列表,NULL=全量)、status(running/done)、total/done、created_at、finished_at |
gen_task |
AI 生成任务(异步批量) | id(PK)、dataset_id、status(running/done/failed)、total/done、error、created_at、finished_at |
建表与迁移见 技术设计.md(新库直接建表;存量库以 PRAGMA user_version 版本化迁移)。
API 清单
响应统一格式 {"code": 0, "message": "ok", "data": {...}},code != 0 视为失败。接口只允许 GET / POST。
POST /api/v1/auth/register
手机号注册(与登录无关的公开接口,无需 token)。请求:{"phone": "13800000000", "password": "..."}。已注册手机号返回错误。注册成功即创建账号(未充值,无额度)。
POST /api/v1/auth/login
手机号登录。请求:{"phone": "13800000000", "password": "..."}。响应 data: {"token": "..."}(HMAC 自签名,有效期 auth.token_ttl,默认 30 天)。后续登录态接口携带 Authorization: Bearer <token>。
GET /api/v1/plans
套餐列表(需 Bearer token)。响应 data:
{
"list": [
{"planId": "day", "days": 1, "priceCents": 1000},
{"planId": "week", "days": 7, "priceCents": 5600},
{"planId": "month", "days": 30, "priceCents": 18000}
]
}
套餐来自 config.yml plans 节点(静态定价,改价改配置后重启服务生效);客户端不硬编码价格。展示名由客户端按 days 拼「N天」派生,无独立 label 字段。
POST /api/v1/orders
创建订单,返回渠道支付参数(需 Bearer token,手机号由 token 解出)。
请求(JSON body):
{"planId": "week", "channel": "wechat"}
channel:wechat|alipayplanId:day|week|month(需在config.ymlplans节点存在,金额由服务端按配置锁定,客户端不可改价)
响应 data:
{
"orderId": "O20260822001",
"payParams": {
"partnerId": "1900xxxxx",
"prepayId": "wx...",
"nonceStr": "...",
"timeStamp": "1728000000",
"sign": "...",
"packageValue": "Sign=WXPay"
}
}
channel == wechat:payParams为微信 APP 支付统一下单返回参数(后端签名)channel == alipay:payParams为{"orderStr": "alipay_sdk=..."}(后端调alipay.trade.app.pay生成)
POST /api/v1/orders/{orderId}/confirm
客户端支付成功后通知(幂等,需 Bearer token)。服务端以异步回调为准落授权,confirm 仅用于加速刷新;回调已到账时直接返回成功。
请求:{}(无参数)。响应:data: {"status": "paid"|"created"}。
返回 created(回调未到账)时客户端按 3s → 10s → 30s 递增重试 confirm(最多 3 次),期间禁止重新下单;仍未成功回退 GET /license 查询(回调到账后自然变为 active)。回调为唯一授权来源,confirm 仅加速刷新。
GET /api/v1/license
查询账号授权状态(需 Bearer token,手机号由 token 解出)。
响应 data:
{"active": true, "expiresAt": "2026-08-29T23:59:59+08:00"}
active: false 或 expiresAt 已过期(含未充值账号)→ 客户端展示付费墙/充值引导。
支付回调(微信/支付宝,勿直连)
| 渠道 | 路径 | 说明 |
|---|---|---|
| 微信支付(APP 支付 V3) | POST /api/v1/payment/wechat/notify |
解密+验签回调,out_trade_no → 落授权 |
| 支付宝(APP 支付) | POST /api/v1/payment/alipay/notify |
RSA2 验签回调,out_trade_no → 落授权 |
GET /api/v1/app/update
App 版本更新检查(公开接口,无需 token,未登录/旧版本均可访问)。返回服务器最新版本记录;无任何记录时 data 为空对象,客户端视为无需更新。
响应 data:
{
"version": "1.1.0",
"notes": "修复识别准确率问题"
}
- 仅 Android 客户端调用(iOS 不做版本下发,走 App Store 自行更新)
- 检测到新版本(服务器版本高于本地版本)即强制更新,客户端弹不可关闭的全屏提示,必须跳转更新后才能继续使用;本地已是新版本则不提示
- 客户端以「语义化版本号」比较:
1.10.0 > 1.9.9(按数字段比较,禁止字符串比较) - 下载地址为固定静态路径:
/download/observer-latest.apk(app.apkDir目录下永远只有最新一个文件,由后端静态托管),客户端拼apiBaseUrl访问 - 模型热更新(与 APK 更新独立通道):服务器有已发布模型时响应额外返回
models数组(与GET /api/v1/models同构:datasetId/datasetName/version/labels/sizeBytes/sha256/downloadUrl);客户端启动与 APK 更新独立检查——某数据集服务器版本高于本地已下载版本即下载/download/trainings/<文件名前缀>.tflite(前缀空回退数据集名) 到应用私有目录,sha256 校验后原子替换,下次识别生效;非强制,失败回退旧模型下次启动重试。App 模型管理页列出服务器全部可用模型,用户自由下载/删除/启用;识别时加载全部已启用模型并行推理 + 跨模型 NMS 合并(按类别名),内置 assets 模型兜底。无发布模型时不返回 models 字段(旧 App 忽略新字段、新 App 兼容旧服务器)
GET /download-page
APK 下载引导页(静态页面,源码在 h5/index.html,由后端 /download-page 路径托管):微信内置浏览器会拦截 APK 下载,此页面按打开环境分流——微信内打开显示图形引导(点击右上角「···」→「在浏览器打开」+ 复制链接兜底);手机浏览器打开直接显示下载按钮,直链 http://observer.redpowerfuture.com/download/observer-latest.apk。
管理端接口(/api/v1/admin,需请求头 X-Admin-Token = config.yml admin.token)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/orders |
订单列表,筛选参数 phoneNum/status/startAt/endAt + page/size,返回 {total, list} |
| GET | /admin/licenses |
账号/授权列表,筛选 phoneNum + page/size(含未充值账号) |
| POST | /admin/licenses/grant |
手动授权 {"phoneNum":"13800000000","planId":"day"},自然日叠加 |
| POST | /admin/licenses/revoke |
撤销授权 {"phoneNum":"13800000000"}(清授权保留账号) |
| POST | /admin/licenses/remark |
写账号备注 {"phoneNum":"13800000000","remark":"..."}(空串清空,≤200 字) |
| GET | /admin/app-versions |
版本记录列表,page/size 分页,按下发时间倒序 |
| POST | /admin/app-versions |
下发新版本(multipart/form-data):notes + file(APK 文件,仅接受 .apk);版本号从文件名识别,文件须命名为 observer-x.y.z.apk(如 observer-1.0.1.apk),格式不符拒绝;版本号不可重复,APK 上传覆盖 app.apkDir/observer-latest.apk(目录永远只有一个文件);检测到新版本即强制更新 |
| POST | /admin/app-versions/delete |
删除版本记录 {"id":1}:删最新版本时联动删除 APK 文件(客户端不再提示更新、下载 404);删历史版本只删记录不动文件 |
| POST | /admin/datasets |
创建数据集 {"name":"pheasant_v2","namePrefix":"pheasant","source":"manual"|"ai","cover":"<文件名>"}(name ≤50 字唯一,目录自动建;namePrefix=AI 生成图文件名前缀,生成图按 <前缀>_<两位序号>.jpg 顺序命名;物种=数据集名(单物种规则),创建时同步调 VLM(qwen3.6-35b-a3b) 自动生成物种/场景/动作/遮挡/站高/类别名等生成参数池,响应含 poolsGenerated/poolError——VLM 失败不阻断创建,参数可事后用 gen-pools 补生成;封面优先用 cover 参数(新建对话框预生成封面回传,跳过自动生成),否则参数池成功后自动生成 16:9(1024x576)封面(1 雄 1 雌并排,响应含 coverGenerated/coverError,失败可在编辑模式重新生成);模型生成图统一转 jpg 落盘) |
| GET | /admin/datasets |
数据集列表:page/size 分页,返回 {total, list}(含 imageCount/labeledCount/status/cover/description/training 聚合状态:最新训练记录的 status/currentEpoch/totalEpochs) |
| POST | /admin/datasets/update |
更新数据集配置 {"id":1,"name":"新名","namePrefix":"pheasant","description":"...","cover":"a.jpg"}:名称(改名)/文件名前缀/描述/封面,空值字段不覆盖原值;gen_* 生成参数池不在此维护(仅 VLM 生成,见 gen-pools);改名同步迁移图片目录与模型文件,标注/训练进行中拒绝 |
| POST | /admin/datasets/gen-pools |
重新生成数据集生成参数池 {"datasetId":1}:按数据集名(物种)调 VLM(qwen3.6-35b-a3b) 生成轮廓色/站高/场景/动作/遮挡并写表(覆盖旧值);失败报错保留旧值 |
| POST | /admin/datasets/images/vlm-review |
VLM 藏匿位补检 {"datasetId":1,"imageId":5}(两阶段标注第二阶段,须与图像生成显存互斥):qwen3.6-35b-a3b(+mmproj) 排除已确认框,按环境/光线/习性推理藏匿位,追加 ≤3 个疑似框(class 1)进同一 labels_json |
| POST | /admin/datasets/cover |
上传数据集封面(multipart:datasetId+file,jpg/jpeg/png ≤2MB):自动转 jpg + UUID 命名落盘并覆盖旧封面 |
| POST | /admin/datasets/cover/generate |
生成数据集封面 {"datasetId":1}(z-image 文生图:16:9、1 雄 1 雌,物种取 gen_species 空回退数据集名):覆盖旧封面,返回 {cover} 新文件名;datasetId=0 时传 {"name":"家鸽"} 新建预生成(数据集未创建,封面仅落盘不写库,创建请求带 cover 回传写库);与生成任务显存互斥,失败不覆盖旧封面 |
| GET | /admin/datasets/cover |
封面文件(静态字节流,datasetId 定位;datasetId=0 时按 name+filename 直读——新建对话框预生成封面回显) |
| POST | /admin/datasets/cover/delete |
删除数据集封面 {"datasetId":1}:删文件 + 清 cover 字段 |
| POST | /admin/datasets/upload |
上传图片(multipart/form-data:datasetId + files 多张,仅接受 .jpg/.jpeg/.png),存 app.datasetDir/datasets/<name>/,逐张入库 |
| POST | /admin/datasets/generate |
AI 生成图片 {"datasetId":1,"count":1,"distance":25}(物种固定取数据集名,表单无需填写;scene/action/occlusion 后端从数据集表池随机,light 走 config 通用池;性别每张随机雄/雌(两性体型外观差异大,随机让训练数据覆盖两性形态);prompt 可手填覆盖模板;每张固定 1 个目标(数量词 "1只",animal_count=1 供标注裁剪);distance 必填注入提示词距离描述;距离校验已取消;文件名前缀取数据集属性)(prompt 留空时按 imageGen.promptTemplate 组装:物种=数据集名,场景/动作/遮挡/站高从 dataset 表生成参数池读取(创建数据集时 VLM 生成,无 config 兜底——池为空报错提示补参数或手填提示词);手填覆盖)(count 1..1000,异步任务:校验通过即返回 {taskId, total},后台协程逐张生成(不设调用超时,失败由 provider 真实返回判定),进度/结果轮询 GET /admin/datasets/gen-task):调 imageGen provider(dashscope / localai,config 切换)逐张生成落盘 + 入库(记录 prompt);完成或部分失败后自动触发标注;任意失败任务置 failed(已生成图保留,付费资产原则);namePrefix 可选,按 <前缀>_<两位序号>.jpg 顺序命名并续接已有最大序号(如 pigeon_01.jpg),留空用时间戳命名;animalCount 仅作为自动标注框数上限(按置信度裁剪 ≤N),不参与生成校验 |
| GET | /admin/datasets/gen-task |
生成任务进度 {"datasetId":1}:返回最近一次任务 {id, status(running/done/failed), total, done, error, createdAt, finishedAt};无任务返回 null |
| GET | /admin/datasets/images |
数据集图片列表 {"datasetId":1},返回图片元数据(文件名/来源/prompt/创建时间) |
| GET | /admin/datasets/image |
图片文件(静态字节流,datasetId + filename 定位,供缩略图/查看) |
| POST | /admin/datasets/images/delete |
删除图片 {"datasetId":1,"ids":[1,2]}:删文件 + 删记录(AI 生成图是付费资产,前端确认文案提示) |
| GET | /admin/datasets/export |
导出数据集 zip:datasetId,打包图片目录为 zip 下载(标注衔接用) |
| POST | /admin/datasets/sync |
同步数据集到训练机 {"id":1}:按 training 通道推送图片到训练机 datasetDir/<name>/(subprocess 同机 cp、ssh 异机 scp/rsync);训练启动前自动执行 |
| POST | /admin/label-tasks |
发起预标注 {"datasetId":1,"filenames":["a.jpg","b.jpg"]}(详情页「全量标注」按钮入口,图片入库亦自动触发):filenames 缺省=全量,指定图则只扫描选中图(全量重标/单张修补);调 config.yml localAi 配置的 AI 端点 RF-DETR 逐张推理(common 池并行,未配置报错),扫描结果做重叠去重(NMS 风格按置信度降序保留,重叠比 > localAi.overlapThreshold 默认 0.3 的框剔除,同目标只留置信度最高者)后直写 dataset_image.labels_json(重跑覆盖该图标注),label_task 记录进度 |
| GET | /admin/label-tasks |
标注任务列表:page/size 分页(含 status/total/done) |
| GET | /admin/label-tasks/detail |
标注任务详情:返回数据集全部图片 + 每张标注框(boxes,YOLO 归一化 xywh + 置信度 + 类别) |
| POST | /admin/label-tasks/save |
保存单张标注 {"datasetId":1,"filename":"a.jpg","boxes":[{"class":0,"cx":0.5,"cy":0.4,"w":0.1,"h":0.2}]}:整体覆写该图 labels_json(空 boxes=清空标注),返回该数据集当前 labeledCount |
| POST | /admin/trainings |
发起训练 {"dataset":"yolo","imgsz":704,"epochs":150,"batch":16,"device":"0","name":"..."}:先同步数据集到训练机 → 校验目录存在 → runner 启动训练;并发度 1,已有 running 任务时返回错误 |
| GET | /admin/trainings |
训练任务列表:page/size 分页,按下发时间倒序,含 status/进度/指标 |
| GET | /admin/trainings/detail |
任务详情 {"id":1}:参数快照 + 进度 + 指标 + 日志尾部 |
| POST | /admin/trainings/cancel |
取消训练 {"id":1}(仅 running):杀训练进程,状态置 failed(记录 error) |
| POST | /admin/trainings/publish |
发布为最新模型 {"id":1,"notes":"..."}(仅 success):tflite 拷为 workspace/model-latest.tflite(原子覆盖)+ sha256/大小 → 插入 model_version(版本号递增 m1.0.0 → m1.0.1)+ 旧版 is_latest=0 |
| GET | /admin/label-workbench |
标注工作台数据 {"datasetId":1}:数据集全部图片 + 每张标注框(boxes,labels_json 全量)——无历史标注任务时详情页工作台的数据源 |
管理页面由 server_admin/ 构建产物提供,访问 http://<host>/admin/。金额均为整数分,前端展示 ÷100 转元。
GET /api/v1/models
模型目录(需 Bearer token,客户端模型管理页拉取):返回全部数据集当前生效模型。响应 data:
{
"list": [
{"datasetId": 1, "datasetName": "pheasant", "version": "m1.2.0", "labels": ["pheasant", "suspect"],
"sizeBytes": 6400000, "sha256": "ab12...", "notes": "修复小目标漏检", "publishedAt": "2026-08-26T10:00:00+08:00",
"downloadUrl": "/download/models/pheasant/latest.tflite"}
]
}
- 只返回
is_latest=1的模型(每数据集至多一条);无任何发布模型时list为空数组 - 下载地址由客户端拼
apiBaseUrl访问;下载文件 sha256 校验,类别名数组labels用于多模型合并推理展示
使用说明
- 配置
config.yml:监听端口、数据库路径、登录 token 签名密钥auth.secret(必填,换值即全员下线)、套餐plans节点、微信支付(appid/mchid/商户私钥/证书序列号/APIv3 密钥)、支付宝(appid/应用私钥/支付宝公钥)、管理端admin.token;模型训练相关节点:training(训练通道 mode=subprocess/ssh、ssh 连接信息、训练机工作目录/venv/数据集目录、并发度 1、超时)、imageGen(AI 生成图片 provider:dashscope通义万相(apiKey + model qwen-image-3.0)/localai本地 local-ai(baseUrl + model 如 qwen-image;每张不设调用超时,失败由 provider 真实返回判定))、localAi(二期标注用 RF-DETR 服务地址);SQLite 库由服务启动时自动建表并迁移,无需手工初始化 go build ./...编译验证- 本地运行
go run main.go;服务层白盒测试:GF_GCFG_FILE=biz/service/testdata/config.yml go test ./biz/service/(独立测试库,见biz/service/testdata/) - 后台管理端:
cd server_admin && npm run build(构建产物输出到server/admin_dist/,由后端/admin/托管);开发联调npm run dev(Vite 代理/api→:8080)。首次访问/admin/进入登录页,输入config.yml admin.token对应的管理 token(存浏览器 localStorage,随请求携带;token 不内嵌构建产物)。硬性要求:只要修改了server_admin/源码,必须同步重新构建admin_dist/并提交产物(后端托管的是构建产物,不重新构建则线上/部署版本不生效;禁止只改源码不构建) - Docker Compose 部署见
Dockerfile/docker-compose.yml(./data运行时数据目录挂载持久化,容器重建不丢数据) - 客户端对接:
flutter_app/lib/config/app_config.dart的apiBaseUrl、微信/支付宝 AppID 与 URL Scheme 替换为真实值
金额与安全约定
- 金额一律整数分(
price_cents/amount_cents为 INT64),禁止浮点元;前端展示 ÷100 - 支付回调必须验签(微信 APIv3 验签/解密、支付宝 RSA2),回调处理幂等(
wx_trade_no/alipay_trade_no唯一约束,重复回调直接忽略) - 授权落库以较晚到期为准:已有授权未过期时新套餐顺延叠加,禁止覆盖缩短