forked from 77media/video-flow
更新 env变量使用收口到env.ts,constants暂时移除
This commit is contained in:
parent
0b63661ae0
commit
fa524af5b5
@ -4,7 +4,7 @@
|
||||
|
||||
### 目录结构与职责
|
||||
|
||||
- `constants.ts`:基础配置(`BASE_URL` 从 `NEXT_PUBLIC_BASE_URL` 注入)
|
||||
- 基础配置从 `@/lib/env` 导入 `baseUrl`
|
||||
- `request.ts`:Axios 实例与拦截器、通用 `get/post/put/del`、`stream`(SSE风格下载进度)、`downloadStream`、`streamJsonPost`
|
||||
- `errorHandle.ts`:错误码映射与统一提示、特殊码处理(如 401 跳转登录、402 不弹提示)
|
||||
- `common.ts`:通用类型与与上传相关的工具(获取七牛 Token、上传)
|
||||
@ -16,7 +16,7 @@
|
||||
1. 使用 `request.ts` 提供的 `get/post/put/del` 包装函数发起请求,返回后端响应体(已通过响应拦截器做业务码检查)。
|
||||
2. 业务成功码:`code === 0` 或 `code === 202`(长任务/排队等需要前端自行处理状态)。若非成功码,拦截器会调用 `errorHandle` 并 `Promise.reject`。
|
||||
3. 认证:前端从 `localStorage.token` 注入 `Authorization: Bearer <token>`,请确保登录流程写入 `token`。
|
||||
4. 基础地址:通过环境变量 `NEXT_PUBLIC_BASE_URL` 注入,构建前需设置。
|
||||
4. 基础地址:从 `@/lib/env` 的 `baseUrl` 获取,统一管理环境变量。
|
||||
|
||||
### 错误处理约定
|
||||
|
||||
@ -90,7 +90,7 @@ await downloadStream('/download/file', 'result.mp4');
|
||||
#### 浏览器前端(React/Next.js CSR)
|
||||
|
||||
- 直接使用 `get/post/put/del`;确保登录后将 `token` 写入 `localStorage`
|
||||
- 环境变量:在 `.env.local` 配置 `NEXT_PUBLIC_BASE_URL`
|
||||
- 环境变量:在 `.env.local` 配置 `NEXT_PUBLIC_BASE_URL`,通过 `@/lib/env` 统一管理
|
||||
- 错误提示:由 `errorHandle` 统一处理;402 会展示积分不足通知
|
||||
|
||||
#### Next.js Route Handler(服务端 API)
|
||||
@ -101,7 +101,7 @@ await downloadStream('/download/file', 'result.mp4');
|
||||
#### Next.js Server Components/SSR
|
||||
|
||||
- 服务端不具备 `localStorage`,如需鉴权请改为从 Cookie/Headers 传递 token,并在转发时设置 `Authorization`
|
||||
- 服务器端可直接使用 `fetch(BASE_URL + path, { headers })`
|
||||
- 服务器端可直接使用 `fetch(baseUrl + path, { headers })`
|
||||
|
||||
#### Node/Serverless(Vercel/Cloudflare)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Common API 相关接口
|
||||
import { BASE_URL } from './constants'
|
||||
import { baseUrl } from '@/lib/env';
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number
|
||||
@ -44,7 +44,7 @@ export const getUploadToken = async (timeoutMs: number = 10000): Promise<{ token
|
||||
}, timeoutMs)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/common/get-upload-token`, {
|
||||
const response = await fetch(`${baseUrl}/common/get-upload-token`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL
|
||||
// export const BASE_URL = 'https://77.smartvideo.py.qikongjian.com'
|
||||
// export const BASE_URL ='http://192.168.120.5:8000'
|
||||
//
|
||||
@ -1,5 +1,5 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, AxiosHeaders } from 'axios';
|
||||
import { BASE_URL } from './constants'
|
||||
import { baseUrl } from '@/lib/env';
|
||||
import { errorHandle } from './errorHandle';
|
||||
|
||||
/**
|
||||
@ -20,7 +20,7 @@ const handleRequestError = (error: any, defaultMessage: string = '请求失败')
|
||||
};
|
||||
// 创建 axios 实例
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL: BASE_URL, // 设置基础URL
|
||||
baseURL: baseUrl, // 设置基础URL
|
||||
timeout: 300000, // 请求超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -102,7 +102,7 @@ export async function streamJsonPost<T = any>(
|
||||
) {
|
||||
try {
|
||||
const token = localStorage?.getItem('token') || '';
|
||||
const response = await fetch(`${BASE_URL}${url}`, {
|
||||
const response = await fetch(`${baseUrl}${url}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BASE_URL } from "./constants";
|
||||
import { baseUrl } from '@/lib/env';
|
||||
import { post } from './request';
|
||||
|
||||
// 获取路演配置数据
|
||||
@ -8,7 +8,7 @@ export const fetchRoadshowConfigs = async () => {
|
||||
|
||||
try {
|
||||
console.log('开始请求接口数据...');
|
||||
const response = await fetch(BASE_URL + '/serversetting/roadshow-configs', {
|
||||
const response = await fetch(baseUrl + '/serversetting/roadshow-configs', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { post, streamJsonPost } from "./request";
|
||||
import { ProjectTypeEnum } from "@/app/model/enums";
|
||||
import { ApiResponse } from "@/api/common";
|
||||
import { BASE_URL } from "./constants";
|
||||
import {
|
||||
AITextEntity,
|
||||
RoleEntity,
|
||||
|
||||
@ -5,6 +5,7 @@ import { Providers } from '@/components/providers';
|
||||
import { ConfigProvider, theme } from 'antd';
|
||||
import CallbackModal from '@/components/common/CallbackModal';
|
||||
import { useAppStartupAnalytics } from '@/hooks/useAppStartupAnalytics';
|
||||
import { gaEnabled, gaMeasurementId } from '@/lib/env';
|
||||
|
||||
// 创建上下文来传递弹窗控制方法
|
||||
const CallbackModalContext = createContext<{
|
||||
@ -53,16 +54,16 @@ export default function RootLayout({
|
||||
<link rel="icon" type="image/x-icon" sizes="32x32" href="/favicon.ico?v=1" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=1" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.ico?v=1" />
|
||||
{process.env.NEXT_PUBLIC_GA_ENABLED === 'true' && (
|
||||
{gaEnabled && (
|
||||
<>
|
||||
<script async src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}`}></script>
|
||||
<script async src={`https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`}></script>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){window.dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}', {
|
||||
gtag('config', '${gaMeasurementId}', {
|
||||
page_title: document.title,
|
||||
page_location: window.location.href,
|
||||
send_page_view: true
|
||||
|
||||
@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { CheckCircle, XCircle, Loader2, AlertTriangle } from "lucide-react";
|
||||
import type { OAuthCallbackParams } from "@/app/types/google-oauth";
|
||||
import { baseUrl } from '@/lib/env';
|
||||
|
||||
// 根据后端实际返回格式定义响应类型
|
||||
interface GoogleOAuthResponse {
|
||||
@ -98,8 +99,7 @@ export default function OAuthCallback() {
|
||||
console.log('最终使用的邀请码:', finalInviteCode);
|
||||
|
||||
// 根据 jiekou.md 文档调用统一的 Python OAuth 接口
|
||||
// 使用 NEXT_PUBLIC_BASE_URL 配置,默认为 https://77.smartvideo.py.qikongjian.com
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://77.smartvideo.py.qikongjian.com';
|
||||
// 使用统一配置中的 baseUrl
|
||||
console.log('🔧 调用 Python OAuth 接口:', baseUrl);
|
||||
|
||||
const response = await fetch(`${baseUrl}/api/oauth/google`, {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { cutUrl } from '@/lib/env';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
Zap,
|
||||
@ -95,7 +96,6 @@ export const AIEditingIframe = React.forwardRef<AIEditingIframeHandle, AIEditing
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const progressIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const cutUrl = process.env.NEXT_PUBLIC_CUT_URL || 'https://cut.movieflow.ai';
|
||||
console.log('cutUrl', cutUrl);
|
||||
|
||||
// 构建智能剪辑URL
|
||||
|
||||
@ -8,6 +8,7 @@ import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
|
||||
import { useDeviceType } from '@/hooks/useDeviceType';
|
||||
import { cutUrlTo, errorConfig } from '@/lib/env';
|
||||
|
||||
interface UseWorkflowDataProps {
|
||||
onEditPlanGenerated?: () => void;
|
||||
@ -40,7 +41,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
|
||||
|
||||
const { isMobile, isTablet, isDesktop } = useDeviceType();
|
||||
|
||||
const cutUrl = process.env.NEXT_PUBLIC_CUT_URL_TO || 'https://smartcut.api.movieflow.ai';
|
||||
const cutUrl = cutUrlTo;
|
||||
console.log('cutUrl', cutUrl);
|
||||
|
||||
useEffect(() => {
|
||||
@ -77,7 +78,6 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
|
||||
}
|
||||
});
|
||||
let loadingText: any = useRef(LOADING_TEXT_MAP.getInfo);
|
||||
const errorConfig = Number(process.env.NEXT_PUBLIC_ERROR_CONFIG);
|
||||
|
||||
|
||||
// 更新 taskObject 的类型
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
* 视频编辑功能配置
|
||||
*/
|
||||
|
||||
import { getVideoEditApiConfig as getEnvVideoEditConfig, isDevelopment } from '@/lib/env';
|
||||
|
||||
export interface VideoEditApiConfig {
|
||||
/** 是否使用Mock API */
|
||||
useMockApi: boolean;
|
||||
@ -26,32 +28,23 @@ export const defaultVideoEditApiConfig: VideoEditApiConfig = {
|
||||
remoteApiBase: '/video-edit',
|
||||
localApiBase: '/api/video-edit',
|
||||
timeout: 10000,
|
||||
enableDebugLog: process.env.NODE_ENV === 'development'
|
||||
enableDebugLog: isDevelopment
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前API配置
|
||||
*/
|
||||
export function getVideoEditApiConfig(): VideoEditApiConfig {
|
||||
// 可以从环境变量或其他配置源读取
|
||||
const config = { ...defaultVideoEditApiConfig };
|
||||
// 从统一环境配置获取
|
||||
const envConfig = getEnvVideoEditConfig();
|
||||
|
||||
// 环境变量覆盖
|
||||
if (process.env.NEXT_PUBLIC_VIDEO_EDIT_USE_MOCK === 'true') {
|
||||
config.useMockApi = true;
|
||||
config.useLocalApi = false;
|
||||
}
|
||||
|
||||
if (process.env.NEXT_PUBLIC_VIDEO_EDIT_USE_REMOTE === 'true') {
|
||||
config.useLocalApi = false;
|
||||
config.useMockApi = false;
|
||||
}
|
||||
|
||||
if (process.env.NEXT_PUBLIC_VIDEO_EDIT_REMOTE_BASE) {
|
||||
config.remoteApiBase = process.env.NEXT_PUBLIC_VIDEO_EDIT_REMOTE_BASE;
|
||||
}
|
||||
|
||||
return config;
|
||||
return {
|
||||
...defaultVideoEditApiConfig,
|
||||
useMockApi: envConfig.useMockApi,
|
||||
useLocalApi: !envConfig.useRemoteApi,
|
||||
remoteApiBase: envConfig.remoteApiBase,
|
||||
enableDebugLog: envConfig.enableDebugLog,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,7 +148,7 @@ export const errorHandlingConfig = {
|
||||
*/
|
||||
export const performanceConfig = {
|
||||
/** 是否启用性能监控 */
|
||||
enabled: process.env.NODE_ENV === 'development',
|
||||
enabled: isDevelopment,
|
||||
/** 慢请求阈值(毫秒) */
|
||||
slowRequestThreshold: 2000,
|
||||
/** 是否记录所有请求 */
|
||||
@ -193,7 +186,7 @@ export const uiConfig = {
|
||||
/** 描述最大长度 */
|
||||
maxDescriptionLength: 500,
|
||||
/** 是否显示调试信息 */
|
||||
showDebugInfo: process.env.NODE_ENV === 'development',
|
||||
showDebugInfo: isDevelopment,
|
||||
/** 动画配置 */
|
||||
animations: {
|
||||
enabled: true,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// src/components/VantaHaloBackground.jsx
|
||||
// 未使用
|
||||
'use client';
|
||||
|
||||
import React, { useRef, useEffect, memo } from 'react'
|
||||
|
||||
46
lib/auth.ts
46
lib/auth.ts
@ -8,11 +8,9 @@ import type {
|
||||
OAuthState
|
||||
} from '@/app/types/google-oauth';
|
||||
import { setUserProperties } from '@/utils/analytics';
|
||||
import { javaUrl, baseUrl, googleClientId, getGoogleRedirectUri } from '@/lib/env';
|
||||
|
||||
// API配置
|
||||
//const JAVA_BASE_URL = 'http://192.168.120.36:8080';
|
||||
const JAVA_BASE_URL = process.env.NEXT_PUBLIC_JAVA_URL || 'https://77.app.java.auth.qikongjian.com';
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL
|
||||
// API配置 - 直接使用导入的配置变量
|
||||
|
||||
// Token存储键
|
||||
const TOKEN_KEY = 'token';
|
||||
@ -43,7 +41,7 @@ type RegisterUserResponse = {
|
||||
*/
|
||||
export const loginUser = async (email: string, password: string) => {
|
||||
try {
|
||||
const response = await fetch(`${JAVA_BASE_URL}/api/user/login`, {
|
||||
const response = await fetch(`${javaUrl}/api/user/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -205,9 +203,6 @@ export const authFetch = async (url: string, options: RequestInit = {}) => {
|
||||
|
||||
// Google OAuth相关函数
|
||||
|
||||
// Google Client ID - 从环境变量获取
|
||||
const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || '847079918888-o1nne8d3ij80dn20qurivo987pv07225.apps.googleusercontent.com';
|
||||
|
||||
/**
|
||||
* 初始化Google GSI SDK
|
||||
*/
|
||||
@ -268,10 +263,8 @@ export const signInWithGoogle = async (inviteCode?: string): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
// 从环境变量获取配置
|
||||
const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || '847079918888-o1nne8d3ij80dn20qurivo987pv07225.apps.googleusercontent.com';
|
||||
const javaBaseUrl = process.env.NEXT_PUBLIC_JAVA_URL || 'https://auth.test.movieflow.ai';
|
||||
const redirectUri = process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI || `${javaBaseUrl}/api/auth/google/callback`;
|
||||
// 从统一配置获取配置
|
||||
const redirectUri = getGoogleRedirectUri();
|
||||
|
||||
// 生成随机nonce用于安全验证
|
||||
const nonce = Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
||||
@ -286,12 +279,9 @@ export const signInWithGoogle = async (inviteCode?: string): Promise<void> => {
|
||||
};
|
||||
|
||||
console.log('使用的配置:', {
|
||||
clientId,
|
||||
javaBaseUrl,
|
||||
redirectUri,
|
||||
envClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
|
||||
envJavaUrl: process.env.NEXT_PUBLIC_JAVA_URL,
|
||||
envRedirectUri: process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI
|
||||
clientId: googleClientId,
|
||||
javaBaseUrl: javaUrl,
|
||||
redirectUri
|
||||
});
|
||||
|
||||
// 详细的调试日志
|
||||
@ -300,14 +290,14 @@ export const signInWithGoogle = async (inviteCode?: string): Promise<void> => {
|
||||
console.log(' - 当前协议:', window.location.protocol);
|
||||
console.log(' - 当前端口:', window.location.port);
|
||||
console.log(' - 完整 origin:', window.location.origin);
|
||||
console.log(' - 环境变量 NEXT_PUBLIC_GOOGLE_REDIRECT_URI:', process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI);
|
||||
console.log(' - 环境变量 NEXT_PUBLIC_GOOGLE_REDIRECT_URI:', redirectUri);
|
||||
console.log(' - 最终使用的 redirect_uri:', redirectUri);
|
||||
console.log(' - Google Client ID:', GOOGLE_CLIENT_ID);
|
||||
console.log(' - Google Client ID:', googleClientId);
|
||||
|
||||
// 构建Google OAuth2授权URL
|
||||
const authParams = new URLSearchParams({
|
||||
access_type: 'online',
|
||||
client_id: clientId,
|
||||
client_id: googleClientId,
|
||||
nonce: nonce,
|
||||
redirect_uri: redirectUri,
|
||||
response_type: 'code', // 使用授权码模式
|
||||
@ -362,7 +352,7 @@ export const loginWithGoogleToken = async (idToken: string, action: 'login' | 'r
|
||||
inviteCode
|
||||
};
|
||||
|
||||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/login`, {
|
||||
const response = await fetch(`${javaUrl}/api/auth/google/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -417,7 +407,7 @@ export const bindGoogleAccount = async (bindToken: string, idToken?: string) =>
|
||||
confirm: true
|
||||
};
|
||||
|
||||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/bind`, {
|
||||
const response = await fetch(`${javaUrl}/api/auth/google/bind`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -453,7 +443,7 @@ export const getGoogleBindStatus = async () => {
|
||||
throw new Error('User not authenticated');
|
||||
}
|
||||
|
||||
const response = await fetch(`${JAVA_BASE_URL}/api/auth/google/status`, {
|
||||
const response = await fetch(`${javaUrl}/api/auth/google/status`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -529,7 +519,7 @@ export const getUserProfile = async (): Promise<any> => {
|
||||
throw new Error('No token available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${BASE_URL}/auth/profile`, {
|
||||
const response = await fetch(`${baseUrl}/auth/profile`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -638,7 +628,7 @@ export const registerUser = async ({
|
||||
inviteCode?: string;
|
||||
}): Promise<any> => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/user/register`, {
|
||||
const response = await fetch(`${baseUrl}/api/user/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -676,7 +666,7 @@ export const registerUserWithInvite = async ({
|
||||
invite_code?: string;
|
||||
}): Promise<RegisterUserResponse> => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/user_fission/register_with_invite`, {
|
||||
const response = await fetch(`${baseUrl}/api/user_fission/register_with_invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -707,7 +697,7 @@ export const registerUserWithInvite = async ({
|
||||
*/
|
||||
export const sendVerificationLink = async (email: string) => {
|
||||
try {
|
||||
const response = await fetch(`${JAVA_BASE_URL}/api/user/sendVerificationLink?email=${email}`);
|
||||
const response = await fetch(`${javaUrl}/api/user/sendVerificationLink?email=${email}`);
|
||||
const data = await response.json();
|
||||
if(!data.success){
|
||||
throw new Error(data.message||data.msg)
|
||||
|
||||
202
lib/env.ts
Normal file
202
lib/env.ts
Normal file
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* 环境变量统一配置管理
|
||||
* 集中管理所有环境变量,避免重复声明
|
||||
*/
|
||||
|
||||
/**
|
||||
* 环境变量配置接口
|
||||
*/
|
||||
export interface EnvConfig {
|
||||
// 基础配置
|
||||
nodeEnv: string;
|
||||
isDevelopment: boolean;
|
||||
isProduction: boolean;
|
||||
|
||||
// API 基础 URL 配置
|
||||
baseUrl: string;
|
||||
javaUrl: string;
|
||||
cutUrl: string;
|
||||
cutUrlTo: string;
|
||||
|
||||
// Google OAuth 配置
|
||||
googleClientId: string;
|
||||
googleRedirectUri: string;
|
||||
|
||||
// Google Analytics 配置
|
||||
gaEnabled: boolean;
|
||||
gaMeasurementId: string;
|
||||
|
||||
// 视频编辑配置
|
||||
videoEditUseMock: boolean;
|
||||
videoEditUseRemote: boolean;
|
||||
videoEditRemoteBase: string;
|
||||
|
||||
// 其他配置
|
||||
errorConfig: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取环境变量配置
|
||||
*/
|
||||
export const getEnvConfig = (): EnvConfig => {
|
||||
const nodeEnv = process.env.NODE_ENV || 'development';
|
||||
|
||||
return {
|
||||
// 基础配置
|
||||
nodeEnv,
|
||||
isDevelopment: nodeEnv === 'development',
|
||||
isProduction: nodeEnv === 'production',
|
||||
|
||||
// API 基础 URL 配置
|
||||
baseUrl: process.env.NEXT_PUBLIC_BASE_URL || 'https://77.smartvideo.py.qikongjian.com',
|
||||
javaUrl: process.env.NEXT_PUBLIC_JAVA_URL || 'https://77.app.java.auth.qikongjian.com',
|
||||
cutUrl: process.env.NEXT_PUBLIC_CUT_URL || 'https://smartcut.api.movieflow.ai',
|
||||
cutUrlTo: process.env.NEXT_PUBLIC_CUT_URL_TO || 'https://smartcut.api.movieflow.ai',
|
||||
|
||||
// Google OAuth 配置
|
||||
googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || '847079918888-o1nne8d3ij80dn20qurivo987pv07225.apps.googleusercontent.com',
|
||||
googleRedirectUri: process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI || '',
|
||||
|
||||
// Google Analytics 配置
|
||||
gaEnabled: process.env.NEXT_PUBLIC_GA_ENABLED === 'true',
|
||||
gaMeasurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || 'G-4BDXV6TWF4',
|
||||
|
||||
// 视频编辑配置
|
||||
videoEditUseMock: process.env.NEXT_PUBLIC_VIDEO_EDIT_USE_MOCK === 'true',
|
||||
videoEditUseRemote: process.env.NEXT_PUBLIC_VIDEO_EDIT_USE_REMOTE === 'true',
|
||||
videoEditRemoteBase: process.env.NEXT_PUBLIC_VIDEO_EDIT_REMOTE_BASE || '/video-edit',
|
||||
|
||||
// 其他配置
|
||||
errorConfig: Number(process.env.NEXT_PUBLIC_ERROR_CONFIG) || 0,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 环境变量配置实例
|
||||
*/
|
||||
export const env = getEnvConfig();
|
||||
|
||||
/**
|
||||
* 导出常用的环境变量配置
|
||||
*/
|
||||
export const {
|
||||
// 基础配置
|
||||
nodeEnv,
|
||||
isDevelopment,
|
||||
isProduction,
|
||||
|
||||
// API 基础 URL 配置
|
||||
baseUrl,
|
||||
javaUrl,
|
||||
cutUrl,
|
||||
cutUrlTo,
|
||||
|
||||
// Google OAuth 配置
|
||||
googleClientId,
|
||||
googleRedirectUri,
|
||||
|
||||
// Google Analytics 配置
|
||||
gaEnabled,
|
||||
gaMeasurementId,
|
||||
|
||||
// 视频编辑配置
|
||||
videoEditUseMock,
|
||||
videoEditUseRemote,
|
||||
videoEditRemoteBase,
|
||||
|
||||
// 其他配置
|
||||
errorConfig,
|
||||
} = env;
|
||||
|
||||
/**
|
||||
* 获取完整的 Google OAuth 重定向 URI
|
||||
*/
|
||||
export const getGoogleRedirectUri = (): string => {
|
||||
if (googleRedirectUri) {
|
||||
return googleRedirectUri;
|
||||
}
|
||||
return `${javaUrl}/api/auth/google/callback`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查是否启用 Google Analytics
|
||||
*/
|
||||
export const isGAAvailable = (): boolean => {
|
||||
return typeof window !== 'undefined' &&
|
||||
typeof window.gtag === 'function' &&
|
||||
gaEnabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取视频编辑 API 配置
|
||||
*/
|
||||
export const getVideoEditApiConfig = () => {
|
||||
return {
|
||||
useMockApi: videoEditUseMock,
|
||||
useRemoteApi: videoEditUseRemote,
|
||||
remoteApiBase: videoEditRemoteBase,
|
||||
localApiBase: '/api/video-edit',
|
||||
enableDebugLog: isDevelopment,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 验证环境变量配置
|
||||
*/
|
||||
export const validateEnvConfig = (): { isValid: boolean; errors: string[] } => {
|
||||
const errors: string[] = [];
|
||||
|
||||
// 验证必需的配置
|
||||
if (!baseUrl) {
|
||||
errors.push('NEXT_PUBLIC_BASE_URL is required');
|
||||
}
|
||||
|
||||
if (!javaUrl) {
|
||||
errors.push('NEXT_PUBLIC_JAVA_URL is required');
|
||||
}
|
||||
|
||||
if (!googleClientId) {
|
||||
errors.push('NEXT_PUBLIC_GOOGLE_CLIENT_ID is required');
|
||||
}
|
||||
|
||||
// 验证 URL 格式
|
||||
try {
|
||||
new URL(baseUrl);
|
||||
} catch {
|
||||
errors.push('NEXT_PUBLIC_BASE_URL must be a valid URL');
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(javaUrl);
|
||||
} catch {
|
||||
errors.push('NEXT_PUBLIC_JAVA_URL must be a valid URL');
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 开发环境下的配置调试信息
|
||||
*/
|
||||
export const logEnvConfig = (): void => {
|
||||
if (isDevelopment) {
|
||||
console.log('🔧 环境变量配置:', {
|
||||
nodeEnv,
|
||||
baseUrl,
|
||||
javaUrl,
|
||||
cutUrl,
|
||||
cutUrlTo,
|
||||
googleClientId,
|
||||
googleRedirectUri: getGoogleRedirectUri(),
|
||||
gaEnabled,
|
||||
gaMeasurementId,
|
||||
videoEditUseMock,
|
||||
videoEditUseRemote,
|
||||
videoEditRemoteBase,
|
||||
errorConfig,
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -2,6 +2,8 @@
|
||||
* 服务端配置工具函数
|
||||
*/
|
||||
|
||||
import { baseUrl } from '@/lib/env';
|
||||
|
||||
// 注意:这里不使用 @/api/request 中的 post 函数,因为它会将请求发送到远程服务器
|
||||
// 我们需要直接调用本地的 Next.js API 路由
|
||||
|
||||
@ -10,8 +12,7 @@
|
||||
*/
|
||||
const localPost = async <T>(url: string, data: any): Promise<T> => {
|
||||
try {
|
||||
// 使用环境变量中的 BASE_URL(生产要求使用 NEXT_PUBLIC_BASE_URL)
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || '';
|
||||
// 使用统一配置中的 BASE_URL
|
||||
const isAbsolute = /^https?:\/\//i.test(url);
|
||||
const normalizedBase = baseUrl.replace(/\/$/, '');
|
||||
const normalizedPath = url.startsWith('/') ? url : `/${url}`;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* 提供标准化的事件跟踪和页面访问监控
|
||||
*/
|
||||
|
||||
import { isGAAvailable as checkGAAvailable, gaMeasurementId } from '@/lib/env';
|
||||
|
||||
// 扩展全局Window接口
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -74,16 +76,14 @@ const normalizeEventParams = (
|
||||
* 检查GA是否可用
|
||||
*/
|
||||
export const isGAAvailable = (): boolean => {
|
||||
return typeof window !== 'undefined' &&
|
||||
typeof window.gtag === 'function' &&
|
||||
process.env.NEXT_PUBLIC_GA_ENABLED === 'true';
|
||||
return checkGAAvailable();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取GA测量ID
|
||||
*/
|
||||
export const getGAMeasurementId = (): string => {
|
||||
return process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || 'G-4BDXV6TWF4';
|
||||
return gaMeasurementId;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -113,7 +113,7 @@ export const trackEvent = (
|
||||
window.gtag('event', eventName, eventParams);
|
||||
|
||||
// 开发环境下打印日志
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
||||
console.log('GA Event:', eventName, eventParams);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -150,7 +150,7 @@ export const trackPageView = (
|
||||
window.gtag('config', getGAMeasurementId(), pageParams);
|
||||
|
||||
// 开发环境下打印日志
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
||||
console.log('GA Page View:', pagePath, pageParams);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -3,10 +3,7 @@
|
||||
* 用于处理 Next.js 开发环境中的常见问题
|
||||
*/
|
||||
|
||||
/**
|
||||
* 检测是否为开发环境
|
||||
*/
|
||||
export const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
import { isDevelopment } from '@/lib/env';
|
||||
|
||||
/**
|
||||
* 安全的组件重渲染函数
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { notification } from 'antd';
|
||||
import { downloadVideo } from './tools';
|
||||
import { getGenerateEditPlan } from '@/api/video_flow';
|
||||
import { cutUrl } from '@/lib/env';
|
||||
|
||||
/**
|
||||
* 导出服务 - 封装视频导出相关功能
|
||||
@ -115,7 +116,7 @@ export class VideoExportService {
|
||||
this.config = {
|
||||
maxRetries: config.maxRetries || 3,
|
||||
pollInterval: config.pollInterval || 5000, // 5秒轮询
|
||||
apiBaseUrl: process.env.NEXT_PUBLIC_CUT_URL || 'https://smartcut.api.movieflow.ai'
|
||||
apiBaseUrl: cutUrl
|
||||
};
|
||||
}
|
||||
|
||||
@ -857,7 +858,7 @@ export class VideoExportService {
|
||||
export const videoExportService = new VideoExportService({
|
||||
maxRetries: 3,
|
||||
pollInterval: 5000, // 5秒轮询间隔
|
||||
// apiBaseUrl 使用环境变量 NEXT_PUBLIC_CUT_URL,在构造函数中处理
|
||||
// apiBaseUrl 使用统一配置中的 cutUrl,在构造函数中处理
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user