From 7a3677ee9fb0ed31416947bb80ab2a341493a617 Mon Sep 17 00:00:00 2001 From: Huarch Date: Mon, 20 Jul 2026 12:06:57 +0800 Subject: [PATCH] feat(config): inject frontend runtime config --- .dockerignore | 4 +- .gitea/workflows/package.yml | 9 ---- Dockerfile | 17 ++----- docker-compose.yml | 20 ++++----- docker/entrypoint.sh | 35 +++++++++++++++ src/app/api/auth/[...nextauth]/options.ts | 24 +++++++--- src/app/layout.tsx | 2 + src/components/header/index.tsx | 10 +++-- src/config/config.ts | 55 ++++++++++++++++------- src/contexts/ProjectContext.tsx | 22 ++++----- 10 files changed, 127 insertions(+), 71 deletions(-) create mode 100755 docker/entrypoint.sh diff --git a/.dockerignore b/.dockerignore index 3cb48a6..5569a3f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,8 +3,10 @@ node_modules out build .git +.env +.env.* .env*.local README.md docker-compose.yml Dockerfile -.dockerignore \ No newline at end of file +.dockerignore diff --git a/.gitea/workflows/package.yml b/.gitea/workflows/package.yml index 17c3ffb..182d28c 100644 --- a/.gitea/workflows/package.yml +++ b/.gitea/workflows/package.yml @@ -105,15 +105,6 @@ jobs: -t "${IMAGE_NAME}:${IMAGE_TAG}" \ -t "${IMAGE_NAME}:latest" \ --build-arg NPM_CONFIG_REGISTRY="https://registry.npmmirror.com" \ - --build-arg NEXT_PUBLIC_BACKEND_URL="${{ vars.NEXT_PUBLIC_BACKEND_URL }}" \ - --build-arg NEXT_PUBLIC_AGENT_URL="${{ vars.NEXT_PUBLIC_AGENT_URL }}" \ - --build-arg NEXT_PUBLIC_AUDIO_SERVICE_URL="${{ vars.NEXT_PUBLIC_AUDIO_SERVICE_URL }}" \ - --build-arg NEXT_PUBLIC_MAP_URL="${{ vars.NEXT_PUBLIC_MAP_URL }}" \ - --build-arg NEXT_PUBLIC_MAP_WORKSPACE="${{ vars.NEXT_PUBLIC_MAP_WORKSPACE }}" \ - --build-arg NEXT_PUBLIC_MAP_EXTENT="${{ vars.NEXT_PUBLIC_MAP_EXTENT }}" \ - --build-arg NEXT_PUBLIC_NETWORK_NAME="${{ vars.NEXT_PUBLIC_NETWORK_NAME }}" \ - --build-arg NEXT_PUBLIC_MAPBOX_TOKEN="${{ secrets.NEXT_PUBLIC_MAPBOX_TOKEN }}" \ - --build-arg NEXT_PUBLIC_TIANDITU_TOKEN="${{ secrets.NEXT_PUBLIC_TIANDITU_TOKEN }}" \ . push_with_retry "${IMAGE_NAME}:${IMAGE_TAG}" push_with_retry "${IMAGE_NAME}:latest" diff --git a/Dockerfile b/Dockerfile index 1f8cefb..1d48ace 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,18 +18,6 @@ RUN \ FROM base AS builder -# 只定义 ARG 接收来自构建命令或 docker-compose.yaml 的参数 -# Next.js 在 build 时会自动读取同名的 ARG 作为环境变量 -ARG NEXT_PUBLIC_BACKEND_URL -ARG NEXT_PUBLIC_AGENT_URL -ARG NEXT_PUBLIC_AUDIO_SERVICE_URL -ARG NEXT_PUBLIC_MAP_URL -ARG NEXT_PUBLIC_MAP_WORKSPACE -ARG NEXT_PUBLIC_MAP_EXTENT -ARG NEXT_PUBLIC_NETWORK_NAME -ARG NEXT_PUBLIC_MAPBOX_TOKEN -ARG NEXT_PUBLIC_TIANDITU_TOKEN - COPY --from=deps /app/refine/node_modules ./node_modules COPY . . @@ -40,7 +28,9 @@ FROM base AS runner ENV NODE_ENV=production -COPY --from=builder /app/refine/public ./public +COPY --from=builder --chown=node:node /app/refine/public ./public +COPY --chown=node:node docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh RUN mkdir .next RUN chown node:node .next @@ -55,4 +45,5 @@ EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" +ENTRYPOINT ["/entrypoint.sh"] CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml index b8d860b..77938c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,19 +6,15 @@ services: build: context: . dockerfile: Dockerfile - args: - NEXT_PUBLIC_BACKEND_URL: ${NEXT_PUBLIC_BACKEND_URL} - NEXT_PUBLIC_AGENT_URL: ${NEXT_PUBLIC_AGENT_URL} - NEXT_PUBLIC_AUDIO_SERVICE_URL: ${NEXT_PUBLIC_AUDIO_SERVICE_URL} - NEXT_PUBLIC_MAP_URL: ${NEXT_PUBLIC_MAP_URL} - NEXT_PUBLIC_MAP_WORKSPACE: ${NEXT_PUBLIC_MAP_WORKSPACE} - NEXT_PUBLIC_MAP_EXTENT: ${NEXT_PUBLIC_MAP_EXTENT} - NEXT_PUBLIC_NETWORK_NAME: ${NEXT_PUBLIC_NETWORK_NAME} - NEXT_PUBLIC_MAPBOX_TOKEN: ${NEXT_PUBLIC_MAPBOX_TOKEN} - NEXT_PUBLIC_TIANDITU_TOKEN: ${NEXT_PUBLIC_TIANDITU_TOKEN} - env_file: - - .env environment: + BACKEND_URL: ${BACKEND_URL} + AGENT_URL: ${AGENT_URL} + MAP_URL: ${MAP_URL} + MAP_WORKSPACE: ${MAP_WORKSPACE} + MAP_EXTENT: ${MAP_EXTENT} + NETWORK_NAME: ${NETWORK_NAME} + MAPBOX_TOKEN: ${MAPBOX_TOKEN} + TIANDITU_TOKEN: ${TIANDITU_TOKEN} KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID} KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET} KEYCLOAK_ISSUER: ${KEYCLOAK_ISSUER} diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..5c04d60 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -eu + +node <<'NODE' +const fs = require("fs"); + +const parseExtent = (value) => { + if (!value) { + return [13508849, 3608036, 13555781, 3633813]; + } + + const extent = value.split(",").map(Number); + return extent.length === 4 && extent.every(Number.isFinite) + ? extent + : [13508849, 3608036, 13555781, 3633813]; +}; + +const config = { + BACKEND_URL: process.env.BACKEND_URL || "http://127.0.0.1:8000", + AGENT_URL: process.env.AGENT_URL || "http://127.0.0.1:8788", + MAP_URL: process.env.MAP_URL || "http://127.0.0.1:8080/geoserver", + MAP_WORKSPACE: process.env.MAP_WORKSPACE || "tjwater", + MAP_EXTENT: parseExtent(process.env.MAP_EXTENT), + NETWORK_NAME: process.env.NETWORK_NAME || "tjwater", + MAPBOX_TOKEN: process.env.MAPBOX_TOKEN || "", + TIANDITU_TOKEN: process.env.TIANDITU_TOKEN || "", +}; + +fs.writeFileSync( + "/app/refine/public/runtime-config.js", + `window.__TJWATER_RUNTIME_CONFIG__ = ${JSON.stringify(config)};\n`, +); +NODE + +exec "$@" diff --git a/src/app/api/auth/[...nextauth]/options.ts b/src/app/api/auth/[...nextauth]/options.ts index 45ac202..194af83 100644 --- a/src/app/api/auth/[...nextauth]/options.ts +++ b/src/app/api/auth/[...nextauth]/options.ts @@ -9,16 +9,26 @@ type KeycloakTokenResponse = { refresh_token?: string; }; -const keycloakIssuer = process.env.KEYCLOAK_ISSUER!; -const keycloakClientId = process.env.KEYCLOAK_CLIENT_ID!; -const keycloakClientSecret = process.env.KEYCLOAK_CLIENT_SECRET!; -const keycloakTokenEndpoint = `${keycloakIssuer.replace(/\/$/, "")}/protocol/openid-connect/token`; +const getKeycloakTokenEndpoint = () => { + const issuer = process.env.KEYCLOAK_ISSUER; + return issuer + ? `${issuer.replace(/\/$/, "")}/protocol/openid-connect/token` + : undefined; +}; const refreshAccessToken = async (token: JWT): Promise => { if (!token.refreshToken) { return { ...token, error: "RefreshAccessTokenError" }; } + const keycloakClientId = process.env.KEYCLOAK_CLIENT_ID; + const keycloakClientSecret = process.env.KEYCLOAK_CLIENT_SECRET; + const keycloakTokenEndpoint = getKeycloakTokenEndpoint(); + + if (!keycloakClientId || !keycloakClientSecret || !keycloakTokenEndpoint) { + return { ...token, error: "RefreshAccessTokenError" }; + } + const body = new URLSearchParams({ grant_type: "refresh_token", client_id: keycloakClientId, @@ -50,9 +60,9 @@ const authOptions: NextAuthOptions = { // Configure one or more authentication providers providers: [ KeycloakProvider({ - clientId: keycloakClientId, - clientSecret: keycloakClientSecret, - issuer: keycloakIssuer, + clientId: process.env.KEYCLOAK_CLIENT_ID ?? "", + clientSecret: process.env.KEYCLOAK_CLIENT_SECRET ?? "", + issuer: process.env.KEYCLOAK_ISSUER ?? "", profile(profile) { return { id: profile.sub, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9cfb22c..280551d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { cookies } from "next/headers"; +import Script from "next/script"; import React, { Suspense } from "react"; import { RefineContext } from "./RefineContext"; import { META_DATA } from "@config/config"; @@ -18,6 +19,7 @@ export default async function RootLayout({ return ( +