From b375c358251d251d03ca6a1cfa7157cfded05fa0 Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Fri, 7 Nov 2025 18:36:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=201.=E5=B0=86=E5=90=88=E5=90=8C=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=92=8C=E4=BA=A4=E5=8F=89=E8=AF=84=E6=9F=A5=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=90=8E=E7=AB=AFapi=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E8=8E=B7=E5=8F=96=E6=96=87=E4=BB=B6=E6=9D=A5?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E3=80=82=202.=E4=BF=AE=E5=A4=8D=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E8=BF=87=E7=A8=8B=E4=B8=ADtoken=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=E9=97=AE=E9=A2=98=E3=80=82=203.?= =?UTF-8?q?=E5=AE=8C=E5=96=84api-config=E6=96=87=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=AB=AF=E5=8F=A3=E5=8F=B7=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E7=9A=84=E5=9B=9E=E8=B0=83=E5=9C=B0=E5=9D=80=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contract-template/TemplateCard.tsx | 62 ++++++------- app/components/cross-checking/FileInfo.tsx | 47 +++++++--- app/config/api-config.ts | 89 ++++++++----------- app/routes/callback.tsx | 66 +++++--------- app/routes/contract-template.detail.$id.tsx | 69 +++++++------- 5 files changed, 153 insertions(+), 180 deletions(-) diff --git a/app/components/contract-template/TemplateCard.tsx b/app/components/contract-template/TemplateCard.tsx index a8bd9d6..918b51d 100644 --- a/app/components/contract-template/TemplateCard.tsx +++ b/app/components/contract-template/TemplateCard.tsx @@ -1,5 +1,8 @@ // import { useState } from 'react'; import { useNavigate } from '@remix-run/react'; +// 导入统一的下载方法和提示服务 +import { downloadFile } from '~/api/axios-client'; +import { toastService } from '~/components/ui/Toast'; interface Template { id: string; @@ -29,56 +32,49 @@ export function TemplateCard({ template, onClick }: TemplateCardProps) { setIsFavorited(!isFavorited); }; */ - // MinIO下载URL构建函数 - const buildDownloadUrl = (filePath: string): string => { - // 使用实际的MinIO配置 - const minioHost = 'http://nas.7bm.co:9000'; - const bucketName = 'docauditai'; - - // 确保文件路径不以/开头 - const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath; - - return `${minioHost}/${bucketName}/${cleanPath}`; - }; - - // 下载文件函数 - const downloadFile = async (filePath: string, fileName: string) => { + // 使用统一的下载方法(与 rules-files.tsx 相同) + const handleDownloadFile = async (filePath: string, fileName: string) => { try { - const downloadUrl = buildDownloadUrl(filePath); - + // 使用axios封装的下载方法 + const blob = await downloadFile(filePath); + + // 创建Blob URL + const blobUrl = URL.createObjectURL(blob); + // 清理文件名,移除可能导致问题的字符 const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_'); - - // 创建临时下载链接 - const link = document.createElement('a'); - link.href = downloadUrl; - link.download = cleanFileName; - link.target = '_blank'; - - // 触发下载 - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - // console.log('开始下载文件:', cleanFileName); + + // 创建一个隐藏的a标签并点击它 + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = blobUrl; + a.download = cleanFileName; + document.body.appendChild(a); + a.click(); + + // 清理 + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + }, 100); } catch (error) { console.error('下载文件失败:', error); - alert('下载失败,请稍后重试'); + toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`); } }; const handleActionClick = (e: React.MouseEvent, action: string) => { e.stopPropagation(); - + switch (action) { case '立即下载': if (template.file_path) { // 构建文件名,使用模板标题和文件格式 const fileExtension = template.file_format || 'docx'; const fileName = `${template.title}.${fileExtension}`; - downloadFile(template.file_path, fileName); + handleDownloadFile(template.file_path, fileName); } else { - alert('文件路径不存在,无法下载'); + toastService.error('文件路径不存在,无法下载'); } break; case '预览': diff --git a/app/components/cross-checking/FileInfo.tsx b/app/components/cross-checking/FileInfo.tsx index c53a7c0..2eb488d 100644 --- a/app/components/cross-checking/FileInfo.tsx +++ b/app/components/cross-checking/FileInfo.tsx @@ -2,11 +2,15 @@ * 交叉评查文件信息组件 */ +// 导入统一的下载方法和提示服务 +import { downloadFile } from '~/api/axios-client'; +import { toastService } from '~/components/ui/Toast'; + interface FileInfoProps { fileInfo: { fileName: string; contractNumber: string; - fileSize?: string; + fileSize?: string; fileFormat?: string; pageCount?: number; uploadTime?: string; @@ -21,17 +25,36 @@ interface FileInfoProps { export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) { - const handleDownloadFile = () => { - if (fileInfo.path) { - // 创建一个隐藏的下载链接 - const link = document.createElement('a'); - link.href = fileInfo.path; - link.download = fileInfo.fileName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } else { - alert('文件路径不存在,无法下载'); + // 使用统一的下载方法(与 rules-files.tsx 相同) + const handleDownloadFile = async () => { + if (!fileInfo.path) { + toastService.error('文件路径不存在,无法下载'); + return; + } + + try { + // 使用axios封装的下载方法 + const blob = await downloadFile(fileInfo.path); + + // 创建Blob URL + const blobUrl = URL.createObjectURL(blob); + + // 创建一个隐藏的a标签并点击它 + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = blobUrl; + a.download = fileInfo.fileName || 'document'; + document.body.appendChild(a); + a.click(); + + // 清理 + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + }, 100); + } catch (error) { + console.error('下载文件失败:', error); + toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`); } }; diff --git a/app/config/api-config.ts b/app/config/api-config.ts index f58c5e0..687ff2a 100644 --- a/app/config/api-config.ts +++ b/app/config/api-config.ts @@ -14,15 +14,15 @@ interface ApiConfig { // OAuth2.0配置 oauth: { // IDaaS服务器地址 - serverUrl: string; + serverUrl?: string; // OAuth2应用Client ID - clientId: string; + clientId?: string; // OAuth2应用Client Secret - clientSecret: string; + clientSecret?: string; // 回调地址 - redirectUri: string; + redirectUri?: string; // 应用ID(用于登出) - appId: string; + appId?: string; }; } @@ -30,51 +30,15 @@ interface ApiConfig { // 根据不同端口提供不同的API配置 const portConfigs: Record> = { - // 测试主要服务实例 - '5173': { - baseUrl: 'http://172.16.0.55:8000', - documentUrl: 'http://172.16.0.55:8000/docauditai/', - uploadUrl: 'http://172.16.0.55:8000/admin/documents' - }, - // 测试客户端实例 - '5174': { - baseUrl: 'http://172.16.0.55:5174', - documentUrl: 'http://172.16.0.55:5174/docauditai/', - uploadUrl: 'http://172.16.0.55:5174/admin/documents' - }, - // 测试客户端实例 - '5175': { - baseUrl: 'http://172.16.0.55:5175', - documentUrl: 'http://172.16.0.55:5175/docauditai/', - uploadUrl: 'http://172.16.0.55:5175/admin/documents' - }, - // 测试客户端实例 - '5176': { - baseUrl: 'http://172.16.0.55:5176', - documentUrl: 'http://172.16.0.55:5176/docauditai/', - uploadUrl: 'http://172.16.0.55:5176/admin/documents' - }, - // 测试客户端实例 - '5177': { - baseUrl: 'http://172.16.0.55:5177', - documentUrl: 'http://172.16.0.55:5177/docauditai/', - uploadUrl: 'http://172.16.0.55:5177/admin/documents' - }, - // 测试客户端实例 - '5178': { - baseUrl: 'http://172.16.0.55:8008', - documentUrl: 'http://172.16.0.55:8008/docauditai/', - uploadUrl: 'http://172.16.0.55:8008/admin/documents' - }, - - - // 主要 // 梅州 '51703': { baseUrl: 'http://nas.7bm.co:8073', documentUrl: 'http://nas.7bm.co:8073/docauditai/', - uploadUrl: 'http://nas.7bm.co:8073/admin/documents' + uploadUrl: 'http://nas.7bm.co:8073/admin/documents', + oauth: { + redirectUri: 'http://10.79.97.17:51703/callback' + } // baseUrl: 'http://nas.7bm.co:8873', // documentUrl: 'http://nas.7bm.co:8873/docauditai/', // uploadUrl: 'http://nas.7bm.co:8873/admin/documents' @@ -85,21 +49,30 @@ const portConfigs: Record> = { '51704': { baseUrl: 'http://10.79.97.17:8001', documentUrl: 'http://10.79.97.17:8001/docauditai/', - uploadUrl: 'http://10.79.97.17:8001/admin/documents' + uploadUrl: 'http://10.79.97.17:8001/admin/documents', + oauth: { + redirectUri: 'http://10.79.97.17:51704/callback' + } }, // 揭阳 '51705': { baseUrl: 'http://10.79.97.17:8002', documentUrl: 'http://10.79.97.17:8002/docauditai/', - uploadUrl: 'http://10.79.97.17:8002/admin/documents' + uploadUrl: 'http://10.79.97.17:8002/admin/documents', + oauth: { + redirectUri: 'http://10.79.97.17:51705/callback' + } }, // 潮州 '51706': { baseUrl: 'http://10.79.97.17:8003', documentUrl: 'http://10.79.97.17:8003/docauditai/', - uploadUrl: 'http://10.79.97.17:8003/admin/documents' + uploadUrl: 'http://10.79.97.17:8003/admin/documents', + oauth: { + redirectUri: 'http://10.79.97.17:51706/callback' + } }, @@ -107,8 +80,12 @@ const portConfigs: Record> = { '51707': { baseUrl: 'http://10.79.97.17:8004', documentUrl: 'http://10.79.97.17:8004/docauditai/', - uploadUrl: 'http://10.79.97.17:8004/admin/documents' + uploadUrl: 'http://10.79.97.17:8004/admin/documents', + oauth: { + redirectUri: 'http://10.79.97.17:51707/callback' + } }, + //test '51708': { baseUrl: 'http://10.79.97.17:8005', @@ -312,13 +289,17 @@ const getCurrentConfig = (): ApiConfig => { // 如果有端口特定配置,则合并配置 if (port && portConfigs[port]) { - console.log(`🔧 使用端口特定配置: ${port}`, portConfigs[port]); + console.log(`🔧 使用端口特定配置: ${port}`); + const portConfig = portConfigs[port]; defaultConfig = { - ...defaultConfig, - ...portConfigs[port], - // 保持oauth配置不变,只覆盖API相关配置 - oauth: defaultConfig.oauth + ...defaultConfig, + ...portConfig, + // 如果端口配置中有oauth,需要深度合并oauth配置 + oauth: portConfig.oauth + ? { ...defaultConfig.oauth, ...portConfig.oauth } + : defaultConfig.oauth }; + console.log(`🔧 使用端口特定配置---深度合并后: ${JSON.stringify(defaultConfig.oauth)}`) } else { console.log(`🔧 使用环境配置: ${env}`, defaultConfig); } diff --git a/app/routes/callback.tsx b/app/routes/callback.tsx index 32067a3..cc03857 100644 --- a/app/routes/callback.tsx +++ b/app/routes/callback.tsx @@ -1,5 +1,7 @@ import { type LoaderFunctionArgs, redirect } from "@remix-run/node"; import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server"; +import { OAuthClient } from "~/api/login/oauth-client"; +import { OAUTH_CONFIG } from "~/config/api-config"; import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt"; /** @@ -39,7 +41,7 @@ async function redirectToLoginWithError(request: Request, errorMessage: string) export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); - const origin = url.origin; // 获取请求的源 (e.g., "http://10.79.97.17:51703") + // const origin = url.origin; // 获取请求的源 (e.g., "http://10.79.97.17:51703") const port = url.port; // 获取端口号 const area = getAreaByPort(port); // 根据端口号获取地区 const code = url.searchParams.get("code"); @@ -80,55 +82,25 @@ export async function loader({ request }: LoaderFunctionArgs) { try { console.log("🔧 开始处理OAuth2.0回调"); - // --- 修改开始: 不再直接调用OAuthClient,而是通过内部代理API --- + const oauthClient = new OAuthClient(OAUTH_CONFIG) - // 获取访问令牌 (通过代理) - console.log(`🔧 [Callback] 开始通过内部代理获取访问令牌... (目标: ${origin}/api/oauth/token)`); - const proxyResponse = await fetch(`${origin}/api/oauth/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ code }), - }); - - const tokenResponse = await proxyResponse.json(); - - if (!proxyResponse.ok || !tokenResponse.success) { - console.error("❌ [Callback] 通过内部代理获取访问令牌失败:", tokenResponse); - return redirectToLoginWithError(request, "获取访问令牌失败,请重新登录"); + // 开始获取访问令牌 + const tokenResponse = await oauthClient.getAccessToken(code); + if (!tokenResponse) { + console.error("获取访问令牌失败"); + return redirect("/login?error=token_error") } - - // --- 修改结束 --- - console.log("✅ [Callback] 访问令牌获取成功"); - // --- 修改开始: 通过内部代理获取用户信息 --- - console.log(`🔧 [Callback] 开始通过内部代理获取用户信息... (目标: ${origin}/api/oauth/userinfo)`); - const userInfoProxyResponse = await fetch(`${origin}/api/oauth/userinfo`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ accessToken: tokenResponse.access_token }), - }); - const userInfoResponse = await userInfoProxyResponse.json(); - - if (!userInfoProxyResponse.ok || !userInfoResponse.success) { - console.error("❌ [Callback] 通过内部代理获取用户信息失败:", userInfoResponse); - return redirectToLoginWithError(request, "获取用户信息失败,请重新登录"); + const userInfo = await oauthClient.getUserInfo(tokenResponse.access_token); + if(!userInfo || !userInfo.success){ + console.error('获取用户信息失败:',userInfo); + return redirect('/login?error=userinfo_error') } - - // 将代理返回的用户信息包装成与原有一致的结构 - const userInfo = { - success: true, - data: userInfoResponse.data, - }; - // --- 修改结束 --- - console.log("✅ [Callback] 用户信息获取成功"); + // TODO 根据用户信息判断用户角色,这里可以根据实际业务逻辑调整 暂定都是common const userRole = "common"; @@ -138,14 +110,16 @@ export async function loader({ request }: LoaderFunctionArgs) { // 先生成一个临时 JWT const tempUserInfo = { sub: userInfo.data.sub, - user_id: userInfo.data.user_id || "", + // user_id: userInfo.data.user_id || "", + user_id: "", username: userInfo.data.username, - nick_name: userInfo.data.nick_name, + nick_name: userInfo.data.nickname, email: userInfo.data.email, phone_number: userInfo.data.phone_number, ou_id: userInfo.data.ou_id, ou_name: userInfo.data.ou_name, - is_leader: userInfo.data.is_leader, + // is_leader: userInfo.data.is_leader, + is_leader: false, user_role: userRole as 'common' | 'developer' }; const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenResponse.expires_in); @@ -176,7 +150,7 @@ export async function loader({ request }: LoaderFunctionArgs) { }; const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, tokenResponse.expires_in); - console.log("前端JWT已生成"); + // console.log("前端JWT已生成"); // 更新userInfo以包含数据库ID、JWT,并用数据库标准字段覆盖关键属性,确保 nick_name 等存在 const enhancedUserInfo = { diff --git a/app/routes/contract-template.detail.$id.tsx b/app/routes/contract-template.detail.$id.tsx index 9e26834..e95bdaa 100644 --- a/app/routes/contract-template.detail.$id.tsx +++ b/app/routes/contract-template.detail.$id.tsx @@ -8,6 +8,9 @@ import { getUserSession } from '~/api/login/auth.server'; // 导入FilePreview组件 import { FilePreview } from '~/components/reviews'; +// 导入统一的下载方法和提示服务 +import { downloadFile } from '~/api/axios-client'; +import { toastService } from '~/components/ui/Toast'; export const links = () => [ { rel: 'stylesheet', href: styles }, @@ -70,45 +73,41 @@ export default function ContractTemplateDetail() { navigate(-1); }; - // MinIO下载URL构建函数 - const buildDownloadUrl = (filePath: string): string => { - const minioHost = 'http://nas.7bm.co:9000'; - const bucketName = 'docauditai'; - - const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath; - return `${minioHost}/${bucketName}/${cleanPath}`; - }; - - // 下载文件函数 - const downloadFile = async (filePath: string, fileName: string) => { - try { - const downloadUrl = buildDownloadUrl(filePath); - - const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_'); - - const link = document.createElement('a'); - link.href = downloadUrl; - link.download = cleanFileName; - link.target = '_blank'; - - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - // console.log('开始下载文件:', cleanFileName); - } catch (error) { - console.error('下载文件失败:', error); - alert('下载失败,请稍后重试'); + // 使用统一的下载方法(与 rules-files.tsx 相同) + const handleDownload = async () => { + if (!template.file_path) { + toastService.error('文件路径不存在,无法下载'); + return; } - }; - const handleDownload = () => { - if (template.file_path) { + try { + // 使用axios封装的下载方法 + const blob = await downloadFile(template.file_path); + + // 创建Blob URL + const blobUrl = URL.createObjectURL(blob); + + // 清理文件名,移除可能导致问题的字符 const fileExtension = template.file_format || 'docx'; const fileName = `${template.title}.${fileExtension}`; - downloadFile(template.file_path, fileName); - } else { - alert('文件路径不存在,无法下载'); + const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_'); + + // 创建一个隐藏的a标签并点击它 + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = blobUrl; + a.download = cleanFileName; + document.body.appendChild(a); + a.click(); + + // 清理 + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + }, 100); + } catch (error) { + console.error('下载文件失败:', error); + toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`); } };