import axios from "axios"; import { useUserStore } from "@/store/user"; const API_URL = import.meta.env.VITE_API_URL; const VIDEO_API_URL = import.meta.env.VITE_VIDEO_API_URL; const plat_id = import.meta.env.VITE_VIDEO_PLAT_ID; const channel_id = import.meta.env.VITE_VIDEO_CHANNEL_ID; const user_id = import.meta.env.VITE_VIDEO_USER_ID; const token = import.meta.env.VITE_VIDEO_TOKEN; const api = axios.create({ baseURL: API_URL, headers: { "Content-Type": "application/json" }, }); const videoApi = axios.create({ baseURL: VIDEO_API_URL, headers: { "Content-Type": "multipart/form-data" }, }); // api请求拦截器 api.interceptors.request.use( (config) => { const userStore = useUserStore(); if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}`; } return config; }, (error) => Promise.reject(error) ); // api响应拦截器 api.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { const userStore = useUserStore(); userStore.logout(); } console.error("API Error:", error.response?.data || error); return Promise.reject(error.response?.data || error); } ); // videoApi响应拦截器 videoApi.interceptors.response.use( (response) => response, (error) => { console.error("Video API Error:", error.response?.data || error); return Promise.reject(error.response?.data || error); } ); // videoApi共用参数 function createVideoFormData( device: string, page_count = 1, page_size = 20, extraParams: Record = {} ): FormData { const formData = new FormData(); const commonParams: Record = { plat_id, channel_id, user_id, app_type: "4", token, device, device_type: "3", page_count: String(page_count), page_size: String(page_size), }; Object.entries({ ...commonParams, ...extraParams }).forEach(([k, v]) => formData.append(k, v) ); return formData; } // 统一视频请求 async function videoRequest( endpoint: string, formData: FormData ): Promise { const res = await videoApi.post(endpoint, formData); return res.data; } /** * ===================== 用户相关接口 ===================== */ export const login = async (name: string, password: string): Promise => { const res = await api.post("/member/login", { name, password }); return res.data; }; export const register = async ( name: string, password: string, email?: string, phone?: string, code?: string ): Promise => { const res = await api.post("/member/register", { name, password, email: email || null, phone: phone || null, code: code || null, }); return res.data; }; export const profile = async (): Promise => { const res = await api.get("/member/profile"); return res.data; }; export const newGuest = async (code?: string, ref?: string): Promise => { const params: any = {}; if (code) params.code = code; if (ref) params.ref = ref; const res = await api.get("/member/guest", { params }); return res.data; }; export const upgradeGuest = async ( userId: number, name: string, password: string, email?: string, phone?: string ): Promise => { const res = await api.post("/member/guestUpgrade", { userId, name, password, email: email || null, phone: phone || null, }); return res.data; }; // 会员购买 export const purchaseMember = async ( userId: number, type: string ): Promise => { const res = await api.post("/payment/vip/create", { userId, type }); return res.data; }; // 单片购买 export const purchaseSingle = async ( userId: number, resourceId: string ): Promise => { const res = await api.post("/payment/single/create", { userId, resourceId }); return res.data; }; // 用户支付订单查询 export const userQueryOrder = async ( orderNo?: string, resourceId?: string ): Promise => { const params = { orderNo: orderNo || undefined, resourceId: resourceId || undefined, }; const res = await api.get(`/payment/user/query`, { params, }); return res.data; }; // 查询用户是否已购买特定单片资源 export const checkSinglePurchase = async (resourceId: string): Promise => { const res = await api.get("/payment/single/query", { params: { resourceId }, }); return res.data; }; // 获取用户的单片机购买记录列表(分页) export const getSinglePurchaseList = async ( page = 0, size = 20 ): Promise => { const res = await api.get("/payment/single/list", { params: { page, size }, }); return res.data; }; // 获取价格配置 export const getPriceConfig = async (): Promise => { const res = await api.get("/config/team/user"); return res.data; }; // 更新用户信息(用户名和邮箱) export const updateProfile = async ( name: string, email?: string ): Promise => { const res = await api.put("/member/profile", { name, email: email || null, }); return res.data; }; // 重置密码 export const resetPassword = async (password: string): Promise => { const res = await api.post("/member/reset-password", { password, }); return res.data; }; // 获取团队主题颜色 export const getTeamTheme = async (): Promise<{ themeColor: string }> => { const res = await api.get("/teams/my-theme"); return res.data; }; // 封禁当前用户 export const banUser = async (): Promise => { const res = await api.post("/member/ban"); return res.data; }; // 检查IP是否被封禁 export const checkBanStatus = async (): Promise<{ ip: string; isBanned: boolean }> => { const res = await api.get("/member/check-ban"); return res.data; }; /** * ===================== 广告栏相关接口 ===================== */ // 根据位置获取广告栏列表(公开接口) export const getBannersByPosition = async ( position: "top" | "middle" | "bottom" ): Promise => { const res = await api.get(`/banners/position/${position}`); return res.data; }; // 记录广告栏点击 export const recordBannerClick = async (bannerId: number): Promise => { const res = await api.post(`/banners/${bannerId}/click`); return res.data; }; /** * ===================== 视频相关接口 ===================== */ // 视频关键字 查询接口 export const searchVideoByKeyword = async ( device: string, keyword: string, page_count = 1, page_size = 20, resource_type?: "long" | "short", lang?: string ): Promise => { const formData = createVideoFormData(device, page_count, page_size, { keyword, ...(resource_type && { resource_type }), ...(lang && { lang }), }); return videoRequest(`/media/${plat_id}/keyword`, formData); }; // 视频查询接口(支持标签分类和类型查询) export const searchVideoByTags = async ( device: string, page_count = 1, page_size = 20, tag?: string, resource_type?: "long" | "short", sort?: "view" | "like" | "time" ): Promise => { const formData = createVideoFormData(device, page_count, page_size, { ...(tag && { tag }), ...(resource_type && { resource_type }), ...(sort && { sort }), }); return videoRequest(`/media/${plat_id}/search`, formData); }; // 视频详情接口 export const getVideoDetail = async ( device: string, resource_id: string ): Promise => { const formData = createVideoFormData(device, 1, 20, { resource_id }); return videoRequest(`/media/${user_id}/movie/play`, formData); }; // 视频顶部标签 查询接口 - 已废弃,现在使用固定的菜单数据 // export const getVideoMenu = async ( // device: string, // type: "1" | "2", // page_count = 1, // page_size = 20 // ): Promise => { // const formData = createVideoFormData(device, page_count, page_size, { type }); // return videoRequest(`/media/${plat_id}/menu`, formData); // }; // 点播集 查询接口 export const getVodList = async ( device: string, hash: string, page_count = 1, page_size = 20 ): Promise => { const formData = createVideoFormData(device, page_count, page_size, { hash }); return videoRequest(`/media/${plat_id}/menu/vods`, formData); }; // ===================== 全局请求拦截器:移除 HLS 视频相关文件的 Range 请求头 ===================== let originalXHROpen: any = null; let originalXHRSetRequestHeader: any = null; let originalFetch: any = null; let hlsInterceptorActive = false; const shouldRemoveRangeHeader = (url: string): boolean => { if (!url) return false; if (/\.m3u8(\?|$)/i.test(url)) { return true; } if ( /\/dx\d+/i.test(url) || // 匹配 dx002, dx003 等 /\/[^\/]+\.ts(\?|$)/i.test(url) || // 匹配 .ts 分片文件 /\/segment\d+/i.test(url) || // 匹配 segment1, segment2 等 /\/chunk\d+/i.test(url) ) { return true; } return false; }; /** * 设置全局拦截器以移除 HLS 视频相关文件的 Range 请求头 */ const setupHLSInterceptor = (): void => { if (hlsInterceptorActive) return; originalXHROpen = XMLHttpRequest.prototype.open; originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader; originalFetch = window.fetch; XMLHttpRequest.prototype.open = function ( method: string, url: string | URL, ...rest: any[] ) { (this as any)._url = url.toString(); return originalXHROpen.apply(this, [method, url, ...rest]); }; XMLHttpRequest.prototype.setRequestHeader = function ( header: string, value: string ) { const url = (this as any)._url || ""; if (shouldRemoveRangeHeader(url) && header.toLowerCase() === "range") { return; } return originalXHRSetRequestHeader.apply(this, [header, value]); }; window.fetch = function ( input: RequestInfo | URL, init?: RequestInit ): Promise { const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : (input as Request).url; if (shouldRemoveRangeHeader(url)) { const modifiedInit = { ...init }; if (modifiedInit.headers) { const headers = new Headers(modifiedInit.headers); headers.delete("Range"); headers.delete("range"); modifiedInit.headers = headers; } else { modifiedInit.headers = new Headers(); } return originalFetch.apply(this, [input, modifiedInit]); } return originalFetch.apply(this, [input, init]); }; hlsInterceptorActive = true; console.log("HLS Range Header Interceptor Activated"); }; if (typeof window !== "undefined") { setupHLSInterceptor(); } export default api;