优化
This commit is contained in:
@@ -66,7 +66,9 @@
|
|||||||
"Skill(code-simplifier)",
|
"Skill(code-simplifier)",
|
||||||
"Skill(code-simplifier:*)",
|
"Skill(code-simplifier:*)",
|
||||||
"Bash(npm run lint:es -- --no-fix src/views/material/MixTaskList.vue)",
|
"Bash(npm run lint:es -- --no-fix src/views/material/MixTaskList.vue)",
|
||||||
"Bash(npm run)"
|
"Bash(npm run)",
|
||||||
|
"Skill(plan)",
|
||||||
|
"Skill(plan:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,7 +32,7 @@ settings.local.json
|
|||||||
nbproject/private/
|
nbproject/private/
|
||||||
build/*
|
build/*
|
||||||
nbbuild/
|
nbbuild/
|
||||||
dist/
|
dist
|
||||||
nbdist/
|
nbdist/
|
||||||
.nb-gradle/
|
.nb-gradle/
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
/**
|
|
||||||
* Mono 级别的 C 端 Axios 实例
|
|
||||||
* 供 monorepo 中所有应用使用的统一 HTTP 客户端
|
|
||||||
*/
|
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
// 直接使用实例(最简单、最可靠)
|
|
||||||
import tokenManager from '@gold/utils/token-manager'
|
import tokenManager from '@gold/utils/token-manager'
|
||||||
|
|
||||||
/**
|
// Token白名单
|
||||||
* 不需要 token 的接口白名单
|
|
||||||
*/
|
|
||||||
const WHITE_LIST = [
|
const WHITE_LIST = [
|
||||||
'/auth/login',
|
'/auth/login',
|
||||||
'/auth/send-sms-code',
|
'/auth/send-sms-code',
|
||||||
@@ -20,89 +12,38 @@ const WHITE_LIST = [
|
|||||||
'/auth/refresh-token',
|
'/auth/refresh-token',
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查 URL 是否在白名单中
|
|
||||||
*/
|
|
||||||
function isInWhiteList(url) {
|
function isInWhiteList(url) {
|
||||||
return !!(url && WHITE_LIST.some(path => url.includes(path)))
|
return !!(url && WHITE_LIST.some(path => url.includes(path)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Token刷新状态管理
|
||||||
* 自动刷新 token 的锁机制和队列
|
|
||||||
*/
|
|
||||||
let isRefreshing = false
|
let isRefreshing = false
|
||||||
let refreshSubscribers = []
|
let refreshSubscribers = []
|
||||||
|
|
||||||
/**
|
|
||||||
* 订阅 token 刷新完成
|
|
||||||
* @param {Function} callback - 回调函数
|
|
||||||
*/
|
|
||||||
function subscribeTokenRefresh(callback) {
|
function subscribeTokenRefresh(callback) {
|
||||||
if (isRefreshing) {
|
isRefreshing ? refreshSubscribers.push(callback) : callback()
|
||||||
refreshSubscribers.push(callback)
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行所有订阅回调
|
|
||||||
*/
|
|
||||||
function onRefreshed() {
|
function onRefreshed() {
|
||||||
refreshSubscribers.forEach(callback => callback())
|
refreshSubscribers.forEach(callback => callback())
|
||||||
refreshSubscribers = []
|
refreshSubscribers = []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理 401 未授权错误
|
* 创建Axios实例
|
||||||
* 注意:只做清理工作,不处理重定向(重定向由上层回调处理)
|
|
||||||
*/
|
|
||||||
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 实例
|
|
||||||
* @param {Object} options - 配置选项
|
* @param {Object} options - 配置选项
|
||||||
* @param {string} options.baseURL - 基础 URL
|
* @param {string} options.baseURL - 基础URL
|
||||||
* @param {number} options.timeout - 超时时间(毫秒)
|
* @param {number} options.timeout - 超时时间
|
||||||
* @param {Function} options.on401 - 401 错误处理函数
|
* @param {Function} options.on401 - 401错误处理函数
|
||||||
* @param {Function} options.on403 - 403 错误处理函数
|
* @param {Function} options.on403 - 403错误处理函数
|
||||||
* @param {Function} options.refreshTokenFn - Token 刷新函数(可选)
|
* @param {Function} options.refreshTokenFn - Token刷新函数
|
||||||
* @returns {AxiosInstance} Axios 实例
|
* @returns {AxiosInstance} Axios实例
|
||||||
*/
|
*/
|
||||||
export function createClientAxios(options = {}) {
|
export default function createClientAxios(options = {}) {
|
||||||
const {
|
const {
|
||||||
baseURL = '/',
|
baseURL = '/',
|
||||||
timeout = 180000,
|
timeout = 180000,
|
||||||
on401 = handle401Error,
|
on401 = null,
|
||||||
on403 = null,
|
on403 = null,
|
||||||
refreshTokenFn = null,
|
refreshTokenFn = null,
|
||||||
} = options
|
} = options
|
||||||
@@ -114,7 +55,16 @@ export function createClientAxios(options = {}) {
|
|||||||
|
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
client.interceptors.request.use((config) => {
|
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 =
|
const tenantId =
|
||||||
(typeof import.meta !== 'undefined' && import.meta.env?.VITE_TENANT_ID) ||
|
(typeof import.meta !== 'undefined' && import.meta.env?.VITE_TENANT_ID) ||
|
||||||
(typeof process !== 'undefined' && process.env?.VITE_TENANT_ID) ||
|
(typeof process !== 'undefined' && process.env?.VITE_TENANT_ID) ||
|
||||||
@@ -123,27 +73,9 @@ export function createClientAxios(options = {}) {
|
|||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
config.headers['tenant-id'] = tenantId
|
config.headers['tenant-id'] = tenantId
|
||||||
}
|
}
|
||||||
|
const isTokenExpired = tokenManager.isExpired(30 * 1000)
|
||||||
// 检查是否需要认证
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (!isTokenExpired) {
|
if (!isTokenExpired) {
|
||||||
// Token 未过期,直接添加 Authorization 头
|
|
||||||
const authHeader = tokenManager.getAuthHeader()
|
const authHeader = tokenManager.getAuthHeader()
|
||||||
if (authHeader) {
|
if (authHeader) {
|
||||||
config.headers.Authorization = authHeader
|
config.headers.Authorization = authHeader
|
||||||
@@ -151,44 +83,32 @@ export function createClientAxios(options = {}) {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token 即将过期,需要刷新
|
|
||||||
console.info('[Token] Token刷新')
|
|
||||||
|
|
||||||
// 如果不在刷新过程中,启动刷新
|
|
||||||
if (!isRefreshing) {
|
if (!isRefreshing) {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
|
|
||||||
if (!refreshTokenFn || typeof refreshTokenFn !== 'function') {
|
if (!refreshTokenFn || typeof refreshTokenFn !== 'function') {
|
||||||
console.warn('[Token] 未提供刷新函数,跳过刷新')
|
|
||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
onRefreshed()
|
onRefreshed()
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTokenFn()
|
refreshTokenFn().finally(() => {
|
||||||
.then(() => {
|
isRefreshing = false
|
||||||
console.info('[Token] 刷新成功')
|
onRefreshed()
|
||||||
isRefreshing = false
|
})
|
||||||
onRefreshed()
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
isRefreshing = false
|
|
||||||
onRefreshed()
|
|
||||||
console.error('[Token] 刷新失败:', error.message)
|
|
||||||
|
|
||||||
if (error.code === 401 && error.needRelogin) {
|
|
||||||
console.info('[Token] refreshToken无效,需要重新登录')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待刷新完成
|
return new Promise((resolve, reject) => {
|
||||||
return new Promise((resolve) => {
|
|
||||||
subscribeTokenRefresh(() => {
|
subscribeTokenRefresh(() => {
|
||||||
const authHeader = tokenManager.getAuthHeader()
|
const token = tokenManager.getAccessToken()
|
||||||
if (authHeader) {
|
if (!token) {
|
||||||
config.headers.Authorization = authHeader
|
return reject({
|
||||||
|
message: 'Token已过期,需要重新登录',
|
||||||
|
code: 401,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.headers.Authorization = tokenManager.getAuthHeader()
|
||||||
resolve(config)
|
resolve(config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -199,24 +119,21 @@ export function createClientAxios(options = {}) {
|
|||||||
(response) => {
|
(response) => {
|
||||||
const data = response.data
|
const data = response.data
|
||||||
|
|
||||||
// 检查业务状态码
|
|
||||||
if (data && typeof data.code === 'number') {
|
if (data && typeof data.code === 'number') {
|
||||||
if (data.code === 0 || data.code === 200) {
|
if (data.code === 0 || data.code === 200) {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建业务错误对象
|
|
||||||
const error = new Error(data?.message || data?.msg || '请求失败')
|
const error = new Error(data?.message || data?.msg || '请求失败')
|
||||||
error.code = data?.code
|
error.code = data?.code
|
||||||
error.data = data
|
error.data = data
|
||||||
|
|
||||||
// 业务码 403 只在响应拦截器处理,避免重复处理
|
|
||||||
if (data.code === 403 && typeof on403 === 'function') {
|
if (data.code === 403 && typeof on403 === 'function') {
|
||||||
const forbiddenError = new Error('权限不足,无法访问该资源')
|
on403(error)
|
||||||
forbiddenError.code = 403
|
}
|
||||||
on403(forbiddenError)
|
if (data.code === 401 && typeof on401 === 'function') {
|
||||||
|
refreshTokenFn && refreshTokenFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +141,13 @@ export function createClientAxios(options = {}) {
|
|||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const status = error.response?.status
|
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)
|
on401(error)
|
||||||
} else if (status === 403 && typeof on403 === 'function') {
|
}
|
||||||
const forbiddenError = new Error('权限不足,无法访问该资源')
|
else if ((status === 403 || code === 403) && typeof on403 === 'function') {
|
||||||
forbiddenError.code = 403
|
on403(error)
|
||||||
on403(forbiddenError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
@@ -239,12 +156,3 @@ export function createClientAxios(options = {}) {
|
|||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认导出的 C 端 Axios 实例
|
|
||||||
* 可在应用层覆盖配置
|
|
||||||
*/
|
|
||||||
export const clientAxios = createClientAxios()
|
|
||||||
|
|
||||||
export default clientAxios
|
|
||||||
|
|
||||||
|
|||||||
46
frontend/api/services/bailian.js
Normal file
46
frontend/api/services/bailian.js
Normal file
@@ -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<Object>} 响应数据
|
||||||
|
*/
|
||||||
|
async callWorkflow(data) {
|
||||||
|
const client = await getClient()
|
||||||
|
return await client.post(`${BASE_URL}/callWorkflow`, data)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
/**
|
// API服务统一导出
|
||||||
* API 服务统一导出
|
export { createBaiLianService } from './bailian'
|
||||||
* 按功能模块组织,便于维护和扩展
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { TikHubService } from './tikhub'
|
// 后续可添加其他服务模块
|
||||||
|
|
||||||
// 可以继续添加其他服务模块
|
|
||||||
// export { UserService } from './user'
|
// export { UserService } from './user'
|
||||||
// export { ChatService } from './chat'
|
// export { ChatService } from './chat'
|
||||||
|
|
||||||
|
|||||||
@@ -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<Object>} 响应数据
|
|
||||||
*/
|
|
||||||
async callWorkflow(data) {
|
|
||||||
return await clientAxios.post(`${BASE_URL}/callWorkflow`, data)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TikHubService
|
|
||||||
|
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
|
|
||||||
import api from '@/api/http'
|
import api from '@/api/http'
|
||||||
// 直接使用实例(最简单、最可靠)
|
|
||||||
import tokenManager from '@gold/utils/token-manager'
|
import tokenManager from '@gold/utils/token-manager'
|
||||||
// 使用公共配置
|
|
||||||
import { API_BASE } from '@gold/config/api'
|
import { API_BASE } from '@gold/config/api'
|
||||||
|
import router from '@/router'
|
||||||
|
import { getUserInfo,clearUserInfoCache } from './userinfo'
|
||||||
|
|
||||||
const SERVER_BASE = API_BASE.APP_MEMBER
|
const SERVER_BASE = API_BASE.APP_MEMBER
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存 token 的辅助函数
|
* 保存token
|
||||||
* @param {Object} info - 包含 accessToken 和 refreshToken 的对象
|
* @param {Object} info - 包含accessToken和refreshToken的对象
|
||||||
*/
|
*/
|
||||||
function saveTokens(info) {
|
function saveTokens(info) {
|
||||||
if (!info?.accessToken && !info?.refreshToken) {
|
if (!info?.accessToken && !info?.refreshToken) {
|
||||||
@@ -24,62 +23,50 @@ function saveTokens(info) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除用户信息缓存
|
* 清除用户信息缓存
|
||||||
*/
|
*/
|
||||||
async function clearUserCache() {
|
function clearUserCache() {
|
||||||
try {
|
try {
|
||||||
const { clearUserInfoCache } = await import('@gold/hooks/web/useUserInfo')
|
|
||||||
clearUserInfoCache()
|
clearUserInfoCache()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 清除缓存失败不影响登录流程
|
// 清除缓存失败不影响登录流程
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 短信场景枚举
|
||||||
* 短信场景枚举(请与后端配置保持一致)
|
|
||||||
* - MEMBER_LOGIN: 会员短信登录场景
|
|
||||||
* - MEMBER_UPDATE_PASSWORD: 已登录用户修改密码(短信校验)场景
|
|
||||||
* - MEMBER_RESET_PASSWORD: 未登录用户忘记密码重置(短信校验)场景
|
|
||||||
* 如有"注册"独立场景,可在此添加:MEMBER_REGISTER: 13
|
|
||||||
*/
|
|
||||||
export const SMS_SCENE = {
|
export const SMS_SCENE = {
|
||||||
MEMBER_LOGIN: 1,
|
MEMBER_LOGIN: 1,
|
||||||
MEMBER_UPDATE_PASSWORD: 3,
|
MEMBER_UPDATE_PASSWORD: 3,
|
||||||
MEMBER_RESET_PASSWORD: 4,
|
MEMBER_RESET_PASSWORD: 4,
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
// 短信模板编码
|
||||||
* 短信模板编码常量
|
|
||||||
*/
|
|
||||||
export const SMS_TEMPLATE_CODE = {
|
export const SMS_TEMPLATE_CODE = {
|
||||||
USER_REGISTER: 'muye-user-code',
|
USER_REGISTER: 'muye-user-code',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账号密码登录
|
* 账号密码登录
|
||||||
* POST /member/auth/login
|
* @param {string} mobile - 手机号
|
||||||
*
|
* @param {string} password - 密码
|
||||||
* @param {string} mobile - 手机号(必填)
|
* @returns {Promise<Object>} 登录信息
|
||||||
* @param {string} password - 密码(必填,长度 4-16)
|
|
||||||
* @returns {Promise<Object>} data.data: { accessToken, refreshToken, expiresTime, userInfo }
|
|
||||||
*/
|
*/
|
||||||
export async function loginByPassword(mobile, password) {
|
export async function loginByPassword(mobile, password) {
|
||||||
const { data } = await api.post(`${SERVER_BASE}/auth/login`, { mobile, password })
|
const { data } = await api.post(`${SERVER_BASE}/auth/login`, { mobile, password })
|
||||||
const info = data || {}
|
const info = data || {}
|
||||||
saveTokens(info)
|
saveTokens(info)
|
||||||
await clearUserCache()
|
clearUserCache()
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送短信验证码
|
* 发送短信验证码
|
||||||
* POST /member/auth/send-sms-code
|
* @param {string} mobile - 手机号
|
||||||
*
|
* @param {number} scene - 短信场景
|
||||||
* @param {string} mobile - 手机号(登录/忘记密码等需要;修改密码场景可不传)
|
* @param {string} templateCode - 模板编码
|
||||||
* @param {number} scene - 短信场景(见 SMS_SCENE)
|
* @returns {Promise<Object>} 响应数据
|
||||||
* @param {string} templateCode - 模板编码(可选,如 'muye-user-code')
|
|
||||||
* @returns {Promise<Object>} 后端通用响应结构
|
|
||||||
*/
|
*/
|
||||||
export async function sendSmsCode(mobile, scene, templateCode) {
|
export async function sendSmsCode(mobile, scene, templateCode) {
|
||||||
const body = { scene }
|
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 - 验证码
|
||||||
* @param {string} mobile - 手机号(必填)
|
* @param {number} scene - 短信场景
|
||||||
* @param {string} code - 验证码(必填,4-6 位数字)
|
* @returns {Promise<Object>} 响应数据
|
||||||
* @param {number} scene - 短信场景(必填,见 SMS_SCENE)
|
|
||||||
* @returns {Promise<Object>} 后端通用响应结构
|
|
||||||
*/
|
*/
|
||||||
export async function validateSmsCode(mobile, code, scene) {
|
export async function validateSmsCode(mobile, code, scene) {
|
||||||
const { data } = await api.post(`${SERVER_BASE}/auth/validate-sms-code`, { 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 - 短信验证码
|
||||||
* @param {string} mobile - 手机号(必填)
|
* @returns {Promise<Object>} 登录信息
|
||||||
* @param {string} code - 短信验证码(必填)
|
|
||||||
* @returns {Promise<Object>} data.data: { accessToken, refreshToken, expiresTime, userInfo }
|
|
||||||
*/
|
*/
|
||||||
export async function loginBySms(mobile, code) {
|
export async function loginBySms(mobile, code) {
|
||||||
const { data } = await api.post(`${SERVER_BASE}/auth/sms-login`, { mobile, code })
|
const { data } = await api.post(`${SERVER_BASE}/auth/sms-login`, { mobile, code })
|
||||||
const info = data || {}
|
const info = data || {}
|
||||||
saveTokens(info)
|
saveTokens(info)
|
||||||
await clearUserCache()
|
clearUserCache()
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新令牌
|
* 刷新令牌
|
||||||
* POST /member/auth/refresh-token?refreshToken=xxx
|
* @returns {Promise<Object>} 新的token信息
|
||||||
*
|
|
||||||
* @returns {Promise<Object>} data.data: { accessToken, refreshToken, expiresTime, userInfo }
|
|
||||||
*/
|
*/
|
||||||
export async function refreshToken() {
|
export async function refreshToken() {
|
||||||
const rt = tokenManager.getRefreshToken()
|
const rt = tokenManager.getRefreshToken()
|
||||||
if (!rt) throw new Error('缺少 refresh_token')
|
if (!rt) {
|
||||||
|
const authError = 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('登录已过期,请重新登录')
|
|
||||||
authError.code = 401
|
authError.code = 401
|
||||||
authError.needRelogin = true
|
authError.needRelogin = true
|
||||||
throw authError
|
throw authError
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.code !== 0 && data?.code !== 200) {
|
const { data,code,message } = await api.post(`${SERVER_BASE}/auth/refresh-token`, null, { params: { refreshToken: rt } })
|
||||||
throw new Error(data?.message || data?.msg || '刷新token失败')
|
|
||||||
|
if (code === 401) {
|
||||||
|
tokenManager.clearTokens()
|
||||||
|
clearUserCache()
|
||||||
|
return router.push('/login')
|
||||||
|
}
|
||||||
|
if (code !== 0 && code !== 200) {
|
||||||
|
throw new Error(message || '刷新token失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = data || {}
|
const info = data || {}
|
||||||
saveTokens(info)
|
saveTokens(info)
|
||||||
await clearUserCache()
|
clearUserCache()
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录态下:发送"修改密码"验证码
|
* 发送修改密码验证码
|
||||||
* - 场景:SMS_SCENE.MEMBER_UPDATE_PASSWORD
|
* @returns {Promise<Object>} 响应数据
|
||||||
* - 特性:后端会以当前登录用户的手机号为准,无需传 mobile
|
|
||||||
* POST /member/auth/send-sms-code
|
|
||||||
*
|
|
||||||
* @returns {Promise<Object>} 后端通用响应结构
|
|
||||||
*/
|
*/
|
||||||
export async function sendUpdatePasswordCode() {
|
export async function sendUpdatePasswordCode() {
|
||||||
const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, {
|
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 - 新密码
|
||||||
*
|
* @param {string} smsCode - 短信验证码
|
||||||
* @param {string} newPassword - 新密码(必填,4-16 位)
|
* @returns {Promise<Object>} 响应数据
|
||||||
* @param {string} smsCode - 短信验证码(必填,4-6 位数字)
|
|
||||||
* @returns {Promise<Object>} 后端通用响应结构
|
|
||||||
*/
|
*/
|
||||||
export async function updatePasswordBySmsCode(newPassword, smsCode) {
|
export async function updatePasswordBySmsCode(newPassword, smsCode) {
|
||||||
const { data } = await api.put(`${SERVER_BASE}/user/update-password`, {
|
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
|
* @param {string} mobile - 手机号
|
||||||
* POST /member/auth/send-sms-code
|
* @returns {Promise<Object>} 响应数据
|
||||||
*
|
|
||||||
* @param {string} mobile - 手机号(必填)
|
|
||||||
* @returns {Promise<Object>} 后端通用响应结构
|
|
||||||
*/
|
*/
|
||||||
export async function sendResetPasswordCode(mobile) {
|
export async function sendResetPasswordCode(mobile) {
|
||||||
const { data } = await api.post(`${SERVER_BASE}/auth/send-sms-code`, {
|
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 - 新密码
|
||||||
* @param {string} mobile - 手机号(必填)
|
* @param {string} smsCode - 短信验证码
|
||||||
* @param {string} newPassword - 新密码(必填,4-16 位)
|
* @returns {Promise<Object>} 响应数据
|
||||||
* @param {string} smsCode - 短信验证码(必填,4-6 位数字)
|
|
||||||
* @returns {Promise<Object>} 后端通用响应结构
|
|
||||||
*/
|
*/
|
||||||
export async function resetPasswordBySms(mobile, newPassword, smsCode) {
|
export async function resetPasswordBySms(mobile, newPassword, smsCode) {
|
||||||
const { data } = await api.put(`${SERVER_BASE}/user/reset-password`, {
|
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<Object>} 用户信息对象
|
* @returns {Promise<Object>} 用户信息对象
|
||||||
*/
|
*/
|
||||||
export async function getUserInfo() {
|
export function getUserInfoAuth() {
|
||||||
const { data } = await api.get(`${SERVER_BASE}/user/get`)
|
return getUserInfo()
|
||||||
return data || {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "手机+验证码+密码注册"组合流程(基于短信登录即注册 + 设置密码)
|
* 手机+验证码+密码注册流程
|
||||||
* 说明:
|
* @param {string} mobile - 手机号
|
||||||
* - 1) 发送登录场景验证码(可选:若已拿到 code 可跳过)
|
* @param {string} loginCode - 登录验证码
|
||||||
* - 2) 短信登录:首次会自动注册并返回 token
|
* @param {string} newPassword - 新密码
|
||||||
* - 3) 登录态下发送"修改密码"验证码
|
* @param {string} updatePwdCode - 设置密码验证码
|
||||||
* - 4) 用短信验证码设置密码
|
|
||||||
*
|
|
||||||
* @param {string} mobile - 手机号(必填)
|
|
||||||
* @param {string} loginCode - 第一次登录使用的短信验证码(必填)
|
|
||||||
* @param {string} newPassword - 注册后设置的新密码(必填)
|
|
||||||
* @param {string} updatePwdCode - 设置密码用到的短信验证码(必填)
|
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function registerWithMobileCodePassword(mobile, loginCode, newPassword, updatePwdCode) {
|
export async function registerWithMobileCodePassword(mobile, loginCode, newPassword, updatePwdCode) {
|
||||||
// 2) 短信登录(首次即注册)
|
|
||||||
await loginBySms(mobile, loginCode)
|
await loginBySms(mobile, loginCode)
|
||||||
|
|
||||||
// 4) 用短信验证码设置密码
|
|
||||||
await updatePasswordBySmsCode(newPassword, updatePwdCode)
|
await updatePasswordBySmsCode(newPassword, updatePwdCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 默认导出
|
||||||
* 导出一个默认对象,便于统一引入
|
|
||||||
*/
|
|
||||||
export default {
|
export default {
|
||||||
SMS_SCENE,
|
SMS_SCENE,
|
||||||
SMS_TEMPLATE_CODE,
|
SMS_TEMPLATE_CODE,
|
||||||
@@ -269,5 +222,5 @@ export default {
|
|||||||
sendResetPasswordCode,
|
sendResetPasswordCode,
|
||||||
resetPasswordBySms,
|
resetPasswordBySms,
|
||||||
registerWithMobileCodePassword,
|
registerWithMobileCodePassword,
|
||||||
getUserInfo,
|
getUserInfo: getUserInfoAuth,
|
||||||
};
|
}
|
||||||
|
|||||||
6
frontend/app/web-gold/src/api/bailian.js
Normal file
6
frontend/app/web-gold/src/api/bailian.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import http from './http'
|
||||||
|
import { createBaiLianService } from '@gold/api/services/bailian'
|
||||||
|
|
||||||
|
const defaultService = createBaiLianService(http)
|
||||||
|
|
||||||
|
export default defaultService
|
||||||
@@ -1,36 +1,25 @@
|
|||||||
/**
|
|
||||||
* 应用层 API 服务
|
|
||||||
* 封装应用特定的 API 调用,使用 mono 级别的服务
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||||
// 直接使用实例(最简单、最可靠)
|
|
||||||
import tokenManager from '@gold/utils/token-manager'
|
import tokenManager from '@gold/utils/token-manager'
|
||||||
import { TikHubService } from '@gold/api/services'
|
import BaiLianService from './bailian'
|
||||||
import { API_BASE } from '@gold/config/api'
|
import { API_BASE } from '@gold/config/api'
|
||||||
|
|
||||||
/**
|
// 百炼API基础路径
|
||||||
* TikHub API 基础路径
|
|
||||||
*/
|
|
||||||
const TIKHUB_BASE = API_BASE.TIKHUB_APP || API_BASE.TIKHUB || ''
|
const TIKHUB_BASE = API_BASE.TIKHUB_APP || API_BASE.TIKHUB || ''
|
||||||
|
|
||||||
/**
|
// 应用层通用服务
|
||||||
* 应用层通用服务
|
|
||||||
*/
|
|
||||||
export const CommonService = {
|
export const CommonService = {
|
||||||
/**
|
/**
|
||||||
* 视频转字符(音频转文字)
|
* 视频转字符(音频转文字)
|
||||||
* 直接使用 mono 级别的 TikHub 服务
|
|
||||||
*/
|
*/
|
||||||
videoToCharacters(data) {
|
videoToCharacters(data) {
|
||||||
return TikHubService.videoToCharacters(data)
|
return BaiLianService.videoToCharacters(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用工作流
|
* 调用工作流
|
||||||
*/
|
*/
|
||||||
callWorkflow(data) {
|
callWorkflow(data) {
|
||||||
return TikHubService.callWorkflow(data)
|
return BaiLianService.callWorkflow(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,5 +70,3 @@ export const CommonService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default CommonService
|
export default CommonService
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +1,27 @@
|
|||||||
/**
|
import createClientAxios from '@gold/api/axios/client'
|
||||||
* 应用层 HTTP 客户端
|
|
||||||
* 支持自定义拦截器的可扩展版本
|
|
||||||
* - 提供 createHttpClient 工厂函数
|
|
||||||
* - 业务代码可以创建自己的 http 实例并添加拦截器
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { createClientAxios } from '@gold/api/axios/client'
|
|
||||||
import { refreshToken } from '@/api/auth'
|
import { refreshToken } from '@/api/auth'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建应用层 HTTP 客户端实例
|
* 创建HTTP客户端实例
|
||||||
* @param {Object} options - 配置选项
|
* @param {Object} options - 配置选项
|
||||||
* @param {Function} options.on401 - 401 错误处理回调
|
* @param {Function} options.on401 - 401错误处理回调
|
||||||
* @param {Function} options.on403 - 403 错误处理回调
|
* @param {Function} options.on403 - 403错误处理回调
|
||||||
* @returns {AxiosInstance} HTTP 客户端实例
|
* @returns {AxiosInstance} HTTP客户端实例
|
||||||
*/
|
*/
|
||||||
export function createHttpClient(options = {}) {
|
export function createHttpClient(options = {}) {
|
||||||
const { on401, on403 } = options
|
const { on401, on403 } = options
|
||||||
|
|
||||||
const httpClient = createClientAxios({
|
return createClientAxios({
|
||||||
baseURL: '/',
|
baseURL: '/',
|
||||||
timeout: 180000,
|
timeout: 180000,
|
||||||
refreshTokenFn: refreshToken,
|
refreshTokenFn: refreshToken,
|
||||||
on401: async (error) => {
|
on401: on401 || ((error) => router.push('/login')),
|
||||||
if (on401) {
|
on403: on403 || ((error) => router.push('/login')),
|
||||||
await on401(error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
router.push('/login')
|
|
||||||
},
|
|
||||||
on403: (error) => {
|
|
||||||
on403 ? on403(error) : router.push('/login')
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return httpClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 默认HTTP客户端实例
|
||||||
* 默认的 HTTP 客户端实例
|
|
||||||
* 业务代码可以:
|
|
||||||
* 1. 直接使用 http 实例
|
|
||||||
* 2. 调用 createHttpClient() 创建自定义实例
|
|
||||||
* 3. 在自定义实例上添加拦截器
|
|
||||||
*/
|
|
||||||
const http = createHttpClient()
|
const http = createHttpClient()
|
||||||
|
|
||||||
export default http
|
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 处理')
|
|
||||||
* }
|
|
||||||
* })
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
154
frontend/app/web-gold/src/api/userinfo.js
Normal file
154
frontend/app/web-gold/src/api/userinfo.js
Normal file
@@ -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<Object>} 用户信息对象
|
||||||
|
*/
|
||||||
|
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<Object>} 用户信息对象
|
||||||
|
*/
|
||||||
|
export async function getUserInfo(options = {}) {
|
||||||
|
const hook = useUserInfo(options)
|
||||||
|
return await hook.fetchUserInfo()
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { ref, computed, watch } from 'vue'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { getJSON, setJSON, remove } from '@/utils/storage'
|
import { getJSON, setJSON, remove } from '@/utils/storage'
|
||||||
import tokenManager from '@gold/utils/token-manager'
|
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'
|
const STORAGE_KEY = 'user_store_v1'
|
||||||
|
|
||||||
|
|||||||
@@ -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<Object>} 用户信息对象
|
|
||||||
*/
|
|
||||||
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<Object>} 用户信息对象
|
|
||||||
*/
|
|
||||||
export async function getUserInfo(options = {}) {
|
|
||||||
const { fetchUserInfo } = useUserInfo(options)
|
|
||||||
return await fetchUserInfo()
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// 使用公共类型定义
|
|
||||||
import type {
|
import type {
|
||||||
AudioItem,
|
AudioItem,
|
||||||
TranscriptionResult,
|
TranscriptionResult,
|
||||||
@@ -6,34 +5,24 @@ import type {
|
|||||||
TranscriptionData
|
TranscriptionData
|
||||||
} from '@gold/config/types'
|
} from '@gold/config/types'
|
||||||
|
|
||||||
// 直接导入 TikHub 服务,无需全局注入
|
import BaiLianService from '@/api/bailian'
|
||||||
import { TikHubService } from '@gold/api/services'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将音频列表转换为文本转录
|
* 音频转文本
|
||||||
* @param list - 音频项列表
|
* @param list - 音频项列表
|
||||||
* @returns 转录结果数组
|
* @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(
|
export async function getVoiceText(
|
||||||
list: AudioItem[]
|
list: AudioItem[]
|
||||||
): Promise<TranscriptionResult[]> {
|
): Promise<TranscriptionResult[]> {
|
||||||
// 直接使用 TikHub 服务
|
const ret = await (BaiLianService as any).videoToCharacters({
|
||||||
const ret = await TikHubService.videoToCharacters({
|
|
||||||
fileLinkList: list.map(item => item.audio_url),
|
fileLinkList: list.map(item => item.audio_url),
|
||||||
})
|
})
|
||||||
|
|
||||||
// 解析响应数据
|
|
||||||
const data: string = ret.data
|
const data: string = ret.data
|
||||||
const rst: TranscriptionResponse = JSON.parse(data)
|
const rst: TranscriptionResponse = JSON.parse(data)
|
||||||
const transcription_url: string[] = rst.results.map(item => item.transcription_url)
|
const transcription_url: string[] = rst.results.map(item => item.transcription_url)
|
||||||
|
|
||||||
// 并行获取所有转录内容
|
|
||||||
const transcriptions: TranscriptionResult[] = await Promise.all(
|
const transcriptions: TranscriptionResult[] = await Promise.all(
|
||||||
(transcription_url || []).filter(Boolean).map(async (url: string): Promise<TranscriptionResult> => {
|
(transcription_url || []).filter(Boolean).map(async (url: string): Promise<TranscriptionResult> => {
|
||||||
try {
|
try {
|
||||||
@@ -57,26 +46,16 @@ export async function getVoiceText(
|
|||||||
return transcriptions
|
return transcriptions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook 返回值接口
|
|
||||||
*/
|
|
||||||
interface UseVoiceTextReturn {
|
interface UseVoiceTextReturn {
|
||||||
getVoiceText: (list: AudioItem[]) => Promise<TranscriptionResult[]>
|
getVoiceText: (list: AudioItem[]) => Promise<TranscriptionResult[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 语音文本转换 Hook
|
* 语音文本转换Hook
|
||||||
* @returns 包含 getVoiceText 方法的对象
|
* @returns 包含getVoiceText方法的对象
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { getVoiceText } = useVoiceText()
|
|
||||||
* const result = await getVoiceText(audioList)
|
|
||||||
*/
|
*/
|
||||||
export default function useVoiceText(): UseVoiceTextReturn {
|
export default function useVoiceText(): UseVoiceTextReturn {
|
||||||
return {
|
return {
|
||||||
getVoiceText
|
getVoiceText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user