打手端迁移至独立子域名
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
# Mac Docker 本机联调模板。复制为 .env 后按需修改。
|
# Mac Docker 本机联调模板。复制为 .env 后按需修改。
|
||||||
# 如果使用 ngrok / cloudflared 等公网地址,只需要同步修改 APP_DOMAIN / CADDY_SITE_ADDR / CLAIM_BASE_URL。
|
# 如果使用 ngrok / cloudflared 等公网地址,只需要同步修改 APP_DOMAIN / CADDY_SITE_ADDR / WORKER_SITE_ADDR / CLAIM_BASE_URL。
|
||||||
|
|
||||||
# App / Caddy
|
# App / Caddy
|
||||||
APP_DOMAIN=localhost
|
APP_DOMAIN=localhost
|
||||||
CADDY_SITE_ADDR=http://localhost
|
CADDY_SITE_ADDR=http://localhost
|
||||||
|
WORKER_SITE_ADDR=http://worker.localhost
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
BACKEND_PORT=3000
|
BACKEND_PORT=3000
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# App / Caddy
|
# App / Caddy
|
||||||
APP_DOMAIN=order.khhao.com
|
APP_DOMAIN=order.khhao.com
|
||||||
CADDY_SITE_ADDR=https://order.khhao.com
|
CADDY_SITE_ADDR=https://order.khhao.com
|
||||||
|
WORKER_SITE_ADDR=https://worker.khhao.com
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
BACKEND_PORT=3000
|
BACKEND_PORT=3000
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,8 @@ cp .env.server.example .env
|
|||||||
|
|
||||||
| 变量 | 说明 |
|
| 变量 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `APP_DOMAIN` / `CADDY_SITE_ADDR` | 对外域名与 Caddy 监听地址 |
|
| `APP_DOMAIN` / `CADDY_SITE_ADDR` | 后台与领取页域名、Caddy 主站监听地址 |
|
||||||
|
| `WORKER_SITE_ADDR` | 打手端独立域名,如 `https://worker.khhao.com` |
|
||||||
| `CLAIM_BASE_URL` | 领取页完整前缀,如 `https://域名/#/claim` |
|
| `CLAIM_BASE_URL` | 领取页完整前缀,如 `https://域名/#/claim` |
|
||||||
| `CLAIM_TOKEN_TTL_HOURS` | 领取链接有效期(小时);`0` 表示长期有效,正整数表示限时 |
|
| `CLAIM_TOKEN_TTL_HOURS` | 领取链接有效期(小时);`0` 表示长期有效,正整数表示限时 |
|
||||||
| `DATABASE_URL` | PostgreSQL 连接串(容器内主机名用 `postgres`) |
|
| `DATABASE_URL` | PostgreSQL 连接串(容器内主机名用 `postgres`) |
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const DEPLOYMENT_ONLY_ENV_NAMES = [
|
|||||||
'TZ',
|
'TZ',
|
||||||
'VITE_API_TARGET',
|
'VITE_API_TARGET',
|
||||||
'VITE_ALLOWED_HOSTS',
|
'VITE_ALLOWED_HOSTS',
|
||||||
|
'VITE_WORKER_SITE_URL',
|
||||||
|
'WORKER_SITE_ADDR',
|
||||||
]
|
]
|
||||||
|
|
||||||
const KNOWN_ENV_NAMES = new Set([
|
const KNOWN_ENV_NAMES = new Set([
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
|
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
||||||
"typecheck": "tsc -b --noEmit",
|
"typecheck": "tsc -b --noEmit",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
|||||||
+55
-12
@@ -1,9 +1,19 @@
|
|||||||
import { Suspense, lazy } from 'react'
|
import { Suspense, lazy, useEffect } from 'react'
|
||||||
import { Spin } from 'antd'
|
import { Spin } from 'antd'
|
||||||
import { HashRouter, Navigate, Outlet, Route, Routes, useLocation } from 'react-router'
|
import {
|
||||||
|
BrowserRouter,
|
||||||
|
HashRouter,
|
||||||
|
Navigate,
|
||||||
|
Outlet,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
useLocation,
|
||||||
|
} from 'react-router'
|
||||||
|
|
||||||
import { getAdminRole, hasAdminSession } from '@/utils/admin-auth'
|
import { getAdminRole, hasAdminSession } from '@/utils/admin-auth'
|
||||||
|
import { isWorkerSite, WORKER_SITE_ORIGIN } from '@/utils/site'
|
||||||
import { hasWorkerSession } from '@/utils/worker-auth'
|
import { hasWorkerSession } from '@/utils/worker-auth'
|
||||||
|
import { resolveLegacyWorkerPath } from '@/utils/worker-routes'
|
||||||
import AdminLayout from '@/layouts/AdminLayout'
|
import AdminLayout from '@/layouts/AdminLayout'
|
||||||
import WorkerLayout from '@/layouts/WorkerLayout'
|
import WorkerLayout from '@/layouts/WorkerLayout'
|
||||||
|
|
||||||
@@ -63,13 +73,27 @@ function RequireWorker() {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
if (!hasWorkerSession()) {
|
if (!hasWorkerSession()) {
|
||||||
return <Navigate to="/worker/login" replace state={{ from: location }} />
|
return <Navigate to="/" replace state={{ from: location }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Outlet />
|
return <Outlet />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const legacyWorkerPath = resolveLegacyWorkerPath(window.location.hash)
|
||||||
|
|
||||||
|
if (legacyWorkerPath) {
|
||||||
|
return <WorkerSiteRedirect path={legacyWorkerPath} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWorkerSite()) {
|
||||||
|
return <WorkerApplication />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AdminApplication />
|
||||||
|
}
|
||||||
|
|
||||||
|
function AdminApplication() {
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Suspense fallback={<RouteLoading />}>
|
<Suspense fallback={<RouteLoading />}>
|
||||||
@@ -96,15 +120,6 @@ export default function App() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/worker/login" element={<WorkerLoginPage />} />
|
|
||||||
<Route element={<RequireWorker />}>
|
|
||||||
<Route path="/worker" element={<WorkerLayout />}>
|
|
||||||
<Route index element={<Navigate to="/worker/hall" replace />} />
|
|
||||||
<Route path="hall" element={<WorkerHallPage />} />
|
|
||||||
<Route path="orders" element={<WorkerOrdersPage />} />
|
|
||||||
<Route path="profile" element={<WorkerProfilePage />} />
|
|
||||||
</Route>
|
|
||||||
</Route>
|
|
||||||
<Route path="/claim/:token" element={<ClaimPage />} />
|
<Route path="/claim/:token" element={<ClaimPage />} />
|
||||||
<Route path="/collect" element={<CollectPage />} />
|
<Route path="/collect" element={<CollectPage />} />
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
@@ -114,6 +129,34 @@ export default function App() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WorkerApplication() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Suspense fallback={<RouteLoading />}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<WorkerLoginPage />} />
|
||||||
|
<Route element={<RequireWorker />}>
|
||||||
|
<Route element={<WorkerLayout />}>
|
||||||
|
<Route path="hall" element={<WorkerHallPage />} />
|
||||||
|
<Route path="orders" element={<WorkerOrdersPage />} />
|
||||||
|
<Route path="profile" element={<WorkerProfilePage />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function WorkerSiteRedirect({ path }: { path: string }) {
|
||||||
|
useEffect(() => {
|
||||||
|
window.location.replace(`${WORKER_SITE_ORIGIN}${path}`)
|
||||||
|
}, [path])
|
||||||
|
|
||||||
|
return <RouteLoading />
|
||||||
|
}
|
||||||
|
|
||||||
function RouteLoading() {
|
function RouteLoading() {
|
||||||
return (
|
return (
|
||||||
<div className="route-loading">
|
<div className="route-loading">
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ import { useIsMobile } from '@/utils/use-is-mobile'
|
|||||||
import { clearWorkerSession, getWorkerStatus, getWorkerUsername } from '@/utils/worker-auth'
|
import { clearWorkerSession, getWorkerStatus, getWorkerUsername } from '@/utils/worker-auth'
|
||||||
|
|
||||||
const menuItems: MenuProps['items'] = [
|
const menuItems: MenuProps['items'] = [
|
||||||
{ key: '/worker/hall', icon: <ShoppingOutlined />, label: '抢单大厅' },
|
{ key: '/hall', icon: <ShoppingOutlined />, label: '抢单大厅' },
|
||||||
{ key: '/worker/orders', icon: <ContainerOutlined />, label: '我的订单' },
|
{ key: '/orders', icon: <ContainerOutlined />, label: '我的订单' },
|
||||||
{ key: '/worker/profile', icon: <UserOutlined />, label: '个人中心' },
|
{ key: '/profile', icon: <UserOutlined />, label: '个人中心' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const tabItems = [
|
const tabItems = [
|
||||||
{ key: '/worker/hall', icon: ShoppingOutlined, label: '抢单大厅' },
|
{ key: '/hall', icon: ShoppingOutlined, label: '抢单大厅' },
|
||||||
{ key: '/worker/orders', icon: ContainerOutlined, label: '我的订单' },
|
{ key: '/orders', icon: ContainerOutlined, label: '我的订单' },
|
||||||
{ key: '/worker/profile', icon: UserOutlined, label: '个人中心' },
|
{ key: '/profile', icon: UserOutlined, label: '个人中心' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function WorkerLayout() {
|
export default function WorkerLayout() {
|
||||||
@@ -41,7 +41,7 @@ export default function WorkerLayout() {
|
|||||||
} finally {
|
} finally {
|
||||||
clearWorkerSession()
|
clearWorkerSession()
|
||||||
message.success('已退出登录')
|
message.success('已退出登录')
|
||||||
navigate('/worker/login', { replace: true })
|
navigate('/', { replace: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,9 +144,9 @@ export default function WorkerLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveSelectedKey(pathname: string) {
|
function resolveSelectedKey(pathname: string) {
|
||||||
if (pathname.startsWith('/worker/orders')) return '/worker/orders'
|
if (pathname.startsWith('/orders')) return '/orders'
|
||||||
if (pathname.startsWith('/worker/profile')) return '/worker/profile'
|
if (pathname.startsWith('/profile')) return '/profile'
|
||||||
return '/worker/hall'
|
return '/hall'
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatWorkerStatus(status: string) {
|
function formatWorkerStatus(status: string) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { clearAdminSession, getAdminToken } from '@/utils/admin-auth'
|
import { clearAdminSession, getAdminToken } from '@/utils/admin-auth'
|
||||||
|
import { isWorkerSite } from '@/utils/site'
|
||||||
import { clearWorkerSession, getWorkerToken } from '@/utils/worker-auth'
|
import { clearWorkerSession, getWorkerToken } from '@/utils/worker-auth'
|
||||||
|
|
||||||
export interface ApiEnvelope<T> {
|
export interface ApiEnvelope<T> {
|
||||||
@@ -84,11 +85,8 @@ http.interceptors.response.use(
|
|||||||
if (status === 401 && shouldClearWorkerSession(errorCode)) {
|
if (status === 401 && shouldClearWorkerSession(errorCode)) {
|
||||||
clearWorkerSession()
|
clearWorkerSession()
|
||||||
|
|
||||||
if (
|
if (isWorkerSite() && window.location.pathname !== '/') {
|
||||||
window.location.hash.startsWith('#/worker') &&
|
window.location.replace('/')
|
||||||
!window.location.hash.startsWith('#/worker/login')
|
|
||||||
) {
|
|
||||||
window.location.hash = '#/worker/login'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function WorkerLoginPage() {
|
|||||||
}, [countdown])
|
}, [countdown])
|
||||||
|
|
||||||
if (hasWorkerSession()) {
|
if (hasWorkerSession()) {
|
||||||
return <Navigate to="/worker/hall" replace />
|
return <Navigate to="/hall" replace />
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitLogin(values: { username: string; password: string }) {
|
async function submitLogin(values: { username: string; password: string }) {
|
||||||
@@ -57,7 +57,7 @@ export default function WorkerLoginPage() {
|
|||||||
const response = await loginWorker(values)
|
const response = await loginWorker(values)
|
||||||
setWorkerSession(response.data.token, response.data.expiresAt, response.data.worker)
|
setWorkerSession(response.data.token, response.data.expiresAt, response.data.worker)
|
||||||
message.success('登录成功')
|
message.success('登录成功')
|
||||||
navigate('/worker/hall', { replace: true })
|
navigate('/hall', { replace: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error instanceof Error ? error.message : '登录失败')
|
message.error(error instanceof Error ? error.message : '登录失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export default function WorkerProfilePage() {
|
|||||||
} finally {
|
} finally {
|
||||||
clearWorkerSession()
|
clearWorkerSession()
|
||||||
message.success('已退出登录')
|
message.success('已退出登录')
|
||||||
navigate('/worker/login', { replace: true })
|
navigate('/', { replace: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ export default function WorkerProfilePage() {
|
|||||||
})
|
})
|
||||||
message.success('密码已修改,请重新登录')
|
message.success('密码已修改,请重新登录')
|
||||||
clearWorkerSession()
|
clearWorkerSession()
|
||||||
navigate('/worker/login', { replace: true })
|
navigate('/', { replace: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error instanceof Error ? error.message : '修改密码失败')
|
message.error(error instanceof Error ? error.message : '修改密码失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const DEFAULT_WORKER_SITE_URL = 'https://worker.khhao.com'
|
||||||
|
|
||||||
|
export const WORKER_SITE_ORIGIN = resolveWorkerSiteOrigin()
|
||||||
|
|
||||||
|
export function isWorkerSite() {
|
||||||
|
const configuredHostname = new URL(WORKER_SITE_ORIGIN).hostname
|
||||||
|
|
||||||
|
return (
|
||||||
|
window.location.hostname === configuredHostname ||
|
||||||
|
(import.meta.env.DEV && window.location.hostname === 'worker.localhost')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveWorkerSiteOrigin() {
|
||||||
|
const configuredUrl = String(import.meta.env.VITE_WORKER_SITE_URL || '').trim()
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URL(configuredUrl || DEFAULT_WORKER_SITE_URL).origin
|
||||||
|
} catch {
|
||||||
|
return DEFAULT_WORKER_SITE_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export function resolveLegacyWorkerPath(hash: string) {
|
||||||
|
const value = hash.startsWith('#') ? hash.slice(1) : hash
|
||||||
|
const queryIndex = value.indexOf('?')
|
||||||
|
const pathname = queryIndex >= 0 ? value.slice(0, queryIndex) : value
|
||||||
|
const search = queryIndex >= 0 ? value.slice(queryIndex) : ''
|
||||||
|
|
||||||
|
if (pathname === '/worker' || pathname === '/worker/' || pathname === '/worker/login') {
|
||||||
|
return `/${search}`
|
||||||
|
}
|
||||||
|
if (pathname === '/worker/hall') return `/hall${search}`
|
||||||
|
if (pathname === '/worker/orders') return `/orders${search}`
|
||||||
|
if (pathname === '/worker/profile') return `/profile${search}`
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
import { resolveLegacyWorkerPath } from '../src/utils/worker-routes.ts'
|
||||||
|
|
||||||
|
test('旧打手地址映射到子域名短路径', () => {
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/worker/login'), '/')
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/worker/hall'), '/hall')
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/worker/orders'), '/orders')
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/worker/profile'), '/profile')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('旧打手地址保留查询参数', () => {
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/worker/orders?status=in_progress'), '/orders?status=in_progress')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('非打手地址不触发跨域跳转', () => {
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/admin/dashboard'), '')
|
||||||
|
assert.equal(resolveLegacyWorkerPath('#/claim/example'), '')
|
||||||
|
})
|
||||||
@@ -20,6 +20,7 @@ export default defineConfig({
|
|||||||
// 隧道/自定义域名访问 dev server 时需加入白名单,否则 Vite 会拦截 Host
|
// 隧道/自定义域名访问 dev server 时需加入白名单,否则 Vite 会拦截 Host
|
||||||
allowedHosts: [
|
allowedHosts: [
|
||||||
'localhost',
|
'localhost',
|
||||||
|
'.localhost',
|
||||||
'127.0.0.1',
|
'127.0.0.1',
|
||||||
'221329.cc.cd',
|
'221329.cc.cd',
|
||||||
'.cc.cd',
|
'.cc.cd',
|
||||||
|
|||||||
+26
-18
@@ -1,24 +1,32 @@
|
|||||||
|
(order-site) {
|
||||||
|
route {
|
||||||
|
handle /api/* {
|
||||||
|
reverse_proxy backend:3000
|
||||||
|
}
|
||||||
|
|
||||||
|
handle /health* {
|
||||||
|
reverse_proxy backend:3000
|
||||||
|
}
|
||||||
|
|
||||||
|
handle /s/* {
|
||||||
|
reverse_proxy backend:3000
|
||||||
|
}
|
||||||
|
|
||||||
|
encode zstd gzip
|
||||||
|
root * /srv
|
||||||
|
try_files {path} /index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{$CADDY_SITE_ADDR:http://localhost} {
|
{$CADDY_SITE_ADDR:http://localhost} {
|
||||||
route {
|
import order-site
|
||||||
handle /api/* {
|
}
|
||||||
reverse_proxy backend:3000
|
|
||||||
}
|
|
||||||
|
|
||||||
handle /health* {
|
{$WORKER_SITE_ADDR:https://worker.khhao.com} {
|
||||||
reverse_proxy backend:3000
|
import order-site
|
||||||
}
|
|
||||||
|
|
||||||
handle /s/* {
|
|
||||||
reverse_proxy backend:3000
|
|
||||||
}
|
|
||||||
|
|
||||||
encode zstd gzip
|
|
||||||
root * /srv
|
|
||||||
try_files {path} /index.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
live.khhao.com {
|
live.khhao.com {
|
||||||
reverse_proxy douyu-login:8800
|
reverse_proxy douyu-login:8800
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
FROM docker.m.daocloud.io/library/node:22-bookworm AS builder
|
FROM docker.m.daocloud.io/library/node:22-bookworm AS builder
|
||||||
|
|
||||||
ARG NPM_REGISTRY=https://registry.npmmirror.com
|
ARG NPM_REGISTRY=https://registry.npmmirror.com
|
||||||
|
ARG VITE_WORKER_SITE_URL=https://worker.khhao.com
|
||||||
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
ENV NPM_CONFIG_REGISTRY=${NPM_REGISTRY}
|
ENV NPM_CONFIG_REGISTRY=${NPM_REGISTRY}
|
||||||
|
ENV VITE_WORKER_SITE_URL=${VITE_WORKER_SITE_URL}
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -119,9 +119,11 @@ validate_env_file() {
|
|||||||
|
|
||||||
[[ "${READY_TIMEOUT}" =~ ^[1-9][0-9]*$ ]] || fail "READY_TIMEOUT 必须是正整数秒数,当前值:${READY_TIMEOUT}"
|
[[ "${READY_TIMEOUT}" =~ ^[1-9][0-9]*$ ]] || fail "READY_TIMEOUT 必须是正整数秒数,当前值:${READY_TIMEOUT}"
|
||||||
|
|
||||||
local site_addr
|
local site_addr worker_site_addr
|
||||||
site_addr="$(read_env_value CADDY_SITE_ADDR)"
|
site_addr="$(read_env_value CADDY_SITE_ADDR)"
|
||||||
[[ -n "${site_addr}" ]] || fail ".env 缺少 CADDY_SITE_ADDR。"
|
[[ -n "${site_addr}" ]] || fail ".env 缺少 CADDY_SITE_ADDR。"
|
||||||
|
worker_site_addr="$(read_env_value WORKER_SITE_ADDR)"
|
||||||
|
[[ -n "${worker_site_addr}" ]] || fail ".env 缺少 WORKER_SITE_ADDR。"
|
||||||
HEALTH_URL="${site_addr%/}/health/ready"
|
HEALTH_URL="${site_addr%/}/health/ready"
|
||||||
|
|
||||||
validate_database_url_for_compose
|
validate_database_url_for_compose
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ services:
|
|||||||
NPM_CONFIG_REGISTRY: https://registry.npmmirror.com
|
NPM_CONFIG_REGISTRY: https://registry.npmmirror.com
|
||||||
VITE_API_TARGET: http://backend:3000
|
VITE_API_TARGET: http://backend:3000
|
||||||
VITE_ALLOWED_HOSTS: ${VITE_ALLOWED_HOSTS:-localhost,127.0.0.1}
|
VITE_ALLOWED_HOSTS: ${VITE_ALLOWED_HOSTS:-localhost,127.0.0.1}
|
||||||
|
VITE_WORKER_SITE_URL: ${WORKER_SITE_ADDR:-http://worker.localhost}
|
||||||
volumes:
|
volumes:
|
||||||
- ./apps/frontend:/app
|
- ./apps/frontend:/app
|
||||||
- frontend_node_modules:/app/node_modules
|
- frontend_node_modules:/app/node_modules
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ services:
|
|||||||
dockerfile: deploy/docker/frontend.Dockerfile
|
dockerfile: deploy/docker/frontend.Dockerfile
|
||||||
args:
|
args:
|
||||||
APP_DOMAIN: ${APP_DOMAIN:-localhost}
|
APP_DOMAIN: ${APP_DOMAIN:-localhost}
|
||||||
|
VITE_WORKER_SITE_URL: ${WORKER_SITE_ADDR:-https://worker.khhao.com}
|
||||||
ALPINE_MIRROR: ${ALPINE_MIRROR:-mirrors.aliyun.com}
|
ALPINE_MIRROR: ${ALPINE_MIRROR:-mirrors.aliyun.com}
|
||||||
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
|
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -105,6 +106,7 @@ services:
|
|||||||
TZ: ${TZ:-Asia/Shanghai}
|
TZ: ${TZ:-Asia/Shanghai}
|
||||||
APP_DOMAIN: ${APP_DOMAIN:-localhost}
|
APP_DOMAIN: ${APP_DOMAIN:-localhost}
|
||||||
CADDY_SITE_ADDR: ${CADDY_SITE_ADDR:-http://localhost}
|
CADDY_SITE_ADDR: ${CADDY_SITE_ADDR:-http://localhost}
|
||||||
|
WORKER_SITE_ADDR: ${WORKER_SITE_ADDR:-https://worker.khhao.com}
|
||||||
volumes:
|
volumes:
|
||||||
- caddy_data:/data
|
- caddy_data:/data
|
||||||
- caddy_config:/config
|
- caddy_config:/config
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ cp .env.server.example .env
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
CADDY_SITE_ADDR
|
CADDY_SITE_ADDR
|
||||||
|
WORKER_SITE_ADDR
|
||||||
CLAIM_BASE_URL
|
CLAIM_BASE_URL
|
||||||
POSTGRES_PASSWORD
|
POSTGRES_PASSWORD
|
||||||
DATABASE_URL
|
DATABASE_URL
|
||||||
|
|||||||
Reference in New Issue
Block a user