feat(deploy): Complete PostgreSQL migration and Docker image build
Summary: - PostgreSQL database migration to RDS completed (90MB SQL, 11 schemas) - Frontend Nginx Docker image built and pushed to ACR (v1.0, ~50MB) - Python microservice Docker image built and pushed to ACR (v1.0, 1.12GB) - Created 3 deployment documentation files Docker Configuration Files: - frontend-v2/Dockerfile: Multi-stage build with nginx:alpine - frontend-v2/.dockerignore: Optimize build context - frontend-v2/nginx.conf: SPA routing and API proxy - frontend-v2/docker-entrypoint.sh: Dynamic env injection - extraction_service/Dockerfile: Multi-stage build with Aliyun Debian mirror - extraction_service/.dockerignore: Optimize build context - extraction_service/requirements-prod.txt: Production dependencies (removed Nougat) Deployment Documentation: - docs/05-部署文档/00-部署进度总览.md: One-stop deployment status overview - docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md: Frontend deployment guide - docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md: Database deployment guide - docs/00-系统总体设计/00-系统当前状态与开发指南.md: Updated with deployment status Database Migration: - RDS instance: pgm-2zex1m2y3r23hdn5 (2C4G, PostgreSQL 15.0) - Database: ai_clinical_research - Schemas: 11 business schemas migrated successfully - Data: 3 users, 2 projects, 1204 literatures verified - Backup: rds_init_20251224_154529.sql (90MB) Docker Images: - Frontend: crpi-cd5ij4pjt65mweeo.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/ai-clinical_frontend-nginx:v1.0 - Python: crpi-cd5ij4pjt65mweeo.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/python-extraction:v1.0 Key Achievements: - Resolved Docker Hub network issues (using generic tags) - Fixed 30 TypeScript compilation errors - Removed Nougat OCR to reduce image size by 1.5GB - Used Aliyun Debian mirror to resolve apt-get network issues - Implemented multi-stage builds for optimization Next Steps: - Deploy Python microservice to SAE - Build Node.js backend Docker image - Deploy Node.js backend to SAE - Deploy frontend Nginx to SAE - End-to-end verification testing Status: Docker images ready, SAE deployment pending
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
import type { ConclusionType } from '../types';
|
||||
|
||||
interface ConclusionTagProps {
|
||||
conclusion: ConclusionType;
|
||||
conclusion: ConclusionType | 'pending';
|
||||
showIcon?: boolean;
|
||||
size?: 'small' | 'middle' | 'large';
|
||||
}
|
||||
@@ -42,6 +42,12 @@ const ConclusionTag: React.FC<ConclusionTagProps> = ({
|
||||
icon: <QuestionCircleOutlined />,
|
||||
text: '不确定',
|
||||
};
|
||||
case 'pending':
|
||||
return {
|
||||
color: 'processing',
|
||||
icon: <QuestionCircleOutlined />,
|
||||
text: '待处理',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
color: 'default',
|
||||
|
||||
@@ -531,6 +531,8 @@ export default FulltextDetailDrawer;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* 4. 人工复核
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { message } from 'antd';
|
||||
import { aslApi } from '../api';
|
||||
@@ -131,5 +130,6 @@ export function useFulltextResults({
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ export function useFulltextTask({
|
||||
enabled: enabled && !!taskId,
|
||||
refetchInterval: refetchInterval !== undefined
|
||||
? refetchInterval
|
||||
: ((data) => {
|
||||
: ((data: any) => {
|
||||
// 默认行为:任务进行中时每2秒轮询,否则停止
|
||||
if (!data?.data) return false;
|
||||
const status = (data.data as any).status;
|
||||
const status = data.data.status;
|
||||
return status === 'processing' || status === 'pending' ? 2000 : false;
|
||||
}),
|
||||
retry: 1,
|
||||
@@ -94,5 +94,6 @@ export function useFulltextTask({
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -33,12 +33,11 @@ export function useScreeningResults({
|
||||
queryFn: () => aslApi.getScreeningResultsList(projectId, { page, pageSize, filter }),
|
||||
enabled: enabled && !!projectId,
|
||||
staleTime: 1000 * 30, // 30秒内认为数据是新鲜的
|
||||
keepPreviousData: true, // 切换页面时保留上一页数据,避免闪烁
|
||||
});
|
||||
|
||||
const results = data?.data?.items || [];
|
||||
const total = data?.data?.total || 0;
|
||||
const totalPages = data?.data?.totalPages || 0;
|
||||
const results = (data as any)?.data?.items || [];
|
||||
const total = (data as any)?.data?.total || 0;
|
||||
const totalPages = (data as any)?.data?.totalPages || 0;
|
||||
|
||||
// 人工复核Mutation
|
||||
const reviewMutation = useMutation({
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import {
|
||||
Card,
|
||||
Statistic,
|
||||
@@ -59,8 +59,6 @@ interface FulltextResultItem {
|
||||
|
||||
const FulltextResults = () => {
|
||||
const { taskId } = useParams<{ taskId: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const projectId = searchParams.get('projectId') || '';
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'all' | 'included' | 'excluded' | 'pending'>('all');
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
||||
@@ -108,7 +106,7 @@ const FulltextResults = () => {
|
||||
const results = resultsData?.items || [];
|
||||
|
||||
// 导出Excel
|
||||
const handleExport = async (filter: 'all' | 'included' | 'excluded' | 'pending') => {
|
||||
const handleExport = async (_filter: 'all' | 'included' | 'excluded' | 'pending') => {
|
||||
try {
|
||||
message.loading({ content: '正在生成Excel...', key: 'export' });
|
||||
|
||||
@@ -432,7 +430,7 @@ const FulltextResults = () => {
|
||||
expandable={{
|
||||
expandedRowRender,
|
||||
expandedRowKeys,
|
||||
onExpand: (expanded, record) => toggleRowExpanded(record.resultId),
|
||||
onExpand: (_expanded, record) => toggleRowExpanded(record.resultId),
|
||||
expandIcon: () => null,
|
||||
}}
|
||||
pagination={{
|
||||
@@ -485,5 +483,6 @@ export default FulltextResults;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ export const useAssets = (activeTab: AssetTabType) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ export const useRecentTasks = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ interface VerifyRow {
|
||||
status: 'clean' | 'conflict' | 'pending' | 'failed'; // 行状态
|
||||
}
|
||||
|
||||
const Step4Verify: React.FC<Step4VerifyProps> = ({ state, updateState, onComplete }) => {
|
||||
const Step4Verify: React.FC<Step4VerifyProps> = ({ state, onComplete }) => {
|
||||
const [rows, setRows] = useState<VerifyRow[]>([]);
|
||||
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Select, Input, Button, Radio, Space, Tag, App, Alert } from 'antd';
|
||||
import { Plus, X, Info } from 'lucide-react';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
interface BinningDialogProps {
|
||||
visible: boolean;
|
||||
@@ -353,3 +353,4 @@ export default BinningDialog;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Input, Button, Select, Space, Alert, App, Card, Tag } from 'antd';
|
||||
import { Modal, Input, Button, Select, Alert, App, Card, Tag } from 'antd';
|
||||
import { PlusCircle, Trash2, AlertCircle } from 'lucide-react';
|
||||
|
||||
interface Condition {
|
||||
@@ -28,7 +28,6 @@ const ConditionalDialog: React.FC<Props> = ({
|
||||
onClose,
|
||||
onApply,
|
||||
columns,
|
||||
data,
|
||||
sessionId,
|
||||
}) => {
|
||||
const { message } = App.useApp();
|
||||
|
||||
@@ -315,4 +315,6 @@ export default DropnaDialog;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -401,3 +401,5 @@ export default MetricTimePanel;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Select, Button, Alert, Checkbox, Radio, App, Tag } from 'antd';
|
||||
import { Modal, Select, Button, Alert, Checkbox, Radio, App } from 'antd';
|
||||
import { ArrowLeftRight, Info } from 'lucide-react';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -287,3 +287,5 @@ export default PivotPanel;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,14 +27,13 @@ interface MappingRow {
|
||||
const RecodeDialog: React.FC<RecodeDialogProps> = ({
|
||||
visible,
|
||||
columns,
|
||||
data,
|
||||
sessionId,
|
||||
onClose,
|
||||
onApply,
|
||||
}) => {
|
||||
const { message } = App.useApp();
|
||||
const [selectedColumn, setSelectedColumn] = useState<string>('');
|
||||
const [uniqueValues, setUniqueValues] = useState<any[]>([]);
|
||||
const [_uniqueValues, setUniqueValues] = useState<any[]>([]);
|
||||
const [mappingTable, setMappingTable] = useState<MappingRow[]>([]);
|
||||
const [createNewColumn, setCreateNewColumn] = useState(true);
|
||||
const [newColumnName, setNewColumnName] = useState('');
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
import {
|
||||
Calculator,
|
||||
CalendarClock,
|
||||
ArrowLeftRight,
|
||||
FileSearch,
|
||||
Wand2,
|
||||
Filter,
|
||||
Search,
|
||||
@@ -71,9 +69,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
onConditionalClick,
|
||||
onDropnaClick,
|
||||
onComputeClick,
|
||||
onDedupClick,
|
||||
onPivotClick,
|
||||
onMiceClick,
|
||||
sessionId,
|
||||
}) => {
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Tabs } from 'antd';
|
||||
import { ArrowLeftRight, Sparkles, BarChart3 } from 'lucide-react';
|
||||
import { ArrowLeftRight, BarChart3 } from 'lucide-react';
|
||||
import PivotPanel from './PivotPanel';
|
||||
import UnpivotPanel from './UnpivotPanel';
|
||||
import { MultiMetricPanel } from './MultiMetricPanel';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Select, Button, Alert, Checkbox, App, Input, Collapse, Radio } from 'antd';
|
||||
import { Button, Alert, Checkbox, App, Input, Collapse, Radio } from 'antd';
|
||||
import { ArrowLeftRight, Info } from 'lucide-react';
|
||||
|
||||
interface Props {
|
||||
@@ -392,3 +392,4 @@ export default UnpivotPanel;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -87,3 +87,5 @@ export function useSessionStatus({
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -460,7 +460,6 @@ const ToolC = () => {
|
||||
<MissingValueDialog
|
||||
visible={state.dropnaDialogVisible}
|
||||
columns={state.columns}
|
||||
data={state.data}
|
||||
sessionId={state.sessionId}
|
||||
onClose={() => updateState({ dropnaDialogVisible: false })}
|
||||
onApply={handleQuickActionDataUpdate}
|
||||
|
||||
@@ -76,6 +76,8 @@ export interface DataStats {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ export type AssetTabType = 'all' | 'processed' | 'raw';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user