feat: DashScope 模型代理服务初始提交
支持 Anthropic 和 OpenAI 兼容协议的模型池反代,自动切换免费额度耗尽的模型。
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import { chmodSync, cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
||||
const projectRoot = resolve(scriptDir, '..')
|
||||
const releaseDir = join(projectRoot, 'release')
|
||||
|
||||
rmSync(releaseDir, { recursive: true, force: true })
|
||||
mkdirSync(releaseDir, { recursive: true })
|
||||
mkdirSync(join(releaseDir, 'data'), { recursive: true })
|
||||
mkdirSync(join(releaseDir, 'docs'), { recursive: true })
|
||||
|
||||
copyRequiredPath('dist')
|
||||
copyRequiredPath('package.json')
|
||||
copyRequiredPath('pnpm-lock.yaml')
|
||||
copyRequiredPath('.env.example')
|
||||
copyRequiredPath('LLM.md')
|
||||
copyRequiredPath('docs/usage.md')
|
||||
copyRequiredPath('docs/state-file.md')
|
||||
|
||||
writeFileSync(join(releaseDir, 'data', '.gitkeep'), '\n')
|
||||
writeFileSync(join(releaseDir, '.npmrc'), 'production=true\n')
|
||||
writeFileSync(join(releaseDir, 'start.sh'), buildStartScript())
|
||||
chmodSync(join(releaseDir, 'start.sh'), 0o755)
|
||||
writeFileSync(join(releaseDir, 'start.cmd'), toWindowsLineEndings(buildWindowsStartScript()))
|
||||
writeFileSync(join(releaseDir, '使用方式.txt'), buildReadme())
|
||||
|
||||
console.log(`[release] prepared at ${releaseDir}`)
|
||||
|
||||
function copyRequiredPath(relativePath) {
|
||||
const source = join(projectRoot, relativePath)
|
||||
const target = join(releaseDir, relativePath)
|
||||
|
||||
if (!existsSync(source)) {
|
||||
throw new Error(`Missing required path: ${relativePath}`)
|
||||
}
|
||||
|
||||
cpSync(source, target, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
dereference: true,
|
||||
filter: (currentSource) => {
|
||||
const normalized = currentSource.replace(/\\/g, '/')
|
||||
if (normalized.endsWith('/data/proxy-state.json')) return false
|
||||
if (normalized.includes('/data/proxy-state.json.')) return false
|
||||
return true
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function buildStartScript() {
|
||||
return `#!/bin/sh
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if [ ! -f ".env" ]; then
|
||||
cp .env.example .env
|
||||
echo "已生成 .env,请先填写 PROXY_API_KEY、DASHSCOPE_API_KEYS、MODEL_IDS 后再重新运行 ./start.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p data
|
||||
|
||||
if [ ! -d "node_modules" ]; then
|
||||
if command -v pnpm >/dev/null 2>&1; then
|
||||
pnpm install --prod --frozen-lockfile
|
||||
elif command -v corepack >/dev/null 2>&1; then
|
||||
corepack pnpm install --prod --frozen-lockfile
|
||||
else
|
||||
echo "未找到 pnpm 或 corepack,请先安装 Node.js 20+(含 corepack)或 pnpm"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exec node dist/index.js
|
||||
`
|
||||
}
|
||||
|
||||
function buildReadme() {
|
||||
return `1. 确认机器已安装 Node.js 20 或更高版本。
|
||||
2. 首次运行:
|
||||
- Windows:双击 start.cmd,或在 cmd/PowerShell 里执行 start.cmd
|
||||
- macOS / Linux:执行 ./start.sh
|
||||
3. 如果当前目录没有 .env,会自动从 .env.example 生成一份模板。
|
||||
4. 打开 .env,至少填写:
|
||||
- PROXY_API_KEY
|
||||
- DASHSCOPE_API_KEYS
|
||||
- MODEL_IDS
|
||||
5. 再次运行启动脚本:
|
||||
- Windows:start.cmd
|
||||
- macOS / Linux:./start.sh
|
||||
|
||||
说明:
|
||||
- 首次真正启动时会自动安装运行时依赖
|
||||
- 首次启动会自动创建新的 data/proxy-state.json
|
||||
- 运行时依赖不包含原生编译模块,Windows 上不需要额外安装 C++ 编译环境
|
||||
`
|
||||
}
|
||||
|
||||
function buildWindowsStartScript() {
|
||||
return `@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
if not exist ".env" (
|
||||
copy /Y ".env.example" ".env" >nul
|
||||
echo Created .env from .env.example
|
||||
echo Fill PROXY_API_KEY, DASHSCOPE_API_KEYS and MODEL_IDS
|
||||
echo Then run start.cmd again
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "data" mkdir data
|
||||
|
||||
if not exist "node_modules" (
|
||||
where pnpm >nul 2>nul
|
||||
if not errorlevel 1 (
|
||||
call pnpm install --prod --frozen-lockfile
|
||||
if errorlevel 1 goto :install_failed
|
||||
goto :run
|
||||
)
|
||||
|
||||
where corepack >nul 2>nul
|
||||
if not errorlevel 1 (
|
||||
call corepack pnpm install --prod --frozen-lockfile
|
||||
if errorlevel 1 goto :install_failed
|
||||
goto :run
|
||||
)
|
||||
|
||||
echo pnpm or corepack was not found
|
||||
echo Install Node.js 20+ or pnpm first
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:run
|
||||
call node dist\\index.js
|
||||
set EXIT_CODE=%errorlevel%
|
||||
if not "%EXIT_CODE%"=="0" (
|
||||
echo.
|
||||
echo Service exited with code %EXIT_CODE%
|
||||
pause
|
||||
)
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:install_failed
|
||||
echo.
|
||||
echo Dependency install failed
|
||||
echo Check Node.js, pnpm and build tools
|
||||
pause
|
||||
exit /b 1
|
||||
`
|
||||
}
|
||||
|
||||
function toWindowsLineEndings(value) {
|
||||
return value.replace(/\n/g, '\r\n')
|
||||
}
|
||||
Reference in New Issue
Block a user