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:
2025-12-24 18:21:55 +08:00
parent 5fa7b0bbe1
commit b64896a307
134 changed files with 4185 additions and 53 deletions

View File

@@ -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',

View File

@@ -531,6 +531,8 @@ export default FulltextDetailDrawer;

View File

@@ -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({

View File

@@ -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({

View File

@@ -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({

View File

@@ -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;

View File

@@ -124,6 +124,8 @@ export const useAssets = (activeTab: AssetTabType) => {

View File

@@ -114,6 +114,8 @@ export const useRecentTasks = () => {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -315,4 +315,6 @@ export default DropnaDialog;

View File

@@ -401,3 +401,5 @@ export default MetricTimePanel;

View File

@@ -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 {

View File

@@ -287,3 +287,5 @@ export default PivotPanel;

View File

@@ -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('');

View File

@@ -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 (

View File

@@ -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';

View File

@@ -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;

View File

@@ -87,3 +87,5 @@ export function useSessionStatus({
};
}

View File

@@ -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}

View File

@@ -76,6 +76,8 @@ export interface DataStats {

View File

@@ -72,6 +72,8 @@ export type AssetTabType = 'all' | 'processed' | 'raw';