fix: 1.将合同模板和交叉评查中的文件下载改用通过后端api进行转发获取文件来下载。 2.修复登录过程中token认证的代码问题。 3.完善api-config文件中不同端口号不同的回调地址配置。

This commit is contained in:
2025-11-07 18:36:15 +08:00
parent 80f05da984
commit b375c35825
5 changed files with 153 additions and 180 deletions
@@ -1,5 +1,8 @@
// import { useState } from 'react'; // import { useState } from 'react';
import { useNavigate } from '@remix-run/react'; import { useNavigate } from '@remix-run/react';
// 导入统一的下载方法和提示服务
import { downloadFile } from '~/api/axios-client';
import { toastService } from '~/components/ui/Toast';
interface Template { interface Template {
id: string; id: string;
@@ -29,56 +32,49 @@ export function TemplateCard({ template, onClick }: TemplateCardProps) {
setIsFavorited(!isFavorited); setIsFavorited(!isFavorited);
}; */ }; */
// MinIO下载URL构建函数 // 使用统一的下载方法(与 rules-files.tsx 相同)
const buildDownloadUrl = (filePath: string): string => { const handleDownloadFile = async (filePath: string, fileName: 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) => {
try { try {
const downloadUrl = buildDownloadUrl(filePath); // 使用axios封装的下载方法
const blob = await downloadFile(filePath);
// 创建Blob URL
const blobUrl = URL.createObjectURL(blob);
// 清理文件名,移除可能导致问题的字符 // 清理文件名,移除可能导致问题的字符
const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_'); const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_');
// 创建临时下载链接 // 创建一个隐藏的a标签并点击它
const link = document.createElement('a'); const a = document.createElement('a');
link.href = downloadUrl; a.style.display = 'none';
link.download = cleanFileName; a.href = blobUrl;
link.target = '_blank'; a.download = cleanFileName;
document.body.appendChild(a);
// 触发下载 a.click();
document.body.appendChild(link);
link.click(); // 清理
document.body.removeChild(link); setTimeout(() => {
document.body.removeChild(a);
// console.log('开始下载文件:', cleanFileName); URL.revokeObjectURL(blobUrl);
}, 100);
} catch (error) { } catch (error) {
console.error('下载文件失败:', error); console.error('下载文件失败:', error);
alert('下载失败,请稍后重试'); toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
} }
}; };
const handleActionClick = (e: React.MouseEvent, action: string) => { const handleActionClick = (e: React.MouseEvent, action: string) => {
e.stopPropagation(); e.stopPropagation();
switch (action) { switch (action) {
case '立即下载': case '立即下载':
if (template.file_path) { if (template.file_path) {
// 构建文件名,使用模板标题和文件格式 // 构建文件名,使用模板标题和文件格式
const fileExtension = template.file_format || 'docx'; const fileExtension = template.file_format || 'docx';
const fileName = `${template.title}.${fileExtension}`; const fileName = `${template.title}.${fileExtension}`;
downloadFile(template.file_path, fileName); handleDownloadFile(template.file_path, fileName);
} else { } else {
alert('文件路径不存在,无法下载'); toastService.error('文件路径不存在,无法下载');
} }
break; break;
case '预览': case '预览':
+35 -12
View File
@@ -2,11 +2,15 @@
* 交叉评查文件信息组件 * 交叉评查文件信息组件
*/ */
// 导入统一的下载方法和提示服务
import { downloadFile } from '~/api/axios-client';
import { toastService } from '~/components/ui/Toast';
interface FileInfoProps { interface FileInfoProps {
fileInfo: { fileInfo: {
fileName: string; fileName: string;
contractNumber: string; contractNumber: string;
fileSize?: string; fileSize?: string;
fileFormat?: string; fileFormat?: string;
pageCount?: number; pageCount?: number;
uploadTime?: string; uploadTime?: string;
@@ -21,17 +25,36 @@ interface FileInfoProps {
export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) { export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) {
const handleDownloadFile = () => { // 使用统一的下载方法(与 rules-files.tsx 相同)
if (fileInfo.path) { const handleDownloadFile = async () => {
// 创建一个隐藏的下载链接 if (!fileInfo.path) {
const link = document.createElement('a'); toastService.error('文件路径不存在,无法下载');
link.href = fileInfo.path; return;
link.download = fileInfo.fileName; }
document.body.appendChild(link);
link.click(); try {
document.body.removeChild(link); // 使用axios封装的下载方法
} else { const blob = await downloadFile(fileInfo.path);
alert('文件路径不存在,无法下载');
// 创建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 : '未知错误'}`);
} }
}; };
+35 -54
View File
@@ -14,15 +14,15 @@ interface ApiConfig {
// OAuth2.0配置 // OAuth2.0配置
oauth: { oauth: {
// IDaaS服务器地址 // IDaaS服务器地址
serverUrl: string; serverUrl?: string;
// OAuth2应用Client ID // OAuth2应用Client ID
clientId: string; clientId?: string;
// OAuth2应用Client Secret // OAuth2应用Client Secret
clientSecret: string; clientSecret?: string;
// 回调地址 // 回调地址
redirectUri: string; redirectUri?: string;
// 应用ID(用于登出) // 应用ID(用于登出)
appId: string; appId?: string;
}; };
} }
@@ -30,51 +30,15 @@ interface ApiConfig {
// 根据不同端口提供不同的API配置 // 根据不同端口提供不同的API配置
const portConfigs: Record<string, Partial<ApiConfig>> = { const portConfigs: Record<string, Partial<ApiConfig>> = {
// 测试主要服务实例
'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': { '51703': {
baseUrl: 'http://nas.7bm.co:8073', baseUrl: 'http://nas.7bm.co:8073',
documentUrl: 'http://nas.7bm.co:8073/docauditai/', 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', // baseUrl: 'http://nas.7bm.co:8873',
// documentUrl: 'http://nas.7bm.co:8873/docauditai/', // documentUrl: 'http://nas.7bm.co:8873/docauditai/',
// uploadUrl: 'http://nas.7bm.co:8873/admin/documents' // uploadUrl: 'http://nas.7bm.co:8873/admin/documents'
@@ -85,21 +49,30 @@ const portConfigs: Record<string, Partial<ApiConfig>> = {
'51704': { '51704': {
baseUrl: 'http://10.79.97.17:8001', baseUrl: 'http://10.79.97.17:8001',
documentUrl: 'http://10.79.97.17:8001/docauditai/', 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': { '51705': {
baseUrl: 'http://10.79.97.17:8002', baseUrl: 'http://10.79.97.17:8002',
documentUrl: 'http://10.79.97.17:8002/docauditai/', 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': { '51706': {
baseUrl: 'http://10.79.97.17:8003', baseUrl: 'http://10.79.97.17:8003',
documentUrl: 'http://10.79.97.17:8003/docauditai/', 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<string, Partial<ApiConfig>> = {
'51707': { '51707': {
baseUrl: 'http://10.79.97.17:8004', baseUrl: 'http://10.79.97.17:8004',
documentUrl: 'http://10.79.97.17:8004/docauditai/', 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 //test
'51708': { '51708': {
baseUrl: 'http://10.79.97.17:8005', baseUrl: 'http://10.79.97.17:8005',
@@ -312,13 +289,17 @@ const getCurrentConfig = (): ApiConfig => {
// 如果有端口特定配置,则合并配置 // 如果有端口特定配置,则合并配置
if (port && portConfigs[port]) { if (port && portConfigs[port]) {
console.log(`🔧 使用端口特定配置: ${port}`, portConfigs[port]); console.log(`🔧 使用端口特定配置: ${port}`);
const portConfig = portConfigs[port];
defaultConfig = { defaultConfig = {
...defaultConfig, ...defaultConfig,
...portConfigs[port], ...portConfig,
// 保持oauth配置不变,只覆盖API相关配置 // 如果端口配置中有oauth,需要深度合并oauth配置
oauth: defaultConfig.oauth oauth: portConfig.oauth
? { ...defaultConfig.oauth, ...portConfig.oauth }
: defaultConfig.oauth
}; };
console.log(`🔧 使用端口特定配置---深度合并后: ${JSON.stringify(defaultConfig.oauth)}`)
} else { } else {
console.log(`🔧 使用环境配置: ${env}`, defaultConfig); console.log(`🔧 使用环境配置: ${env}`, defaultConfig);
} }
+20 -46
View File
@@ -1,5 +1,7 @@
import { type LoaderFunctionArgs, redirect } from "@remix-run/node"; import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server"; 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"; import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
/** /**
@@ -39,7 +41,7 @@ async function redirectToLoginWithError(request: Request, errorMessage: string)
export async function loader({ request }: LoaderFunctionArgs) { export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url); 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 port = url.port; // 获取端口号
const area = getAreaByPort(port); // 根据端口号获取地区 const area = getAreaByPort(port); // 根据端口号获取地区
const code = url.searchParams.get("code"); const code = url.searchParams.get("code");
@@ -80,55 +82,25 @@ export async function loader({ request }: LoaderFunctionArgs) {
try { try {
console.log("🔧 开始处理OAuth2.0回调"); console.log("🔧 开始处理OAuth2.0回调");
// --- 修改开始: 不再直接调用OAuthClient,而是通过内部代理API --- const oauthClient = new OAuthClient(OAUTH_CONFIG)
// 获取访问令牌 (通过代理) // 开始获取访问令牌
console.log(`🔧 [Callback] 开始通过内部代理获取访问令牌... (目标: ${origin}/api/oauth/token)`); const tokenResponse = await oauthClient.getAccessToken(code);
const proxyResponse = await fetch(`${origin}/api/oauth/token`, { if (!tokenResponse) {
method: 'POST', console.error("获取访问令牌失败");
headers: { return redirect("/login?error=token_error")
'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, "获取访问令牌失败,请重新登录");
} }
// --- 修改结束 ---
console.log("✅ [Callback] 访问令牌获取成功"); 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(); const userInfo = await oauthClient.getUserInfo(tokenResponse.access_token);
if(!userInfo || !userInfo.success){
if (!userInfoProxyResponse.ok || !userInfoResponse.success) { console.error('获取用户信息失败:',userInfo);
console.error("❌ [Callback] 通过内部代理获取用户信息失败:", userInfoResponse); return redirect('/login?error=userinfo_error')
return redirectToLoginWithError(request, "获取用户信息失败,请重新登录");
} }
// 将代理返回的用户信息包装成与原有一致的结构
const userInfo = {
success: true,
data: userInfoResponse.data,
};
// --- 修改结束 ---
console.log("✅ [Callback] 用户信息获取成功"); console.log("✅ [Callback] 用户信息获取成功");
// TODO 根据用户信息判断用户角色,这里可以根据实际业务逻辑调整 暂定都是common // TODO 根据用户信息判断用户角色,这里可以根据实际业务逻辑调整 暂定都是common
const userRole = "common"; const userRole = "common";
@@ -138,14 +110,16 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 先生成一个临时 JWT // 先生成一个临时 JWT
const tempUserInfo = { const tempUserInfo = {
sub: userInfo.data.sub, sub: userInfo.data.sub,
user_id: userInfo.data.user_id || "", // user_id: userInfo.data.user_id || "",
user_id: "",
username: userInfo.data.username, username: userInfo.data.username,
nick_name: userInfo.data.nick_name, nick_name: userInfo.data.nickname,
email: userInfo.data.email, email: userInfo.data.email,
phone_number: userInfo.data.phone_number, phone_number: userInfo.data.phone_number,
ou_id: userInfo.data.ou_id, ou_id: userInfo.data.ou_id,
ou_name: userInfo.data.ou_name, 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' user_role: userRole as 'common' | 'developer'
}; };
const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenResponse.expires_in); 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); const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, tokenResponse.expires_in);
console.log("前端JWT已生成"); // console.log("前端JWT已生成");
// 更新userInfo以包含数据库ID、JWT,并用数据库标准字段覆盖关键属性,确保 nick_name 等存在 // 更新userInfo以包含数据库ID、JWT,并用数据库标准字段覆盖关键属性,确保 nick_name 等存在
const enhancedUserInfo = { const enhancedUserInfo = {
+34 -35
View File
@@ -8,6 +8,9 @@ import { getUserSession } from '~/api/login/auth.server';
// 导入FilePreview组件 // 导入FilePreview组件
import { FilePreview } from '~/components/reviews'; import { FilePreview } from '~/components/reviews';
// 导入统一的下载方法和提示服务
import { downloadFile } from '~/api/axios-client';
import { toastService } from '~/components/ui/Toast';
export const links = () => [ export const links = () => [
{ rel: 'stylesheet', href: styles }, { rel: 'stylesheet', href: styles },
@@ -70,45 +73,41 @@ export default function ContractTemplateDetail() {
navigate(-1); navigate(-1);
}; };
// MinIO下载URL构建函数 // 使用统一的下载方法(与 rules-files.tsx 相同)
const buildDownloadUrl = (filePath: string): string => { const handleDownload = async () => {
const minioHost = 'http://nas.7bm.co:9000'; if (!template.file_path) {
const bucketName = 'docauditai'; toastService.error('文件路径不存在,无法下载');
return;
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('下载失败,请稍后重试');
} }
};
const handleDownload = () => { try {
if (template.file_path) { // 使用axios封装的下载方法
const blob = await downloadFile(template.file_path);
// 创建Blob URL
const blobUrl = URL.createObjectURL(blob);
// 清理文件名,移除可能导致问题的字符
const fileExtension = template.file_format || 'docx'; const fileExtension = template.file_format || 'docx';
const fileName = `${template.title}.${fileExtension}`; const fileName = `${template.title}.${fileExtension}`;
downloadFile(template.file_path, fileName); const cleanFileName = fileName.replace(/[<>:"/\\|?*]/g, '_');
} else {
alert('文件路径不存在,无法下载'); // 创建一个隐藏的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 : '未知错误'}`);
} }
}; };