From 4041b45ccafd9ae36fb6e0bf446a20f119836c50 Mon Sep 17 00:00:00 2001 From: xingyy <64720302+Concur-max@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:21:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(api-public):=20=E6=96=B0=E5=A2=9E=E5=85=AC?= =?UTF-8?q?=E5=85=B1=E7=9B=B4=E6=92=AD=E5=AE=A4=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 HTTP 请求工具和 API 接口定义 - 实现公共直播室页面组件和业务逻辑 - 集成阿里云播放器 - 添加指纹识别功能 - 优化错误处理和国际化支持 --- app/api-public/http.js | 125 ++++++++++++++++++ app/api-public/public/index.js | 25 ++++ app/api/public/index.js | 28 ++++ .../components/broadcast/index.vue | 76 +++++++++++ app/pages/publicLiveRoom/index.client.vue | 123 +++++++++++++++++ app/plugins/http.ts | 5 +- app/stores/live/index.js | 1 + app/stores/public/index.js | 8 ++ app/utils/fingerprint.js | 17 +++ 9 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 app/api-public/http.js create mode 100644 app/api-public/public/index.js create mode 100644 app/api/public/index.js create mode 100644 app/pages/publicLiveRoom/components/broadcast/index.vue create mode 100644 app/pages/publicLiveRoom/index.client.vue create mode 100644 app/stores/public/index.js create mode 100644 app/utils/fingerprint.js diff --git a/app/api-public/http.js b/app/api-public/http.js new file mode 100644 index 0000000..c86dc5c --- /dev/null +++ b/app/api-public/http.js @@ -0,0 +1,125 @@ +import {useRuntimeConfig} from '#app' +import {ofetch} from 'ofetch' +import {message} from '@/components/x-message/useMessage.js' +import { getFingerprint } from '@/utils/fingerprint' +let httpStatusErrorHandler +let http + +// HTTP 状态码映射 - 使用i18n国际化 +export function setupHttp() { + if (http) return http + + const config = useRuntimeConfig() + const baseURL = config.public.NUXT_PUBLIC_API_BASE + const router = useRouter() + const i18n = useNuxtApp().$i18n + + // 国际化的HTTP状态码映射 + const HTTP_STATUS_MAP = { + 400: i18n.t('http.error.badRequest'), + 401: i18n.t('http.error.unauthorized'), + 403: i18n.t('http.error.forbidden'), + 404: i18n.t('http.error.notFound'), + 500: i18n.t('http.error.serverError'), + 502: i18n.t('http.error.badGateway'), + 503: i18n.t('http.error.serviceUnavailable'), + 504: i18n.t('http.error.gatewayTimeout') + } + + const defaultOptions = { + baseURL, + headers: { 'Content-Type': 'application/json' }, + timeout: 15000, // 15秒超时 + retry: 3, + retryDelay: 1000, + } + + http = ofetch.create({ + ...defaultOptions, + + // 请求拦截 + async onRequest({ options, request }) { + const fingerprint = await getFingerprint() + console.log('fingerprint',fingerprint) + // 添加 token + options.headers = { + 'Authorization': '12312', + ...options.headers, + 'fingerprint':fingerprint, + 'accept-language': i18n.locale.value + } + + // GET 请求添加时间戳防止缓存 + if (request.toLowerCase().includes('get')) { + options.params = { + ...options.params, + _t: Date.now() + } + } + }, + + // 响应拦截 + async onResponse({ response }) { + const data = response._data + + // 处理业务错误 + if (data.status === 1) { + message.error(data.msg || i18n.t('http.error.operationFailed')) + } + + return response + }, + + // 响应错误处理 + async onResponseError({ response, request }) { + // 网络错误 + if (!response) { + message.error(i18n.t('http.error.networkError')) + return Promise.reject(new Error(i18n.t('http.error.networkError'))) + } + const status = response.status + const data = response._data + + // 处理 HTTP 状态错误 + const errorMessage = data.msg || HTTP_STATUS_MAP[status] || i18n.t('http.error.requestFailed') + + if (Array.isArray(data.msg)) { + data.msg.forEach(item => { + httpStatusErrorHandler?.(item, status) + }) + } else { + httpStatusErrorHandler?.(errorMessage, status) + } + + message.error(errorMessage) + return Promise.reject(data) + }, + }) + + return http +} + +export function createAbortController() { + return new AbortController() +} + +export function injectHttpStatusErrorHandler(handler) { + httpStatusErrorHandler = handler +} + +export function getHttp() { + if (!http) { + throw new Error(useNuxtApp().$i18n.t('http.error.httpNotInitialized')) + } + return http +} + +// 导出请求工具函数 +export async function request({url,...options}) { + const http = getHttp() + try { + return await http(url, {...options,body:options.data}) + } catch (error) { + throw error + } +} \ No newline at end of file diff --git a/app/api-public/public/index.js b/app/api-public/public/index.js new file mode 100644 index 0000000..5242052 --- /dev/null +++ b/app/api-public/public/index.js @@ -0,0 +1,25 @@ +import { request } from "../http"; + +export async function defaultDetail(data) { + + return await request( { + url:'/api/v1/m/auction/out/default/detail', + method: 'POST', + data + }) +} +export async function getLink(data) { + + return await request( { + url:'/api/v1/m/auction/out/log/sendlog/aljdfoqueoirhkjsadhfiu', + method: 'POST', + data + }) +} +export async function outBuyList(data) { + return await request( { + url:'/api/v1/m/auction/out/buy/list', + method: 'POST', + data + }) +} \ No newline at end of file diff --git a/app/api/public/index.js b/app/api/public/index.js new file mode 100644 index 0000000..2679faf --- /dev/null +++ b/app/api/public/index.js @@ -0,0 +1,28 @@ +import { request } from "../http"; + +export async function defaultDetail(data) { + + return await request( { + url:'/api/v1/m/auction/out/default/detail', + headers:{ + 'fingerprint':'12312' + }, + method: 'POST', + data + }) +} +export async function getLink(data) { + + return await request( { + url:'/api/v1/m/auction/out/log/sendlog/aljdfoqueoirhkjsadhfiu', + method: 'POST', + data + }) +} +export async function outBuyList(data) { + return await request( { + url:'/api/v1/m/auction/out/buy/list', + method: 'POST', + data + }) +} \ No newline at end of file diff --git a/app/pages/publicLiveRoom/components/broadcast/index.vue b/app/pages/publicLiveRoom/components/broadcast/index.vue new file mode 100644 index 0000000..284e48e --- /dev/null +++ b/app/pages/publicLiveRoom/components/broadcast/index.vue @@ -0,0 +1,76 @@ + + + + + \ No newline at end of file diff --git a/app/pages/publicLiveRoom/index.client.vue b/app/pages/publicLiveRoom/index.client.vue new file mode 100644 index 0000000..35937d9 --- /dev/null +++ b/app/pages/publicLiveRoom/index.client.vue @@ -0,0 +1,123 @@ + + + + + + \ No newline at end of file diff --git a/app/plugins/http.ts b/app/plugins/http.ts index 16e2c67..2838dc9 100644 --- a/app/plugins/http.ts +++ b/app/plugins/http.ts @@ -1,6 +1,9 @@ import { setupHttp } from '@/api/http' -import { setupHttp as setupHttp1} from '@/api-collect-code/http' +import { setupHttp as setupHttp1} from '@/api-collect-code/http' +import { setupHttp as setupHttp2} from '@/api-public/http' export default defineNuxtPlugin(() => { setupHttp() setupHttp1() + setupHttp2() }) + diff --git a/app/stores/live/index.js b/app/stores/live/index.js index adab290..ff7fbcf 100644 --- a/app/stores/live/index.js +++ b/app/stores/live/index.js @@ -248,6 +248,7 @@ export const liveStore = createGlobalState(() => { } } return{ + decryptUtils, wsClient, fullLive, isMinWindow, diff --git a/app/stores/public/index.js b/app/stores/public/index.js new file mode 100644 index 0000000..7bf44aa --- /dev/null +++ b/app/stores/public/index.js @@ -0,0 +1,8 @@ +import {createGlobalState, useLocalStorage} from '@vueuse/core' +export const publicStore = createGlobalState(() => { + const auctionData=useLocalStorage('auctionData',{}) + return { + auctionData + } +}) + diff --git a/app/utils/fingerprint.js b/app/utils/fingerprint.js new file mode 100644 index 0000000..d0f9bb6 --- /dev/null +++ b/app/utils/fingerprint.js @@ -0,0 +1,17 @@ +import FingerprintJS from '@fingerprintjs/fingerprintjs' + +export async function getFingerprint() { + try { + // 初始化 FingerprintJS + const fp = await FingerprintJS.load() + + // 获取访问者的指纹 + const result = await fp.get() + + // 返回指纹哈希值 + return result.visitorId + } catch (error) { + console.error('获取浏览器指纹失败:', error) + return null + } +} \ No newline at end of file