Major Changes: - Add StreamingService with OpenAI Compatible format - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Update API routes to unified /api/v1 prefix - Update system documentation Backend (~1300 lines): - common/streaming: OpenAI Compatible adapter - modules/aia: 12 agents, conversation service, streaming integration - Update route versions (RVW, PKB to v1) Frontend (~3500 lines): - modules/aia: AgentHub + ChatWorkspace (100% prototype restoration) - shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook - Update API endpoints to v1 Documentation: - AIA module status guide - Universal capabilities catalog - System overview updates - All module documentation sync Tested: Stream response verified, authentication working Status: AIA V2.0 core completed (85%)
808 lines
19 KiB
Markdown
808 lines
19 KiB
Markdown
# <20>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD><E69285><EFBFBD>臬<EFBFBD>箏𦛚皜<F0A69B9A><E79A9C>
|
||
|
||
> **<2A><>﹝<EFBFBD><EFB99D>𧋦嚗?* v1.0
|
||
> **<2A>𥕦遣<F0A595A6>交<EFBFBD>嚗?* 2025-12-22
|
||
> **蝏湔擪<E6B994><E693AA><EFBFBD>** 撟喳蝱<E596B3>嗆<EFBFBD><E59786>a<EFBFBD>
|
||
> **<2A><>﹝<EFBFBD>桃<EFBFBD>嚗?* 霈啣<E99C88><E595A3>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD><E69285>隡睃<E99AA1>憿對<E686BF><E5B08D><EFBFBD>紡<EFBFBD>芣䔉餈凋誨
|
||
|
||
---
|
||
|
||
## <20><> 璁<>膩
|
||
|
||
<EFBFBD>祆<EFBFBD>獢<EFBFBD>抅鈭?**DC Tool C 撘<>郊<EFBFBD>嗆<EFBFBD>摰噼殿**嚗?025-12-22嚗㚁<E59A97><E39A81>餌<EFBFBD><E9A48C>𤑳緵<F0A491B3><E7B7B5>虾隞交𡂝鞊∩蛹<E288A9>𡁶鍂<F0A181B6>賢<EFBFBD><E8B3A2><EFBFBD>芋撘譌<E69298>?
|
||
|
||
**<EFBFBD>詨<EFBFBD><EFBFBD>脲<EFBFBD>**嚗?
|
||
- <20>?**敶枏<E695B6><E69E8F><EFBFBD><EFBFBD>撌脩<E6928C>敺<EFBFBD>末**嚗<><E59A97>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD><E69285>靘𥕦<E99D98><F0A595A6>渡<EFBFBD><E6B8A1>箇<EFBFBD>霈暹鴌嚗?
|
||
- <20>?**銝𡁜𦛚璅∪<E79285>甇<EFBFBD>&雿輻鍂鈭<E98D82><E988AD>鈭𥡝<E988AD><F0A5A19D>?*
|
||
- <20>哨<EFBFBD> **<EFBFBD>芣䔉<EFBFBD>臭誑餈𥕢<EFBFBD>甇交𡂝鞊?*嚗<>釺銝𦠜溶<F0A6A09C>梧<EFBFBD><E6A2A7>𧼮<EFBFBD><F0A7BCAE><EFBFBD>嚗?
|
||
|
||
---
|
||
|
||
## <20>㴓 <20><><EFBFBD>臬<EFBFBD>箏𦛚皜<F0A69B9A><E79A9C>
|
||
|
||
### TD-COMMON-001: <20>滨垢<E6BBA8>𡁶鍂頧株砭Hook
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃鐥<E6BD83> P2嚗<32>葉嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?.5憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗帋誨<E5B88B><E8AAA8><EFBFBD><EFBFBD>冽<EFBFBD>扳<EFBFBD><E689B3><EFBFBD><EFBFBD>璅∪<E79285><E288AA>渡<EFBFBD>銝<EFBFBD>
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗?
|
||
- Tool C 摰䂿緵嚗䫤dc/tool-c/hooks/useSessionStatus.ts`
|
||
- ASL 摰䂿緵嚗䫤asl/hooks/useScreeningTask.ts`
|
||
- 隞<><E99A9E><EFBFBD>滚<EFBFBD>摨佗<E691A8>70%
|
||
|
||
**<EFBFBD>滚<EFBFBD><EFBFBD><EFBFBD><EFBFBD>餉<EFBFBD>**嚗?
|
||
```typescript
|
||
// 瘥譍葵璅∪<E79285><E288AA>質<EFBFBD><E8B3AA>嗵掩隡潛<E99AA1>隞<EFBFBD><E99A9E>
|
||
useQuery({
|
||
queryKey: ['taskStatus', id],
|
||
queryFn: () => api.getStatus(id),
|
||
enabled: !!id,
|
||
refetchInterval: (query) => {
|
||
const status = query.state.data?.status;
|
||
if (status === 'ready' || status === 'error') return false;
|
||
return 2000;
|
||
},
|
||
staleTime: 0,
|
||
});
|
||
```
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
**<EFBFBD>質情銝粹<EFBFBD>𡁶鍂Hook**嚗?
|
||
|
||
```typescript
|
||
// frontend-v2/src/common/hooks/useAsyncTaskPolling.ts嚗<73>鰵撱綽<E692B1>
|
||
|
||
import { useQuery } from '@tanstack/react-query';
|
||
|
||
interface UseAsyncTaskPollingOptions<T> {
|
||
/** 隞餃𦛚ID */
|
||
taskId: string | null;
|
||
|
||
/** <20>嗆<EFBFBD><E59786>䰻霂∕PI<50>賣㺭 */
|
||
queryFn: (taskId: string) => Promise<T>;
|
||
|
||
/** <20>嗆<EFBFBD><E59786><EFBFBD><EFBFBD>硋遆<E7A18B>?*/
|
||
getStatus: (data: T) => 'pending' | 'processing' | 'ready' | 'error' | string;
|
||
|
||
/** 餈𥕦漲<F0A595A6>𣂼<EFBFBD><F0A382BC>賣㺭嚗<E3BAAD>虾<EFBFBD>㚁<EFBFBD> */
|
||
getProgress?: (data: T) => number;
|
||
|
||
/** <20>臬炏<E887AC>舐鍂 */
|
||
enabled?: boolean;
|
||
|
||
/** 頧株砭<E6A0AA>湧<EFBFBD>嚗<EFBFBD>神蝘𡜐<E89D98>嚗屸<E59A97>霈?000 */
|
||
pollingInterval?: number;
|
||
}
|
||
|
||
export function useAsyncTaskPolling<T>({
|
||
taskId,
|
||
queryFn,
|
||
getStatus,
|
||
getProgress,
|
||
enabled = true,
|
||
pollingInterval = 2000,
|
||
}: UseAsyncTaskPollingOptions<T>) {
|
||
const { data, isLoading, error } = useQuery({
|
||
queryKey: ['asyncTask', taskId],
|
||
queryFn: () => queryFn(taskId!),
|
||
enabled: enabled && !!taskId,
|
||
refetchInterval: (query) => {
|
||
if (!query.state.data) return false;
|
||
|
||
const status = getStatus(query.state.data);
|
||
|
||
// 摰峕<E691B0><E5B395>硋仃韐交𧒄<E4BAA4>𨀣迫頧株砭
|
||
if (status === 'ready' || status === 'completed' || status === 'error' || status === 'failed') {
|
||
return false;
|
||
}
|
||
|
||
return pollingInterval;
|
||
},
|
||
staleTime: 0,
|
||
retry: 1,
|
||
});
|
||
|
||
const status = data ? getStatus(data) : 'pending';
|
||
const progress = getProgress && data ? getProgress(data) : 0;
|
||
|
||
return {
|
||
data,
|
||
status,
|
||
progress,
|
||
isReady: status === 'ready' || status === 'completed',
|
||
isError: status === 'error' || status === 'failed',
|
||
isProcessing: status === 'processing' || status === 'pending',
|
||
isLoading,
|
||
error,
|
||
};
|
||
}
|
||
```
|
||
|
||
**雿輻鍂蝷箔<E89DB7>**嚗?
|
||
|
||
```typescript
|
||
// Tool C 雿輻鍂
|
||
const { status, progress, isReady } = useAsyncTaskPolling({
|
||
taskId: sessionId,
|
||
queryFn: (sid) => api.getSessionStatus(sid, jobId),
|
||
getStatus: (res) => res.data.status,
|
||
getProgress: (res) => res.data.progress,
|
||
});
|
||
|
||
// ASL 雿輻鍂
|
||
const { status, isReady } = useAsyncTaskPolling({
|
||
taskId: projectId,
|
||
queryFn: (pid) => aslApi.getScreeningTask(pid),
|
||
getStatus: (res) => res.data.status,
|
||
getProgress: (res) => Math.round((res.data.processedItems / res.data.totalItems) * 100),
|
||
});
|
||
```
|
||
|
||
#### 敶勗<E695B6><E58B97><EFBFBD>凒
|
||
|
||
- Tool C: `useSessionStatus.ts` <20>舐<EFBFBD><E88890>?
|
||
- ASL: `useScreeningTask.ts` <20>舐<EFBFBD><E88890>?
|
||
- Tool B: <20>芣䔉<E88AA3>滨垢<E6BBA8>舐凒<E88890>乩蝙<E4B9A9>?
|
||
- <20>嗡<EFBFBD>璅∪<E79285>: <20>湔𦻖憭滨鍂
|
||
|
||
---
|
||
|
||
### TD-COMMON-002: Clean Data 蝻枏<E89DBB><E69E8F>滚𦛚
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃鐥<E6BD83>潃?P1嚗<31><E59A97>嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗𡁏<E59A97>扯<EFBFBD><E689AF>𣂼<EFBFBD>99%嚗峕<E59A97><E5B395>㗇芋<E39787>堒<EFBFBD><E5A092>?
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗?
|
||
- Tool C 摰䂿緵嚗帋<E59A97>摮?`${fileKey}_clean.json`
|
||
- ASL<53><4C>ool B嚗𡁏𧊋摰䂿緵嚗䔶<E59A97><E494B6>嗆<EFBFBD>甈⊿<E79488><E28ABF>啗圾<E59597>?
|
||
|
||
**<EFBFBD>滚<EFBFBD>霈∠<EFBFBD><EFBFBD>桅<EFBFBD>**嚗?
|
||
- ASL <20><>讃蝑偦<E89D91>㚁<EFBFBD>瘥𤩺活隞?OSS 銝贝蝸 PDF嚗屸<E59A97><E5B1B8>啗圾<E59597>琜<EFBFBD>5-10蝘?蝭<><E89DAD>
|
||
- Tool B <20>唳旿<E594B3>𣂼<EFBFBD>嚗𡁏<E59A97>甈⊿<E79488><E28ABF>啗粉<E59597>?Excel
|
||
- Tool C <20>滢<EFBFBD>嚗𡁜歇隡睃<E99AA1>嚗Ếlean data蝻枏<E89DBB>嚗?
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
**<EFBFBD>質情銝粹<EFBFBD>𡁶鍂<EFBFBD>滚𦛚**嚗?
|
||
|
||
```typescript
|
||
// backend/src/common/services/DataCacheService.ts嚗<73>鰵撱綽<E692B1>
|
||
|
||
import { storage } from '../storage';
|
||
import { prisma } from '../../config/database';
|
||
import { logger } from '../logging';
|
||
|
||
/**
|
||
* <20>唳旿蝻枏<E89DBB><E69E8F>滚𦛚嚗<F0A69B9A><E59A97>𡁶鍂嚗?
|
||
*
|
||
* <20>券<EFBFBD>䈑<EFBFBD>
|
||
* - Worker 閫<><E996AB><EFBFBD>𦒘<EFBFBD>摮睃<E691AE><E79D83><EFBFBD><EFBFBD><EFBFBD>?
|
||
* - Service 隡睃<E99AA1>霂餃<E99C82>蝻枏<E89DBB>嚗屸<E59A97><E5B1B8>漤<EFBFBD>憭滩恣蝞?
|
||
* - <20>滢<EFBFBD><E6BBA2>𤾸<EFBFBD>甇交凒<E4BAA4>啁<EFBFBD>摮?
|
||
*/
|
||
export class DataCacheService {
|
||
/**
|
||
* 靽嘥<E99DBD>皜<EFBFBD><E79A9C>/憭<><E686AD><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
|
||
*
|
||
* @param originalKey <20>笔<EFBFBD><E7AC94><EFBFBD>辣key
|
||
* @param cleanData 皜<><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
|
||
* @param suffix <20>𡒊<EFBFBD>嚗<EFBFBD><E59A97>霈?'_clean.json'嚗?
|
||
* @returns clean data <20>?OSS key
|
||
*/
|
||
async saveCleanData(
|
||
originalKey: string,
|
||
cleanData: any,
|
||
suffix: string = '_clean.json'
|
||
): Promise<string> {
|
||
const cleanDataKey = `${originalKey}${suffix}`;
|
||
|
||
logger.info('[DataCacheService] Saving clean data', {
|
||
originalKey,
|
||
cleanDataKey,
|
||
rows: Array.isArray(cleanData) ? cleanData.length : 'N/A',
|
||
});
|
||
|
||
// 摨誩<E691A8><E8AAA9>硋僎銝𠹺<E98A9D>
|
||
const buffer = Buffer.from(JSON.stringify(cleanData), 'utf-8');
|
||
await storage.upload(cleanDataKey, buffer);
|
||
|
||
logger.info('[DataCacheService] Clean data saved', {
|
||
size: `${(buffer.length / 1024).toFixed(2)} KB`
|
||
});
|
||
|
||
return cleanDataKey;
|
||
}
|
||
|
||
/**
|
||
* 霂餃<E99C82>皜<EFBFBD><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
|
||
*
|
||
* @param cleanDataKey clean data <20>?OSS key
|
||
* @returns 皜<><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
|
||
*/
|
||
async getCleanData(cleanDataKey: string): Promise<any> {
|
||
logger.info('[DataCacheService] Loading clean data', { cleanDataKey });
|
||
|
||
const buffer = await storage.download(cleanDataKey);
|
||
const data = JSON.parse(buffer.toString('utf-8'));
|
||
|
||
logger.info('[DataCacheService] Clean data loaded', {
|
||
rows: Array.isArray(data) ? data.length : 'N/A',
|
||
});
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* <20>湔鰵皜<E9B0B5><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
|
||
*
|
||
* @param cleanDataKey clean data <20>?OSS key
|
||
* @param newData <20>唳㺭<E594B3>?
|
||
*/
|
||
async updateCleanData(cleanDataKey: string, newData: any): Promise<void> {
|
||
logger.info('[DataCacheService] Updating clean data', {
|
||
cleanDataKey,
|
||
rows: Array.isArray(newData) ? newData.length : 'N/A',
|
||
});
|
||
|
||
const buffer = Buffer.from(JSON.stringify(newData), 'utf-8');
|
||
await storage.upload(cleanDataKey, buffer);
|
||
|
||
logger.info('[DataCacheService] Clean data updated');
|
||
}
|
||
|
||
/**
|
||
* <20>𣳇膄皜<E88684><E79A9C><EFBFBD>𡒊<EFBFBD><F0A1928A>唳旿
|
||
*
|
||
* @param cleanDataKey clean data <20>?OSS key
|
||
*/
|
||
async deleteCleanData(cleanDataKey: string): Promise<void> {
|
||
try {
|
||
await storage.delete(cleanDataKey);
|
||
logger.info('[DataCacheService] Clean data deleted', { cleanDataKey });
|
||
} catch (error: any) {
|
||
logger.warn('[DataCacheService] Clean data deletion failed', {
|
||
cleanDataKey,
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
export const dataCacheService = new DataCacheService();
|
||
```
|
||
|
||
**雿輻鍂蝷箔<E89DB7>**嚗?
|
||
|
||
```typescript
|
||
// Worker 銝?
|
||
const cleanDataKey = await dataCacheService.saveCleanData(fileKey, cleanedData);
|
||
await prisma.update({ where: { id }, data: { cleanDataKey } });
|
||
|
||
// Service 銝?
|
||
if (record.cleanDataKey) {
|
||
return await dataCacheService.getCleanData(record.cleanDataKey);
|
||
}
|
||
|
||
// <20>滢<EFBFBD><E6BBA2>擧凒<E693A7>?
|
||
await dataCacheService.updateCleanData(record.cleanDataKey, newData);
|
||
```
|
||
|
||
#### 敶勗<E695B6><E58B97><EFBFBD>凒
|
||
|
||
- Tool C: 蝞<><E89D9E>𣇉緵<F0A38789>劐誨<E58A90>?
|
||
- ASL: <20><>讃閫<E8AE83><E996AB>蝏𤘪<E89D8F>蝻枏<E89DBB>嚗<EFBFBD><E59A97><EFBFBD>?9%嚗?
|
||
- Tool B: <20>唳旿<E594B3>𣂼<EFBFBD>蝏𤘪<E89D8F>蝻枏<E89DBB>
|
||
- <20><><EFBFBD>㗇芋<E39787>? 蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD>摮䀹㦤<E480B9>?
|
||
|
||
---
|
||
|
||
### TD-COMMON-003: <20>箄<EFBFBD>皜<EFBFBD><E79A9C>蝞埈<E89D9E><E59F88>𡁶鍂<F0A181B6>?
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃?P2嚗<32>葉嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?.5憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗帋誨<E5B88B><E8AAA8><EFBFBD><EFBFBD>剁<EFBFBD><E58981>踹<EFBFBD><E8B8B9>滚<EFBFBD>摰䂿緵
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗?
|
||
- Tool C 摰䂿緵嚗䫤intelligentCleanData`嚗<>器<EFBFBD>峕<EFBFBD>瘚?摰匧<E691B0><E58CA7><EFBFBD>嚗?
|
||
- <20>嗡<EFBFBD>璅∪<E79285>嚗𡁏𧊋摰䂿緵蝐颱撮<E9A2B1>蠘<EFBFBD>
|
||
|
||
**<EFBFBD>臭誑<EFBFBD>𡁶鍂<EFBFBD>𣇉<EFBFBD>蝞埈<EFBFBD>**嚗?
|
||
1. 撟賜<E6929F><E8B39C>埈<EFBFBD>瘚页<E7989A>颲寧<E9A2B2>璉<EFBFBD>瘚页<E7989A>
|
||
2. 撟賜<E6929F>銵諹<E98AB5>皛?
|
||
3. 摰匧<E691B0><E58CA7><EFBFBD>嚗<EFBFBD><E59A97>憭批<E686AD><E689B9>啜<EFBFBD><E5959C><EFBFBD><EFBFBD><EFBFBD>聢<EFBFBD>圈<EFBFBD><E59C88>塚<EFBFBD>
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
```typescript
|
||
// backend/src/common/utils/dataCleaningUtils.ts嚗<73>鰵撱綽<E692B1>
|
||
|
||
export interface CleaningOptions {
|
||
maxCols?: number; // <20><>憭批<E686AD><E689B9>堆<EFBFBD>暺䁅恕3000
|
||
maxCells?: number; // <20><>憭批<E686AD><E689B9><EFBFBD>聢<EFBFBD>堆<EFBFBD>暺䁅恕500銝?
|
||
removeEmptyRows?: boolean; // <20>臬炏<E887AC>𣳇膄蝛箄<E89D9B>嚗屸<E59A97>霈川rue
|
||
removeEmptyCols?: boolean; // <20>臬炏<E887AC>𣳇膄蝛箏<E89D9B>嚗屸<E59A97>霈川rue
|
||
}
|
||
|
||
export function intelligentCleanData(
|
||
data: any[],
|
||
options: CleaningOptions = {}
|
||
): any[] {
|
||
// 摰䂿緵颲寧<E9A2B2>璉<EFBFBD>瘚卝<E7989A><E58D9D>厭<EFBFBD>萄<EFBFBD>/銵峕<E98AB5>瘣𨰜<E798A3><F0A8B09C><EFBFBD><EFBFBD>券<EFBFBD>
|
||
// ...
|
||
}
|
||
|
||
export function isValidValue(value: any): boolean {
|
||
// 蝏煺<E89D8F><E785BA><EFBFBD>征<EFBFBD>澆ế<E6BE86>?
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### 敶勗<E695B6><E58B97><EFBFBD>凒
|
||
|
||
- Tool C: 憭滨鍂<E6BBA8>𡁶鍂摰䂿緵
|
||
- <20>嗡<EFBFBD>銝𠹺<E98A9D>Excel<65><6C><EFBFBD><EFBFBD>? <20>湔𦻖雿輻鍂
|
||
|
||
---
|
||
|
||
### TD-COMMON-004: Worker瘜典<E7989C>颲<EFBFBD>𨭌撌亙<E6928C>
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97> P3嚗<33><E59A97>嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?.3憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗𡁻<E59A97>雿竝orker瘜典<E7989C>隞<EFBFBD><E99A9E><EFBFBD>滚<EFBFBD>
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗?
|
||
- 瘥譍葵璅∪<E79285><E288AA>質<EFBFBD><E8B3AA>见<EFBFBD> Worker 瘜典<E7989C>隞<EFBFBD><E99A9E>
|
||
- <20>躰秤憭<E7A7A4><E686AD><EFBFBD>餉<EFBFBD><E9A489>滚<EFBFBD>
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
```typescript
|
||
// backend/src/common/jobs/WorkerHelper.ts嚗<73>鰵撱綽<E692B1>
|
||
|
||
import { jobQueue } from './index';
|
||
import { logger } from '../logging';
|
||
import type { JobHandler } from './types';
|
||
|
||
interface RegisterWorkerOptions {
|
||
queueName: string;
|
||
handler: JobHandler;
|
||
description?: string;
|
||
onStart?: (job: any) => void;
|
||
onComplete?: (job: any, result: any) => void;
|
||
onError?: (job: any, error: any) => void;
|
||
}
|
||
|
||
export function registerWorker(options: RegisterWorkerOptions) {
|
||
const { queueName, handler, description } = options;
|
||
|
||
logger.info(`[WorkerHelper] Registering worker: ${queueName}`, { description });
|
||
|
||
jobQueue.process(queueName, async (job) => {
|
||
try {
|
||
options.onStart?.(job);
|
||
|
||
const result = await handler(job);
|
||
|
||
options.onComplete?.(job, result);
|
||
|
||
return result;
|
||
} catch (error) {
|
||
options.onError?.(job, error);
|
||
throw error;
|
||
}
|
||
});
|
||
|
||
logger.info(`[WorkerHelper] <20>?Worker registered: ${queueName}`);
|
||
}
|
||
```
|
||
|
||
**雿輻鍂蝷箔<E89DB7>**嚗?
|
||
|
||
```typescript
|
||
// 蝞<><E89D9E>𣇉<EFBFBD>瘜典<E7989C>隞<EFBFBD><E99A9E>
|
||
registerWorker({
|
||
queueName: 'dc_toolc_parse_excel',
|
||
description: 'Excel閫<6C><E996AB>Worker',
|
||
handler: async (job) => {
|
||
// 銝𡁜𦛚<F0A1819C>餉<EFBFBD>
|
||
return result;
|
||
},
|
||
onStart: (job) => console.log(`撘<EFBFBD>憪见<EFBFBD><EFBFBD>? ${job.id}`),
|
||
onComplete: (job, result) => console.log(`摰峕<EFBFBD>: ${result}`),
|
||
onError: (job, error) => console.error(`憭梯揖: ${error}`),
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### TD-COMMON-005: <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E>蝟餌<E89D9F>
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃鐥<E6BD83>潃?P1嚗<31><E59A97>嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗𡁏𣈲<F0A1818F><F0A388B2>曎撘𤩺<E69298>雿栶<E99BBF><E6A0B6>ndo<64>蠘<EFBFBD><E8A098><EFBFBD>紡<EFBFBD>箏<EFBFBD><E7AE8F>脩<EFBFBD><E884A9>?
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>𣂼<EFBFBD>**嚗?
|
||
- <20>滢<EFBFBD>銝齿糓蝝舐妖<E88890><E5A696><EFBFBD>瘥𤩺活<F0A4A9BA>箔<EFBFBD><E7AE94>笔<EFBFBD><E7AC94>唳旿嚗?
|
||
- <20>䭾<EFBFBD><E4ADBE>鮋<EFBFBD><E9AE8B><EFBFBD>唳<EFBFBD>銝芣<E98A9D>雿𨅯<E99BBF><F0A885AF><EFBFBD>𠶖<EFBFBD>?
|
||
- <20>䭾<EFBFBD>撖澆枂銝剝𡢿<E5899D><F0A1A2BF>𧋦<EFBFBD><F0A78BA6>㺭<EFBFBD>?
|
||
|
||
**<EFBFBD>冽<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>極雿𨀣<EFBFBD>**嚗?
|
||
```
|
||
銝𠹺<EFBFBD>嚗ǒ0: 100銵䕘<E98AB5>
|
||
<20>?
|
||
蝑偦<EFBFBD>㚁<EFBFBD>v1: 50銵䕘<E98AB5><E49598>?<3F>臭誑<E887AD>鮋<EFBFBD><E9AE8B><EFBFBD>啗<EFBFBD><E59597>?
|
||
<20>?
|
||
<EFBFBD>啣<EFBFBD>潭<EFBFBD>撠<EFBFBD><EFBFBD>v2: 50銵䕘<E98AB5><E49598>㗇<EFBFBD>撠<EFBFBD><E692A0><EFBFBD>?<3F>臭誑<E887AD>鮋<EFBFBD><E9AE8B><EFBFBD>啗<EFBFBD><E59597>?
|
||
<20>?
|
||
Pivot嚗ǒ3: 銝滚<E98A9D>蝏𤘪<E89D8F>嚗?
|
||
<20>?
|
||
撖澆枂嚗𡁜虾隞亙紡<EFBFBD>?v0<76><30>1<EFBFBD><31>2<EFBFBD><32>3 隞餅<E99A9E><E9A485><EFBFBD>𧋦
|
||
```
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
**Prisma Schema霈曇恣**嚗?
|
||
|
||
```prisma
|
||
// <20><>𧋦蝞∠<E89D9E>銵剁<E98AB5><E58981>𡁶鍂嚗?
|
||
model DataVersion {
|
||
id String @id @default(uuid())
|
||
|
||
// <20>唾<EFBFBD>靽⊥<E99DBD>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䈑<EFBFBD>
|
||
entityType String // 'dc_toolc_session' | 'asl_project' | ...
|
||
entityId String // Session ID | Project ID | ...
|
||
|
||
// <20><>𧋦靽⊥<E99DBD>
|
||
versionNumber Int // 0=<3D>笔<EFBFBD>, 1=蝚?甈⊥<E79488>雿𨅯<E99BBF>, 2=蝚?甈⊥<E79488>雿𨅯<E99BBF>...
|
||
dataKey String // OSS銝剔<E98A9D><E58994>唳旿<E594B3><E697BF>辣key
|
||
|
||
// <20>滢<EFBFBD>霈啣<E99C88>
|
||
operation String? // 'upload' | 'filter' | 'pivot' | 'recode' ...
|
||
operationParams Json? // <20>滢<EFBFBD><E6BBA2><EFBFBD>㺭
|
||
|
||
// <20><>㺭<EFBFBD>?
|
||
totalRows Int
|
||
totalCols Int
|
||
columns Json
|
||
|
||
// <20>園𡢿<E59C92>?
|
||
createdAt DateTime @default(now())
|
||
createdBy String // <20>冽<EFBFBD>ID
|
||
|
||
@@unique([entityType, entityId, versionNumber])
|
||
@@index([entityType, entityId])
|
||
@@map("data_versions")
|
||
@@schema("platform_schema")
|
||
}
|
||
|
||
// 銝𡁜𦛚銵冽溶<E586BD>惩<EFBFBD>畾?
|
||
model DcToolCSession {
|
||
// ...<2E>唳<EFBFBD>摮埈挾
|
||
|
||
currentVersion Int @default(0) // 敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
|
||
}
|
||
```
|
||
|
||
**Service摰䂿緵**嚗?
|
||
|
||
```typescript
|
||
// backend/src/common/services/DataVersionService.ts嚗<73>鰵撱綽<E692B1>
|
||
|
||
export class DataVersionService {
|
||
/**
|
||
* <20>𥕦遣<F0A595A6>啁<EFBFBD><E59581>?
|
||
*/
|
||
async createVersion(
|
||
entityType: string,
|
||
entityId: string,
|
||
versionNumber: number,
|
||
data: any[],
|
||
operation?: string,
|
||
params?: any
|
||
): Promise<string> {
|
||
// 靽嘥<E99DBD><E598A5>唳旿<E594B3>?OSS
|
||
const dataKey = `versions/${entityType}/${entityId}/v${versionNumber}.json`;
|
||
await storage.upload(dataKey, JSON.stringify(data));
|
||
|
||
// <20>𥕦遣<F0A595A6><E981A3>𧋦霈啣<E99C88>
|
||
await prisma.dataVersion.create({
|
||
data: {
|
||
entityType,
|
||
entityId,
|
||
versionNumber,
|
||
dataKey,
|
||
operation,
|
||
operationParams: params,
|
||
totalRows: data.length,
|
||
columns: Object.keys(data[0] || {}),
|
||
createdBy: userId,
|
||
}
|
||
});
|
||
|
||
return dataKey;
|
||
}
|
||
|
||
/**
|
||
* 霂餃<E99C82><E9A483><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧋦
|
||
*/
|
||
async getVersion(
|
||
entityType: string,
|
||
entityId: string,
|
||
versionNumber: number
|
||
): Promise<any[]> {
|
||
const version = await prisma.dataVersion.findUnique({
|
||
where: {
|
||
entityType_entityId_versionNumber: {
|
||
entityType,
|
||
entityId,
|
||
versionNumber,
|
||
}
|
||
}
|
||
});
|
||
|
||
if (!version) throw new Error('<27><>𧋦銝滚<E98A9D><E6BB9A>?);
|
||
|
||
const buffer = await storage.download(version.dataKey);
|
||
return JSON.parse(buffer.toString('utf-8'));
|
||
}
|
||
|
||
/**
|
||
* <20>鮋<EFBFBD><E9AE8B><EFBFBD>唳<EFBFBD>摰𡁶<E691B0><F0A181B6>?
|
||
*/
|
||
async rollbackToVersion(
|
||
entityType: string,
|
||
entityId: string,
|
||
versionNumber: number
|
||
): Promise<void> {
|
||
// <20>湔鰵敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
|
||
await this.updateCurrentVersion(entityType, entityId, versionNumber);
|
||
}
|
||
|
||
/**
|
||
* <20>瑕<EFBFBD><E79195><EFBFBD>𧋦<EFBFBD><F0A78BA6>蟮
|
||
*/
|
||
async listVersions(entityType: string, entityId: string) {
|
||
return await prisma.dataVersion.findMany({
|
||
where: { entityType, entityId },
|
||
orderBy: { versionNumber: 'asc' }
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**QuickAction <20><><EFBFBD>**嚗?
|
||
|
||
```typescript
|
||
// QuickAction <20>扯<EFBFBD><E689AF>舘䌊<E88898>典<EFBFBD>撱箸鰵<E7AEB8><E9B0B5>𧋦
|
||
const result = await python.execute(fullData, params);
|
||
|
||
// 霂餃<E99C82>敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
|
||
const currentVersion = session.currentVersion || 0;
|
||
|
||
// <20>𥕦遣<F0A595A6>啁<EFBFBD><E59581>?
|
||
await dataVersionService.createVersion(
|
||
'dc_toolc_session',
|
||
sessionId,
|
||
currentVersion + 1,
|
||
result.data,
|
||
action, // 'filter' | 'pivot' | ...
|
||
params
|
||
);
|
||
|
||
// <20>湔鰵敶枏<E695B6><E69E8F><EFBFBD>𧋦<EFBFBD>?
|
||
await prisma.update({
|
||
where: { id: sessionId },
|
||
data: { currentVersion: currentVersion + 1 }
|
||
});
|
||
```
|
||
|
||
#### 敶勗<E695B6><E58B97><EFBFBD>凒
|
||
|
||
- Tool C: <20>舀<EFBFBD><E88880>曉<EFBFBD><E69B89>滢<EFBFBD><E6BBA2><EFBFBD>ndo<64><6F>紡<EFBFBD>箏<EFBFBD><E7AE8F>?
|
||
- ASL: <20><>讃蝑偦<E89D91>厩<EFBFBD>憭𡁻𧫴畾萇<E795BE><E89087>𦦵恣<F0A6A6B5>?
|
||
- Tool B: <20>唳旿<E594B3>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD><EFBFBD>祉恣<E7A589>?
|
||
- <20><><EFBFBD>㗇芋<E39787>? 蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD><EFBFBD>祉恣<E7A589><E681A3><EFBFBD><EFBFBD>?
|
||
|
||
---
|
||
|
||
### TD-COMMON-006: 撟賜<E6929F><E8B39C>?銵峕<E98AB5>瘚讠<E7989A>瘜訫<E7989C>
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃?P2嚗<32>葉嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?.5憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗鍃xcel憭<6C><E686AD>韐券<E99F90><E588B8>𣂼<EFBFBD>
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗?
|
||
- Tool C 摰䂿緵嚗朞器<E69C9E>峕<EFBFBD>瘚讠<E7989A>瘜?
|
||
- <20>嗡<EFBFBD>璅∪<E79285>嚗𡁏𧊋摰䂿緵
|
||
|
||
**Excel<65>澆<EFBFBD>瘙⊥<E79899><E28AA5>桅<EFBFBD>**嚗?
|
||
- <20>冽<EFBFBD><E586BD>琿<EFBFBD><E790BF>脣<EFBFBD>16384<38>?<3F>?閫<><E996AB><EFBFBD>?6384<38>梹<EFBFBD>摰鮋<E691B0><E9AE8B>芣<EFBFBD>151<35>埈<EFBFBD><E59F88><EFBFBD><EFBFBD>
|
||
- <20>冽<EFBFBD><E586BD>𣳇膄<F0A3B387>唳旿<E594B3>芣<EFBFBD><E88AA3>?<3F>?閫<><E996AB><EFBFBD>箏之<E7AE8F>讐征銵?
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
```typescript
|
||
// backend/src/common/utils/excelCleaning.ts嚗<73>鰵撱綽<E692B1>
|
||
|
||
export interface CleaningResult {
|
||
cleanedData: any[];
|
||
originalRows: number;
|
||
originalCols: number;
|
||
finalRows: number;
|
||
finalCols: number;
|
||
removedRows: number;
|
||
removedCols: number;
|
||
warnings: string[];
|
||
}
|
||
|
||
export function cleanExcelData(
|
||
rawData: any[],
|
||
options?: CleaningOptions
|
||
): CleaningResult {
|
||
// 摰䂿緵<E482BF>𡁶鍂<F0A181B6><E98D82><EFBFBD>瘣㛖<E798A3>瘜?
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### TD-COMMON-007: <20>滨垢餈𥕦漲<F0A595A6>∠<EFBFBD>隞?
|
||
|
||
**隡睃<E99AA1>蝥?*嚗尠<E59A97>潃?P2嚗<32>葉嚗?
|
||
**撌乩<E6928C><E4B9A9>?*嚗?.3憭?
|
||
**憸<><E686B8><EFBFBD>嗥<EFBFBD>**嚗䦧I蝏煺<E89D8F>嚗𣬚鍂<F0A3AC9A>瑚<EFBFBD>撉峕<E69289><E5B395>?
|
||
|
||
#### <20>桅<EFBFBD><E6A185>讛膩
|
||
|
||
**敶枏<E695B6><E69E8F>嗆<EFBFBD>?*嚗?
|
||
- Tool C 摰䂿緵嚗𡁜<E59A97><F0A1819C>磰<EFBFBD>摨行辺嚗<E8BEBA><E59A97><EFBFBD>莎<EFBFBD>Header銝𧢲䲮嚗?
|
||
- <20>嗡<EFBFBD>璅∪<E79285>嚗𡁏𧊋摰䂿緵<E482BF>碶<EFBFBD>蝏煺<E89D8F>
|
||
|
||
#### 閫<><E996AB><EFBFBD>寞<EFBFBD>
|
||
|
||
```typescript
|
||
// frontend-v2/src/common/components/AsyncProgressBar.tsx嚗<78>鰵撱綽<E692B1>
|
||
|
||
interface AsyncProgressBarProps {
|
||
visible: boolean;
|
||
progress: number; // 0-100
|
||
message: string;
|
||
status: 'uploading' | 'processing' | 'completed' | 'error';
|
||
}
|
||
|
||
export const AsyncProgressBar: React.FC<AsyncProgressBarProps> = ({
|
||
visible,
|
||
progress,
|
||
message,
|
||
status,
|
||
}) => {
|
||
if (!visible) return null;
|
||
|
||
return (
|
||
<div className="bg-blue-50 border-b border-blue-200 px-6 py-3">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="text-sm font-medium text-blue-900">{message}</span>
|
||
<span className="text-sm text-blue-700">{progress}%</span>
|
||
</div>
|
||
<div className="w-full bg-blue-200 rounded-full h-2">
|
||
<div
|
||
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||
style={{ width: `${progress}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> 摰墧鴌霈∪<E99C88>
|
||
|
||
| <20><><EFBFBD>臬<EFBFBD>箏𦛚 | 隡睃<E99AA1>蝥?| 撌乩<E6928C><E4B9A9>?| 撱箄悅<E7AE84>嗆㦤 | <20>嗥<EFBFBD> |
|
||
|---------|--------|--------|---------|------|
|
||
| TD-COMMON-001 <20>滨垢頧株砭Hook | P2 | 0.5憭?| 銝𧢲活餈凋誨 | 隞<><E99A9E>蝏煺<E89D8F> |
|
||
| TD-COMMON-002 Clean Data<74>滚𦛚 | **P1** | 1憭?| **銝𧢲活餈凋誨** | **<EFBFBD>扯<EFBFBD><EFBFBD>𣂼<EFBFBD>99%** |
|
||
| TD-COMMON-003 <20>箄<EFBFBD>皜<EFBFBD><E79A9C>蝞埈<E89D9E> | P2 | 0.5憭?| <20><>閬<EFBFBD>𧒄 | 韐券<E99F90><E588B8>𣂼<EFBFBD> |
|
||
| TD-COMMON-004 Worker瘜典<E7989C>颲<EFBFBD>𨭌 | P3 | 0.3憭?| <20>舫<EFBFBD>?| 隞<><E99A9E>蝞<EFBFBD><E89D9E>?|
|
||
| TD-COMMON-005 <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E> | **P1** | 3憭?| **銝𧢲活餈凋誨** | **<EFBFBD>曉<EFBFBD><EFBFBD>滢<EFBFBD>** |
|
||
| TD-COMMON-006 撟賜<E6929F><E8B39C>?銵峕<E98AB5>瘚?| P2 | 0.5憭?| <20><>閬<EFBFBD>𧒄 | 韐券<E99F90><E588B8>𣂼<EFBFBD> |
|
||
| TD-COMMON-007 餈𥕦漲<F0A595A6>∠<EFBFBD>隞?| P2 | 0.3憭?| <20><>閬<EFBFBD>𧒄 | UI蝏煺<E89D8F> |
|
||
|
||
**<EFBFBD>刻<EFBFBD>隡睃<EFBFBD>蝥?*嚗?
|
||
1. 潃鐥<E6BD83>潃鐥<E6BD83>潃?TD-COMMON-002嚗㇃lean Data<74>滚𦛚嚗?
|
||
2. 潃鐥<E6BD83>潃鐥<E6BD83>潃?TD-COMMON-005嚗<35>㺭<EFBFBD>桃<EFBFBD><E6A183>祉恣<E7A589><E681A3><EFBFBD>
|
||
3. 潃鐥<E6BD83>潃?TD-COMMON-001嚗<31><E59A97>蝡航蔭霂%ook嚗?
|
||
|
||
---
|
||
|
||
## <20><> 敶枏<E695B6><E69E8F>嗆<EFBFBD>霂<EFBFBD>遠
|
||
|
||
### <20>?撌脩<E6928C>敺<EFBFBD>末<EFBFBD><E69CAB><EFBFBD><EFBFBD>?
|
||
|
||
1. <20>?**<2A>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD>抅蝖<E68A85>霈暹鴌摰峕㟲**
|
||
- jobQueue嚗īg-boss<73>笔<EFBFBD>嚗?
|
||
- CheckpointService嚗<65>鱏<EFBFBD>寧賒隡𩤃<E99AA1>
|
||
- 隞餃𦛚<E9A483><F0A69B9A><EFBFBD>撌亙<E6928C>
|
||
- storage<67><65>ogger<65><72>ache
|
||
|
||
2. <20>?**銝𡁜𦛚璅∪<E79285>甇<EFBFBD>&雿輻鍂<E8BCBB>𡁶鍂<F0A181B6>賢<EFBFBD>**
|
||
- Tool C<><43>SL<53><4C>ool B 蝏煺<E89D8F>雿輻鍂 jobQueue
|
||
- Platform-Only璅∪<E79285>
|
||
- 銝滚銁銝𡁜𦛚銵其葉摮睃<E691AE>隞餃𦛚蝞∠<E89D9E>靽⊥<E99DBD>
|
||
|
||
3. <20>?**<2A><><EFBFBD>皜<EFBFBD>苊嚗諹<E59A97>韐<EFBFBD><E99F90>蝖?*
|
||
- <20>𡁶鍂<F0A181B6>賢<EFBFBD>撅<EFBFBD><E69285><EFBFBD>箇<EFBFBD>霈暹鴌
|
||
- 銝𡁜𦛚璅∪<E79285>嚗𡁜<E59A97>雿枏<E99BBF><E69E8F>?
|
||
|
||
### <20>哨<EFBFBD> <20>臭誑<E887AD>寡<EFBFBD><E5AFA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧼮<EFBFBD><F0A7BCAE><EFBFBD>嚗?
|
||
|
||
1. <20>哨<EFBFBD> <20>滨垢頧株砭璅∪<E79285><E288AA>臭誑<E887AD>渡<EFBFBD>銝<EFBFBD>
|
||
2. <20>哨<EFBFBD> clean data蝻枏<E89DBB><E69E8F>臭誑<E887AD>湧<EFBFBD>𡁶鍂
|
||
3. <20>哨<EFBFBD> <20>唳旿<E594B3><E697BF>𧋦蝞∠<E89D9E>敺<EFBFBD>遣蝡?
|
||
|
||
**蝏栞捏**嚗𡁜<E59A97><F0A1819C>齿沲<E9BDBF><E6B2B2>歇蝏讐泵<E8AE90><E6B3B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>芣䔉隡睃<E99AA1><E79D83>舫釺銝𦠜溶<F0A6A09C>梧<EFBFBD>
|
||
|
||
---
|
||
|
||
## <20><> <20><><EFBFBD><EFBFBD><EFBFBD>獢?
|
||
|
||
- [Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>](./Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>.md) - 摰峕㟲摰噼殿
|
||
- [鈭穃<E988AD><E7A983>笔<EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8><EFBFBD>(../04-撘<><E69298>𤏸<EFBFBD><F0A48FB8>?08-鈭穃<E988AD><E7A983>笔<EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?md) - 撘<><E69298>𤏸<EFBFBD><F0A48FB8>?
|
||
- [DC Tool C<>嗆<EFBFBD><E59786>(../03-銝𡁜𦛚璅∪<E79285>/DC-<2D>唳旿皜<E697BF><E79A9C><EFBFBD>渡<EFBFBD>/00-撌亙<E6928C>C敶枏<E695B6><E69E8F>嗆<EFBFBD><E59786><EFBFBD>撘<EFBFBD><E69298>烐<EFBFBD><E78390>?md) - Day 10摰噼殿
|
||
|
||
---
|
||
|
||
**蝏湔擪<E6B994>?*: 撟喳蝱<E596B3>嗆<EFBFBD><E59786>a<EFBFBD>
|
||
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>?*: 2025-12-22
|
||
**<EFBFBD><EFBFBD>﹝<EFBFBD>嗆<EFBFBD>?*: <20>?<3F>嘥<EFBFBD><E598A5><EFBFBD>𧋦
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|