refactor: restructure core architecture and responsive system
This commit implements a major architectural refactor to improve modularity, type safety, and maintainability across the codebase.
Key changes include:
- **Responsive System Refactor**: Migrated from a monolithic `useResponsive` hook to a set of specialized, granular hooks (`useBreakpoint`, `useOrientation`, `usePlatform`, etc.) located in `src/presentation/hooks/responsive`. This reduces unnecessary re-renders and improves developer experience.
- **Core Service Restructuring**: Introduced a new directory structure for services, including dedicated folders for `datasources`, `mappers`, and domain-specific types (`message`, `post`).
- **Data Layer Improvements**:
- Centralized JSON parsing logic in `src/database/core/jsonUtils.ts`.
- Cleaned up repository implementations by removing redundant local utility functions.
- Refactored `MessageMapper` to use the new centralized JSON utility.
- **Type System Cleanup**:
- Decomposed the large `src/types/dto.ts` into a modular `src/types/dto/` directory.
- Simplified `src/types/index.ts` and introduced backward-compatible aliases for core entities.
- **Utility Consolidation**:
- Created a centralized `src/utils/formatTime.ts` to replace fragmented date formatting logic across various screens and components.
- Removed deprecated responsive utility files in favor of the new hook-based system.
- **Service Logic Refinement**: Refactored `ApiClient` and `WebSocketService` to use a centralized `showVerificationModal` service, removing duplicated state management for verification prompts.
This commit is contained in:
302
src/services/mappers/ConversationMapper.ts
Normal file
302
src/services/mappers/ConversationMapper.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* 会话数据映射器
|
||||
* 负责 Conversation 模型与 API 响应、数据库记录之间的转换
|
||||
*/
|
||||
|
||||
import {
|
||||
ConversationModel,
|
||||
ConversationType,
|
||||
UserModel,
|
||||
GroupModel,
|
||||
MessageModel,
|
||||
} from '../models';
|
||||
import type {
|
||||
ConversationResponse,
|
||||
ConversationDetailResponse,
|
||||
UserDTO,
|
||||
GroupResponse,
|
||||
MessageResponse,
|
||||
} from '../../types/dto';
|
||||
import { MessageMapper } from './MessageMapper';
|
||||
|
||||
// 数据库会话记录类型
|
||||
export interface ConversationDbRecord {
|
||||
id: string;
|
||||
participantId: string;
|
||||
lastMessageId: string | null;
|
||||
lastSeq: number;
|
||||
unreadCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// 缓存数据类型
|
||||
export interface ConversationCacheData {
|
||||
id: string;
|
||||
type: string;
|
||||
is_pinned?: boolean;
|
||||
last_seq?: number;
|
||||
last_message?: any;
|
||||
last_message_at?: string;
|
||||
unread_count?: number;
|
||||
participants?: any[];
|
||||
my_last_read_seq?: number;
|
||||
other_last_read_seq?: number;
|
||||
group?: any;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export class ConversationMapper {
|
||||
/**
|
||||
* 将 API 列表响应转换为应用模型
|
||||
*/
|
||||
static fromApiResponse(response: ConversationResponse): ConversationModel {
|
||||
return {
|
||||
id: String(response.id || ''),
|
||||
type: (response.type as ConversationType) || 'private',
|
||||
participantId: this.extractParticipantId(response),
|
||||
lastMessageId: response.last_message?.id,
|
||||
lastMessage: response.last_message
|
||||
? MessageMapper.fromApiResponse(response.last_message)
|
||||
: undefined,
|
||||
lastSeq: response.last_seq || 0,
|
||||
myLastReadSeq: response.my_last_read_seq || 0,
|
||||
otherLastReadSeq: response.other_last_read_seq || 0,
|
||||
unreadCount: response.unread_count || 0,
|
||||
isPinned: response.is_pinned || false,
|
||||
participants: response.participants?.map(p => this.mapUserFromApi(p)),
|
||||
group: response.group ? this.mapGroupFromApi(response.group) : undefined,
|
||||
createdAt: new Date(response.created_at || Date.now()),
|
||||
updatedAt: new Date(response.updated_at || Date.now()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 API 详情响应转换为应用模型
|
||||
*/
|
||||
static fromDetailApiResponse(response: ConversationDetailResponse): ConversationModel {
|
||||
return {
|
||||
id: String(response.id || ''),
|
||||
type: (response.type as ConversationType) || 'private',
|
||||
participantId: this.extractParticipantIdFromDetail(response),
|
||||
lastMessageId: response.last_message?.id,
|
||||
lastMessage: response.last_message
|
||||
? MessageMapper.fromApiResponse(response.last_message)
|
||||
: undefined,
|
||||
lastSeq: response.last_seq || 0,
|
||||
myLastReadSeq: response.my_last_read_seq || 0,
|
||||
otherLastReadSeq: response.other_last_read_seq || 0,
|
||||
unreadCount: response.unread_count || 0,
|
||||
isPinned: response.is_pinned || false,
|
||||
participants: response.participants?.map(p => this.mapUserFromApi(p)),
|
||||
group: response.group ? this.mapGroupFromApi(response.group) : undefined,
|
||||
createdAt: new Date(response.created_at || Date.now()),
|
||||
updatedAt: new Date(response.updated_at || Date.now()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 API 响应数组转换为应用模型数组
|
||||
*/
|
||||
static fromApiResponseList(responses: ConversationResponse[]): ConversationModel[] {
|
||||
return responses.map(r => this.fromApiResponse(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存数据转换为应用模型
|
||||
*/
|
||||
static fromCacheData(id: string, cached: ConversationCacheData): ConversationModel {
|
||||
return {
|
||||
id: String(cached?.id || id),
|
||||
type: (cached?.type as ConversationType) || 'private',
|
||||
participantId: '',
|
||||
lastSeq: Number(cached?.last_seq || 0),
|
||||
lastMessage: cached?.last_message
|
||||
? MessageMapper.fromApiResponse(cached.last_message)
|
||||
: undefined,
|
||||
lastMessageId: cached?.last_message?.id,
|
||||
myLastReadSeq: Number(cached?.my_last_read_seq || 0),
|
||||
otherLastReadSeq: Number(cached?.other_last_read_seq || 0),
|
||||
unreadCount: Number(cached?.unread_count || 0),
|
||||
isPinned: Boolean(cached?.is_pinned),
|
||||
participants: Array.isArray(cached?.participants)
|
||||
? cached.participants.map(p => this.mapUserFromApi(p))
|
||||
: [],
|
||||
group: cached?.group ? this.mapGroupFromApi(cached.group) : undefined,
|
||||
createdAt: new Date(cached?.created_at || Date.now()),
|
||||
updatedAt: new Date(cached?.updated_at || Date.now()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库记录转换为应用模型
|
||||
*/
|
||||
static fromDbRecord(record: ConversationDbRecord): ConversationModel {
|
||||
return {
|
||||
id: record.id,
|
||||
type: 'private', // 数据库中不存储类型,需要额外查询
|
||||
participantId: record.participantId,
|
||||
lastMessageId: record.lastMessageId || undefined,
|
||||
lastSeq: record.lastSeq,
|
||||
myLastReadSeq: 0,
|
||||
otherLastReadSeq: 0,
|
||||
unreadCount: record.unreadCount,
|
||||
isPinned: false,
|
||||
createdAt: new Date(record.createdAt),
|
||||
updatedAt: new Date(record.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为数据库记录
|
||||
*/
|
||||
static toDbRecord(model: ConversationModel): ConversationDbRecord {
|
||||
return {
|
||||
id: model.id,
|
||||
participantId: model.participantId,
|
||||
lastMessageId: model.lastMessageId || null,
|
||||
lastSeq: model.lastSeq,
|
||||
unreadCount: model.unreadCount,
|
||||
createdAt: model.createdAt.toISOString(),
|
||||
updatedAt: model.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为缓存数据
|
||||
*/
|
||||
static toCacheData(model: ConversationModel): ConversationCacheData {
|
||||
return {
|
||||
id: model.id,
|
||||
type: model.type,
|
||||
is_pinned: model.isPinned,
|
||||
last_seq: model.lastSeq,
|
||||
last_message: model.lastMessage ? this.messageToCache(model.lastMessage) : undefined,
|
||||
last_message_at: model.updatedAt.toISOString(),
|
||||
unread_count: model.unreadCount,
|
||||
participants: model.participants?.map(p => this.userToCache(p)),
|
||||
my_last_read_seq: model.myLastReadSeq,
|
||||
other_last_read_seq: model.otherLastReadSeq,
|
||||
group: model.group ? this.groupToCache(model.group) : undefined,
|
||||
created_at: model.createdAt.toISOString(),
|
||||
updated_at: model.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取会话参与者 ID
|
||||
*/
|
||||
private static extractParticipantId(response: ConversationResponse): string {
|
||||
if (response.participants && response.participants.length > 0) {
|
||||
return String(response.participants[0].id || '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从详情响应提取参与者 ID
|
||||
*/
|
||||
private static extractParticipantIdFromDetail(response: ConversationDetailResponse): string {
|
||||
if (response.participants && response.participants.length > 0) {
|
||||
return String(response.participants[0].id || '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射用户 API 响应
|
||||
*/
|
||||
private static mapUserFromApi(user: UserDTO): UserModel {
|
||||
return {
|
||||
id: String(user.id || ''),
|
||||
username: user.username || '',
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射群组 API 响应
|
||||
*/
|
||||
private static mapGroupFromApi(group: GroupResponse): GroupModel {
|
||||
// 将数字类型的 join_type 转换为字符串类型
|
||||
// JoinType: 0 = 允许任何人, 1 = 需要审批, 2 = 不允许
|
||||
const mapJoinType = (joinType: number): 'anyone' | 'approval' | 'invite' => {
|
||||
switch (joinType) {
|
||||
case 0: return 'anyone';
|
||||
case 1: return 'approval';
|
||||
case 2: return 'invite';
|
||||
default: return 'approval';
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
id: String(group.id || ''),
|
||||
name: group.name || '',
|
||||
avatar: group.avatar,
|
||||
description: group.description,
|
||||
// GroupResponse 没有 announcement 和 updated_at 字段,使用默认值
|
||||
announcement: undefined,
|
||||
ownerId: String(group.owner_id || ''),
|
||||
memberCount: group.member_count || 0,
|
||||
maxMemberCount: group.max_members || 500,
|
||||
joinType: mapJoinType(group.join_type),
|
||||
isMuted: group.mute_all || false,
|
||||
createdAt: new Date(group.created_at || Date.now()),
|
||||
updatedAt: new Date(group.created_at || Date.now()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息模型转缓存格式
|
||||
*/
|
||||
private static messageToCache(message: MessageModel): any {
|
||||
return {
|
||||
id: message.id,
|
||||
conversation_id: message.conversationId,
|
||||
sender_id: message.senderId,
|
||||
content: message.content,
|
||||
message_type: message.type,
|
||||
is_read: message.isRead,
|
||||
created_at: message.createdAt.toISOString(),
|
||||
seq: message.seq,
|
||||
status: message.status,
|
||||
segments: message.segments,
|
||||
reply_to_id: message.replyToId,
|
||||
sender: message.sender ? this.userToCache(message.sender) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户模型转缓存格式
|
||||
*/
|
||||
private static userToCache(user: UserModel): any {
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组模型转缓存格式
|
||||
*/
|
||||
private static groupToCache(group: GroupModel): any {
|
||||
return {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
avatar: group.avatar,
|
||||
description: group.description,
|
||||
announcement: group.announcement,
|
||||
owner_id: group.ownerId,
|
||||
member_count: group.memberCount,
|
||||
max_member_count: group.maxMemberCount,
|
||||
join_type: group.joinType,
|
||||
mute_all: group.isMuted,
|
||||
created_at: group.createdAt.toISOString(),
|
||||
updated_at: group.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
194
src/services/mappers/MessageMapper.ts
Normal file
194
src/services/mappers/MessageMapper.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 消息数据映射器
|
||||
* 负责 Message 模型与 API 响应、数据库记录之间的转换
|
||||
*/
|
||||
|
||||
import {
|
||||
MessageModel,
|
||||
MessageSegment,
|
||||
UserModel,
|
||||
} from '../models';
|
||||
import type {
|
||||
MessageResponse,
|
||||
UserDTO,
|
||||
} from '../../types/dto';
|
||||
import { safeParseJsonOrUndefined } from '../../database/core/jsonUtils';
|
||||
import type { CachedMessage } from '../../database/types/CachedMessage';
|
||||
|
||||
// 数据库消息记录类型
|
||||
export interface MessageDbRecord {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
senderId: string;
|
||||
content: string | null;
|
||||
type: string;
|
||||
isRead: number;
|
||||
createdAt: string;
|
||||
seq: number;
|
||||
status: string;
|
||||
segments: string | null;
|
||||
}
|
||||
|
||||
export class MessageMapper {
|
||||
/**
|
||||
* 将 API 响应转换为应用模型
|
||||
*/
|
||||
static fromApiResponse(response: MessageResponse): MessageModel {
|
||||
const textSegment = response.segments?.find(s => s.type === 'text');
|
||||
const content = textSegment?.data?.text || textSegment?.data?.content || '';
|
||||
|
||||
return {
|
||||
id: String(response.id || ''),
|
||||
conversationId: String(response.conversation_id || ''),
|
||||
senderId: String(response.sender_id || ''),
|
||||
content,
|
||||
type: 'text',
|
||||
isRead: false,
|
||||
createdAt: new Date(response.created_at || Date.now()),
|
||||
seq: response.seq || 0,
|
||||
status: response.status || 'normal',
|
||||
segments: response.segments || [],
|
||||
replyToId: response.reply_to_id,
|
||||
sender: response.sender ? this.mapSenderFromApi(response.sender) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 API 响应数组转换为应用模型数组
|
||||
*/
|
||||
static fromApiResponseList(responses: MessageResponse[]): MessageModel[] {
|
||||
return responses.map(r => this.fromApiResponse(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库记录转换为应用模型
|
||||
*/
|
||||
static fromDbRecord(record: MessageDbRecord): MessageModel {
|
||||
return {
|
||||
id: record.id,
|
||||
conversationId: record.conversationId,
|
||||
senderId: record.senderId,
|
||||
content: record.content || undefined,
|
||||
type: record.type || 'text',
|
||||
isRead: record.isRead === 1,
|
||||
createdAt: new Date(record.createdAt),
|
||||
seq: record.seq || 0,
|
||||
status: record.status || 'normal',
|
||||
segments: record.segments ? safeParseJsonOrUndefined<MessageSegment[]>(record.segments) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库记录数组转换为应用模型数组
|
||||
*/
|
||||
static fromDbRecordList(records: MessageDbRecord[]): MessageModel[] {
|
||||
return records.map(r => this.fromDbRecord(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为数据库记录
|
||||
*/
|
||||
static toDbRecord(model: MessageModel): MessageDbRecord {
|
||||
return {
|
||||
id: model.id,
|
||||
conversationId: model.conversationId,
|
||||
senderId: model.senderId,
|
||||
content: model.content || null,
|
||||
type: model.type,
|
||||
isRead: model.isRead ? 1 : 0,
|
||||
createdAt: model.createdAt.toISOString(),
|
||||
seq: model.seq,
|
||||
status: model.status,
|
||||
segments: model.segments ? JSON.stringify(model.segments) : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为 API 请求数据
|
||||
*/
|
||||
static toApiRequest(model: Partial<MessageModel>): Record<string, any> {
|
||||
const request: Record<string, any> = {};
|
||||
|
||||
if (model.segments) {
|
||||
request.segments = model.segments;
|
||||
}
|
||||
if (model.content) {
|
||||
request.content = model.content;
|
||||
}
|
||||
if (model.type) {
|
||||
request.message_type = model.type;
|
||||
}
|
||||
if (model.replyToId) {
|
||||
request.reply_to_id = model.replyToId;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建发送消息请求体
|
||||
*/
|
||||
static createSendRequest(
|
||||
detailType: 'private' | 'group',
|
||||
segments: MessageSegment[],
|
||||
replyToId?: string
|
||||
): Record<string, any> {
|
||||
const body: Record<string, any> = {
|
||||
detail_type: detailType,
|
||||
segments,
|
||||
};
|
||||
if (replyToId) {
|
||||
body.reply_to_id = replyToId;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文本消息段
|
||||
*/
|
||||
static createTextSegment(text: string): MessageSegment {
|
||||
return {
|
||||
type: 'text',
|
||||
data: { text },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图片消息段
|
||||
*/
|
||||
static createImageSegment(url: string): MessageSegment {
|
||||
return {
|
||||
type: 'image',
|
||||
data: { url },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static mapSenderFromApi(sender: UserDTO): UserModel {
|
||||
return {
|
||||
id: String(sender.id || ''),
|
||||
username: sender.username || '',
|
||||
nickname: sender.nickname,
|
||||
avatar: sender.avatar || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
static toCachedMessage(apiMessage: any, conversationId?: string): CachedMessage {
|
||||
return {
|
||||
id: apiMessage.id,
|
||||
conversationId: apiMessage.conversation_id || conversationId || '',
|
||||
senderId: apiMessage.sender_id,
|
||||
content: apiMessage.content,
|
||||
type: apiMessage.type || 'text',
|
||||
isRead: apiMessage.is_read || false,
|
||||
createdAt: apiMessage.created_at,
|
||||
seq: apiMessage.seq,
|
||||
status: apiMessage.status || 'normal',
|
||||
segments: apiMessage.segments,
|
||||
};
|
||||
}
|
||||
|
||||
static toCachedMessages(apiMessages: any[], conversationId?: string): CachedMessage[] {
|
||||
return apiMessages.map(m => this.toCachedMessage(m, conversationId));
|
||||
}
|
||||
}
|
||||
129
src/services/mappers/PostMapper.ts
Normal file
129
src/services/mappers/PostMapper.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 帖子数据映射器
|
||||
* 负责 Post 模型与 API 响应之间的转换
|
||||
*/
|
||||
|
||||
import { PostModel, UserModel } from '../models';
|
||||
import type { PostDTO } from '../../types/dto';
|
||||
|
||||
export class PostMapper {
|
||||
static fromApiResponse(response: PostDTO): PostModel {
|
||||
return {
|
||||
id: String(response.id || ''),
|
||||
authorId: String(response.user_id || ''),
|
||||
author: response.author ? this.mapAuthorFromApi(response.author) : undefined,
|
||||
title: response.title || '',
|
||||
content: response.content || '',
|
||||
images: response.images?.map(img => img.url) || [],
|
||||
likeCount: response.likes_count || 0,
|
||||
commentCount: response.comments_count || 0,
|
||||
shareCount: response.shares_count || 0,
|
||||
viewCount: response.views_count || 0,
|
||||
favoriteCount: response.favorites_count || 0,
|
||||
isLiked: response.is_liked || false,
|
||||
isFavorited: response.is_favorited || false,
|
||||
isTop: response.is_pinned || false,
|
||||
status: (response.status || 'published') as 'published' | 'draft' | 'deleted',
|
||||
channelId: response.channel_id,
|
||||
channel:
|
||||
response.channel && response.channel.id
|
||||
? { id: String(response.channel.id), name: String(response.channel.name || '') }
|
||||
: undefined,
|
||||
tags: [],
|
||||
createdAt: new Date(response.created_at || Date.now()),
|
||||
// 空字符串/缺省时用 created_at,避免误用 Date.now() 导致全部显示「刚修改」
|
||||
updatedAt: new Date(
|
||||
response.updated_at ||
|
||||
response.created_at ||
|
||||
Date.now()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 API 响应数组转换为应用模型数组
|
||||
*/
|
||||
static fromApiResponseList(responses: PostDTO[]): PostModel[] {
|
||||
return responses.map(r => this.fromApiResponse(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为 API 请求数据
|
||||
*/
|
||||
static toApiRequest(model: Partial<PostModel>): Record<string, any> {
|
||||
const request: Record<string, any> = {};
|
||||
|
||||
if (model.title !== undefined) {
|
||||
request.title = model.title;
|
||||
}
|
||||
if (model.content !== undefined) {
|
||||
request.content = model.content;
|
||||
}
|
||||
if (model.images !== undefined) {
|
||||
request.images = model.images;
|
||||
}
|
||||
if (model.channelId !== undefined) {
|
||||
request.channel_id = model.channelId;
|
||||
}
|
||||
if (model.tags !== undefined) {
|
||||
request.tags = model.tags;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建帖子请求
|
||||
*/
|
||||
static createPostRequest(
|
||||
title: string,
|
||||
content: string,
|
||||
images?: string[],
|
||||
channelId?: string
|
||||
): Record<string, any> {
|
||||
const request: Record<string, any> = {
|
||||
title,
|
||||
content,
|
||||
};
|
||||
if (images && images.length > 0) {
|
||||
request.images = images;
|
||||
}
|
||||
if (channelId) {
|
||||
request.channel_id = channelId;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新帖子请求
|
||||
*/
|
||||
static updatePostRequest(
|
||||
title?: string,
|
||||
content?: string,
|
||||
images?: string[]
|
||||
): Record<string, any> {
|
||||
const request: Record<string, any> = {};
|
||||
if (title !== undefined) {
|
||||
request.title = title;
|
||||
}
|
||||
if (content !== undefined) {
|
||||
request.content = content;
|
||||
}
|
||||
if (images !== undefined) {
|
||||
request.images = images;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射作者信息
|
||||
*/
|
||||
private static mapAuthorFromApi(author: any): UserModel {
|
||||
return {
|
||||
id: String(author.id || ''),
|
||||
username: author.username || '',
|
||||
nickname: author.nickname,
|
||||
avatar: author.avatar,
|
||||
};
|
||||
}
|
||||
}
|
||||
134
src/services/mappers/UserMapper.ts
Normal file
134
src/services/mappers/UserMapper.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 用户数据映射器
|
||||
* 负责 User 模型与 API 响应、数据库记录之间的转换
|
||||
*/
|
||||
|
||||
import { UserModel } from '../models';
|
||||
import type { UserDTO } from '../../types/dto';
|
||||
|
||||
// 数据库用户记录类型
|
||||
export interface UserDbRecord {
|
||||
id: string;
|
||||
data: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export class UserMapper {
|
||||
static fromDTO(dto: UserDTO): UserModel {
|
||||
return {
|
||||
id: String(dto.id || ''),
|
||||
username: dto.username || '',
|
||||
nickname: dto.nickname,
|
||||
avatar: dto.avatar,
|
||||
bio: dto.bio,
|
||||
website: dto.website,
|
||||
location: dto.location,
|
||||
email: dto.email,
|
||||
phone: dto.phone,
|
||||
followersCount: dto.followers_count,
|
||||
followingCount: dto.following_count,
|
||||
postsCount: dto.posts_count,
|
||||
isFollowing: dto.is_following,
|
||||
createdAt: dto.created_at ? new Date(dto.created_at) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
static fromDTOList(dtos: UserDTO[]): UserModel[] {
|
||||
return dtos.map(dto => this.fromDTO(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库记录转换为应用模型
|
||||
*/
|
||||
static fromDbRecord(record: UserDbRecord): UserModel | null {
|
||||
try {
|
||||
const data = JSON.parse(record.data) as UserDTO;
|
||||
return this.fromDTO(data);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为数据库记录
|
||||
*/
|
||||
static toDbRecord(model: UserModel): UserDbRecord {
|
||||
const dto: UserDTO = {
|
||||
id: model.id,
|
||||
username: model.username,
|
||||
nickname: model.nickname || '',
|
||||
avatar: model.avatar || '',
|
||||
cover_url: '',
|
||||
bio: model.bio || '',
|
||||
website: model.website || '',
|
||||
location: model.location || '',
|
||||
email: model.email,
|
||||
phone: model.phone,
|
||||
followers_count: model.followersCount || 0,
|
||||
following_count: model.followingCount || 0,
|
||||
posts_count: model.postsCount || 0,
|
||||
is_following: model.isFollowing,
|
||||
created_at: model.createdAt?.toISOString() || '',
|
||||
};
|
||||
|
||||
return {
|
||||
id: model.id,
|
||||
data: JSON.stringify(dto),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为 API 请求数据
|
||||
*/
|
||||
static toApiRequest(model: Partial<UserModel>): Record<string, any> {
|
||||
const request: Record<string, any> = {};
|
||||
|
||||
if (model.nickname !== undefined) {
|
||||
request.nickname = model.nickname;
|
||||
}
|
||||
if (model.avatar !== undefined) {
|
||||
request.avatar = model.avatar;
|
||||
}
|
||||
if (model.bio !== undefined) {
|
||||
request.bio = model.bio;
|
||||
}
|
||||
if (model.website !== undefined) {
|
||||
request.website = model.website;
|
||||
}
|
||||
if (model.location !== undefined) {
|
||||
request.location = model.location;
|
||||
}
|
||||
if (model.phone !== undefined) {
|
||||
request.phone = model.phone;
|
||||
}
|
||||
if (model.email !== undefined) {
|
||||
request.email = model.email;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将应用模型转换为 DTO
|
||||
*/
|
||||
static toDTO(model: UserModel): UserDTO {
|
||||
return {
|
||||
id: model.id,
|
||||
username: model.username,
|
||||
nickname: model.nickname || '',
|
||||
avatar: model.avatar || '',
|
||||
cover_url: '',
|
||||
bio: model.bio || '',
|
||||
website: model.website || '',
|
||||
location: model.location || '',
|
||||
email: model.email,
|
||||
phone: model.phone,
|
||||
followers_count: model.followersCount || 0,
|
||||
following_count: model.followingCount || 0,
|
||||
posts_count: model.postsCount || 0,
|
||||
is_following: model.isFollowing,
|
||||
created_at: model.createdAt?.toISOString() || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
9
src/services/mappers/index.ts
Normal file
9
src/services/mappers/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 映射器导出
|
||||
* 统一导出所有数据映射器
|
||||
*/
|
||||
|
||||
export * from './MessageMapper';
|
||||
export * from './ConversationMapper';
|
||||
export * from './UserMapper';
|
||||
export * from './PostMapper';
|
||||
Reference in New Issue
Block a user