OCI Portal logo # OCI Portal **自托管的 OCI 多租户管理面板与 GenAI 兼容网关** ![Release](https://img.shields.io/github/v/release/wangdefaa/oci-portal?display_name=tag) ![Go](https://img.shields.io/badge/Go-1.26.5-00ADD8?logo=go&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-amd64%20%7C%20arm64-2496ED?logo=docker&logoColor=white) ![License](https://img.shields.io/badge/License-MIT-blue) [快速开始](#快速开始) · [核心能力](#核心能力) · [生产部署](#生产部署) · [AI 网关](#ai-网关) · [开发](#开发)
OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和 OCI GenAI 渠道集中到一个管理界面。Vue 前端通过 `go:embed` 嵌入 Go 服务,Release 以单个二进制和多架构容器镜像交付。 本仓库为后端与发行仓库;前端源码位于 [oci-portal-dash](https://github.com/wangdefaa/oci-portal-dash)。 ## 界面预览 | 总览 | 登录 | | --- | --- | | ![总览](docs/assets/screenshot-overview.png) | ![登录](docs/assets/screenshot-signin.png) | | 租户 | 任务 | | --- | --- | | ![租户](docs/assets/screenshot-tenants.png) | ![任务](docs/assets/screenshot-tasks.png) | | AI 网关 | 通知设置 | | --- | --- | | ![AI 网关](docs/assets/screenshot-ai-gateway.png) | ![通知设置](docs/assets/screenshot-settings-notify.png) | ## 核心能力 - **租户与区域**:集中管理多份 OCI API Key,支持分组、批量测活、账户画像、订阅区域缓存与区域切换;私钥和口令使用 AES-256-GCM 加密落库 - **计算、网络与存储**:实例创建与电源操作、公网 IP、IPv6、VNIC、串行控制台连接,VCN / 子网 / 安全列表,引导卷与块存储挂载,限额和成本查询 - **自动化任务**:抢机、租户测活、成本同步、AI 渠道探测四类 cron 任务,提供执行日志、重叠执行防护、熔断与结果通知 - **网页控制台**:浏览器内使用 xterm 串行终端和 noVNC,通过 OCI 控制台连接建立两跳 SSH 隧道 - **身份与审计**:IAM 用户、MFA、API Key、密码策略、SAML 身份提供商、通知收件人和多 Identity Domain 管理;OCI Audit 事件可经 Service Connector Hub 与 Notifications 回传,关键事件按类别通过「云端事件」通知推送 - **通知与安全**:Telegram、Webhook、ntfy、Bark、SMTP 五类渠道;JWT、bcrypt、TOTP、OIDC / GitHub 登录、登录锁定、IP 限速、会话撤销和系统操作审计 - **AI 网关**:提供 OpenAI Responses、Chat Completions、Embeddings 与 Anthropic Messages 兼容接口,支持渠道分组、加权路由、熔断探测、模型黑白名单、密钥管理和调用日志 ## 运行形态 ```text 浏览器 / API 客户端 │ ▼ ┌──────────────────── OCI Portal 单个 Go 进程 ────────────────────┐ │ Vue 3 静态资源(go:embed) │ │ /api/v1 → Gin → Service → OCI SDK │ │ /ai/v1 → 兼容转换与路由 → OCI Generative AI │ │ GORM → SQLite(默认)/ MySQL / PostgreSQL(experimental) │ └─────────────────────────────────────────────────────────────────┘ ``` 默认推荐 SQLite 单实例部署。MySQL 和 PostgreSQL 适配仍属 experimental,不应据此推断服务支持多副本并发运行。 ## 快速开始 ### Docker Compose(推荐) 前置条件:Docker、Docker Compose v2、OpenSSL。 1. 克隆仓库并生成一份需要长期保存的 `.env`: ```bash git clone https://github.com/wangdefaa/oci-portal.git cd oci-portal umask 077 printf 'DATA_KEY=%s\nJWT_SECRET=%s\nADMIN_PASSWORD=%s\n' \ "$(openssl rand -hex 32)" \ "$(openssl rand -hex 32)" \ "$(openssl rand -base64 24)" > .env chmod 600 .env ``` 2. 准备数据目录并启动: ```bash mkdir -p data # Linux bind mount 需要让镜像内的 nonroot 用户(uid 65532)可写。 sudo chown 65532:65532 data docker compose up -d docker compose ps ``` 3. 查看初始管理员密码并登录: ```bash grep '^ADMIN_PASSWORD=' .env ``` 访问 `http://127.0.0.1:18888`,默认用户名为 `admin`。 > `DATA_KEY` 用于解密数据库中的 OCI 私钥、口令和渠道凭据。它只能生成一次并持续复用;丢失或更换后,已有密文无法恢复。请将 `.env` 与 `data/oci-portal.db` 一起备份。 ### 二进制运行 Release 提供 Linux amd64 / arm64 二进制。以下以 amd64 为例;arm64 主机将文件名中的 `amd64` 替换为 `arm64`: ```bash curl -fLO https://github.com/wangdefaa/oci-portal/releases/latest/download/oci-portal-server-linux-amd64 chmod +x oci-portal-server-linux-amd64 # 复用上文生成并妥善保存的 .env。 set -a . ./.env set +a ./oci-portal-server-linux-amd64 ``` 默认访问地址为 `http://localhost:8080`。`ADMIN_PASSWORD` 只在数据库中没有用户时创建初始管理员,后续启动不会用它重置密码。 ### 源码构建 源码构建要求 Go 1.26.5、`curl`、`unzip` 和 `sha256sum`。仓库只保留前端占位页,编译完整单文件前应下载 `DASH_VERSION` 指定的前端产物并校验: ```bash DASH_TAG="$(tr -d '\r\n' < DASH_VERSION)" DASH_BASE="https://github.com/wangdefaa/oci-portal-dash/releases/download/${DASH_TAG}" curl -fL -o dist.zip "${DASH_BASE}/dist.zip" curl -fL -o dist.zip.sha256 "${DASH_BASE}/dist.zip.sha256" sha256sum -c dist.zip.sha256 rm -rf internal/webui/dist mkdir -p internal/webui/dist unzip -q dist.zip -d internal/webui/dist CGO_ENABLED=0 go build -trimpath -o bin/oci-portal-server ./cmd/server ``` 本地构建显示 `dev` 版本;Release 工作流会注入正式版本和构建时间。`internal/webui/dist` 中的真实前端产物不应提交到仓库。 ## 生产部署 服务本身只提供 HTTP。除本机试用外,应保持服务仅监听回环地址或容器内部网络,并由 TLS 反向代理提供 HTTPS;否则管理员密码、JWT、AI 密钥和租户凭据会经明文连接传输。 ### Caddy ```caddyfile portal.example.com { reverse_proxy 127.0.0.1:18888 } ``` Caddy 会自动处理 WebSocket。使用 nginx、Traefik 或其他反向代理时,还需要满足: - 为串行终端和 VNC 转发 WebSocket Upgrade 头 - 为 AI 流式响应和控制台连接设置较长的读写超时,建议 `1h` - 整站请求体上限至少为 `10MB`;后端会继续限制面板 API 为 `1MB`、AI 网关为 `10MB` - 将面板内「设置 → 安全 → 真实 IP 请求头」配置为反向代理实际写入的头;系统审计、登录锁定和 IP 限速依赖它 - 配置 `PUBLIC_URL` 或面板地址,供 OAuth 回调和 OCI 日志回传链路使用
nginx 最小示例 ```nginx map $http_upgrade $connection_upgrade { default upgrade; "" close; } server { listen 443 ssl; server_name portal.example.com; ssl_certificate /etc/nginx/certs/portal.example.com.crt; ssl_certificate_key /etc/nginx/certs/portal.example.com.key; client_max_body_size 10m; location / { proxy_pass http://127.0.0.1:18888; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 1h; proxy_send_timeout 1h; } } ```
若前端静态文件与后端分离部署,`/api/*` 和 `/ai/*` 必须代理到后端,其余路径由 SPA 静态服务处理并回退到 `index.html`。Traefik 通过容器网络连接时应直达容器端口 `8080`,无需暴露宿主机端口。 ## AI 网关 AI 网关使用面板创建的独立密钥鉴权,支持 `Authorization: Bearer sk-...` 和 `x-api-key: sk-...`。密钥可绑定渠道分组和模型白名单;全局模型黑名单会从模型列表、路由和探测候选中同时排除目标模型。 | 端点 | 定位 | 流式 | | --- | --- | --- | | `POST /ai/v1/responses` | OpenAI Responses,无状态主接口 | SSE;服务端工具除外 | | `POST /ai/v1/chat/completions` | OpenAI Chat Completions,存量客户端兼容层 | SSE | | `POST /ai/v1/messages` | Anthropic Messages 转换层 | SSE | | `POST /ai/v1/embeddings` | OpenAI Embeddings | 否 | | `GET /ai/v1/models` | 当前密钥可见的模型列表 | 否 | 兼容边界: - 对话请求统一转发 OCI OpenAI 兼容面,当前供给以 `xai.`、`meta.`、`openai.` 前缀模型为主;Cohere Embeddings 不受该对话模型范围影响 - 网关不保存会话历史,客户端需要携带完整上下文;Responses 拒绝非空 `previous_response_id`、非 `null` `conversation` 和 `background:true` - Responses 支持 xAI Grok `web_search` / `x_search` 服务端工具,但仅限非流式请求 - Responses 的 `reasoning.effort`、Messages 的 `output_config.effort` 和 Chat Completions 的 `reasoning_effort` 会传给上游,实际档位和效果由模型决定 - Chat Completions 只承担协议转换与兼容修复;新能力优先在 Responses 和 Messages 提供 - 单次请求最多尝试三个渠道;可重试错误会切换渠道,流式响应建立后不会换渠道重试 ### 已知上游限制:大 system 区流式断流 实测(2026-07-13)OCI 兼容面对 `instructions` 与 `tools` 合计超约 64.5KB 的**流式**请求会在发出少量事件后静默断开连接(无任何错误事件;同请求非流式正常),与模型、字符集、消息正文大小均无关——消息正文(`input`)不计入该限制。Chat Completions 与 Messages 的 system/developer 提示会转换为 `instructions`,因此 Claude Code 等自带大体量系统提示与工具定义的客户端极易触发。 网关侧应对: - Messages 与 Chat Completions 的流式请求在客户端尚未收到任何输出时遭遇上游断流,会自动降级为非流式重做,并按标准事件/chunk 序列一次推送;调用日志记 `retries=1` 与降级标记 - Responses 直通因初始事件已转发、协议上无法透明降级,调用日志记「上游流提前终止」,客户端需自行回退非流式 - 应急规避:将超长 system 内容移入首条 user 消息正文可绕过该限制(正文不计入),但语义有别,根治有待上游修复 这里提供的是兼容接口而非 OpenAI / Anthropic 协议的完整实现。OCI OpenAI 兼容面的部分行为来自实测,未见 Oracle 文档承诺,可能随上游调整。路由与鉴权定义以 [Swagger YAML](docs/swagger.yaml) 或运行时 Swagger UI 为准;无法由 OpenAPI 完整表达的兼容边界列于上方。 ### 字段兼容矩阵 以下矩阵以 2026-07-13 的 [OpenAI Responses](https://developers.openai.com/api/reference/resources/responses/methods/create)、[Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)、[Embeddings](https://developers.openai.com/api/reference/resources/embeddings/methods/create) 和 [Anthropic Messages](https://platform.claude.com/docs/en/api/messages/create) 为标准基线,并与当前实现逐项核对。 这是一份兼容性快照,不替代 Swagger。标准接口和 OCI 上游都可能变化,最终行为以当前版本代码与实测为准。 | 标记 | 含义 | | :---: | --- | | ✅ | 网关直接支持 | | ➡️ | 网关原样透传;是否生效由 OCI 上游决定 | | 🔄 | 网关进行字段或协议转换后支持 | | ◐ | 部分支持、存在前置条件或语义降级 | | ⚠️ | 请求可被接受,但字段会被忽略 | | ❌ | 网关在请求到达上游前拒绝 |
POST /ai/v1/responses 对比 OpenAI Responses Responses 是“原始 JSON 直通 + 本地门禁”。除 `store` 外,网关不会重建请求体;未知顶层字段也会保留并送往 OCI。 | 标准字段 | 状态 | 网关行为 | | --- | :---: | --- | | `model` | ◐ | 必须非空,还要通过密钥模型白名单并匹配可用渠道;随后原样透传 | | `input` | ➡️ | 支持标准的字符串或 item 数组,原始内容透传;网关不逐项保证 OCI 能处理所有 item 类型 | | `instructions`、`max_output_tokens` | ➡️ | 类型可解析后原样透传,不做范围或模型能力校验 | | `temperature`、`top_p`、`parallel_tool_calls` | ➡️ | 原样透传,不做取值范围校验 | | `text` / `text.format` / `text.verbosity` | ➡️ | 整个原始对象透传;结构化输出是否可用由 OCI 模型决定 | | `reasoning` | ➡️ | 整个原始对象透传;`effort` 不校验档位,`summary` 等字段不会被网关删除 | | `tool_choice` | ➡️ | 任意 JSON 原样透传,本地不校验枚举或结构 | | `context_management`、`include`、`max_tool_calls`、`metadata`、`moderation`、`prompt` | ➡️ | 网关未建模但会保留在原始请求中,由 OCI 决定是否接受 | | `prompt_cache_key`、`prompt_cache_retention`、`safety_identifier`、`service_tier` | ➡️ | 原样透传,不代表 OCI 一定实现 OpenAI 的对应语义 | | `stream_options`、`top_logprobs`、`truncation`、`user` | ➡️ | 原样透传,不做本地语义校验 | | `store` | 🔄 | 无论客户端传什么,上游请求都强制改写为 `false` | | `previous_response_id` | ❌ | 非空即返回 400;网关不保存历史响应 | | `conversation` | ◐ | 省略或 `null` 可通过;任何非 `null` 值返回 400 | | `background` | ◐ | `true` 返回 400;`false`、`null` 或省略时继续透传 | | `stream` | ◐ | 普通请求和 `function` 工具支持 SSE;含 `web_search` / `x_search` 时拒绝流式 | | `tools[].type=function` | ➡️ | 非流式和流式均可,工具对象原样透传 | | `tools[].type=web_search` / `x_search` | ◐ | 使用 xAI 服务端工具语义且仅支持非流式;`x_search` 不是 OpenAI 标准工具 | | 其他标准工具类型 | ❌ | `web_search_preview`、`file_search`、`computer`、`code_interpreter`、`image_generation`、`mcp`、`shell`、`custom` 等返回 400 | | 其他未知顶层字段 | ➡️ | 只要整个请求是合法 JSON,字段名、结构和数值都会保留 | 响应边界: - 非流式成功响应不做转换,OCI JSON 原样返回;usage 解析只用于面板调用日志 - SSE 事件逐行原样转发,不补 `data: [DONE]`,推理增量也不会被网关过滤 - 流建立前最多切换三个渠道;流建立后中断不重试,客户端可能只收到部分事件 - 未知模型返回 404、无渠道返回 503;上游错误会套入 OpenAI 风格错误体,不保证与标准 OpenAI 错误字段完全相同 实现依据:[`airesponses.go`](internal/service/airesponses.go) · [`responses.go`](internal/aiwire/responses.go) · [`aigateway.go`](internal/api/aigateway.go)
POST /ai/v1/chat/completions 对比 OpenAI Chat Completions Chat Completions 会先转换为 Responses 请求,再把 OCI Responses 响应桥接回 Chat Completions 形态。 | 标准字段 | 状态 | 网关行为 | | --- | :---: | --- | | `model` | ◐ | 必填,受密钥白名单和可用渠道限制;模型名保留到上游请求 | | `messages` | 🔄 | 必填并转换为 Responses `input` / `instructions` | | `system` / `developer` 消息 | ◐ | 文本按出现顺序合并为 `instructions`;块数组中的非文本内容被忽略 | | `user` / `assistant` 文本内容 | 🔄 | 字符串及 `text` 块分别转为 `input_text` / `output_text` | | `image_url` 内容块 | ◐ | URL 或 data URI 转为 `input_image`;`image_url.detail` 被忽略 | | `input_audio`、`file`、`refusal` 等内容块 | ❌ | 当前消息转换器不支持,返回 400 | | assistant `tool_calls` / `role=tool` | 🔄 | 转为 `function_call` / `function_call_output`,保留调用 ID、函数名和参数 | | 消息 `name`、`refusal`、`audio`、旧式 `function_call` | ⚠️ | 当前消息结构未建模,静默忽略 | | `max_completion_tokens` | 🔄 | 转为 `max_output_tokens`,优先于 `max_tokens` | | `max_tokens` | 🔄 | 未提供 `max_completion_tokens` 时转为 `max_output_tokens` | | `temperature`、`top_p`、`parallel_tool_calls` | ◐ | 原值写入 Responses 请求,但不校验范围或模型能力 | | `stream` | 🔄 | OCI Responses SSE 桥接为 `chat.completion.chunk`,末尾补 `data: [DONE]`;上游断流且尚无输出时自动降级非流式重做,结果按 chunk 序列一次推送 | | `stream_options.include_usage` | 🔄 | 控制网关在终块后追加 `choices: []` 的 usage 块 | | `stream_options.include_obfuscation` | ⚠️ | 未建模,静默忽略 | | `tools[].type=function` | ◐ | `name`、`description`、`parameters` 支持;`function.strict` 被忽略 | | `tools[].type=custom` 及其他工具 | ❌ | 只接受 `function`,其他类型返回 400 | | `tool_choice` | ◐ | 支持 `auto` / `none` / `required` 和具名 function;非法或未知值被静默忽略 | | `response_format` | ◐ | `json_object`、`json_schema` 转为 Responses `text.format`;未知类型交给 OCI 处理 | | `reasoning_effort` | ◐ | 转小写后映射为 `reasoning.effort`,不校验模型或档位 | | `store` | ⚠️ / 🔄 | 客户端字段被忽略,转换后的上游请求始终使用 `store:false` | | `stop`、`seed`、`n`、`frequency_penalty`、`presence_penalty` | ⚠️ | 静默忽略;`n` 因此恒为单个 choice | | `logprobs`、`top_logprobs`、`logit_bias`、`user` | ⚠️ | 静默忽略 | | `audio`、`modalities`、`prediction`、`metadata`、`moderation` | ⚠️ | 静默忽略 | | `prompt_cache_key`、`safety_identifier`、`service_tier`、`verbosity`、`web_search_options` | ⚠️ | 静默忽略 | | 其他未知字段 | ⚠️ | JSON 解码器不会拒绝未知字段,但转换后的上游请求不包含它们 | 响应边界: - 非流式固定生成一个 `choices[0]`;文本会合并,函数调用转为 `tool_calls` - `finish_reason` 只生成 `stop`、`tool_calls`、`length`;其他上游终止原因不保留 - reasoning 输出、logprobs、refusal、annotations、audio、service tier 和 system fingerprint 不返回 - usage 保留 `prompt_tokens`、`completion_tokens`、`total_tokens` 和 `prompt_tokens_details.cached_tokens` - 已建立的流中断或上游 `response.failed` 不会转换成标准 SSE 错误事件 实现依据:[`chatresponses.go`](internal/service/chatresponses.go) · [`openai.go`](internal/aiwire/openai.go) · [`aigateway.go`](internal/api/aigateway.go)
POST /ai/v1/embeddings 对比 OpenAI Embeddings | 标准字段 | 状态 | 网关行为 | | --- | :---: | --- | | `model` | ◐ | 必填,受密钥白名单限制,并且必须存在具有 `EMBEDDING` 能力的渠道 | | `input` 为字符串 | ✅ | 包装为单个输入后调用 OCI | | `input` 为字符串数组 | ✅ | 按原顺序调用 OCI | | `input` 为 token ID 数组或二维 token ID 数组 | ❌ | 只能解码字符串或字符串数组,绑定阶段返回 400 | | 空数组 / `null` | ❌ | 本地返回 400;空字符串不在本地拒绝,由 OCI 决定 | | `dimensions` | ◐ | 映射为 OCI 输出维度,不做范围或模型能力校验 | | `encoding_format=float` | ✅ | 返回 float 数组;省略时行为相同 | | `encoding_format=base64` | ❌ | 本地返回 400,不提供 base64 响应 | | `user` | ⚠️ | 能解析但不会传给 OCI | | 其他未知字段 | ⚠️ | 静默忽略 | 响应使用标准的 `object:"list"`、`data[].object:"embedding"`、`index`、`model` 和可选 `usage` 外壳;向量为 `float32` 数组,不支持流式。 实现依据:[`embeddings.go`](internal/aiwire/embeddings.go) · [`aigateway_chat.go`](internal/service/aigateway_chat.go) · [`aigateway.go`](internal/api/aigateway.go)
POST /ai/v1/messages 对比 Anthropic Messages 网关接受 `Authorization: Bearer` 或 `x-api-key`,但不会校验或使用标准 Anthropic `anthropic-version`、`anthropic-beta` 请求头。 | 标准字段 | 状态 | 网关行为 | | --- | :---: | --- | | `model` | ◐ | 用于模型与渠道选择,但空字符串不会在 handler 中按参数错误拒绝,通常最终返回模型不存在 | | `max_tokens` | 🔄 | 可缺省(缺省或 ≤0 时按默认值 8192),转换为 `max_output_tokens` | | `messages` | ◐ | 必须非空;角色、交替顺序和空内容不做完整校验 | | `system` | ◐ | 支持字符串或 text 块数组;多个文本块直接拼接,`cache_control` 等附加字段被忽略 | | `temperature`、`top_p` | ◐ | 写入 Responses 请求,不做取值范围或模型能力校验 | | `top_k` | ⚠️ | 能解析但不会传给上游 | | `stop_sequences` | ⚠️ | 能解析但不会传给上游;响应 `stop_sequence` 恒为 `null` | | `stream` | 🔄 | Responses SSE 桥接为 Anthropic 事件序列;上游断流且尚无输出时自动降级非流式重做,结果按事件序列一次推送 | | `tools` | ◐ | 每个工具都转换成 Responses `function`;自定义客户端工具可用,Anthropic 服务端工具类型不保留原语义 | | `tool_choice` | ◐ | 支持 `auto`、`any`、`none`、具名 `tool`;`disable_parallel_tool_use` 等附加字段被忽略 | | `metadata` | ⚠️ | 能解析但不会传给上游 | | `thinking` | ⚠️ | 顶层 thinking 配置不会控制上游思考预算 | | `output_config.effort` | 🔄 | 转小写后映射为 Responses `reasoning.effort` | | `output_config` 其他子字段 | ⚠️ | 未建模,静默忽略 | | `cache_control`、`container`、`inference_geo`、`service_tier` | ⚠️ | 标准 SDK 中存在,但当前请求结构未建模,静默忽略 | | 其他未知顶层字段 | ⚠️ | JSON 解码器接受,但转换后的上游请求不包含它们 | `messages[].content`: | 标准内容块 | 状态 | 网关行为 | | --- | :---: | --- | | 字符串 / `text` | 🔄 | user 转 `input_text`,assistant 历史转 `output_text` | | `image` | ◐ | 仅支持 `base64` 和 `url` source;缺字段或其他 source 类型返回 400 | | `tool_use` | 🔄 | 转为 `function_call`,保留 ID、名称和输入 | | `tool_result` | ◐ | 转为 `function_call_output`;块数组只拼接 text,`is_error` 和非文本结果丢失 | | `thinking` / `redacted_thinking` | ⚠️ | 历史思考块被删除,不进入上游上下文 | | `document`、服务端工具结果及其他未知块 | ❌ | 返回 400 | | `null` / 空块数组 | ◐ | 该消息可能从上游 input 中消失,不返回参数错误 | 响应边界: - 非流式只把 Responses `output_text` 转成 `text`、`function_call` 转成 `tool_use` - `stop_reason` 只生成 `end_turn`、`tool_use`、`max_tokens`;`stop_sequence` 恒为 `null` - reasoning 不会生成 Anthropic `thinking` / `redacted_thinking` 块,也没有 signature - usage 只保留 `input_tokens`、`output_tokens` 和 `cache_read_input_tokens`,不提供 `cache_creation_input_tokens` - 流式输出标准事件骨架,但不生成 `thinking_delta` 和 `signature_delta`;上游错误事件与无终态断流会转成 Anthropic `error` 事件 实现依据:[`anthresponses.go`](internal/service/anthresponses.go) · [`anthropic.go`](internal/aiwire/anthropic.go) · [`aigateway.go`](internal/api/aigateway.go)
`GET /ai/v1/models` 使用 OpenAI Models 列表外壳(`object`、`data[].id/object/created/owned_by`),但只返回当前渠道目录中通过分组、全局黑名单和密钥白名单筛选后的模型;网关不提供标准的单模型检索端点。 ## API 与配置 ### API 文档 | 路径 | 鉴权 | 用途 | | --- | --- | --- | | `/api/v1/*` | 登录返回的 JWT Bearer Token | 面板管理 API | | `/ai/v1/*` | AI 网关密钥 | OpenAI / Anthropic 兼容接口 | | `/api/v1/webhooks/oci-logs/:secret` | URL 中的独立回传密钥 | OCI Notifications 日志回传 | OpenAPI 文件随仓库维护:[`docs/swagger.yaml`](docs/swagger.yaml) · [`docs/swagger.json`](docs/swagger.json)。 运行进程时设置 `SWAGGER=1` 可开放 `/swagger/index.html`。Swagger 默认关闭,生产环境建议仅在受控网络内按需开启。接口注释变更后必须重新生成 OpenAPI。 ### 环境变量 | 变量 | 必填 | 默认值 | 说明 | | --- | :---: | --- | --- | | `DATA_KEY` | 是 | — | 敏感字段加密主密钥;必须持久保存,不能随意轮换 | | `JWT_SECRET` | 是 | — | JWT 签名密钥;更换会使已有登录令牌失效 | | `ADMIN_USERNAME` | 否 | `admin` | 初始管理员用户名 | | `ADMIN_PASSWORD` | 首次启动 | — | 仅在数据库无用户时创建管理员,不会重置已有密码 | | `ADDR` | 否 | `:8080` | HTTP 监听地址 | | `DB_DRIVER` | 否 | `sqlite` | `sqlite` / `mysql` / `postgres`;后两者为 experimental | | `DB_PATH` | SQLite | `oci-portal.db` | SQLite 文件路径 | | `DB_DSN` | 外部数据库 | — | MySQL 需 `parseTime=True`;不要在日志或文档中暴露凭据 | | `PUBLIC_URL` | 否 | — | 面板公网基址,作为 OAuth 回调和日志回传引导的回退值 | | `TZ` | 否 | 系统时区 | cron 表达式的解释时区;容器示例使用 `Asia/Shanghai` | | `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` | 否 | — | Go 标准出站代理变量;面板内显式代理配置优先用于对应业务 | | `SWAGGER` | 否 | 关闭 | 设为 `1` 时开放 Swagger UI | | `GIN_MODE` | 否 | `release` | `debug` / `release` | ## 升级与备份 - 升级前同时备份 `.env` 和数据库;SQLite Compose 部署的数据文件为 `data/oci-portal.db` - 保持 `DATA_KEY` 不变;只恢复数据库而没有原密钥,敏感字段无法解密 - 服务启动时会自动执行数据库迁移;跨版本升级前先阅读 [CHANGELOG](CHANGELOG.md) - Compose 部署使用 `docker compose pull && docker compose up -d` 更新镜像 - OCI API Key 应遵循最小权限原则;生产环境保持 Swagger 关闭并限制管理面访问来源 ## 开发 ```bash gofmt -l . go vet ./... go test ./... # Handler 注释变更后重新生成唯一的对外 API 文档。 go tool swag init -g cmd/server/main.go -o docs --parseInternal --parseDependency ``` - Go 后端规范与目录约定:[`AGENTS.md`](AGENTS.md) · [`.trellis/spec/backend/`](.trellis/spec/backend/) - 前端开发与构建:[oci-portal-dash](https://github.com/wangdefaa/oci-portal-dash) - 版本变更:[`CHANGELOG.md`](CHANGELOG.md) ## License [MIT](LICENSE)