Merge branch 'main' of https://git.586vip.cn/oadmin/xinghuoapi
# Conflicts: # frontend/src/views/auth/EmailVerifyView.vue
This commit is contained in:
85
CLAUDE.md
Normal file
85
CLAUDE.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Fork Identity
|
||||||
|
|
||||||
|
This is a **customized fork** of [Wei-Shaw/sub2api](https://github.com/Wei-Shaw/sub2api.git), branded as **StarFireAPI**. Upstream remote: `https://github.com/Wei-Shaw/sub2api.git`.
|
||||||
|
|
||||||
|
### Customizations to preserve on every upstream sync
|
||||||
|
|
||||||
|
| Category | Details |
|
||||||
|
|----------|---------|
|
||||||
|
| **Brand** | All user-facing `Sub2API` → `StarFireAPI` in `frontend/src/` (stores, i18n, views, components, router) |
|
||||||
|
| **Links** | GitHub links → `https://anthropic.edu.pl` in AppHeader, HomeView, i18n |
|
||||||
|
| **docker-compose** | Image: `starfireapi:latest`, port `6580:8080`, external Redis at `172.18.0.2:6379`, project name `xinghuoapi`, built-in Redis disabled |
|
||||||
|
| **Update module** | Disabled in `system_handler.go` (always returns no update), `app.ts` (forces `hasUpdate=false`), `VersionBadge.vue` (`isReleaseBuild=false`, removed GitHub release links) |
|
||||||
|
|
||||||
|
Full details: `skills/sync-upstream/SKILL.md`
|
||||||
|
|
||||||
|
## Build & Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full build (backend + frontend)
|
||||||
|
make build
|
||||||
|
|
||||||
|
# Backend only
|
||||||
|
cd backend && make build # Binary → bin/server
|
||||||
|
cd backend && make test # go test ./... + golangci-lint
|
||||||
|
cd backend && make test-unit # Unit tests only
|
||||||
|
cd backend && go test ./internal/service/... -run TestXxx # Single test
|
||||||
|
|
||||||
|
# Frontend only
|
||||||
|
cd frontend && pnpm install --frozen-lockfile
|
||||||
|
cd frontend && pnpm dev # Dev server
|
||||||
|
cd frontend && pnpm build # Type-check + production build
|
||||||
|
cd frontend && pnpm test:run # Vitest
|
||||||
|
cd frontend && pnpm lint:check # ESLint (no fix)
|
||||||
|
|
||||||
|
# Code generation (Ent ORM + Wire DI)
|
||||||
|
cd backend && make generate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**AI API Gateway**: receives requests with user API keys → authenticates → selects upstream account (sticky session / load-aware) → forwards to upstream AI service → records usage for billing.
|
||||||
|
|
||||||
|
### Backend (`backend/internal/`)
|
||||||
|
|
||||||
|
- **Entry**: `cmd/server/main.go` — supports setup mode, auto-setup (Docker), normal server
|
||||||
|
- **DI**: Google Wire (`cmd/server/wire.go`)
|
||||||
|
- **Layers**: handler → service → repository (enforced by `depguard` — service must not import repository)
|
||||||
|
- **Key services**: `GatewayService` (request forwarding, account selection, failover), `OpenAIGatewayService`, `GeminiMessagesCompatService`, `AntigravityGatewayService`
|
||||||
|
- **ORM**: Ent (`ent/schema/` for definitions, `ent/` for generated code)
|
||||||
|
- **Middleware stack**: Recovery → Logger → CORS → SecurityHeaders → RequestBodyLimit → ClientRequestID → APIKeyAuth
|
||||||
|
- **Run modes**: `standard` (full SaaS with billing) / `simple` (skip billing checks)
|
||||||
|
|
||||||
|
### Frontend (`frontend/src/`)
|
||||||
|
|
||||||
|
- **Framework**: Vue 3 + Pinia + Vue Router + TailwindCSS
|
||||||
|
- **State**: `stores/app.ts` (global), `stores/auth.ts` (JWT auth)
|
||||||
|
- **i18n**: `i18n/locales/en.ts`, `zh.ts` — all user-facing strings
|
||||||
|
- **Embedded**: production build goes to `backend/internal/web/dist/` and is embedded into the Go binary
|
||||||
|
|
||||||
|
### Gateway Routes
|
||||||
|
|
||||||
|
```
|
||||||
|
/v1/messages — Claude API compatible
|
||||||
|
/v1/responses — OpenAI API compatible
|
||||||
|
/v1beta/models/* — Gemini native API
|
||||||
|
/antigravity/v1/* — Antigravity platform
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coding Conventions
|
||||||
|
|
||||||
|
- **Go**: `gofmt`, `golangci-lint` (see `backend/.golangci.yml`), max 3 levels of `if` nesting
|
||||||
|
- **Frontend**: Vue SFC + TypeScript, 2-space indent, ESLint, components `PascalCase.vue`, composables `useXxx.ts`
|
||||||
|
- **Commits**: Conventional Commits (`feat(scope):`, `fix(scope):`, `chore(scope):`)
|
||||||
|
- **JSON hot-path**: prefer `gjson` over `encoding/json` for read-only/partial extraction
|
||||||
|
- **Layering**: handler/service must NOT import repository/gorm/redis directly
|
||||||
|
|
||||||
|
## Skills (in `skills/`)
|
||||||
|
|
||||||
|
- **sync-upstream** — Pulls and merges upstream sub2api, re-applies all StarFireAPI customizations. Trigger: "拉取上游", "同步上游", "sync upstream"
|
||||||
|
- **bug-fix-expert** — Multi-agent bug fix workflow with worktree isolation and cross-validation
|
||||||
|
- **code-review-expert** — Parallel 5-dimension code review with worktree isolation
|
||||||
@@ -2,7 +2,6 @@ package admin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ services:
|
|||||||
# postgres:18-alpine 默认 PGDATA=/var/lib/postgresql/18/docker(位于镜像声明的匿名卷 /var/lib/postgresql 内)。
|
# postgres:18-alpine 默认 PGDATA=/var/lib/postgresql/18/docker(位于镜像声明的匿名卷 /var/lib/postgresql 内)。
|
||||||
# 若不显式设置 PGDATA,则即使挂载了 postgres_data 到 /var/lib/postgresql/data,数据也不会落盘到该命名卷,
|
# 若不显式设置 PGDATA,则即使挂载了 postgres_data 到 /var/lib/postgresql/data,数据也不会落盘到该命名卷,
|
||||||
# docker compose down/up 后会触发 initdb 重新初始化,导致用户/密码等数据丢失。
|
# docker compose down/up 后会触发 initdb 重新初始化,导致用户/密码等数据丢失。
|
||||||
- PGDATA=/var/lib/postgresql/data
|
- PGDATA=/var/lib/postgresql/18/docker
|
||||||
- POSTGRES_USER=${POSTGRES_USER:-sub2api}
|
- POSTGRES_USER=${POSTGRES_USER:-sub2api}
|
||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
- POSTGRES_DB=${POSTGRES_DB:-sub2api}
|
- POSTGRES_DB=${POSTGRES_DB:-sub2api}
|
||||||
|
|||||||
@@ -1678,7 +1678,7 @@ export default {
|
|||||||
antigravityOauth: 'Antigravity OAuth',
|
antigravityOauth: 'Antigravity OAuth',
|
||||||
antigravityApikey: 'Connect via Base URL + API Key',
|
antigravityApikey: 'Connect via Base URL + API Key',
|
||||||
soraApiKey: 'API Key / Upstream',
|
soraApiKey: 'API Key / Upstream',
|
||||||
soraApiKeyHint: 'Connect to another Sub2API or compatible API',
|
soraApiKeyHint: 'Connect to another StarFireAPI or compatible API',
|
||||||
soraBaseUrlRequired: 'Sora API Key account requires a Base URL',
|
soraBaseUrlRequired: 'Sora API Key account requires a Base URL',
|
||||||
soraBaseUrlInvalidScheme: 'Base URL must start with http:// or https://',
|
soraBaseUrlInvalidScheme: 'Base URL must start with http:// or https://',
|
||||||
upstream: 'Upstream',
|
upstream: 'Upstream',
|
||||||
@@ -2383,7 +2383,7 @@ export default {
|
|||||||
selectTestModel: 'Select Test Model',
|
selectTestModel: 'Select Test Model',
|
||||||
testModel: 'Test model',
|
testModel: 'Test model',
|
||||||
testPrompt: 'Prompt: "hi"',
|
testPrompt: 'Prompt: "hi"',
|
||||||
soraUpstreamBaseUrlHint: 'Upstream Sora service URL (another Sub2API instance or compatible API)',
|
soraUpstreamBaseUrlHint: 'Upstream Sora service URL (another StarFireAPI instance or compatible API)',
|
||||||
soraTestHint: 'Sora test runs connectivity and capability checks (/backend/me, subscription, Sora2 invite and remaining quota).',
|
soraTestHint: 'Sora test runs connectivity and capability checks (/backend/me, subscription, Sora2 invite and remaining quota).',
|
||||||
soraTestTarget: 'Target: Sora account capability',
|
soraTestTarget: 'Target: Sora account capability',
|
||||||
soraTestMode: 'Mode: Connectivity + Capability checks',
|
soraTestMode: 'Mode: Connectivity + Capability checks',
|
||||||
|
|||||||
@@ -1835,7 +1835,7 @@ export default {
|
|||||||
antigravityOauth: 'Antigravity OAuth',
|
antigravityOauth: 'Antigravity OAuth',
|
||||||
antigravityApikey: '通过 Base URL + API Key 连接',
|
antigravityApikey: '通过 Base URL + API Key 连接',
|
||||||
soraApiKey: 'API Key / 上游透传',
|
soraApiKey: 'API Key / 上游透传',
|
||||||
soraApiKeyHint: '连接另一个 Sub2API 或兼容 API',
|
soraApiKeyHint: '连接另一个 StarFireAPI 或兼容 API',
|
||||||
soraBaseUrlRequired: 'Sora apikey 账号必须设置上游地址(Base URL)',
|
soraBaseUrlRequired: 'Sora apikey 账号必须设置上游地址(Base URL)',
|
||||||
soraBaseUrlInvalidScheme: 'Base URL 必须以 http:// 或 https:// 开头',
|
soraBaseUrlInvalidScheme: 'Base URL 必须以 http:// 或 https:// 开头',
|
||||||
upstream: '对接上游',
|
upstream: '对接上游',
|
||||||
@@ -2512,7 +2512,7 @@ export default {
|
|||||||
selectTestModel: '选择测试模型',
|
selectTestModel: '选择测试模型',
|
||||||
testModel: '测试模型',
|
testModel: '测试模型',
|
||||||
testPrompt: '提示词:"hi"',
|
testPrompt: '提示词:"hi"',
|
||||||
soraUpstreamBaseUrlHint: '上游 Sora 服务地址(另一个 Sub2API 实例或兼容 API)',
|
soraUpstreamBaseUrlHint: '上游 Sora 服务地址(另一个 StarFireAPI 实例或兼容 API)',
|
||||||
soraTestHint: 'Sora 测试将执行连通性与能力检测(/backend/me、订阅信息、Sora2 邀请码与剩余额度)。',
|
soraTestHint: 'Sora 测试将执行连通性与能力检测(/backend/me、订阅信息、Sora2 邀请码与剩余额度)。',
|
||||||
soraTestTarget: '检测目标:Sora 账号能力',
|
soraTestTarget: '检测目标:Sora 账号能力',
|
||||||
soraTestMode: '模式:连通性 + 能力探测',
|
soraTestMode: '模式:连通性 + 能力探测',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async function bootstrap() {
|
|||||||
appStore.initFromInjectedConfig()
|
appStore.initFromInjectedConfig()
|
||||||
|
|
||||||
// Set document title immediately after config is loaded
|
// Set document title immediately after config is loaded
|
||||||
if (appStore.siteName && appStore.siteName !== 'Sub2API') {
|
if (appStore.siteName && appStore.siteName !== 'StarFireAPI') {
|
||||||
document.title = `${appStore.siteName} - AI API Gateway`
|
document.title = `${appStore.siteName} - AI API Gateway`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ describe('resolveDocumentTitle', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('站点名为空时,回退默认站点名', () => {
|
it('站点名为空时,回退默认站点名', () => {
|
||||||
expect(resolveDocumentTitle('Dashboard', '')).toBe('Dashboard - Sub2API')
|
expect(resolveDocumentTitle('Dashboard', '')).toBe('Dashboard - StarFireAPI')
|
||||||
expect(resolveDocumentTitle(undefined, ' ')).toBe('Sub2API')
|
expect(resolveDocumentTitle(undefined, ' ')).toBe('StarFireAPI')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('站点名变更时仅影响后续路由标题计算', () => {
|
it('站点名变更时仅影响后续路由标题计算', () => {
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ router.beforeEach((to, _from, next) => {
|
|||||||
const menuItem = publicItems.find((item) => item.id === id)
|
const menuItem = publicItems.find((item) => item.id === id)
|
||||||
?? (authStore.isAdmin ? adminSettingsStore.customMenuItems.find((item) => item.id === id) : undefined)
|
?? (authStore.isAdmin ? adminSettingsStore.customMenuItems.find((item) => item.id === id) : undefined)
|
||||||
if (menuItem?.label) {
|
if (menuItem?.label) {
|
||||||
const siteName = appStore.siteName || 'Sub2API'
|
const siteName = appStore.siteName || 'StarFireAPI'
|
||||||
document.title = `${menuItem.label} - ${siteName}`
|
document.title = `${menuItem.label} - ${siteName}`
|
||||||
} else {
|
} else {
|
||||||
document.title = resolveDocumentTitle(to.meta.title, appStore.siteName, to.meta.titleKey as string)
|
document.title = resolveDocumentTitle(to.meta.title, appStore.siteName, to.meta.titleKey as string)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { i18n } from '@/i18n'
|
|||||||
* 优先使用 titleKey 通过 i18n 翻译,fallback 到静态 routeTitle。
|
* 优先使用 titleKey 通过 i18n 翻译,fallback 到静态 routeTitle。
|
||||||
*/
|
*/
|
||||||
export function resolveDocumentTitle(routeTitle: unknown, siteName?: string, titleKey?: string): string {
|
export function resolveDocumentTitle(routeTitle: unknown, siteName?: string, titleKey?: string): string {
|
||||||
const normalizedSiteName = typeof siteName === 'string' && siteName.trim() ? siteName.trim() : 'Sub2API'
|
const normalizedSiteName = typeof siteName === 'string' && siteName.trim() ? siteName.trim() : 'StarFireAPI'
|
||||||
|
|
||||||
if (typeof titleKey === 'string' && titleKey.trim()) {
|
if (typeof titleKey === 'string' && titleKey.trim()) {
|
||||||
const translated = i18n.global.t(titleKey)
|
const translated = i18n.global.t(titleKey)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
// Public settings cache state
|
// Public settings cache state
|
||||||
const publicSettingsLoaded = ref<boolean>(false)
|
const publicSettingsLoaded = ref<boolean>(false)
|
||||||
const publicSettingsLoading = ref<boolean>(false)
|
const publicSettingsLoading = ref<boolean>(false)
|
||||||
const siteName = ref<string>('Sub2API')
|
const siteName = ref<string>('StarFireAPI')
|
||||||
const siteLogo = ref<string>('')
|
const siteLogo = ref<string>('')
|
||||||
const siteVersion = ref<string>('')
|
const siteVersion = ref<string>('')
|
||||||
const contactInfo = ref<string>('')
|
const contactInfo = ref<string>('')
|
||||||
@@ -284,7 +284,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
*/
|
*/
|
||||||
function applySettings(config: PublicSettings): void {
|
function applySettings(config: PublicSettings): void {
|
||||||
cachedPublicSettings.value = config
|
cachedPublicSettings.value = config
|
||||||
siteName.value = config.site_name || 'Sub2API'
|
siteName.value = config.site_name || 'StarFireAPI'
|
||||||
siteLogo.value = config.site_logo || ''
|
siteLogo.value = config.site_logo || ''
|
||||||
siteVersion.value = config.version || ''
|
siteVersion.value = config.version || ''
|
||||||
contactInfo.value = config.contact_info || ''
|
contactInfo.value = config.contact_info || ''
|
||||||
|
|||||||
@@ -1655,7 +1655,7 @@ const form = reactive<SettingsForm>({
|
|||||||
default_balance: 0,
|
default_balance: 0,
|
||||||
default_concurrency: 1,
|
default_concurrency: 1,
|
||||||
default_subscriptions: [],
|
default_subscriptions: [],
|
||||||
site_name: 'Sub2API',
|
site_name: 'StarFireAPI',
|
||||||
site_logo: '',
|
site_logo: '',
|
||||||
site_subtitle: 'Subscription to API Conversion Platform',
|
site_subtitle: 'Subscription to API Conversion Platform',
|
||||||
api_base_url: '',
|
api_base_url: '',
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ const hasRegisterData = ref<boolean>(false)
|
|||||||
// Public settings
|
// Public settings
|
||||||
const turnstileEnabled = ref<boolean>(false)
|
const turnstileEnabled = ref<boolean>(false)
|
||||||
const turnstileSiteKey = ref<string>('')
|
const turnstileSiteKey = ref<string>('')
|
||||||
const siteName = ref<string>('Sub2API')
|
const siteName = ref<string>('StarFireAPI')
|
||||||
const registrationEmailSuffixWhitelist = ref<string[]>([])
|
const registrationEmailSuffixWhitelist = ref<string[]>([])
|
||||||
|
|
||||||
// Turnstile for resend
|
// Turnstile for resend
|
||||||
@@ -249,7 +249,7 @@ onMounted(async () => {
|
|||||||
const settings = await getPublicSettings()
|
const settings = await getPublicSettings()
|
||||||
turnstileEnabled.value = settings.turnstile_enabled
|
turnstileEnabled.value = settings.turnstile_enabled
|
||||||
turnstileSiteKey.value = settings.turnstile_site_key || ''
|
turnstileSiteKey.value = settings.turnstile_site_key || ''
|
||||||
siteName.value = settings.site_name || 'Sub2API'
|
siteName.value = settings.site_name || 'StarFireAPI'
|
||||||
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
|
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
|
||||||
settings.registration_email_suffix_whitelist || []
|
settings.registration_email_suffix_whitelist || []
|
||||||
)
|
)
|
||||||
|
|||||||
206
skills/sync-upstream/SKILL.md
Normal file
206
skills/sync-upstream/SKILL.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
---
|
||||||
|
name: sync-upstream
|
||||||
|
description: 从上游 Wei-Shaw/sub2api 拉取最新代码并合并,自动保留所有 StarFireAPI 自定义配置(品牌、链接、docker-compose、更新模块禁用)。
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
author: huangzhenpc
|
||||||
|
version: "1.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 同步上游(sync-upstream)
|
||||||
|
|
||||||
|
## 触发条件
|
||||||
|
|
||||||
|
当以下任一条件满足时激活本技能:
|
||||||
|
|
||||||
|
- 用户要求"拉取上游"、"同步上游"、"合并上游"、"sync upstream"。
|
||||||
|
- 用户使用 `/sync-upstream` 命令。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
从上游仓库 `https://github.com/Wei-Shaw/sub2api.git` 拉取最新代码,合并到本地 `main` 分支,并确保所有 StarFireAPI 自定义配置不被覆盖。
|
||||||
|
|
||||||
|
## 自定义配置清单(必须保留)
|
||||||
|
|
||||||
|
以下是本项目相对于上游的所有自定义改动,合并后必须逐项检查并保留:
|
||||||
|
|
||||||
|
### 1. 品牌替换:`Sub2API` → `StarFireAPI`
|
||||||
|
|
||||||
|
所有面向用户的前端代码中,`Sub2API` 必须替换为 `StarFireAPI`。涉及的文件类型:
|
||||||
|
|
||||||
|
- `frontend/index.html` — 页面标题
|
||||||
|
- `frontend/src/stores/app.ts` — siteName 默认值
|
||||||
|
- `frontend/src/i18n/locales/en.ts` — 英文翻译中所有 `Sub2API`
|
||||||
|
- `frontend/src/i18n/locales/zh.ts` — 中文翻译中所有 `Sub2API`
|
||||||
|
- `frontend/src/views/**/*.vue` — HomeView、RegisterView、EmailVerifyView 等
|
||||||
|
- `frontend/src/components/layout/AuthLayout.vue` — siteName 默认值
|
||||||
|
- `frontend/src/router/title.ts` — 页面标题 fallback
|
||||||
|
- `frontend/src/router/index.ts` — siteName fallback
|
||||||
|
- `frontend/src/views/admin/SettingsView.vue` — site_name 默认值
|
||||||
|
- `frontend/src/router/__tests__/title.spec.ts` — 测试期望值
|
||||||
|
|
||||||
|
**扫描方法**:合并后执行 `grep -rn "Sub2API" frontend/src/ --include="*.ts" --include="*.vue" --include="*.html"`,对所有影响运行时的结果替换为 `StarFireAPI`(代码注释可保留)。
|
||||||
|
|
||||||
|
### 2. 链接替换:GitHub → 官网
|
||||||
|
|
||||||
|
| 原始值 | 替换为 |
|
||||||
|
|--------|--------|
|
||||||
|
| `https://github.com/Wei-Shaw/sub2api` | `https://anthropic.edu.pl` |
|
||||||
|
| GitHub 图标 SVG(长 path) | 房子图标 `<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>` |
|
||||||
|
| `viewOnGithub: 'View on GitHub'` | `viewOnGithub: 'Official Site'` |
|
||||||
|
| `viewOnGithub: '在 GitHub 上查看'` | `viewOnGithub: '访问官网'` |
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
- `frontend/src/components/layout/AppHeader.vue`
|
||||||
|
- `frontend/src/views/HomeView.vue`
|
||||||
|
- `frontend/src/i18n/locales/en.ts`
|
||||||
|
- `frontend/src/i18n/locales/zh.ts`
|
||||||
|
|
||||||
|
**扫描方法**:合并后执行 `grep -rn "github.com/Wei-Shaw/sub2api" frontend/src/`,检查是否有新的前端 GitHub 链接被引入。
|
||||||
|
|
||||||
|
### 3. docker-compose 自定义(`deploy/docker-compose.yml`)
|
||||||
|
|
||||||
|
| 配置项 | 上游默认值 | 我们的值 |
|
||||||
|
|--------|-----------|---------|
|
||||||
|
| compose project name | 无 | `name: xinghuoapi` |
|
||||||
|
| 镜像 | `weishaw/sub2api:latest` | `starfireapi:latest` |
|
||||||
|
| 端口映射 | `8080:8080` | `6580:8080` |
|
||||||
|
| Redis Host | `redis`(内置容器) | `${REDIS_HOST:-172.18.0.2}`(外部 Redis) |
|
||||||
|
| Redis Password | `${REDIS_PASSWORD:-}` | `${REDIS_PASSWORD:-redis_JCHeKT}` |
|
||||||
|
| Redis 容器 | 正常启用 | `profiles: [disabled]`(禁用) |
|
||||||
|
| sub2api depends_on | postgres + redis | 仅 postgres |
|
||||||
|
|
||||||
|
**重要**:合并 docker-compose.yml 时要特别小心,新增的环境变量(如数据库连接池参数、OAuth secrets 等)应该接受,但上述自定义项必须保留。
|
||||||
|
|
||||||
|
### 4. 更新模块禁用
|
||||||
|
|
||||||
|
#### 后端 `backend/internal/handler/admin/system_handler.go`
|
||||||
|
|
||||||
|
`CheckUpdates` 方法始终返回无更新:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (h *SystemHandler) CheckUpdates(c *gin.Context) {
|
||||||
|
info, _ := h.updateSvc.CheckUpdate(c.Request.Context(), false)
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"current_version": info.CurrentVersion,
|
||||||
|
"latest_version": info.CurrentVersion,
|
||||||
|
"has_update": false,
|
||||||
|
"build_type": "source",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 前端 `frontend/src/stores/app.ts`
|
||||||
|
|
||||||
|
`checkUpdates` action 中强制返回无更新:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
latestVersion.value = data.current_version // 不用 data.latest_version
|
||||||
|
hasUpdate.value = false
|
||||||
|
buildType.value = 'source'
|
||||||
|
releaseInfo.value = null
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 前端 `frontend/src/components/common/VersionBadge.vue`
|
||||||
|
|
||||||
|
- `isReleaseBuild` 始终为 `false`
|
||||||
|
- 移除 `buildType` 和 `releaseInfo` computed 变量(避免 TS 未使用变量报错)
|
||||||
|
- 移除所有 GitHub release 链接、changelog 跳转链接
|
||||||
|
- 移除 `viewRelease` / `viewChangelog` 相关的 `<a>` 标签
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
|
||||||
|
### 第 1 步:准备
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 确保工作区干净
|
||||||
|
git status
|
||||||
|
|
||||||
|
# 2. 添加上游 remote(如果不存在)
|
||||||
|
git remote get-url upstream 2>/dev/null || git remote add upstream https://github.com/Wei-Shaw/sub2api.git
|
||||||
|
|
||||||
|
# 3. 拉取上游最新代码
|
||||||
|
git fetch upstream
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 2 步:分析差异
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 查看上游领先的提交数
|
||||||
|
git log --oneline HEAD..upstream/main | wc -l
|
||||||
|
|
||||||
|
# 2. 查看变更统计
|
||||||
|
git diff --stat HEAD...upstream/main | tail -5
|
||||||
|
|
||||||
|
# 3. 查看我们领先上游的自定义提交
|
||||||
|
git log --oneline --no-merges upstream/main..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
向用户报告差异概况,确认后继续。
|
||||||
|
|
||||||
|
### 第 3 步:执行合并
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git merge upstream/main --no-edit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 4 步:处理冲突(如有)
|
||||||
|
|
||||||
|
- 如果有冲突文件,逐一解决
|
||||||
|
- 优先采用上游的架构/代码重构,但保留自定义配置清单中的值
|
||||||
|
- 解决后 `git add` 冲突文件
|
||||||
|
|
||||||
|
### 第 5 步:重新应用自定义配置
|
||||||
|
|
||||||
|
合并完成后(无论是否有冲突),必须全面扫描并修复:
|
||||||
|
|
||||||
|
1. **品牌扫描**:
|
||||||
|
```bash
|
||||||
|
grep -rn "Sub2API" frontend/src/ --include="*.ts" --include="*.vue" --include="*.html"
|
||||||
|
```
|
||||||
|
将所有影响运行时的 `Sub2API` 替换为 `StarFireAPI`(代码注释除外)。
|
||||||
|
|
||||||
|
2. **链接扫描**:
|
||||||
|
```bash
|
||||||
|
grep -rn "github.com/Wei-Shaw/sub2api" frontend/src/
|
||||||
|
```
|
||||||
|
将前端代码中的 GitHub 链接替换为 `anthropic.edu.pl`。
|
||||||
|
|
||||||
|
3. **docker-compose 检查**:
|
||||||
|
确认 `deploy/docker-compose.yml` 中的自定义项未被覆盖。
|
||||||
|
|
||||||
|
4. **更新模块检查**:
|
||||||
|
确认 `system_handler.go`、`app.ts`、`VersionBadge.vue` 中的更新禁用逻辑仍然生效。
|
||||||
|
|
||||||
|
### 第 6 步:提交
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "同步上游至最新版本并重新应用自定义配置
|
||||||
|
|
||||||
|
上游新增功能:
|
||||||
|
- [列出主要新功能]
|
||||||
|
|
||||||
|
自定义配置保留:
|
||||||
|
- 品牌化:Sub2API → StarFireAPI
|
||||||
|
- 链接:GitHub → anthropic.edu.pl 官网
|
||||||
|
- docker-compose:starfireapi镜像、端口6580、外部Redis、项目名xinghuoapi
|
||||||
|
- 更新模块禁用
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 7 步:确认推送
|
||||||
|
|
||||||
|
询问用户是否推送到 origin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 合并前必须确保工作区干净(无未提交的改动)
|
||||||
|
- 如果上游有大量重构导致自定义文件结构变化,需要根据新结构重新应用自定义
|
||||||
|
- `deploy/` 和 `backend/` 目录下的文档/配置模板中的 GitHub 链接和 Sub2API 字样可以保留(不影响运行时)
|
||||||
|
- 只替换 `frontend/src/` 下影响运行时行为的代码
|
||||||
Reference in New Issue
Block a user