From f8386d7b0209c50f1f9c92e85b20f76b410a64d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=9B=E5=85=AE?= Date: Fri, 5 Jun 2026 15:35:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E6=A0=87=E8=AF=86=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.develop.example | 6 ++++++ .env.example | 6 +++++- .env.test.example | 4 ++-- README.md | 18 +++++++++++++++++- package.json | 9 ++++++--- src/app/layout.tsx | 3 ++- src/lib/environment.ts | 35 +++++++++++++++++++++++++++++++++++ src/lib/session.ts | 4 +++- 8 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 .env.develop.example create mode 100644 src/lib/environment.ts diff --git a/.env.develop.example b/.env.develop.example new file mode 100644 index 0000000..1486c42 --- /dev/null +++ b/.env.develop.example @@ -0,0 +1,6 @@ +ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api +ROLE_USER_SESSION_COOKIE=role_user_session_develop +APP_ENV=develop +APP_ENV_LABEL=测试环境 +PORT=3211 +HOSTNAME=0.0.0.0 diff --git a/.env.example b/.env.example index 6d30546..b7adc8c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,6 @@ ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api -ROLE_USER_SESSION_COOKIE=role_user_session +ROLE_USER_SESSION_COOKIE=role_user_session_local +APP_ENV=local +APP_ENV_LABEL=本地环境 +PORT=3210 +HOSTNAME=0.0.0.0 diff --git a/.env.test.example b/.env.test.example index 7de2e6f..1486c42 100644 --- a/.env.test.example +++ b/.env.test.example @@ -1,6 +1,6 @@ ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api -ROLE_USER_SESSION_COOKIE=role_user_session_test -APP_ENV=test +ROLE_USER_SESSION_COOKIE=role_user_session_develop +APP_ENV=develop APP_ENV_LABEL=测试环境 PORT=3211 HOSTNAME=0.0.0.0 diff --git a/README.md b/README.md index f34f650..ee1be33 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,23 @@ pnpm dev ```bash ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api -ROLE_USER_SESSION_COOKIE=role_user_session +ROLE_USER_SESSION_COOKIE=role_user_session_local +APP_ENV=local +APP_ENV_LABEL=本地环境 +PORT=3210 +HOSTNAME=0.0.0.0 ``` `ACCESS_MANAGE_API_BASE_URL` 指向 `access-manage` 服务端 API 根路径。员工登录会调用后端 `POST /api/auth/employee/login`,登录成功后只把 JWT 写入 Next.js 服务端 HttpOnly Cookie,不写入 `localStorage`。 +环境标识按 `APP_ENV` 区分: + +- `local`: 本地环境,默认使用 `.env.example` 复制出的 `.env.local`。 +- `develop`: 测试环境,参考 `.env.develop.example`;`.env.test.example` 保留为兼容入口。 +- `production`: 生产环境,参考 `.env.production.example`。 + +`APP_ENV_LABEL` 可覆盖页面右上角显示文案;未配置时会按 `APP_ENV` 自动显示“本地环境”“测试环境”或“生产环境”。Cookie 只在 `APP_ENV=production` 时设置 `Secure`,避免测试环境 HTTP 访问时登录后被浏览器丢弃 Cookie。 + ## 可用命令 ```bash @@ -38,6 +50,10 @@ pnpm dev pnpm typecheck pnpm lint pnpm build +pnpm build:develop +pnpm build:prod +pnpm start:develop +pnpm start:prod ``` ## 当前实现范围 diff --git a/package.json b/package.json index 157b107..7dece92 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev -p 3210", + "dev": "APP_ENV=local APP_ENV_LABEL=本地环境 ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api ROLE_USER_SESSION_COOKIE=role_user_session_local next dev -p 3210", "build": "next build", - "build:test": "next build", - "build:prod": "next build", + "build:develop": "APP_ENV=develop APP_ENV_LABEL=测试环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api ROLE_USER_SESSION_COOKIE=role_user_session_develop next build", + "build:test": "pnpm build:develop", + "build:prod": "APP_ENV=production APP_ENV_LABEL=生产环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3500/api ROLE_USER_SESSION_COOKIE=role_user_session next build", "start": "next start", + "start:develop": "APP_ENV=develop APP_ENV_LABEL=测试环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3501/api ROLE_USER_SESSION_COOKIE=role_user_session_develop next start -p 3211", + "start:prod": "APP_ENV=production APP_ENV_LABEL=生产环境 ACCESS_MANAGE_API_BASE_URL=http://127.0.0.1:3500/api ROLE_USER_SESSION_COOKIE=role_user_session next start -p 3210", "lint": "eslint", "typecheck": "tsc --noEmit --incremental false" }, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6b65cfc..7792a07 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from "next"; import { connection } from "next/server"; +import { getAppEnvLabel } from "@/lib/environment"; import "./globals.css"; export const metadata: Metadata = { @@ -28,7 +29,7 @@ export const viewport: Viewport = { export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { await connection(); - const environmentLabel = process.env.APP_ENV_LABEL || "生产环境"; + const environmentLabel = getAppEnvLabel(); return ( diff --git a/src/lib/environment.ts b/src/lib/environment.ts new file mode 100644 index 0000000..6d0e8c4 --- /dev/null +++ b/src/lib/environment.ts @@ -0,0 +1,35 @@ +import "server-only"; + +export type AppEnv = "local" | "develop" | "production"; + +export function getAppEnv(): AppEnv { + const appEnv = process.env.APP_ENV; + + if (appEnv === "production") { + return "production"; + } + + if (appEnv === "develop" || appEnv === "test") { + return "develop"; + } + + if (appEnv === "local") { + return "local"; + } + + return process.env.NODE_ENV === "production" ? "production" : "local"; +} + +export function getAppEnvLabel() { + if (process.env.APP_ENV_LABEL) { + return process.env.APP_ENV_LABEL; + } + + const labels: Record = { + local: "本地环境", + develop: "测试环境", + production: "生产环境" + }; + + return labels[getAppEnv()]; +} diff --git a/src/lib/session.ts b/src/lib/session.ts index 12cf34c..ad1c7b3 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -2,6 +2,8 @@ import "server-only"; import { cookies } from "next/headers"; +import { getAppEnv } from "@/lib/environment"; + const DEFAULT_COOKIE_NAME = "role_user_session"; export function getSessionCookieName() { @@ -17,7 +19,7 @@ export async function setSessionToken(token: string) { cookieStore.set(getSessionCookieName(), token, { httpOnly: true, - secure: process.env.NODE_ENV === "production", + secure: getAppEnv() === "production", sameSite: "lax", path: "/", maxAge: 60 * 60 * 8