From 567e77cd8753ec2cad25131c1446d3924a30907f Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sun, 18 Jan 2026 15:27:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 4 +- .gitignore | 2 +- frontend/api/axios/client.js | 184 +++++--------------- frontend/api/services/bailian.js | 46 +++++ frontend/api/services/index.js | 11 +- frontend/api/services/tikhub.js | 43 ----- frontend/app/web-gold/src/api/auth.js | 177 +++++++------------ frontend/app/web-gold/src/api/bailian.js | 6 + frontend/app/web-gold/src/api/common.js | 27 +-- frontend/app/web-gold/src/api/http.js | 69 ++------ frontend/app/web-gold/src/api/userinfo.js | 154 +++++++++++++++++ frontend/app/web-gold/src/stores/user.js | 2 +- frontend/hooks/web/useUserInfo.js | 200 ---------------------- frontend/hooks/web/useVoiceText.ts | 53 ++---- 14 files changed, 358 insertions(+), 620 deletions(-) create mode 100644 frontend/api/services/bailian.js delete mode 100644 frontend/api/services/tikhub.js create mode 100644 frontend/app/web-gold/src/api/bailian.js create mode 100644 frontend/app/web-gold/src/api/userinfo.js delete mode 100644 frontend/hooks/web/useUserInfo.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1ad83c6ebf..22658c62d0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -66,7 +66,9 @@ "Skill(code-simplifier)", "Skill(code-simplifier:*)", "Bash(npm run lint:es -- --no-fix src/views/material/MixTaskList.vue)", - "Bash(npm run)" + "Bash(npm run)", + "Skill(plan)", + "Skill(plan:*)" ], "deny": [], "ask": [] diff --git a/.gitignore b/.gitignore index 8ba8494283..95c4901270 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ settings.local.json nbproject/private/ build/* nbbuild/ -dist/ +dist nbdist/ .nb-gradle/ diff --git a/frontend/api/axios/client.js b/frontend/api/axios/client.js index 93e60c6bf9..b3e580bb45 100644 --- a/frontend/api/axios/client.js +++ b/frontend/api/axios/client.js @@ -1,15 +1,7 @@ -/** - * Mono 级别的 C 端 Axios 实例 - * 供 monorepo 中所有应用使用的统一 HTTP 客户端 - */ - import axios from 'axios' -// 直接使用实例(最简单、最可靠) import tokenManager from '@gold/utils/token-manager' -/** - * 不需要 token 的接口白名单 - */ +// Token白名单 const WHITE_LIST = [ '/auth/login', '/auth/send-sms-code', @@ -20,89 +12,38 @@ const WHITE_LIST = [ '/auth/refresh-token', ] -/** - * 检查 URL 是否在白名单中 - */ function isInWhiteList(url) { return !!(url && WHITE_LIST.some(path => url.includes(path))) } -/** - * 自动刷新 token 的锁机制和队列 - */ +// Token刷新状态管理 let isRefreshing = false let refreshSubscribers = [] -/** - * 订阅 token 刷新完成 - * @param {Function} callback - 回调函数 - */ function subscribeTokenRefresh(callback) { - if (isRefreshing) { - refreshSubscribers.push(callback) - } else { - callback() - } + isRefreshing ? refreshSubscribers.push(callback) : callback() } -/** - * 执行所有订阅回调 - */ function onRefreshed() { refreshSubscribers.forEach(callback => callback()) refreshSubscribers = [] } /** - * 处理 401 未授权错误 - * 注意:只做清理工作,不处理重定向(重定向由上层回调处理) - */ -let isHandling401 = false -let handling401Timeout = null - -function handle401Error(error) { - // 防止重复处理 - if (isHandling401 || error?._handled) { - return - } - - isHandling401 = true - error && (error._handled = true) - - try { - tokenManager.clearTokens() - const message = error?.needRelogin - ? '[HTTP] Token已清空,refreshToken无效需要重新登录' - : '[HTTP] Token已清空,因401错误' - console.warn(message) - } catch (e) { - console.error('[HTTP] 清空 token 失败:', e) - } - - if (handling401Timeout) { - clearTimeout(handling401Timeout) - } - - handling401Timeout = setTimeout(() => { - isHandling401 = false - }, 3000) -} - -/** - * 创建 C 端 Axios 实例 + * 创建Axios实例 * @param {Object} options - 配置选项 - * @param {string} options.baseURL - 基础 URL - * @param {number} options.timeout - 超时时间(毫秒) - * @param {Function} options.on401 - 401 错误处理函数 - * @param {Function} options.on403 - 403 错误处理函数 - * @param {Function} options.refreshTokenFn - Token 刷新函数(可选) - * @returns {AxiosInstance} Axios 实例 + * @param {string} options.baseURL - 基础URL + * @param {number} options.timeout - 超时时间 + * @param {Function} options.on401 - 401错误处理函数 + * @param {Function} options.on403 - 403错误处理函数 + * @param {Function} options.refreshTokenFn - Token刷新函数 + * @returns {AxiosInstance} Axios实例 */ -export function createClientAxios(options = {}) { +export default function createClientAxios(options = {}) { const { baseURL = '/', timeout = 180000, - on401 = handle401Error, + on401 = null, on403 = null, refreshTokenFn = null, } = options @@ -114,7 +55,16 @@ export function createClientAxios(options = {}) { // 请求拦截器 client.interceptors.request.use((config) => { - // 添加 tenant-id + const needToken = config.headers?.isToken !== false && !isInWhiteList(config.url || '') + + if (!needToken) { + return config + } + + const currentToken = tokenManager.getAccessToken() + if (!currentToken) { + return config + } const tenantId = (typeof import.meta !== 'undefined' && import.meta.env?.VITE_TENANT_ID) || (typeof process !== 'undefined' && process.env?.VITE_TENANT_ID) || @@ -123,27 +73,9 @@ export function createClientAxios(options = {}) { if (tenantId) { config.headers['tenant-id'] = tenantId } - - // 检查是否需要认证 - const needToken = config.headers?.isToken !== false && !isInWhiteList(config.url || '') - - if (!needToken) { - return config - } - - // 检查 token 是否即将过期(30秒缓冲) - const BUFFER_TIME = 30 * 1000 - const currentToken = tokenManager.getAccessToken() - - if (!currentToken) { - console.warn('[Token] 没有可用的 accessToken') - return config - } - - const isTokenExpired = tokenManager.isExpired(BUFFER_TIME) + const isTokenExpired = tokenManager.isExpired(30 * 1000) if (!isTokenExpired) { - // Token 未过期,直接添加 Authorization 头 const authHeader = tokenManager.getAuthHeader() if (authHeader) { config.headers.Authorization = authHeader @@ -151,44 +83,32 @@ export function createClientAxios(options = {}) { return config } - // Token 即将过期,需要刷新 - console.info('[Token] Token刷新') - - // 如果不在刷新过程中,启动刷新 if (!isRefreshing) { isRefreshing = true if (!refreshTokenFn || typeof refreshTokenFn !== 'function') { - console.warn('[Token] 未提供刷新函数,跳过刷新') isRefreshing = false onRefreshed() return config } - refreshTokenFn() - .then(() => { - console.info('[Token] 刷新成功') - isRefreshing = false - onRefreshed() - }) - .catch((error) => { - isRefreshing = false - onRefreshed() - console.error('[Token] 刷新失败:', error.message) - - if (error.code === 401 && error.needRelogin) { - console.info('[Token] refreshToken无效,需要重新登录') - } - }) + refreshTokenFn().finally(() => { + isRefreshing = false + onRefreshed() + }) } - // 等待刷新完成 - return new Promise((resolve) => { + return new Promise((resolve, reject) => { subscribeTokenRefresh(() => { - const authHeader = tokenManager.getAuthHeader() - if (authHeader) { - config.headers.Authorization = authHeader + const token = tokenManager.getAccessToken() + if (!token) { + return reject({ + message: 'Token已过期,需要重新登录', + code: 401, + }) } + + config.headers.Authorization = tokenManager.getAuthHeader() resolve(config) }) }) @@ -199,24 +119,21 @@ export function createClientAxios(options = {}) { (response) => { const data = response.data - // 检查业务状态码 if (data && typeof data.code === 'number') { if (data.code === 0 || data.code === 200) { return data } - // 创建业务错误对象 const error = new Error(data?.message || data?.msg || '请求失败') error.code = data?.code error.data = data - // 业务码 403 只在响应拦截器处理,避免重复处理 if (data.code === 403 && typeof on403 === 'function') { - const forbiddenError = new Error('权限不足,无法访问该资源') - forbiddenError.code = 403 - on403(forbiddenError) + on403(error) + } + if (data.code === 401 && typeof on401 === 'function') { + refreshTokenFn && refreshTokenFn() } - return Promise.reject(error) } @@ -224,13 +141,13 @@ export function createClientAxios(options = {}) { }, (error) => { const status = error.response?.status + const code = error.code - if (status === 401 && typeof on401 === 'function') { + if ((status === 401 || code === 401) && typeof on401 === 'function') { on401(error) - } else if (status === 403 && typeof on403 === 'function') { - const forbiddenError = new Error('权限不足,无法访问该资源') - forbiddenError.code = 403 - on403(forbiddenError) + } + else if ((status === 403 || code === 403) && typeof on403 === 'function') { + on403(error) } return Promise.reject(error) @@ -238,13 +155,4 @@ export function createClientAxios(options = {}) { ) return client -} - -/** - * 默认导出的 C 端 Axios 实例 - * 可在应用层覆盖配置 - */ -export const clientAxios = createClientAxios() - -export default clientAxios - +} \ No newline at end of file diff --git a/frontend/api/services/bailian.js b/frontend/api/services/bailian.js new file mode 100644 index 0000000000..fb3787ee19 --- /dev/null +++ b/frontend/api/services/bailian.js @@ -0,0 +1,46 @@ +import { API_BASE } from '@gold/config/api' + +// 百炼API基础路径 +const BASE_URL = API_BASE.TIKHUB_APP || API_BASE.TIKHUB || '' + +/** + * 创建百炼Service实例 + * @param {Object} httpClient - HTTP客户端实例(可选) + * @returns {Object} 百炼API对象 + */ +export function createBaiLianService(httpClient) { + const getClient = async () => { + if (httpClient) { + return httpClient + } + const clientModule = await import('@gold/api/axios/client') + return clientModule.default + } + + return { + /** + * 视频转字符(音频转文字) + * @param {Object} params - 请求参数 + * @param {string[]} params.fileLinkList - 音频文件链接列表 + * @returns {Promise<{ data: string }>} 响应数据 + */ + async videoToCharacters(params) { + const { fileLinkList } = params + const client = await getClient() + + return await client.post(`${BASE_URL}/videoToCharacters2`, { + fileLinkList, + }) + }, + + /** + * 调用工作流 + * @param {Object} data - 请求数据 + * @returns {Promise} 响应数据 + */ + async callWorkflow(data) { + const client = await getClient() + return await client.post(`${BASE_URL}/callWorkflow`, data) + }, + } +} diff --git a/frontend/api/services/index.js b/frontend/api/services/index.js index 1e33117ec0..4004bc5ad8 100644 --- a/frontend/api/services/index.js +++ b/frontend/api/services/index.js @@ -1,11 +1,6 @@ -/** - * API 服务统一导出 - * 按功能模块组织,便于维护和扩展 - */ +// API服务统一导出 +export { createBaiLianService } from './bailian' -export { TikHubService } from './tikhub' - -// 可以继续添加其他服务模块 +// 后续可添加其他服务模块 // export { UserService } from './user' // export { ChatService } from './chat' - diff --git a/frontend/api/services/tikhub.js b/frontend/api/services/tikhub.js deleted file mode 100644 index 744e796cb1..0000000000 --- a/frontend/api/services/tikhub.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * TikHub API 服务 - * 封装 TikHub 相关的 API 调用 - */ - -import { clientAxios } from '@gold/api/axios/client' -import { API_BASE } from '@gold/config/api' - -/** - * TikHub API 基础路径 - */ -const BASE_URL = API_BASE.TIKHUB_APP || API_BASE.TIKHUB || '' - -/** - * TikHub API 服务 - */ -export const TikHubService = { - /** - * 视频转字符(音频转文字) - * @param {Object} params - 请求参数 - * @param {string[]} params.fileLinkList - 音频文件链接列表 - * @returns {Promise<{ data: string }>} 响应数据 - */ - async videoToCharacters(params) { - const { fileLinkList } = params - - return await clientAxios.post(`${BASE_URL}/videoToCharacters2`, { - fileLinkList, - }) - }, - - /** - * 调用工作流 - * @param {Object} data - 请求数据 - * @returns {Promise} 响应数据 - */ - async callWorkflow(data) { - return await clientAxios.post(`${BASE_URL}/callWorkflow`, data) - }, -} - -export default TikHubService - diff --git a/frontend/app/web-gold/src/api/auth.js b/frontend/app/web-gold/src/api/auth.js index 3bc2ca99cf..c7ebb38fd9 100644 --- a/frontend/app/web-gold/src/api/auth.js +++ b/frontend/app/web-gold/src/api/auth.js @@ -1,15 +1,14 @@ - import api from '@/api/http' -// 直接使用实例(最简单、最可靠) import tokenManager from '@gold/utils/token-manager' -// 使用公共配置 import { API_BASE } from '@gold/config/api' +import router from '@/router' +import { getUserInfo,clearUserInfoCache } from './userinfo' const SERVER_BASE = API_BASE.APP_MEMBER /** - * 保存 token 的辅助函数 - * @param {Object} info - 包含 accessToken 和 refreshToken 的对象 + * 保存token + * @param {Object} info - 包含accessToken和refreshToken的对象 */ function saveTokens(info) { if (!info?.accessToken && !info?.refreshToken) { @@ -24,62 +23,50 @@ function saveTokens(info) { }) } + /** * 清除用户信息缓存 */ -async function clearUserCache() { +function clearUserCache() { try { - const { clearUserInfoCache } = await import('@gold/hooks/web/useUserInfo') clearUserInfoCache() } catch (e) { // 清除缓存失败不影响登录流程 } } -/** - * 短信场景枚举(请与后端配置保持一致) - * - MEMBER_LOGIN: 会员短信登录场景 - * - MEMBER_UPDATE_PASSWORD: 已登录用户修改密码(短信校验)场景 - * - MEMBER_RESET_PASSWORD: 未登录用户忘记密码重置(短信校验)场景 - * 如有"注册"独立场景,可在此添加:MEMBER_REGISTER: 13 - */ +// 短信场景枚举 export const SMS_SCENE = { MEMBER_LOGIN: 1, MEMBER_UPDATE_PASSWORD: 3, MEMBER_RESET_PASSWORD: 4, -}; +} -/** - * 短信模板编码常量 - */ +// 短信模板编码 export const SMS_TEMPLATE_CODE = { USER_REGISTER: 'muye-user-code', } /** * 账号密码登录 - * POST /member/auth/login - * - * @param {string} mobile - 手机号(必填) - * @param {string} password - 密码(必填,长度 4-16) - * @returns {Promise} data.data: { accessToken, refreshToken, expiresTime, userInfo } + * @param {string} mobile - 手机号 + * @param {string} password - 密码 + * @returns {Promise} 登录信息 */ export async function loginByPassword(mobile, password) { const { data } = await api.post(`${SERVER_BASE}/auth/login`, { mobile, password }) const info = data || {} saveTokens(info) - await clearUserCache() + clearUserCache() return info } /** * 发送短信验证码 - * POST /member/auth/send-sms-code - * - * @param {string} mobile - 手机号(登录/忘记密码等需要;修改密码场景可不传) - * @param {number} scene - 短信场景(见 SMS_SCENE) - * @param {string} templateCode - 模板编码(可选,如 'muye-user-code') - * @returns {Promise} 后端通用响应结构 + * @param {string} mobile - 手机号 + * @param {number} scene - 短信场景 + * @param {string} templateCode - 模板编码 + * @returns {Promise} 响应数据 */ export async function sendSmsCode(mobile, scene, templateCode) { const body = { scene } @@ -91,13 +78,11 @@ export async function sendSmsCode(mobile, scene, templateCode) { } /** - * 校验短信验证码(可选前置校验,一般直接在业务接口 use) - * POST /member/auth/validate-sms-code - * - * @param {string} mobile - 手机号(必填) - * @param {string} code - 验证码(必填,4-6 位数字) - * @param {number} scene - 短信场景(必填,见 SMS_SCENE) - * @returns {Promise} 后端通用响应结构 + * 校验短信验证码 + * @param {string} mobile - 手机号 + * @param {string} code - 验证码 + * @param {number} scene - 短信场景 + * @returns {Promise} 响应数据 */ export async function validateSmsCode(mobile, code, scene) { const { data } = await api.post(`${SERVER_BASE}/auth/validate-sms-code`, { mobile, code, scene }) @@ -105,63 +90,52 @@ export async function validateSmsCode(mobile, code, scene) { } /** - * 手机+验证码登录(首次即自动注册) - * POST /member/auth/sms-login - * - * @param {string} mobile - 手机号(必填) - * @param {string} code - 短信验证码(必填) - * @returns {Promise} data.data: { accessToken, refreshToken, expiresTime, userInfo } + * 手机+验证码登录 + * @param {string} mobile - 手机号 + * @param {string} code - 短信验证码 + * @returns {Promise} 登录信息 */ export async function loginBySms(mobile, code) { const { data } = await api.post(`${SERVER_BASE}/auth/sms-login`, { mobile, code }) const info = data || {} saveTokens(info) - await clearUserCache() + clearUserCache() return info } /** * 刷新令牌 - * POST /member/auth/refresh-token?refreshToken=xxx - * - * @returns {Promise} data.data: { accessToken, refreshToken, expiresTime, userInfo } + * @returns {Promise} 新的token信息 */ export async function refreshToken() { const rt = tokenManager.getRefreshToken() - if (!rt) throw new Error('缺少 refresh_token') - - const { data } = await api.post(`${SERVER_BASE}/auth/refresh-token`, null, { params: { refreshToken: rt } }) - - // 检查业务状态码 - if (data?.code === 401) { - // 401: refreshToken 无效或过期 - tokenManager.clearTokens() - await clearUserCache() - router.push('/login') - - const authError = new Error('登录已过期,请重新登录') + if (!rt) { + const authError = new Error('缺少 refresh_token') authError.code = 401 authError.needRelogin = true throw authError } - if (data?.code !== 0 && data?.code !== 200) { - throw new Error(data?.message || data?.msg || '刷新token失败') + const { data,code,message } = await api.post(`${SERVER_BASE}/auth/refresh-token`, null, { params: { refreshToken: rt } }) + + if (code === 401) { + tokenManager.clearTokens() + clearUserCache() + return router.push('/login') + } + if (code !== 0 && code !== 200) { + throw new Error(message || '刷新token失败') } const info = data || {} saveTokens(info) - await clearUserCache() + clearUserCache() return info } /** - * 登录态下:发送"修改密码"验证码 - * - 场景:SMS_SCENE.MEMBER_UPDATE_PASSWORD - * - 特性:后端会以当前登录用户的手机号为准,无需传 mobile - * POST /member/auth/send-sms-code - * - * @returns {Promise} 后端通用响应结构 + * 发送修改密码验证码 + * @returns {Promise} 响应数据 */ export async function sendUpdatePasswordCode() { const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, { @@ -171,12 +145,10 @@ export async function sendUpdatePasswordCode() { } /** - * 登录态下:通过短信验证码修改密码 - * PUT /member/user/update-password - * - * @param {string} newPassword - 新密码(必填,4-16 位) - * @param {string} smsCode - 短信验证码(必填,4-6 位数字) - * @returns {Promise} 后端通用响应结构 + * 通过短信验证码修改密码 + * @param {string} newPassword - 新密码 + * @param {string} smsCode - 短信验证码 + * @returns {Promise} 响应数据 */ export async function updatePasswordBySmsCode(newPassword, smsCode) { const { data } = await api.put(`${SERVER_BASE}/user/update-password`, { @@ -187,12 +159,9 @@ export async function updatePasswordBySmsCode(newPassword, smsCode) { } /** - * 未登录:发送"忘记密码"验证码 - * - 场景:SMS_SCENE.MEMBER_RESET_PASSWORD - * POST /member/auth/send-sms-code - * - * @param {string} mobile - 手机号(必填) - * @returns {Promise} 后端通用响应结构 + * 发送重置密码验证码 + * @param {string} mobile - 手机号 + * @returns {Promise} 响应数据 */ export async function sendResetPasswordCode(mobile) { const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, { @@ -203,13 +172,11 @@ export async function sendResetPasswordCode(mobile) { } /** - * 未登录:通过手机验证码重置密码(忘记密码) - * PUT /member/user/reset-password - * - * @param {string} mobile - 手机号(必填) - * @param {string} newPassword - 新密码(必填,4-16 位) - * @param {string} smsCode - 短信验证码(必填,4-6 位数字) - * @returns {Promise} 后端通用响应结构 + * 通过手机验证码重置密码 + * @param {string} mobile - 手机号 + * @param {string} newPassword - 新密码 + * @param {string} smsCode - 短信验证码 + * @returns {Promise} 响应数据 */ export async function resetPasswordBySms(mobile, newPassword, smsCode) { const { data } = await api.put(`${SERVER_BASE}/user/reset-password`, { @@ -221,41 +188,27 @@ export async function resetPasswordBySms(mobile, newPassword, smsCode) { } /** - * 获取用户信息(C端) - * GET /member/user/get - * + * 获取用户信息 * @returns {Promise} 用户信息对象 */ -export async function getUserInfo() { - const { data } = await api.get(`${SERVER_BASE}/user/get`) - return data || {} +export function getUserInfoAuth() { + return getUserInfo() } /** - * "手机+验证码+密码注册"组合流程(基于短信登录即注册 + 设置密码) - * 说明: - * - 1) 发送登录场景验证码(可选:若已拿到 code 可跳过) - * - 2) 短信登录:首次会自动注册并返回 token - * - 3) 登录态下发送"修改密码"验证码 - * - 4) 用短信验证码设置密码 - * - * @param {string} mobile - 手机号(必填) - * @param {string} loginCode - 第一次登录使用的短信验证码(必填) - * @param {string} newPassword - 注册后设置的新密码(必填) - * @param {string} updatePwdCode - 设置密码用到的短信验证码(必填) + * 手机+验证码+密码注册流程 + * @param {string} mobile - 手机号 + * @param {string} loginCode - 登录验证码 + * @param {string} newPassword - 新密码 + * @param {string} updatePwdCode - 设置密码验证码 * @returns {Promise} */ export async function registerWithMobileCodePassword(mobile, loginCode, newPassword, updatePwdCode) { - // 2) 短信登录(首次即注册) await loginBySms(mobile, loginCode) - - // 4) 用短信验证码设置密码 await updatePasswordBySmsCode(newPassword, updatePwdCode) } -/** - * 导出一个默认对象,便于统一引入 - */ +// 默认导出 export default { SMS_SCENE, SMS_TEMPLATE_CODE, @@ -269,5 +222,5 @@ export default { sendResetPasswordCode, resetPasswordBySms, registerWithMobileCodePassword, - getUserInfo, -}; + getUserInfo: getUserInfoAuth, +} diff --git a/frontend/app/web-gold/src/api/bailian.js b/frontend/app/web-gold/src/api/bailian.js new file mode 100644 index 0000000000..2314179a37 --- /dev/null +++ b/frontend/app/web-gold/src/api/bailian.js @@ -0,0 +1,6 @@ +import http from './http' +import { createBaiLianService } from '@gold/api/services/bailian' + +const defaultService = createBaiLianService(http) + +export default defaultService diff --git a/frontend/app/web-gold/src/api/common.js b/frontend/app/web-gold/src/api/common.js index 9d90b52058..1657c5efd8 100644 --- a/frontend/app/web-gold/src/api/common.js +++ b/frontend/app/web-gold/src/api/common.js @@ -1,38 +1,27 @@ -/** - * 应用层 API 服务 - * 封装应用特定的 API 调用,使用 mono 级别的服务 - */ - import { fetchEventSource } from '@microsoft/fetch-event-source' -// 直接使用实例(最简单、最可靠) import tokenManager from '@gold/utils/token-manager' -import { TikHubService } from '@gold/api/services' +import BaiLianService from './bailian' import { API_BASE } from '@gold/config/api' -/** - * TikHub API 基础路径 - */ +// 百炼API基础路径 const TIKHUB_BASE = API_BASE.TIKHUB_APP || API_BASE.TIKHUB || '' -/** - * 应用层通用服务 - */ +// 应用层通用服务 export const CommonService = { /** * 视频转字符(音频转文字) - * 直接使用 mono 级别的 TikHub 服务 */ videoToCharacters(data) { - return TikHubService.videoToCharacters(data) + return BaiLianService.videoToCharacters(data) }, /** * 调用工作流 */ callWorkflow(data) { - return TikHubService.callWorkflow(data) + return BaiLianService.callWorkflow(data) }, - + /** * 流式调用工作流(SSE) */ @@ -49,7 +38,7 @@ export const CommonService = { let retryCount = 0 const maxRetries = 0 - + return fetchEventSource(`${TIKHUB_BASE}/callWorkflow`, { method: 'post', headers: { @@ -81,5 +70,3 @@ export const CommonService = { } export default CommonService - - diff --git a/frontend/app/web-gold/src/api/http.js b/frontend/app/web-gold/src/api/http.js index c76b38fa03..2948a9b092 100644 --- a/frontend/app/web-gold/src/api/http.js +++ b/frontend/app/web-gold/src/api/http.js @@ -1,76 +1,27 @@ -/** - * 应用层 HTTP 客户端 - * 支持自定义拦截器的可扩展版本 - * - 提供 createHttpClient 工厂函数 - * - 业务代码可以创建自己的 http 实例并添加拦截器 - */ - -import { createClientAxios } from '@gold/api/axios/client' +import createClientAxios from '@gold/api/axios/client' import { refreshToken } from '@/api/auth' import router from '@/router' /** - * 创建应用层 HTTP 客户端实例 + * 创建HTTP客户端实例 * @param {Object} options - 配置选项 - * @param {Function} options.on401 - 401 错误处理回调 - * @param {Function} options.on403 - 403 错误处理回调 - * @returns {AxiosInstance} HTTP 客户端实例 + * @param {Function} options.on401 - 401错误处理回调 + * @param {Function} options.on403 - 403错误处理回调 + * @returns {AxiosInstance} HTTP客户端实例 */ export function createHttpClient(options = {}) { const { on401, on403 } = options - const httpClient = createClientAxios({ + return createClientAxios({ baseURL: '/', timeout: 180000, refreshTokenFn: refreshToken, - on401: async (error) => { - if (on401) { - await on401(error) - return - } - router.push('/login') - }, - on403: (error) => { - on403 ? on403(error) : router.push('/login') - }, + on401: on401 || ((error) => router.push('/login')), + on403: on403 || ((error) => router.push('/login')), }) - - return httpClient } -/** - * 默认的 HTTP 客户端实例 - * 业务代码可以: - * 1. 直接使用 http 实例 - * 2. 调用 createHttpClient() 创建自定义实例 - * 3. 在自定义实例上添加拦截器 - */ +// 默认HTTP客户端实例 const http = createHttpClient() -export default http - -/** - * 使用示例: - * - * 1. 直接使用默认实例 - * import http from '@/api/http' - * await http.post('/api/data') - * - * 2. 创建自定义实例并添加拦截器 - * import { createHttpClient } from '@/api/http' - * - * const myHttp = createHttpClient() - * myHttp.interceptors.request.use((config) => { - * config.headers['X-Custom-Header'] = 'value' - * return config - * }) - * - * 3. 自定义 401 处理 - * const myHttp = createHttpClient({ - * on401: (error) => { - * console.log('自定义 401 处理') - * } - * }) - */ - - +export default http \ No newline at end of file diff --git a/frontend/app/web-gold/src/api/userinfo.js b/frontend/app/web-gold/src/api/userinfo.js new file mode 100644 index 0000000000..3fa3e8227f --- /dev/null +++ b/frontend/app/web-gold/src/api/userinfo.js @@ -0,0 +1,154 @@ +import http from './http' +import { ref } from 'vue' +import { API_BASE } from '@gold/config/api' +import tokenManager from '@gold/utils/token-manager' + +// 缓存配置 +const CACHE_KEY = 'USER_INFO_CACHE' +const CACHE_DURATION = 5 * 60 * 1000 + +/** + * 使用useUserInfo构造用户信息管理 + * @param {Object} options - 配置选项 + * @param {string} options.baseUrl - API基础URL + * @returns {Object} 包含fetchUserInfo、loading、error、userInfo的对象 + */ +export function useUserInfo(options = {}) { + const loading = ref(false) + const error = ref(null) + const userInfo = ref(null) + + const baseUrl = options.baseUrl || API_BASE.APP_MEMBER + const apiUrl = `${baseUrl}/user/get` + + /** + * 获取Authorization头 + * @returns {string} + */ + const getAuthHeader = () => { + const token = tokenManager.getAccessToken() + if (token) { + return `Bearer ${token}` + } + + try { + const manualToken = sessionStorage.getItem('DEV_MANUAL_TOKEN') + if (manualToken) { + return `Bearer ${manualToken}` + } + } catch (e) { + // 忽略错误 + } + return '' + } + + /** + * 获取缓存的用户信息 + * @returns {Object|null} 缓存的用户信息或null + */ + const getCachedUserInfo = () => { + try { + const cached = sessionStorage.getItem(CACHE_KEY) + if (!cached) return null + + const { data, timestamp } = JSON.parse(cached) + const now = Date.now() + + if (now - timestamp > CACHE_DURATION) { + sessionStorage.removeItem(CACHE_KEY) + return null + } + + console.log('[UserInfo] 使用本地缓存') + return data + } catch (e) { + return null + } + } + + /** + * 存储用户信息到本地 + * @param {Object} userInfoData - 用户信息 + */ + const setCachedUserInfo = (userInfoData) => { + try { + const cacheData = { + data: userInfoData, + timestamp: Date.now() + } + sessionStorage.setItem(CACHE_KEY, JSON.stringify(cacheData)) + console.log('[UserInfo] 更新本地缓存') + } catch (e) { + console.error('缓存用户信息失败:', e) + } + } + + /** + * 获取用户信息 + * @returns {Promise} 用户信息对象 + */ + const fetchUserInfo = async () => { + loading.value = true + error.value = null + + try { + const cachedUserInfo = getCachedUserInfo() + if (cachedUserInfo) { + userInfo.value = cachedUserInfo + return cachedUserInfo + } + + const authHeader = getAuthHeader() + const response = await http.get(apiUrl, { + headers: { + 'Content-Type': 'application/json', + ...(authHeader && { Authorization: authHeader }), + }, + }) + + const data = response.data?.data || response.data + if (data) { + setCachedUserInfo(data) + userInfo.value = data + return data + } + } catch (err) { + error.value = err + console.error('获取用户信息失败:', err) + throw err + } finally { + loading.value = false + } + } + + return { + fetchUserInfo, + loading, + error, + userInfo, + } +} + + + +/** + * 清除用户信息缓存 + */ +export function clearUserInfoCache() { + try { + sessionStorage.removeItem(CACHE_KEY) + console.log('[UserInfo] 清除缓存') + } catch (e) { + console.error('清除缓存失败:', e) + } +} + +/** + * 直接获取用户信息 + * @param {Object} options - 配置选项 + * @returns {Promise} 用户信息对象 + */ +export async function getUserInfo(options = {}) { + const hook = useUserInfo(options) + return await hook.fetchUserInfo() +} diff --git a/frontend/app/web-gold/src/stores/user.js b/frontend/app/web-gold/src/stores/user.js index b75326fb2a..961b14c83a 100644 --- a/frontend/app/web-gold/src/stores/user.js +++ b/frontend/app/web-gold/src/stores/user.js @@ -2,7 +2,7 @@ import { ref, computed, watch } from 'vue' import { defineStore } from 'pinia' import { getJSON, setJSON, remove } from '@/utils/storage' import tokenManager from '@gold/utils/token-manager' -import { getUserInfo, clearUserInfoCache } from '@gold/hooks/web/useUserInfo' +import { getUserInfo, clearUserInfoCache } from '@/api/userinfo' const STORAGE_KEY = 'user_store_v1' diff --git a/frontend/hooks/web/useUserInfo.js b/frontend/hooks/web/useUserInfo.js deleted file mode 100644 index c2cb5a1752..0000000000 --- a/frontend/hooks/web/useUserInfo.js +++ /dev/null @@ -1,200 +0,0 @@ -/** - * 用户信息 Hook - * 封装获取用户信息的逻辑,可在各个应用中复用 - * - * 使用方式: - * import { useUserInfo } from '@gold/hooks/web/useUserInfo' - * - * const { fetchUserInfo, loading, error } = useUserInfo() - * await fetchUserInfo() - */ - -import { ref } from 'vue' -import axios from 'axios' -import { API_BASE } from '@gold/config/api' -import tokenManager from '@gold/utils/token-manager' - -// 本地存储配置 -const CACHE_KEY = 'USER_INFO_CACHE' -const CACHE_DURATION = 5 * 60 * 1000 // 5分钟缓存 - -/** - * 获取 Authorization Header - * @returns {string} - */ -function getAuthHeader() { - // 首先尝试从 tokenManager 获取 - const token = tokenManager.getAccessToken() - if (token) { - return `Bearer ${token}` - } - - // 如果 tokenManager 没有 token,尝试从 sessionStorage 获取(开发环境使用) - try { - const manualToken = sessionStorage.getItem('DEV_MANUAL_TOKEN') - if (manualToken) { - return `Bearer ${manualToken}` - } - } catch (e) { - // 忽略错误 - } - return '' -} - -/** - * 从本地存储获取缓存的用户信息 - * @returns {Object|null} 缓存的用户信息或 null - */ -function getCachedUserInfo() { - try { - const cached = sessionStorage.getItem(CACHE_KEY) - if (!cached) return null - - const { data, timestamp } = JSON.parse(cached) - const now = Date.now() - - // 检查缓存是否过期 - if (now - timestamp > CACHE_DURATION) { - sessionStorage.removeItem(CACHE_KEY) - return null - } - - console.log('[UserInfo] 使用本地缓存') - return data - } catch (e) { - return null - } -} - -/** - * 将用户信息存储到本地 - * @param {Object} userInfo - 用户信息 - */ -function setCachedUserInfo(userInfo) { - try { - const cacheData = { - data: userInfo, - timestamp: Date.now() - } - sessionStorage.setItem(CACHE_KEY, JSON.stringify(cacheData)) - console.log('[UserInfo] 更新本地缓存') - } catch (e) { - console.error('缓存用户信息失败:', e) - } -} - -/** - * 清除用户信息缓存 - */ -export function clearUserInfoCache() { - try { - sessionStorage.removeItem(CACHE_KEY) - console.log('[UserInfo] 清除缓存') - } catch (e) { - console.error('清除缓存失败:', e) - } -} - -/** - * 用户信息 Hook - * @param {Object} options - 配置选项 - * @param {string} options.baseUrl - API 基础 URL(可选,默认使用 APP_MEMBER) - * @returns {Object} { fetchUserInfo, loading, error, userInfo } - */ -export function useUserInfo(options = {}) { - const loading = ref(false) - const error = ref(null) - const userInfo = ref(null) - - // 确定 API 基础路径 - const baseUrl = options.baseUrl || API_BASE.APP_MEMBER - const apiUrl = `${baseUrl}/user/get` - - /** - * 获取用户信息 - * @returns {Promise} 用户信息对象 - */ - async function fetchUserInfo() { - loading.value = true - error.value = null - - try { - // 1. 先尝试从本地缓存获取 - const cachedUserInfo = getCachedUserInfo() - if (cachedUserInfo) { - userInfo.value = cachedUserInfo - loading.value = false - return cachedUserInfo - } - - // 2. 发起请求获取用户信息 - console.log('[UserInfo] 发起请求') - const authHeader = getAuthHeader() - const headers = { - 'Content-Type': 'application/json', - } - - if (authHeader) { - headers.Authorization = authHeader - } - - // 获取 tenant-id(从环境变量或默认值) - const tenantId = - (typeof import.meta !== 'undefined' && import.meta.env?.VITE_TENANT_ID) || - (typeof process !== 'undefined' && process.env?.VITE_TENANT_ID) || - '1' - - if (tenantId) { - headers['tenant-id'] = tenantId - } - - const response = await axios.get(apiUrl, { headers }) - - // 处理响应数据(根据后端返回格式调整) - // 后端通常返回 { code: 0, data: {...}, msg: '...' } 格式 - let data = null - if (response.data) { - // 如果响应有 code 字段,说明是标准格式 - if (typeof response.data.code === 'number') { - // code 为 0 或 200 表示成功 - if (response.data.code === 0 || response.data.code === 200) { - data = response.data.data || response.data - } - } else { - // 没有 code 字段,直接使用 data - data = response.data.data || response.data - } - } - - if (data) { - // 3. 将获取到的数据写入本地缓存 - setCachedUserInfo(data) - userInfo.value = data - return data - } - } catch (err) { - error.value = err - console.error('获取用户信息失败:', err) - throw err - } finally { - loading.value = false - } - } - - return { - fetchUserInfo, - loading, - error, - userInfo, - } -} - -/** - * 便捷函数:直接获取用户信息(不返回响应式状态) - * @param {Object} options - 配置选项 - * @returns {Promise} 用户信息对象 - */ -export async function getUserInfo(options = {}) { - const { fetchUserInfo } = useUserInfo(options) - return await fetchUserInfo() -} diff --git a/frontend/hooks/web/useVoiceText.ts b/frontend/hooks/web/useVoiceText.ts index 22e1491074..af66705160 100644 --- a/frontend/hooks/web/useVoiceText.ts +++ b/frontend/hooks/web/useVoiceText.ts @@ -1,39 +1,28 @@ -// 使用公共类型定义 -import type { - AudioItem, - TranscriptionResult, +import type { + AudioItem, + TranscriptionResult, TranscriptionResponse, - TranscriptionData + TranscriptionData } from '@gold/config/types' -// 直接导入 TikHub 服务,无需全局注入 -import { TikHubService } from '@gold/api/services' +import BaiLianService from '@/api/bailian' /** - * 将音频列表转换为文本转录 + * 音频转文本 * @param list - 音频项列表 * @returns 转录结果数组 - * @throws 当转录过程出错时抛出错误 - * - * @example - * const audioList = [{ audio_url: 'https://example.com/audio.mp3' }] - * const transcriptions = await getVoiceText(audioList) - * console.log(transcriptions) // [{ key: 'url', value: 'transcribed text' }] */ export async function getVoiceText( list: AudioItem[] ): Promise { - // 直接使用 TikHub 服务 - const ret = await TikHubService.videoToCharacters({ + const ret = await (BaiLianService as any).videoToCharacters({ fileLinkList: list.map(item => item.audio_url), }) - - // 解析响应数据 + const data: string = ret.data const rst: TranscriptionResponse = JSON.parse(data) const transcription_url: string[] = rst.results.map(item => item.transcription_url) - - // 并行获取所有转录内容 + const transcriptions: TranscriptionResult[] = await Promise.all( (transcription_url || []).filter(Boolean).map(async (url: string): Promise => { try { @@ -43,10 +32,10 @@ export async function getVoiceText( ? JSON.stringify(await resp.json()) : await resp.text() const parsed: TranscriptionData = JSON.parse(value) - return { - key: url, - audio_url: parsed.file_url, - value: parsed.transcripts?.[0]?.text || '' + return { + key: url, + audio_url: parsed.file_url, + value: parsed.transcripts?.[0]?.text || '' } } catch (e: unknown) { console.warn('获取转写内容失败:', url, e) @@ -57,26 +46,16 @@ export async function getVoiceText( return transcriptions } -/** - * Hook 返回值接口 - */ interface UseVoiceTextReturn { getVoiceText: (list: AudioItem[]) => Promise } /** - * 语音文本转换 Hook - * @returns 包含 getVoiceText 方法的对象 - * - * @example - * const { getVoiceText } = useVoiceText() - * const result = await getVoiceText(audioList) + * 语音文本转换Hook + * @returns 包含getVoiceText方法的对象 */ export default function useVoiceText(): UseVoiceTextReturn { - return { + return { getVoiceText } } - - -