26f0cdd760
🧹 项目结构优化: - 删除重复和过时的文件 - 整理文档到docs目录结构 - 优化配置文件到configs目录 - 创建清晰的PROJECT_STRUCTURE.md 🔧 中间件配置: - 重启MySQL/Redis/Nacos中间件 - 使用现有数据目录,确保数据完整性 - 统一密码配置: MySQL(EmotionMuseum2025*#), Nacos(Peanut2817*#) 🌐 Nginx配置: - 配置前端路径: /emotion-museum - 配置API代理: /api/ -> 19000 - 配置WebSocket代理: /ws/ -> 19007 - 添加健康检查端点: /health 📋 部署脚本优化: - restart-middleware.sh - 中间件重启脚本 - setup-nginx.sh - Nginx配置脚本 - cleanup-project.sh - 项目清理脚本 - one-click-deploy.sh - 一键部署脚本 📖 文档完善: - DEPLOYMENT_FINAL.md - 最终部署指南 - PROJECT_STRUCTURE.md - 项目结构说明 - 完整的运维和故障排查指南 ✅ 生产环境就绪: - 中间件: MySQL/Redis/Nacos 运行正常 - Nginx: 反向代理配置完成 - 访问地址: http://47.111.10.27/emotion-museum - 健康检查: http://47.111.10.27/health 🎯 项目现状: - 10个微服务模块完整 - 前后端分离架构 - 容器化部署 - 统一配置管理 - 完整的部署和运维体系
611 lines
15 KiB
Markdown
611 lines
15 KiB
Markdown
# 情绪博物馆技术架构完善建议
|
||
|
||
**文档版本**: v1.0
|
||
**创建时间**: 2025-07-12
|
||
**基于**: 现有代码架构分析
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
- [1. 当前架构分析](#1-当前架构分析)
|
||
- [2. 架构完善建议](#2-架构完善建议)
|
||
- [3. 具体实施方案](#3-具体实施方案)
|
||
- [4. 性能优化建议](#4-性能优化建议)
|
||
- [5. 安全性增强](#5-安全性增强)
|
||
- [6. 可维护性提升](#6-可维护性提升)
|
||
|
||
---
|
||
|
||
## 1. 当前架构分析
|
||
|
||
### 1.1 架构优势 ✅
|
||
|
||
#### 清晰的分层结构
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ SwiftUI Views │ ← 表现层
|
||
├─────────────────────────────────────┤
|
||
│ ViewModel/Manager │ ← 业务逻辑层
|
||
├─────────────────────────────────────┤
|
||
│ Services │ ← 服务层
|
||
├─────────────────────────────────────┤
|
||
│ Models │ ← 数据模型层
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
#### 良好的状态管理
|
||
- ✅ 使用 `@ObservableObject` 进行状态管理
|
||
- ✅ `NavigationManager` 统一管理导航状态
|
||
- ✅ `ThemeManager` 管理主题切换
|
||
- ✅ `MockDataManager` 提供数据服务
|
||
|
||
#### 模块化设计
|
||
- ✅ 清晰的文件组织结构
|
||
- ✅ 功能模块相对独立
|
||
- ✅ 可复用的UI组件
|
||
|
||
### 1.2 架构不足 ❌
|
||
|
||
#### 数据持久化缺失
|
||
- ❌ 缺少真实的数据存储层
|
||
- ❌ 依赖模拟数据,无法持久化用户数据
|
||
- ❌ 没有数据同步机制
|
||
|
||
#### 服务层不完整
|
||
- ❌ AI服务只有接口定义,缺少实现
|
||
- ❌ 缺少网络服务层
|
||
- ❌ 缺少错误处理机制
|
||
|
||
#### 依赖注入不规范
|
||
- ❌ 大量使用 `shared` 单例模式
|
||
- ❌ 组件间耦合度较高
|
||
- ❌ 测试困难
|
||
|
||
---
|
||
|
||
## 2. 架构完善建议
|
||
|
||
### 2.1 引入依赖注入容器
|
||
|
||
#### 创建DI容器
|
||
```swift
|
||
// DIContainer.swift
|
||
class DIContainer: ObservableObject {
|
||
static let shared = DIContainer()
|
||
|
||
private var services: [String: Any] = [:]
|
||
|
||
func register<T>(_ type: T.Type, instance: T) {
|
||
let key = String(describing: type)
|
||
services[key] = instance
|
||
}
|
||
|
||
func resolve<T>(_ type: T.Type) -> T {
|
||
let key = String(describing: type)
|
||
guard let service = services[key] as? T else {
|
||
fatalError("Service \(key) not registered")
|
||
}
|
||
return service
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 服务注册
|
||
```swift
|
||
// ServiceRegistration.swift
|
||
extension DIContainer {
|
||
func registerServices() {
|
||
// 数据服务
|
||
register(DataServiceProtocol.self, instance: CoreDataService())
|
||
|
||
// AI服务
|
||
register(AIServiceProtocol.self, instance: OpenAIService())
|
||
|
||
// 位置服务
|
||
register(LocationServiceProtocol.self, instance: LocationService())
|
||
|
||
// 网络服务
|
||
register(NetworkServiceProtocol.self, instance: NetworkService())
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.2 完善服务层架构
|
||
|
||
#### 网络服务层
|
||
```swift
|
||
// NetworkService.swift
|
||
protocol NetworkServiceProtocol {
|
||
func request<T: Codable>(_ endpoint: APIEndpoint) async throws -> T
|
||
func upload(data: Data, to endpoint: APIEndpoint) async throws -> UploadResponse
|
||
}
|
||
|
||
class NetworkService: NetworkServiceProtocol {
|
||
private let session: URLSession
|
||
private let baseURL: URL
|
||
|
||
init(session: URLSession = .shared, baseURL: URL) {
|
||
self.session = session
|
||
self.baseURL = baseURL
|
||
}
|
||
|
||
func request<T: Codable>(_ endpoint: APIEndpoint) async throws -> T {
|
||
let request = try buildRequest(for: endpoint)
|
||
let (data, response) = try await session.data(for: request)
|
||
|
||
guard let httpResponse = response as? HTTPURLResponse,
|
||
200...299 ~= httpResponse.statusCode else {
|
||
throw NetworkError.invalidResponse
|
||
}
|
||
|
||
return try JSONDecoder().decode(T.self, from: data)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 数据服务层
|
||
```swift
|
||
// DataService.swift
|
||
protocol DataServiceProtocol {
|
||
func save<T: Codable>(_ object: T) async throws
|
||
func fetch<T: Codable>(_ type: T.Type, predicate: NSPredicate?) async throws -> [T]
|
||
func delete<T: Codable>(_ object: T) async throws
|
||
}
|
||
|
||
class CoreDataService: DataServiceProtocol {
|
||
private let container: NSPersistentContainer
|
||
|
||
init() {
|
||
container = NSPersistentContainer(name: "EmotionMuseum")
|
||
container.loadPersistentStores { _, error in
|
||
if let error = error {
|
||
fatalError("Core Data error: \(error)")
|
||
}
|
||
}
|
||
}
|
||
|
||
func save<T: Codable>(_ object: T) async throws {
|
||
let context = container.viewContext
|
||
// 实现保存逻辑
|
||
try context.save()
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.3 引入Repository模式
|
||
|
||
#### 数据仓库接口
|
||
```swift
|
||
// Repository.swift
|
||
protocol Repository {
|
||
associatedtype Entity
|
||
|
||
func create(_ entity: Entity) async throws
|
||
func read(id: UUID) async throws -> Entity?
|
||
func update(_ entity: Entity) async throws
|
||
func delete(id: UUID) async throws
|
||
func list(predicate: NSPredicate?) async throws -> [Entity]
|
||
}
|
||
|
||
// 具体实现
|
||
class ConversationRepository: Repository {
|
||
typealias Entity = Conversation
|
||
|
||
private let dataService: DataServiceProtocol
|
||
|
||
init(dataService: DataServiceProtocol) {
|
||
self.dataService = dataService
|
||
}
|
||
|
||
func create(_ conversation: Conversation) async throws {
|
||
try await dataService.save(conversation)
|
||
}
|
||
|
||
func list(predicate: NSPredicate? = nil) async throws -> [Conversation] {
|
||
return try await dataService.fetch(Conversation.self, predicate: predicate)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.4 改进ViewModel架构
|
||
|
||
#### 基础ViewModel
|
||
```swift
|
||
// BaseViewModel.swift
|
||
@MainActor
|
||
class BaseViewModel: ObservableObject {
|
||
@Published var isLoading = false
|
||
@Published var error: AppError?
|
||
|
||
protected func withLoading<T>(_ operation: () async throws -> T) async rethrows -> T {
|
||
isLoading = true
|
||
defer { isLoading = false }
|
||
return try await operation()
|
||
}
|
||
|
||
protected func handleError(_ error: Error) {
|
||
self.error = AppError(from: error)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 具体ViewModel实现
|
||
```swift
|
||
// ConversationViewModel.swift
|
||
@MainActor
|
||
class ConversationViewModel: BaseViewModel {
|
||
@Published var conversations: [Conversation] = []
|
||
@Published var currentConversation: Conversation?
|
||
|
||
private let repository: ConversationRepository
|
||
private let aiService: AIServiceProtocol
|
||
|
||
init(repository: ConversationRepository, aiService: AIServiceProtocol) {
|
||
self.repository = repository
|
||
self.aiService = aiService
|
||
super.init()
|
||
}
|
||
|
||
func loadConversations() async {
|
||
await withLoading {
|
||
do {
|
||
conversations = try await repository.list()
|
||
} catch {
|
||
handleError(error)
|
||
}
|
||
}
|
||
}
|
||
|
||
func sendMessage(_ content: String) async {
|
||
guard let conversation = currentConversation else { return }
|
||
|
||
do {
|
||
// 添加用户消息
|
||
let userMessage = Message(
|
||
conversationId: conversation.id,
|
||
content: content,
|
||
type: .text,
|
||
sender: .user
|
||
)
|
||
|
||
// 获取AI回复
|
||
let aiResponse = try await aiService.sendMessage(content)
|
||
let aiMessage = Message(
|
||
conversationId: conversation.id,
|
||
content: aiResponse.content,
|
||
type: .text,
|
||
sender: .ai
|
||
)
|
||
|
||
// 更新对话
|
||
var updatedConversation = conversation
|
||
updatedConversation.messages.append(contentsOf: [userMessage, aiMessage])
|
||
|
||
try await repository.update(updatedConversation)
|
||
currentConversation = updatedConversation
|
||
|
||
} catch {
|
||
handleError(error)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 具体实施方案
|
||
|
||
### 3.1 阶段性重构计划
|
||
|
||
#### Phase 1: 基础设施搭建 (1周)
|
||
1. **创建DI容器**
|
||
- 实现依赖注入容器
|
||
- 注册核心服务
|
||
- 更新App启动流程
|
||
|
||
2. **完善错误处理**
|
||
- 定义统一错误类型
|
||
- 实现错误处理机制
|
||
- 添加用户友好的错误提示
|
||
|
||
3. **网络服务层**
|
||
- 实现基础网络服务
|
||
- 添加请求/响应拦截器
|
||
- 实现重试机制
|
||
|
||
#### Phase 2: 数据层重构 (1-2周)
|
||
1. **Core Data集成**
|
||
- 设计数据模型
|
||
- 实现数据服务
|
||
- 数据迁移策略
|
||
|
||
2. **Repository模式**
|
||
- 实现各个Repository
|
||
- 数据缓存策略
|
||
- 离线数据支持
|
||
|
||
#### Phase 3: 业务层重构 (1-2周)
|
||
1. **ViewModel重构**
|
||
- 重构现有ViewModel
|
||
- 添加单元测试
|
||
- 优化状态管理
|
||
|
||
2. **服务集成**
|
||
- AI服务真实实现
|
||
- 地图服务集成
|
||
- 推送服务集成
|
||
|
||
### 3.2 代码重构示例
|
||
|
||
#### 重构前 (现状)
|
||
```swift
|
||
// RecordView.swift - 现状
|
||
struct RecordView: View {
|
||
@EnvironmentObject var mockData: MockDataManager
|
||
@EnvironmentObject var navigationManager: NavigationManager
|
||
|
||
var body: some View {
|
||
// 直接使用mockData
|
||
// 紧耦合,难以测试
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 重构后 (建议)
|
||
```swift
|
||
// RecordView.swift - 重构后
|
||
struct RecordView: View {
|
||
@StateObject private var viewModel: RecordViewModel
|
||
|
||
init(viewModel: RecordViewModel = DIContainer.shared.resolve(RecordViewModel.self)) {
|
||
self._viewModel = StateObject(wrappedValue: viewModel)
|
||
}
|
||
|
||
var body: some View {
|
||
// 使用viewModel
|
||
// 松耦合,易于测试
|
||
}
|
||
}
|
||
|
||
// RecordViewModel.swift
|
||
@MainActor
|
||
class RecordViewModel: BaseViewModel {
|
||
@Published var conversations: [Conversation] = []
|
||
@Published var currentMessage: String = ""
|
||
|
||
private let conversationRepository: ConversationRepository
|
||
private let aiService: AIServiceProtocol
|
||
|
||
init(conversationRepository: ConversationRepository, aiService: AIServiceProtocol) {
|
||
self.conversationRepository = conversationRepository
|
||
self.aiService = aiService
|
||
super.init()
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 性能优化建议
|
||
|
||
### 4.1 内存管理优化
|
||
|
||
#### 图片缓存策略
|
||
```swift
|
||
// ImageCache.swift
|
||
class ImageCache {
|
||
static let shared = ImageCache()
|
||
private let cache = NSCache<NSString, UIImage>()
|
||
private let fileManager = FileManager.default
|
||
|
||
func image(for url: String) async -> UIImage? {
|
||
// 内存缓存 -> 磁盘缓存 -> 网络下载
|
||
}
|
||
|
||
func store(_ image: UIImage, for url: String) {
|
||
// 存储到内存和磁盘缓存
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 数据分页加载
|
||
```swift
|
||
// PaginationManager.swift
|
||
class PaginationManager<T>: ObservableObject {
|
||
@Published var items: [T] = []
|
||
@Published var isLoading = false
|
||
@Published var hasMoreData = true
|
||
|
||
private var currentPage = 0
|
||
private let pageSize = 20
|
||
|
||
func loadNextPage() async {
|
||
guard !isLoading && hasMoreData else { return }
|
||
|
||
isLoading = true
|
||
defer { isLoading = false }
|
||
|
||
// 加载下一页数据
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 UI性能优化
|
||
|
||
#### LazyLoading实现
|
||
```swift
|
||
// LazyImageView.swift
|
||
struct LazyImageView: View {
|
||
let url: String
|
||
@State private var image: UIImage?
|
||
@State private var isLoading = true
|
||
|
||
var body: some View {
|
||
Group {
|
||
if let image = image {
|
||
Image(uiImage: image)
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fit)
|
||
} else if isLoading {
|
||
ProgressView()
|
||
} else {
|
||
Image(systemName: "photo")
|
||
.foregroundColor(.gray)
|
||
}
|
||
}
|
||
.onAppear {
|
||
loadImage()
|
||
}
|
||
}
|
||
|
||
private func loadImage() {
|
||
Task {
|
||
image = await ImageCache.shared.image(for: url)
|
||
isLoading = false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 安全性增强
|
||
|
||
### 5.1 数据加密
|
||
```swift
|
||
// EncryptionService.swift
|
||
protocol EncryptionServiceProtocol {
|
||
func encrypt(_ data: Data) throws -> Data
|
||
func decrypt(_ encryptedData: Data) throws -> Data
|
||
}
|
||
|
||
class EncryptionService: EncryptionServiceProtocol {
|
||
private let key: SymmetricKey
|
||
|
||
init() {
|
||
// 从Keychain获取或生成密钥
|
||
self.key = SymmetricKey(size: .bits256)
|
||
}
|
||
|
||
func encrypt(_ data: Data) throws -> Data {
|
||
let sealedBox = try AES.GCM.seal(data, using: key)
|
||
return sealedBox.combined!
|
||
}
|
||
|
||
func decrypt(_ encryptedData: Data) throws -> Data {
|
||
let sealedBox = try AES.GCM.SealedBox(combined: encryptedData)
|
||
return try AES.GCM.open(sealedBox, using: key)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 API安全
|
||
```swift
|
||
// APIKeyManager.swift
|
||
class APIKeyManager {
|
||
private let keychain = Keychain(service: "com.emotionmuseum.app")
|
||
|
||
func storeAPIKey(_ key: String, for service: String) {
|
||
keychain[service] = key
|
||
}
|
||
|
||
func getAPIKey(for service: String) -> String? {
|
||
return keychain[service]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 可维护性提升
|
||
|
||
### 6.1 代码规范
|
||
```swift
|
||
// SwiftLint配置
|
||
// .swiftlint.yml
|
||
rules:
|
||
- line_length
|
||
- function_body_length
|
||
- type_body_length
|
||
- cyclomatic_complexity
|
||
- force_cast
|
||
- force_try
|
||
|
||
line_length:
|
||
warning: 120
|
||
error: 150
|
||
|
||
function_body_length:
|
||
warning: 50
|
||
error: 100
|
||
```
|
||
|
||
### 6.2 单元测试框架
|
||
```swift
|
||
// ConversationViewModelTests.swift
|
||
@MainActor
|
||
class ConversationViewModelTests: XCTestCase {
|
||
var viewModel: ConversationViewModel!
|
||
var mockRepository: MockConversationRepository!
|
||
var mockAIService: MockAIService!
|
||
|
||
override func setUp() {
|
||
super.setUp()
|
||
mockRepository = MockConversationRepository()
|
||
mockAIService = MockAIService()
|
||
viewModel = ConversationViewModel(
|
||
repository: mockRepository,
|
||
aiService: mockAIService
|
||
)
|
||
}
|
||
|
||
func testLoadConversations() async {
|
||
// 测试加载对话功能
|
||
await viewModel.loadConversations()
|
||
|
||
XCTAssertFalse(viewModel.isLoading)
|
||
XCTAssertEqual(viewModel.conversations.count, mockRepository.mockConversations.count)
|
||
}
|
||
|
||
func testSendMessage() async {
|
||
// 测试发送消息功能
|
||
let testMessage = "Hello"
|
||
await viewModel.sendMessage(testMessage)
|
||
|
||
XCTAssertTrue(mockAIService.sendMessageCalled)
|
||
XCTAssertEqual(mockAIService.lastMessage, testMessage)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 文档和注释规范
|
||
```swift
|
||
/// 对话管理的ViewModel
|
||
///
|
||
/// 负责管理用户对话的状态和业务逻辑,包括:
|
||
/// - 加载历史对话
|
||
/// - 发送和接收消息
|
||
/// - AI服务集成
|
||
/// - 错误处理
|
||
@MainActor
|
||
class ConversationViewModel: BaseViewModel {
|
||
|
||
/// 当前显示的对话列表
|
||
@Published var conversations: [Conversation] = []
|
||
|
||
/// 当前活跃的对话
|
||
@Published var currentConversation: Conversation?
|
||
|
||
/// 加载用户的所有对话
|
||
/// - Returns: 无返回值,结果通过 `conversations` 属性发布
|
||
func loadConversations() async {
|
||
// 实现逻辑
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
*本文档提供了完整的技术架构完善建议,建议按阶段逐步实施*
|