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