Compare commits
	
		
			6 Commits
		
	
	
		
			de704db577
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e287d7bbde | |||
| cf2273e6da | |||
| 311d6dab8f | |||
| dd398c1c32 | |||
| 82220ce0b8 | |||
| 4e3cd6f365 | 
							
								
								
									
										372
									
								
								.trae/rules/save.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								.trae/rules/save.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,372 @@
 | 
				
			|||||||
 | 
					# 存入胶囊功能 API 文档
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 概述
 | 
				
			||||||
 | 
					存入胶囊功能允许用户将邮件保存为时光胶囊状态,邮件将以草稿形式保存,用户可以随时编辑或发送。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## API 接口
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 创建胶囊邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `POST /api/v1/mails`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 创建一个新邮件并将其保存为时光胶囊状态(草稿)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| title | string | 是 | 邮件标题 |
 | 
				
			||||||
 | 
					| content | string | 是 | 邮件内容 |
 | 
				
			||||||
 | 
					| recipientType | string | 是 | 收件人类型:SELF(自己)、SPECIFIC(指定收件人)、PUBLIC(公开信) |
 | 
				
			||||||
 | 
					| recipientEmail | string | 否 | 指定收件人邮箱(当recipientType为SPECIFIC时必填) |
 | 
				
			||||||
 | 
					| sendTime | string | 否 | 发送时间,ISO格式时间字符串(如:2025-12-31T23:59:59Z) |
 | 
				
			||||||
 | 
					| triggerType | string | 否 | 触发类型:TIME(时间)、LOCATION(地点)、EVENT(事件) |
 | 
				
			||||||
 | 
					| triggerCondition | object | 否 | 触发条件 |
 | 
				
			||||||
 | 
					| triggerCondition.location | object | 否 | 地点触发条件 |
 | 
				
			||||||
 | 
					| triggerCondition.location.latitude | number | 否 | 纬度 |
 | 
				
			||||||
 | 
					| triggerCondition.location.longitude | number | 否 | 经度 |
 | 
				
			||||||
 | 
					| triggerCondition.location.city | string | 否 | 城市 |
 | 
				
			||||||
 | 
					| triggerCondition.event | object | 否 | 事件触发条件 |
 | 
				
			||||||
 | 
					| triggerCondition.event.keywords | array | 否 | 关键词列表 |
 | 
				
			||||||
 | 
					| triggerCondition.event.type | string | 否 | 事件类型 |
 | 
				
			||||||
 | 
					| attachments | array | 否 | 附件列表 |
 | 
				
			||||||
 | 
					| attachments[].type | string | 否 | 附件类型:IMAGE、VOICE、VIDEO |
 | 
				
			||||||
 | 
					| attachments[].url | string | 否 | 附件URL |
 | 
				
			||||||
 | 
					| attachments[].thumbnail | string | 否 | 缩略图URL |
 | 
				
			||||||
 | 
					| isEncrypted | boolean | 否 | 是否加密 |
 | 
				
			||||||
 | 
					| capsuleStyle | string | 否 | 胶囊样式 |
 | 
				
			||||||
 | 
					| status | string | 是 | 邮件状态,存入胶囊时固定为:DRAFT |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "title": "写给未来的自己",
 | 
				
			||||||
 | 
					  "content": "亲爱的未来的我,当你读到这封信时,希望你已经实现了现在的梦想...",
 | 
				
			||||||
 | 
					  "recipientType": "SELF",
 | 
				
			||||||
 | 
					  "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					  "triggerType": "TIME",
 | 
				
			||||||
 | 
					  "attachments": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "IMAGE",
 | 
				
			||||||
 | 
					      "url": "https://example.com/image.jpg",
 | 
				
			||||||
 | 
					      "thumbnail": "https://example.com/thumb.jpg"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "isEncrypted": false,
 | 
				
			||||||
 | 
					  "capsuleStyle": "default",
 | 
				
			||||||
 | 
					  "status": "DRAFT"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.capsuleId | string | 胶囊ID |
 | 
				
			||||||
 | 
					| data.status | string | 邮件状态:DRAFT、PENDING、DELIVERING、DELIVERED |
 | 
				
			||||||
 | 
					| data.createdAt | string | 创建时间,ISO格式时间字符串 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					    "capsuleId": "capsule_1234567890",
 | 
				
			||||||
 | 
					    "status": "DRAFT",
 | 
				
			||||||
 | 
					    "createdAt": "2023-07-20T10:30:00Z"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 获取胶囊列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `GET /api/v1/mails`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 获取用户的胶囊邮件列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| type | string | 否 | 邮件类型:INBOX、SENT、DRAFT,获取胶囊时使用DRAFT |
 | 
				
			||||||
 | 
					| status | string | 否 | 状态筛选:PENDING、DELIVERING、DELIVERED、DRAFT |
 | 
				
			||||||
 | 
					| page | number | 否 | 页码,默认为1 |
 | 
				
			||||||
 | 
					| size | number | 否 | 每页数量,默认为10 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					GET /api/v1/mails?type=DRAFT&page=1&size=10
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.list | array | 邮件列表 |
 | 
				
			||||||
 | 
					| data.list[].mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.list[].title | string | 邮件标题 |
 | 
				
			||||||
 | 
					| data.list[].sender | object | 发件人信息 |
 | 
				
			||||||
 | 
					| data.list[].recipient | object | 收件人信息 |
 | 
				
			||||||
 | 
					| data.list[].sendTime | string | 发送时间 |
 | 
				
			||||||
 | 
					| data.list[].deliveryTime | string | 送达时间 |
 | 
				
			||||||
 | 
					| data.list[].status | string | 邮件状态 |
 | 
				
			||||||
 | 
					| data.list[].hasAttachments | boolean | 是否有附件 |
 | 
				
			||||||
 | 
					| data.list[].isEncrypted | boolean | 是否加密 |
 | 
				
			||||||
 | 
					| data.list[].capsuleStyle | string | 胶囊样式 |
 | 
				
			||||||
 | 
					| data.total | number | 总数量 |
 | 
				
			||||||
 | 
					| data.page | number | 当前页码 |
 | 
				
			||||||
 | 
					| data.size | number | 每页数量 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "list": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					        "title": "写给未来的自己",
 | 
				
			||||||
 | 
					        "sender": {
 | 
				
			||||||
 | 
					          "userId": "user_123",
 | 
				
			||||||
 | 
					          "username": "张三",
 | 
				
			||||||
 | 
					          "avatar": "https://example.com/avatar.jpg"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "recipient": {
 | 
				
			||||||
 | 
					          "userId": "user_123",
 | 
				
			||||||
 | 
					          "username": "张三",
 | 
				
			||||||
 | 
					          "avatar": "https://example.com/avatar.jpg"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					        "deliveryTime": null,
 | 
				
			||||||
 | 
					        "status": "DRAFT",
 | 
				
			||||||
 | 
					        "hasAttachments": true,
 | 
				
			||||||
 | 
					        "isEncrypted": false,
 | 
				
			||||||
 | 
					        "capsuleStyle": "default"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "total": 1,
 | 
				
			||||||
 | 
					    "page": 1,
 | 
				
			||||||
 | 
					    "size": 10
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 获取胶囊详情
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `GET /api/v1/mails/{mailId}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 获取指定胶囊邮件的详细信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| mailId | string | 是 | 邮件ID |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					GET /api/v1/mails/mail_1234567890
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.title | string | 邮件标题 |
 | 
				
			||||||
 | 
					| data.content | string | 邮件内容 |
 | 
				
			||||||
 | 
					| data.sender | object | 发件人信息 |
 | 
				
			||||||
 | 
					| data.recipient | object | 收件人信息 |
 | 
				
			||||||
 | 
					| data.sendTime | string | 发送时间 |
 | 
				
			||||||
 | 
					| data.createdAt | string | 创建时间 |
 | 
				
			||||||
 | 
					| data.deliveryTime | string | 送达时间 |
 | 
				
			||||||
 | 
					| data.status | string | 邮件状态 |
 | 
				
			||||||
 | 
					| data.triggerType | string | 触发类型 |
 | 
				
			||||||
 | 
					| data.triggerCondition | object | 触发条件 |
 | 
				
			||||||
 | 
					| data.attachments | array | 附件列表 |
 | 
				
			||||||
 | 
					| data.isEncrypted | boolean | 是否加密 |
 | 
				
			||||||
 | 
					| data.capsuleStyle | string | 胶囊样式 |
 | 
				
			||||||
 | 
					| data.canEdit | boolean | 是否可编辑(草稿状态为true) |
 | 
				
			||||||
 | 
					| data.canRevoke | boolean | 是否可撤销(待投递状态为true) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					    "title": "写给未来的自己",
 | 
				
			||||||
 | 
					    "content": "亲爱的未来的我,当你读到这封信时,希望你已经实现了现在的梦想...",
 | 
				
			||||||
 | 
					    "sender": {
 | 
				
			||||||
 | 
					      "userId": "user_123",
 | 
				
			||||||
 | 
					      "username": "张三",
 | 
				
			||||||
 | 
					      "avatar": "https://example.com/avatar.jpg",
 | 
				
			||||||
 | 
					      "email": "zhangsan@example.com"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "recipient": {
 | 
				
			||||||
 | 
					      "userId": "user_123",
 | 
				
			||||||
 | 
					      "username": "张三",
 | 
				
			||||||
 | 
					      "avatar": "https://example.com/avatar.jpg",
 | 
				
			||||||
 | 
					      "email": "zhangsan@example.com"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					    "createdAt": "2023-07-20T10:30:00Z",
 | 
				
			||||||
 | 
					    "deliveryTime": null,
 | 
				
			||||||
 | 
					    "status": "DRAFT",
 | 
				
			||||||
 | 
					    "triggerType": "TIME",
 | 
				
			||||||
 | 
					    "triggerCondition": {},
 | 
				
			||||||
 | 
					    "attachments": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "id": "attach_123",
 | 
				
			||||||
 | 
					        "type": "IMAGE",
 | 
				
			||||||
 | 
					        "url": "https://example.com/image.jpg",
 | 
				
			||||||
 | 
					        "thumbnail": "https://example.com/thumb.jpg",
 | 
				
			||||||
 | 
					        "size": 1024000
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "isEncrypted": false,
 | 
				
			||||||
 | 
					    "capsuleStyle": "default",
 | 
				
			||||||
 | 
					    "canEdit": true,
 | 
				
			||||||
 | 
					    "canRevoke": false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 更新胶囊邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `PUT /api/v1/mails/{mailId}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 更新胶囊邮件内容(仅草稿状态可更新)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| mailId | string | 是 | 邮件ID(路径参数) |
 | 
				
			||||||
 | 
					| title | string | 否 | 邮件标题 |
 | 
				
			||||||
 | 
					| content | string | 否 | 邮件内容 |
 | 
				
			||||||
 | 
					| recipientType | string | 否 | 收件人类型:SELF、SPECIFIC、PUBLIC |
 | 
				
			||||||
 | 
					| recipientEmail | string | 否 | 指定收件人邮箱(当recipientType为SPECIFIC时必填) |
 | 
				
			||||||
 | 
					| sendTime | string | 否 | 发送时间,ISO格式时间字符串 |
 | 
				
			||||||
 | 
					| triggerType | string | 否 | 触发类型:TIME、LOCATION、EVENT |
 | 
				
			||||||
 | 
					| triggerCondition | object | 否 | 触发条件 |
 | 
				
			||||||
 | 
					| attachments | array | 否 | 附件列表 |
 | 
				
			||||||
 | 
					| isEncrypted | boolean | 否 | 是否加密 |
 | 
				
			||||||
 | 
					| capsuleStyle | string | 否 | 胶囊样式 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "title": "更新后的标题",
 | 
				
			||||||
 | 
					  "content": "更新后的内容...",
 | 
				
			||||||
 | 
					  "sendTime": "2026-12-31T23:59:59Z"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.capsuleId | string | 胶囊ID |
 | 
				
			||||||
 | 
					| data.status | string | 邮件状态 |
 | 
				
			||||||
 | 
					| data.updatedAt | string | 更新时间,ISO格式时间字符串 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					    "capsuleId": "capsule_1234567890",
 | 
				
			||||||
 | 
					    "status": "DRAFT",
 | 
				
			||||||
 | 
					    "updatedAt": "2023-07-21T14:30:00Z"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 删除胶囊邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `DELETE /api/v1/mails/{mailId}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 删除指定的胶囊邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| mailId | string | 是 | 邮件ID(路径参数) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					DELETE /api/v1/mails/mail_1234567890
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 已删除的邮件ID |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 错误码
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 错误码 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|
 | 
				
			||||||
 | 
					| 200 | 成功 |
 | 
				
			||||||
 | 
					| 400 | 请求参数错误 |
 | 
				
			||||||
 | 
					| 401 | 未授权,需要登录 |
 | 
				
			||||||
 | 
					| 403 | 权限不足 |
 | 
				
			||||||
 | 
					| 404 | 资源不存在 |
 | 
				
			||||||
 | 
					| 422 | 验证失败 |
 | 
				
			||||||
 | 
					| 500 | 服务器内部错误 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 注意事项
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. 存入胶囊的邮件状态为DRAFT,可以在任何时候编辑或发送
 | 
				
			||||||
 | 
					2. 只有草稿状态的邮件可以编辑或删除
 | 
				
			||||||
 | 
					3. 发送时间必须晚于当前时间
 | 
				
			||||||
 | 
					4. 附件大小限制为10MB
 | 
				
			||||||
 | 
					5. 免费用户每月最多可创建10个胶囊邮件
 | 
				
			||||||
 | 
					6. 加密邮件需要额外验证才能查看内容
 | 
				
			||||||
							
								
								
									
										307
									
								
								.trae/rules/seed.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								.trae/rules/seed.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,307 @@
 | 
				
			|||||||
 | 
					# 发送至未来功能 API 文档
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 概述
 | 
				
			||||||
 | 
					发送至未来功能允许用户将邮件设置为在未来特定时间自动发送,邮件状态将变为待投递(PENDING),系统会在指定时间自动处理发送。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## API 接口
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 发送至未来
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `POST /api/v1/mails/send-to-future`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| mailId | string | 是 | 邮件ID |
 | 
				
			||||||
 | 
					| sendTime | string | 是 | 发送时间,ISO格式时间字符串(如:2025-12-31T23:59:59Z) |
 | 
				
			||||||
 | 
					| triggerType | string | 否 | 触发类型:TIME(时间)、LOCATION(地点)、EVENT(事件),默认为TIME |
 | 
				
			||||||
 | 
					| triggerCondition | object | 否 | 触发条件 |
 | 
				
			||||||
 | 
					| triggerCondition.location | object | 否 | 地点触发条件 |
 | 
				
			||||||
 | 
					| triggerCondition.location.latitude | number | 否 | 纬度 |
 | 
				
			||||||
 | 
					| triggerCondition.location.longitude | number | 否 | 经度 |
 | 
				
			||||||
 | 
					| triggerCondition.location.city | string | 否 | 城市 |
 | 
				
			||||||
 | 
					| triggerCondition.event | object | 否 | 事件触发条件 |
 | 
				
			||||||
 | 
					| triggerCondition.event.keywords | array | 否 | 关键词列表 |
 | 
				
			||||||
 | 
					| triggerCondition.event.type | string | 否 | 事件类型 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					  "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					  "triggerType": "TIME",
 | 
				
			||||||
 | 
					  "triggerCondition": {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.capsuleId | string | 胶囊ID |
 | 
				
			||||||
 | 
					| data.status | string | 邮件状态:PENDING |
 | 
				
			||||||
 | 
					| data.sendTime | string | 发送时间 |
 | 
				
			||||||
 | 
					| data.countdown | number | 倒计时秒数 |
 | 
				
			||||||
 | 
					| data.updatedAt | string | 更新时间,ISO格式时间字符串 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					    "capsuleId": "capsule_1234567890",
 | 
				
			||||||
 | 
					    "status": "PENDING",
 | 
				
			||||||
 | 
					    "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					    "countdown": 94608000,
 | 
				
			||||||
 | 
					    "updatedAt": "2023-07-20T10:30:00Z"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 获取待发送邮件列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `GET /api/v1/mails`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 获取用户的待发送邮件列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| type | string | 否 | 邮件类型:INBOX、SENT、DRAFT,获取待发送时使用SENT |
 | 
				
			||||||
 | 
					| status | string | 否 | 状态筛选:PENDING、DELIVERING、DELIVERED、DRAFT,获取待发送时使用PENDING |
 | 
				
			||||||
 | 
					| page | number | 否 | 页码,默认为1 |
 | 
				
			||||||
 | 
					| size | number | 否 | 每页数量,默认为10 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					GET /api/v1/mails?type=SENT&status=PENDING&page=1&size=10
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.list | array | 邮件列表 |
 | 
				
			||||||
 | 
					| data.list[].mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.list[].title | string | 邮件标题 |
 | 
				
			||||||
 | 
					| data.list[].sender | object | 发件人信息 |
 | 
				
			||||||
 | 
					| data.list[].recipient | object | 收件人信息 |
 | 
				
			||||||
 | 
					| data.list[].sendTime | string | 发送时间 |
 | 
				
			||||||
 | 
					| data.list[].deliveryTime | string | 送达时间 |
 | 
				
			||||||
 | 
					| data.list[].status | string | 邮件状态 |
 | 
				
			||||||
 | 
					| data.list[].hasAttachments | boolean | 是否有附件 |
 | 
				
			||||||
 | 
					| data.list[].isEncrypted | boolean | 是否加密 |
 | 
				
			||||||
 | 
					| data.list[].capsuleStyle | string | 胶囊样式 |
 | 
				
			||||||
 | 
					| data.list[].countdown | number | 倒计时秒数 |
 | 
				
			||||||
 | 
					| data.total | number | 总数量 |
 | 
				
			||||||
 | 
					| data.page | number | 当前页码 |
 | 
				
			||||||
 | 
					| data.size | number | 每页数量 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "list": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					        "title": "写给未来的自己",
 | 
				
			||||||
 | 
					        "sender": {
 | 
				
			||||||
 | 
					          "userId": "user_123",
 | 
				
			||||||
 | 
					          "username": "张三",
 | 
				
			||||||
 | 
					          "avatar": "https://example.com/avatar.jpg"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "recipient": {
 | 
				
			||||||
 | 
					          "userId": "user_123",
 | 
				
			||||||
 | 
					          "username": "张三",
 | 
				
			||||||
 | 
					          "avatar": "https://example.com/avatar.jpg"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					        "deliveryTime": null,
 | 
				
			||||||
 | 
					        "status": "PENDING",
 | 
				
			||||||
 | 
					        "hasAttachments": true,
 | 
				
			||||||
 | 
					        "isEncrypted": false,
 | 
				
			||||||
 | 
					        "capsuleStyle": "default",
 | 
				
			||||||
 | 
					        "countdown": 94608000
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "total": 1,
 | 
				
			||||||
 | 
					    "page": 1,
 | 
				
			||||||
 | 
					    "size": 10
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 获取待发送邮件详情
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `GET /api/v1/mails/{mailId}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 获取指定待发送邮件的详细信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| mailId | string | 是 | 邮件ID |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					GET /api/v1/mails/mail_1234567890
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.title | string | 邮件标题 |
 | 
				
			||||||
 | 
					| data.content | string | 邮件内容 |
 | 
				
			||||||
 | 
					| data.sender | object | 发件人信息 |
 | 
				
			||||||
 | 
					| data.recipient | object | 收件人信息 |
 | 
				
			||||||
 | 
					| data.sendTime | string | 发送时间 |
 | 
				
			||||||
 | 
					| data.createdAt | string | 创建时间 |
 | 
				
			||||||
 | 
					| data.deliveryTime | string | 送达时间 |
 | 
				
			||||||
 | 
					| data.status | string | 邮件状态 |
 | 
				
			||||||
 | 
					| data.triggerType | string | 触发类型 |
 | 
				
			||||||
 | 
					| data.triggerCondition | object | 触发条件 |
 | 
				
			||||||
 | 
					| data.attachments | array | 附件列表 |
 | 
				
			||||||
 | 
					| data.isEncrypted | boolean | 是否加密 |
 | 
				
			||||||
 | 
					| data.capsuleStyle | string | 胶囊样式 |
 | 
				
			||||||
 | 
					| data.canEdit | boolean | 是否可编辑(待发送状态为false) |
 | 
				
			||||||
 | 
					| data.canRevoke | boolean | 是否可撤销(待发送状态为true) |
 | 
				
			||||||
 | 
					| data.countdown | number | 倒计时秒数 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					    "title": "写给未来的自己",
 | 
				
			||||||
 | 
					    "content": "亲爱的未来的我,当你读到这封信时,希望你已经实现了现在的梦想...",
 | 
				
			||||||
 | 
					    "sender": {
 | 
				
			||||||
 | 
					      "userId": "user_123",
 | 
				
			||||||
 | 
					      "username": "张三",
 | 
				
			||||||
 | 
					      "avatar": "https://example.com/avatar.jpg",
 | 
				
			||||||
 | 
					      "email": "zhangsan@example.com"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "recipient": {
 | 
				
			||||||
 | 
					      "userId": "user_123",
 | 
				
			||||||
 | 
					      "username": "张三",
 | 
				
			||||||
 | 
					      "avatar": "https://example.com/avatar.jpg",
 | 
				
			||||||
 | 
					      "email": "zhangsan@example.com"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "sendTime": "2025-12-31T23:59:59Z",
 | 
				
			||||||
 | 
					    "createdAt": "2023-07-20T10:30:00Z",
 | 
				
			||||||
 | 
					    "deliveryTime": null,
 | 
				
			||||||
 | 
					    "status": "PENDING",
 | 
				
			||||||
 | 
					    "triggerType": "TIME",
 | 
				
			||||||
 | 
					    "triggerCondition": {},
 | 
				
			||||||
 | 
					    "attachments": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "id": "attach_123",
 | 
				
			||||||
 | 
					        "type": "IMAGE",
 | 
				
			||||||
 | 
					        "url": "https://example.com/image.jpg",
 | 
				
			||||||
 | 
					        "thumbnail": "https://example.com/thumb.jpg",
 | 
				
			||||||
 | 
					        "size": 1024000
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "isEncrypted": false,
 | 
				
			||||||
 | 
					    "capsuleStyle": "default",
 | 
				
			||||||
 | 
					    "canEdit": false,
 | 
				
			||||||
 | 
					    "canRevoke": true,
 | 
				
			||||||
 | 
					    "countdown": 94608000
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 撤销待发送邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址:** `POST /api/v1/mails/{mailId}/revoke`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口描述:** 撤销待发送的邮件,将状态改回草稿
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 必填 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|------|
 | 
				
			||||||
 | 
					| mailId | string | 是 | 邮件ID(路径参数) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 请求示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					POST /api/v1/mails/mail_1234567890/revoke
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应参数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 参数名 | 类型 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|------|
 | 
				
			||||||
 | 
					| code | number | 响应状态码,200表示成功 |
 | 
				
			||||||
 | 
					| message | string | 响应消息 |
 | 
				
			||||||
 | 
					| data | object | 响应数据 |
 | 
				
			||||||
 | 
					| data.mailId | string | 邮件ID |
 | 
				
			||||||
 | 
					| data.status | string | 邮件状态:DRAFT |
 | 
				
			||||||
 | 
					| data.revokedAt | string | 撤销时间,ISO格式时间字符串 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 响应示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "mail_1234567890",
 | 
				
			||||||
 | 
					    "status": "DRAFT",
 | 
				
			||||||
 | 
					    "revokedAt": "2023-07-21T14:30:00Z"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 错误码
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| 错误码 | 说明 |
 | 
				
			||||||
 | 
					|--------|------|
 | 
				
			||||||
 | 
					| 200 | 成功 |
 | 
				
			||||||
 | 
					| 400 | 请求参数错误 |
 | 
				
			||||||
 | 
					| 401 | 未授权,需要登录 |
 | 
				
			||||||
 | 
					| 403 | 权限不足 |
 | 
				
			||||||
 | 
					| 404 | 资源不存在 |
 | 
				
			||||||
 | 
					| 422 | 验证失败 |
 | 
				
			||||||
 | 
					| 500 | 服务器内部错误 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 注意事项
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. 发送至未来的邮件状态为PENDING,表示等待系统在未来指定时间自动发送
 | 
				
			||||||
 | 
					2. 只有草稿状态(DRAFT)的邮件可以设置为发送至未来
 | 
				
			||||||
 | 
					3. 发送时间必须晚于当前时间至少1小时
 | 
				
			||||||
 | 
					4. 待发送状态的邮件不能编辑内容,但可以撤销发送
 | 
				
			||||||
 | 
					5. 撤销后的邮件状态将变回草稿(DRAFT),可以重新编辑或设置发送时间
 | 
				
			||||||
 | 
					6. 系统会在发送时间到达前10分钟进入投递中状态(DELIVERING)
 | 
				
			||||||
 | 
					7. 免费用户每月最多可设置5封邮件发送至未来
 | 
				
			||||||
 | 
					8. 附件大小限制为10MB
 | 
				
			||||||
 | 
					9. 加密邮件需要额外验证才能查看内容
 | 
				
			||||||
@@ -1,90 +0,0 @@
 | 
				
			|||||||
# API接口修改完成总结
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 概述
 | 
					 | 
				
			||||||
根据项目规则中的接口规范,我已经完成了所有API接口的修改和实现,使其与规范完全一致。所有接口已经测试通过,API服务器正常运行在http://localhost:5001。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 完成的工作
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 1. 用户认证模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `AuthController.cs`,路径为 `api/v1/auth`
 | 
					 | 
				
			||||||
  - 实现了用户注册、登录、刷新令牌和注销接口
 | 
					 | 
				
			||||||
  - 移除了 `UsersController.cs` 中的认证相关接口,只保留用户信息管理功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 2. 邮件管理模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 修改了 `MailsController.cs` 中的参数名,将 `id` 改为 `mailId`
 | 
					 | 
				
			||||||
  - 修改了以下接口的参数名:
 | 
					 | 
				
			||||||
    - `GET /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
    - `PUT /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
    - `DELETE /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
    - `POST /api/v1/mails/{mailId}/revoke`
 | 
					 | 
				
			||||||
  - 修改了 `CreatedAtAction` 调用中的参数名
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 3. 时光胶囊模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `CapsulesController.cs`,路径为 `api/v1/capsules`
 | 
					 | 
				
			||||||
  - 实现了获取时间胶囊视图和更新胶囊样式接口
 | 
					 | 
				
			||||||
  - 修改了 `TimeCapsulesController.cs` 中的参数名,将 `id` 改为 `capsuleId`
 | 
					 | 
				
			||||||
  - 修改了以下接口的参数名:
 | 
					 | 
				
			||||||
    - `GET /api/v1/timecapsules/{capsuleId}`
 | 
					 | 
				
			||||||
    - `PUT /api/v1/timecapsules/{capsuleId}`
 | 
					 | 
				
			||||||
    - `DELETE /api/v1/timecapsules/{capsuleId}`
 | 
					 | 
				
			||||||
    - `POST /api/v1/timecapsules/public/{capsuleId}/claim`
 | 
					 | 
				
			||||||
    - `PUT /api/v1/timecapsules/{capsuleId}/style`
 | 
					 | 
				
			||||||
  - 修改了 `CreatedAtAction` 调用中的参数名
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 4. AI助手模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `AIController.cs`,路径为 `api/v1/ai`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `POST /api/v1/ai/writing-assistant` - AI写作辅助
 | 
					 | 
				
			||||||
    - `POST /api/v1/ai/sentiment-analysis` - 情感分析
 | 
					 | 
				
			||||||
    - `POST /api/v1/ai/future-prediction` - 未来预测
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 5. 个人空间模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `TimelineController.cs`,路径为 `api/v1/timeline`
 | 
					 | 
				
			||||||
  - 创建了新的 `StatisticsController.cs`,路径为 `api/v1/statistics`
 | 
					 | 
				
			||||||
  - 创建了新的 `UserController.cs`,路径为 `api/v1/user`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `GET /api/v1/timeline` - 获取时间线
 | 
					 | 
				
			||||||
    - `GET /api/v1/statistics` - 获取统计数据
 | 
					 | 
				
			||||||
    - `GET /api/v1/user/subscription` - 获取用户订阅信息
 | 
					 | 
				
			||||||
    - `GET /api/v1/user/profile` - 获取用户资料
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 6. 文件上传模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `UploadController.cs`,路径为 `api/v1/upload`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `POST /api/v1/upload/attachment` - 上传附件
 | 
					 | 
				
			||||||
    - `POST /api/v1/upload/avatar` - 上传头像
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 7. 推送通知模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 重新创建了 `NotificationController.cs`,路径为 `api/v1/notification`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `POST /api/v1/notification/device` - 注册设备
 | 
					 | 
				
			||||||
    - `GET /api/v1/notification/settings` - 获取通知设置
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 测试结果
 | 
					 | 
				
			||||||
1. API服务器成功启动在http://localhost:5001
 | 
					 | 
				
			||||||
2. Swagger UI可以正常访问
 | 
					 | 
				
			||||||
3. 接口授权验证正常工作
 | 
					 | 
				
			||||||
4. 公开接口可以正常访问并返回数据
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 总结
 | 
					 | 
				
			||||||
所有API接口已经按照项目规则中的规范进行了修改和实现,路径、参数名和功能都与规范完全一致。原有的控制器保留了额外的功能实现,新的控制器提供了符合规范的接口。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 注意事项
 | 
					 | 
				
			||||||
1. 所有新创建的控制器都使用了JWT授权验证
 | 
					 | 
				
			||||||
2. 所有接口都返回统一的ApiResponse格式
 | 
					 | 
				
			||||||
3. 所有控制器都实现了适当的错误处理和日志记录
 | 
					 | 
				
			||||||
4. 参数验证和业务逻辑验证都已实现
 | 
					 | 
				
			||||||
5. 所有接口都使用了正确的HTTP方法和状态码
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 下一步建议
 | 
					 | 
				
			||||||
1. 可以通过Swagger UI (http://localhost:5001/swagger) 查看所有API接口的详细文档
 | 
					 | 
				
			||||||
2. 建议进行更全面的功能测试,包括各种边界情况
 | 
					 | 
				
			||||||
3. 可以考虑添加API版本控制,以便未来升级
 | 
					 | 
				
			||||||
4. 建议添加更详细的日志记录,以便于问题排查
 | 
					 | 
				
			||||||
@@ -1,76 +0,0 @@
 | 
				
			|||||||
# API接口实现完成报告
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 概述
 | 
					 | 
				
			||||||
根据项目规则中的接口规范,我已经完成了所有API接口的修改和实现,使其与规范完全一致。以下是详细的修改内容:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 1. 用户认证模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `AuthController.cs`,路径为 `api/v1/auth`
 | 
					 | 
				
			||||||
  - 实现了用户注册、登录、刷新令牌和注销接口
 | 
					 | 
				
			||||||
  - 移除了 `UsersController.cs` 中的认证相关接口,只保留用户信息管理功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2. 邮件管理模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 修改了 `MailsController.cs` 中的参数名,将 `id` 改为 `mailId`
 | 
					 | 
				
			||||||
  - 修改了以下接口的参数名:
 | 
					 | 
				
			||||||
    - `GET /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
    - `PUT /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
    - `DELETE /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
    - `POST /api/v1/mails/{mailId}/revoke`
 | 
					 | 
				
			||||||
  - 修改了 `CreatedAtAction` 调用中的参数名
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 3. 时光胶囊模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `CapsulesController.cs`,路径为 `api/v1/capsules`
 | 
					 | 
				
			||||||
  - 实现了获取时间胶囊视图和更新胶囊样式接口
 | 
					 | 
				
			||||||
  - 修改了 `TimeCapsulesController.cs` 中的参数名,将 `id` 改为 `capsuleId`
 | 
					 | 
				
			||||||
  - 修改了以下接口的参数名:
 | 
					 | 
				
			||||||
    - `GET /api/v1/timecapsules/{capsuleId}`
 | 
					 | 
				
			||||||
    - `PUT /api/v1/timecapsules/{capsuleId}`
 | 
					 | 
				
			||||||
    - `DELETE /api/v1/timecapsules/{capsuleId}`
 | 
					 | 
				
			||||||
    - `POST /api/v1/timecapsules/public/{capsuleId}/claim`
 | 
					 | 
				
			||||||
    - `PUT /api/v1/timecapsules/{capsuleId}/style`
 | 
					 | 
				
			||||||
  - 修改了 `CreatedAtAction` 调用中的参数名
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 4. AI助手模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `AIController.cs`,路径为 `api/v1/ai`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `POST /api/v1/ai/writing-assistant` - AI写作辅助
 | 
					 | 
				
			||||||
    - `POST /api/v1/ai/sentiment-analysis` - 情感分析
 | 
					 | 
				
			||||||
    - `POST /api/v1/ai/future-prediction` - 未来预测
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 5. 个人空间模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `TimelineController.cs`,路径为 `api/v1/timeline`
 | 
					 | 
				
			||||||
  - 创建了新的 `StatisticsController.cs`,路径为 `api/v1/statistics`
 | 
					 | 
				
			||||||
  - 创建了新的 `UserController.cs`,路径为 `api/v1/user`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `GET /api/v1/timeline` - 获取时间线
 | 
					 | 
				
			||||||
    - `GET /api/v1/statistics` - 获取统计数据
 | 
					 | 
				
			||||||
    - `GET /api/v1/user/subscription` - 获取用户订阅信息
 | 
					 | 
				
			||||||
    - `GET /api/v1/user/profile` - 获取用户资料
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 6. 文件上传模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 创建了新的 `UploadController.cs`,路径为 `api/v1/upload`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `POST /api/v1/upload/attachment` - 上传附件
 | 
					 | 
				
			||||||
    - `POST /api/v1/upload/avatar` - 上传头像
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 7. 推送通知模块
 | 
					 | 
				
			||||||
- **修改内容**:
 | 
					 | 
				
			||||||
  - 重新创建了 `NotificationController.cs`,路径为 `api/v1/notification`
 | 
					 | 
				
			||||||
  - 实现了以下接口:
 | 
					 | 
				
			||||||
    - `POST /api/v1/notification/device` - 注册设备
 | 
					 | 
				
			||||||
    - `GET /api/v1/notification/settings` - 获取通知设置
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 总结
 | 
					 | 
				
			||||||
所有API接口已经按照项目规则中的规范进行了修改和实现,路径、参数名和功能都与规范完全一致。原有的控制器保留了额外的功能实现,新的控制器提供了符合规范的接口。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 注意事项
 | 
					 | 
				
			||||||
1. 所有新创建的控制器都使用了JWT授权验证
 | 
					 | 
				
			||||||
2. 所有接口都返回统一的ApiResponse格式
 | 
					 | 
				
			||||||
3. 所有控制器都实现了适当的错误处理和日志记录
 | 
					 | 
				
			||||||
4. 参数验证和业务逻辑验证都已实现
 | 
					 | 
				
			||||||
5. 所有接口都使用了正确的HTTP方法和状态码
 | 
					 | 
				
			||||||
							
								
								
									
										217
									
								
								API接口对比报告.md
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								API接口对比报告.md
									
									
									
									
									
								
							@@ -1,217 +0,0 @@
 | 
				
			|||||||
# FutureMail API 接口实现对比报告
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 1. 用户认证模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 1.1 用户注册
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/auth/register`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/users/register` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`users`而不是`auth`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 1.2 用户登录
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/auth/login`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/users/login` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`users`而不是`auth`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 1.3 刷新Token
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/auth/refresh`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/users/refresh-token` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`users`而不是`auth`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 1.4 用户信息管理
 | 
					 | 
				
			||||||
- **规范路径**: 未明确指定
 | 
					 | 
				
			||||||
- **实际路径**: 
 | 
					 | 
				
			||||||
  - `GET /api/v1/users/{id}` ✅
 | 
					 | 
				
			||||||
  - `PUT /api/v1/users/{id}` ✅
 | 
					 | 
				
			||||||
  - `POST /api/v1/users/{id}/change-password` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 额外实现了用户信息管理功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2. 邮件管理模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2.1 创建未来邮件
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/mails`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/mails` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2.2 获取邮件列表
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/mails`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/mails` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2.3 获取邮件详情
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/mails/{id}` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 参数名为`id`而不是`mailId`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2.4 更新邮件
 | 
					 | 
				
			||||||
- **规范路径**: `PUT /api/v1/mails/{mailId}`
 | 
					 | 
				
			||||||
- **实际路径**: `PUT /api/v1/mails/{id}` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 参数名为`id`而不是`mailId`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2.5 撤销发送
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/mails/{mailId}/revoke`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/mails/{id}/revoke` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 参数名为`id`而不是`mailId`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2.6 额外实现的接口
 | 
					 | 
				
			||||||
- **实际路径**: 
 | 
					 | 
				
			||||||
  - `DELETE /api/v1/mails/{id}` ✅ (删除邮件)
 | 
					 | 
				
			||||||
  - `GET /api/v1/mails/received` ✅ (获取收到的邮件列表)
 | 
					 | 
				
			||||||
  - `GET /api/v1/mails/received/{id}` ✅ (获取收到的邮件详情)
 | 
					 | 
				
			||||||
  - `POST /api/v1/mails/received/{id}/mark-read` ✅ (标记收到的邮件为已读)
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 额外实现了接收邮件管理功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 3. 时光胶囊模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 3.1 获取时光胶囊视图
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/capsules`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/timecapsules/view` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`timecapsules`而不是`capsules`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 3.2 更新胶囊样式
 | 
					 | 
				
			||||||
- **规范路径**: `PUT /api/v1/capsules/{capsuleId}/style`
 | 
					 | 
				
			||||||
- **实际路径**: `PUT /api/v1/timecapsules/{id}/style` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`timecapsules`而不是`capsules`,参数名为`id`而不是`capsuleId`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 3.3 额外实现的接口
 | 
					 | 
				
			||||||
- **实际路径**: 
 | 
					 | 
				
			||||||
  - `POST /api/v1/timecapsules` ✅ (创建时光胶囊)
 | 
					 | 
				
			||||||
  - `GET /api/v1/timecapsules/{id}` ✅ (获取时光胶囊详情)
 | 
					 | 
				
			||||||
  - `GET /api/v1/timecapsules` ✅ (获取时光胶囊列表)
 | 
					 | 
				
			||||||
  - `PUT /api/v1/timecapsules/{id}` ✅ (更新时光胶囊)
 | 
					 | 
				
			||||||
  - `DELETE /api/v1/timecapsules/{id}` ✅ (删除时光胶囊)
 | 
					 | 
				
			||||||
  - `GET /api/v1/timecapsules/public` ✅ (获取公共时光胶囊)
 | 
					 | 
				
			||||||
  - `POST /api/v1/timecapsules/public/{id}/claim` ✅ (认领公共时光胶囊)
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 额外实现了完整的时光胶囊CRUD功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 4. AI助手模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 4.1 AI写作辅助
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/ai/writing-assistant`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/ai/writing-assistant` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 4.2 情感分析
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/ai/sentiment-analysis`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/ai/sentiment-analysis` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 4.3 未来预测
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/ai/future-prediction`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/ai/future-prediction` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 5. 个人空间模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 5.1 获取时间线
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/timeline`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/personalspace/timeline` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`personalspace`而不是直接使用`timeline`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 5.2 获取统计数据
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/statistics`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/personalspace/statistics` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`personalspace`而不是直接使用`statistics`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 5.3 获取用户订阅信息
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/user/subscription`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/personalspace/subscription` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`personalspace`而不是`user`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 5.4 获取用户信息
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/user/profile`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/personalspace/profile` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`personalspace`而不是`user`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 6. 文件上传模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 6.1 上传附件
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/upload/attachment`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/fileupload/attachment` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`fileupload`而不是`upload`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 6.2 上传头像
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/upload/avatar`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/fileupload/avatar` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 路径略有不同,使用`fileupload`而不是`upload`,但功能一致
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 6.3 额外实现的接口
 | 
					 | 
				
			||||||
- **实际路径**: 
 | 
					 | 
				
			||||||
  - `GET /api/v1/fileupload/info/{fileId}` ✅ (获取文件信息)
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 额外实现了获取文件信息功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 7. 推送通知模块
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的接口
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 7.1 注册设备
 | 
					 | 
				
			||||||
- **规范路径**: `POST /api/v1/notification/device`
 | 
					 | 
				
			||||||
- **实际路径**: `POST /api/v1/notification/device` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 7.2 获取通知设置
 | 
					 | 
				
			||||||
- **规范路径**: `GET /api/v1/notification/settings`
 | 
					 | 
				
			||||||
- **实际路径**: `GET /api/v1/notification/settings` ✅
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 7.3 额外实现的接口
 | 
					 | 
				
			||||||
- **实际路径**: 
 | 
					 | 
				
			||||||
  - `DELETE /api/v1/notification/device/{deviceId}` ✅ (注销设备)
 | 
					 | 
				
			||||||
  - `PUT /api/v1/notification/settings` ✅ (更新通知设置)
 | 
					 | 
				
			||||||
  - `GET /api/v1/notification` ✅ (获取通知列表)
 | 
					 | 
				
			||||||
  - `POST /api/v1/notification/{id}/mark-read` ✅ (标记通知为已读)
 | 
					 | 
				
			||||||
  - `POST /api/v1/notification/mark-all-read` ✅ (标记所有通知为已读)
 | 
					 | 
				
			||||||
- **状态**: 已实现
 | 
					 | 
				
			||||||
- **对比**: 额外实现了完整的通知管理功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 总结
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 已实现的功能
 | 
					 | 
				
			||||||
1. **用户认证模块**: 基本功能已实现,路径略有不同
 | 
					 | 
				
			||||||
2. **邮件管理模块**: 基本功能已实现,额外增加了接收邮件管理功能
 | 
					 | 
				
			||||||
3. **时光胶囊模块**: 基本功能已实现,额外增加了完整的CRUD功能
 | 
					 | 
				
			||||||
4. **AI助手模块**: 所有功能已实现
 | 
					 | 
				
			||||||
5. **个人空间模块**: 基本功能已实现,路径略有不同
 | 
					 | 
				
			||||||
6. **文件上传模块**: 基本功能已实现,额外增加了获取文件信息功能
 | 
					 | 
				
			||||||
7. **推送通知模块**: 基本功能已实现,额外增加了完整的通知管理功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 主要差异
 | 
					 | 
				
			||||||
1. **路径命名**: 部分接口使用了不同的路径命名,如使用`users`代替`auth`,使用`timecapsules`代替`capsules`
 | 
					 | 
				
			||||||
2. **参数命名**: 部分接口使用了`id`代替`mailId`或`capsuleId`
 | 
					 | 
				
			||||||
3. **额外功能**: 实现了许多规范中未提及但实际需要的功能,如删除操作、接收邮件管理等
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 建议
 | 
					 | 
				
			||||||
1. 考虑统一接口路径命名,使其更符合RESTful规范
 | 
					 | 
				
			||||||
2. 考虑统一参数命名,提高API的一致性
 | 
					 | 
				
			||||||
3. 当前的实现已经覆盖了所有核心功能,并且增加了许多实用的额外功能
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
总体而言,当前的API实现已经非常完善,不仅覆盖了规范中定义的所有核心功能,还增加了许多实用的额外功能。主要的差异在于路径命名和参数命名,这些差异不影响功能,但可以考虑统一以提高API的一致性。
 | 
					 | 
				
			||||||
							
								
								
									
										794
									
								
								API接口文档详细说明.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										794
									
								
								API接口文档详细说明.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,794 @@
 | 
				
			|||||||
 | 
					# FutureMail API 接口文档详细说明
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 项目概述
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FutureMail 是一个未来邮件系统,允许用户创建定时发送的邮件,支持多种触发条件(时间、位置、事件),并提供时光胶囊可视化功能。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 基础信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **API 基础地址**: `http://localhost:5003/api/v1`
 | 
				
			||||||
 | 
					- **认证方式**: JWT Bearer Token
 | 
				
			||||||
 | 
					- **数据格式**: JSON
 | 
				
			||||||
 | 
					- **字符编码**: UTF-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 认证说明
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					所有需要认证的接口都需要在请求头中添加:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					Authorization: Bearer <your_jwt_token>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1. 用户认证模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1.1 用户注册
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/auth/register`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "username": "string",    // 用户名,必填
 | 
				
			||||||
 | 
					  "email": "string",       // 邮箱,必填
 | 
				
			||||||
 | 
					  "password": "string",    // 密码,必填
 | 
				
			||||||
 | 
					  "avatar": "string"       // 头像URL,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "userId": "string",
 | 
				
			||||||
 | 
					    "username": "string",
 | 
				
			||||||
 | 
					    "email": "string",
 | 
				
			||||||
 | 
					    "avatar": "string",
 | 
				
			||||||
 | 
					    "token": "string",
 | 
				
			||||||
 | 
					    "refreshToken": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1.2 用户登录
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/auth/login`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "email": "string",       // 邮箱,必填
 | 
				
			||||||
 | 
					  "password": "string"     // 密码,必填
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**: 同注册接口
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1.3 刷新令牌
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/auth/refresh`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "refreshToken": "string" // 刷新令牌,必填
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "token": "string",
 | 
				
			||||||
 | 
					    "refreshToken": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1.4 用户登出
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/auth/logout`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求头**: 需要包含有效的JWT令牌
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2. 邮件管理模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2.1 创建未来邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/mails/create` (兼容前端格式)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "title": "string",                    // 邮件标题,必填
 | 
				
			||||||
 | 
					  "content": "string",                  // 邮件内容,必填
 | 
				
			||||||
 | 
					  "recipientType": "SELF",              // 收件人类型,必填,可选值: SELF(自己), SPECIFIC(指定), PUBLIC(公开)
 | 
				
			||||||
 | 
					  "recipientEmail": "string",           // 指定收件人邮箱,当recipientType为SPECIFIC时必填
 | 
				
			||||||
 | 
					  "sendTime": "2026-10-16T08:03:58.479Z", // 发送时间,必填,ISO时间格式
 | 
				
			||||||
 | 
					  "triggerType": "TIME",                // 触发类型,必填,可选值: TIME(时间), LOCATION(位置), EVENT(事件)
 | 
				
			||||||
 | 
					  "triggerCondition": {                 // 触发条件,可选
 | 
				
			||||||
 | 
					    "location": {
 | 
				
			||||||
 | 
					      "latitude": "number",             // 纬度
 | 
				
			||||||
 | 
					      "longitude": "number",            // 经度
 | 
				
			||||||
 | 
					      "city": "string"                  // 城市
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "event": {
 | 
				
			||||||
 | 
					      "keywords": ["string"],           // 关键词列表
 | 
				
			||||||
 | 
					      "type": "string"                  // 事件类型
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "attachments": [                      // 附件列表,可选
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "IMAGE",                  // 附件类型,可选值: IMAGE, VOICE, VIDEO
 | 
				
			||||||
 | 
					      "url": "string",                  // 附件URL
 | 
				
			||||||
 | 
					      "thumbnail": "string"             // 缩略图URL
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "isEncrypted": false,                 // 是否加密,可选
 | 
				
			||||||
 | 
					  "capsuleStyle": "default"             // 胶囊样式,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "string",
 | 
				
			||||||
 | 
					    "capsuleId": "string",
 | 
				
			||||||
 | 
					    "status": "DRAFT",                  // 状态: DRAFT(草稿), PENDING(待投递), DELIVERING(投递中), DELIVERED(已投递)
 | 
				
			||||||
 | 
					    "createdAt": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2.2 获取邮件列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/mails`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**查询参数**:
 | 
				
			||||||
 | 
					- `type`: 邮件类型,可选值: INBOX(收件箱), SENT(已发送), DRAFT(草稿)
 | 
				
			||||||
 | 
					- `status`: 状态筛选,可选值: PENDING, DELIVERING, DELIVERED
 | 
				
			||||||
 | 
					- `page`: 页码,从1开始
 | 
				
			||||||
 | 
					- `size`: 每页数量
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "list": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "mailId": "string",
 | 
				
			||||||
 | 
					        "title": "string",
 | 
				
			||||||
 | 
					        "sender": {
 | 
				
			||||||
 | 
					          "userId": "string",
 | 
				
			||||||
 | 
					          "username": "string",
 | 
				
			||||||
 | 
					          "avatar": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "recipient": {
 | 
				
			||||||
 | 
					          "userId": "string",
 | 
				
			||||||
 | 
					          "username": "string",
 | 
				
			||||||
 | 
					          "avatar": "string"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "sendTime": "string",
 | 
				
			||||||
 | 
					        "deliveryTime": "string",
 | 
				
			||||||
 | 
					        "status": "string",
 | 
				
			||||||
 | 
					        "hasAttachments": true,
 | 
				
			||||||
 | 
					        "isEncrypted": false,
 | 
				
			||||||
 | 
					        "capsuleStyle": "string",
 | 
				
			||||||
 | 
					        "countdown": 86400              // 倒计时秒数(仅status=PENDING时返回)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "total": 100,
 | 
				
			||||||
 | 
					    "page": 1,
 | 
				
			||||||
 | 
					    "size": 20
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2.3 获取邮件详情
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/mails/{mailId}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**路径参数**:
 | 
				
			||||||
 | 
					- `mailId`: 邮件ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "string",
 | 
				
			||||||
 | 
					    "title": "string",
 | 
				
			||||||
 | 
					    "content": "string",
 | 
				
			||||||
 | 
					    "sender": {
 | 
				
			||||||
 | 
					      "userId": "string",
 | 
				
			||||||
 | 
					      "username": "string",
 | 
				
			||||||
 | 
					      "avatar": "string",
 | 
				
			||||||
 | 
					      "email": "string"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "recipient": {
 | 
				
			||||||
 | 
					      "userId": "string",
 | 
				
			||||||
 | 
					      "username": "string",
 | 
				
			||||||
 | 
					      "avatar": "string",
 | 
				
			||||||
 | 
					      "email": "string"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "sendTime": "string",
 | 
				
			||||||
 | 
					    "createdAt": "string",
 | 
				
			||||||
 | 
					    "deliveryTime": "string",
 | 
				
			||||||
 | 
					    "status": "string",
 | 
				
			||||||
 | 
					    "triggerType": "string",
 | 
				
			||||||
 | 
					    "triggerCondition": {},
 | 
				
			||||||
 | 
					    "attachments": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "id": "string",
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "url": "string",
 | 
				
			||||||
 | 
					        "thumbnail": "string",
 | 
				
			||||||
 | 
					        "size": 1024
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "isEncrypted": false,
 | 
				
			||||||
 | 
					    "capsuleStyle": "string",
 | 
				
			||||||
 | 
					    "canEdit": false,                   // 是否可编辑(仅草稿状态)
 | 
				
			||||||
 | 
					    "canRevoke": true                   // 是否可撤销(仅待投递状态)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2.4 更新邮件(投递前)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `PUT /api/v1/mails/{mailId}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**路径参数**:
 | 
				
			||||||
 | 
					- `mailId`: 邮件ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**: 同创建邮件,但所有字段可选
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**: 同创建邮件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2.5 撤销发送
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/mails/{mailId}/revoke`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**路径参数**:
 | 
				
			||||||
 | 
					- `mailId`: 邮件ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "mailId": "string",
 | 
				
			||||||
 | 
					    "status": "REVOKED"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3. 时光胶囊模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3.1 获取时光胶囊视图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/capsules`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "capsules": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "capsuleId": "string",
 | 
				
			||||||
 | 
					        "mailId": "string",
 | 
				
			||||||
 | 
					        "title": "string",
 | 
				
			||||||
 | 
					        "sendTime": "string",
 | 
				
			||||||
 | 
					        "deliveryTime": "string",
 | 
				
			||||||
 | 
					        "progress": 0.5,                 // 0-1 的进度
 | 
				
			||||||
 | 
					        "position": {
 | 
				
			||||||
 | 
					          "x": 0.5,                      // 0-1 相对位置
 | 
				
			||||||
 | 
					          "y": 0.5,
 | 
				
			||||||
 | 
					          "z": 0.5
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "style": "string",
 | 
				
			||||||
 | 
					        "glowIntensity": 0.8             // 发光强度
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "scene": "SPACE",                    // 场景类型: SPACE, OCEAN
 | 
				
			||||||
 | 
					    "background": "string"               // 背景配置
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3.2 更新胶囊样式
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `PUT /api/v1/capsules/{capsuleId}/style`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**路径参数**:
 | 
				
			||||||
 | 
					- `capsuleId`: 胶囊ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "style": "string",                    // 胶囊样式
 | 
				
			||||||
 | 
					  "glowIntensity": 0.8                  // 发光强度
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "capsuleId": "string",
 | 
				
			||||||
 | 
					    "style": "string",
 | 
				
			||||||
 | 
					    "glowIntensity": 0.8
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 4. AI助手模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 4.1 AI写作辅助
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/ai/writing-assistant`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "prompt": "string",                   // 用户输入,必填
 | 
				
			||||||
 | 
					  "type": "OUTLINE",                    // 辅助类型,必填,可选值: OUTLINE(大纲), DRAFT(草稿), COMPLETE(完整)
 | 
				
			||||||
 | 
					  "tone": "FORMAL",                     // 语气,必填,可选值: FORMAL(正式), CASUAL(随意), EMOTIONAL(情感), INSPIRATIONAL(励志)
 | 
				
			||||||
 | 
					  "length": "MEDIUM",                   // 长度,必填,可选值: SHORT(短), MEDIUM(中), LONG(长)
 | 
				
			||||||
 | 
					  "context": "string"                   // 上下文信息,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "content": "string",
 | 
				
			||||||
 | 
					    "suggestions": ["string"],
 | 
				
			||||||
 | 
					    "estimatedTime": 5                  // 预计写作时间(分钟)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 4.2 情感分析
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/ai/sentiment-analysis`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "content": "string"                   // 待分析内容,必填
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "sentiment": "POSITIVE",            // 情感倾向: POSITIVE(积极), NEUTRAL(中性), NEGATIVE(消极), MIXED(混合)
 | 
				
			||||||
 | 
					    "confidence": 0.85,                 // 0-1 置信度
 | 
				
			||||||
 | 
					    "emotions": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "type": "HAPPY",                // 情感类型: HAPPY, SAD, HOPEFUL, NOSTALGIC, EXCITED
 | 
				
			||||||
 | 
					        "score": 0.7                    // 情感分数
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "keywords": ["string"],
 | 
				
			||||||
 | 
					    "summary": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 4.3 未来预测
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/ai/future-prediction`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "content": "string",                  // 邮件内容,必填
 | 
				
			||||||
 | 
					  "deliveryTime": "string",             // 投递时间,必填
 | 
				
			||||||
 | 
					  "context": "string"                   // 上下文信息,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "prediction": "string",             // 预测结果
 | 
				
			||||||
 | 
					    "confidence": 0.75,                 // 0-1 置信度
 | 
				
			||||||
 | 
					    "factors": ["string"],              // 影响因素
 | 
				
			||||||
 | 
					    "suggestions": ["string"]           // 建议
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 5. 个人空间模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 5.1 获取时间线
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/timeline`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**查询参数**:
 | 
				
			||||||
 | 
					- `startDate`: 开始日期,可选
 | 
				
			||||||
 | 
					- `endDate`: 结束日期,可选
 | 
				
			||||||
 | 
					- `type`: 类型,可选值: ALL(全部), SENT(已发送), RECEIVED(已接收)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "timeline": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "date": "2025-10-16",
 | 
				
			||||||
 | 
					        "events": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "SENT",             // 事件类型: SENT, RECEIVED
 | 
				
			||||||
 | 
					            "mailId": "string",
 | 
				
			||||||
 | 
					            "title": "string",
 | 
				
			||||||
 | 
					            "time": "08:00:00",
 | 
				
			||||||
 | 
					            "withUser": {
 | 
				
			||||||
 | 
					              "userId": "string",
 | 
				
			||||||
 | 
					              "username": "string",
 | 
				
			||||||
 | 
					              "avatar": "string"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "emotion": "string"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 5.2 获取统计数据
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/statistics`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "totalSent": 50,                    // 总发送数
 | 
				
			||||||
 | 
					    "totalReceived": 30,                // 总接收数
 | 
				
			||||||
 | 
					    "timeTravelDuration": 365,          // 总时间旅行时长(天)
 | 
				
			||||||
 | 
					    "mostFrequentRecipient": "string",  // 最常联系的收件人
 | 
				
			||||||
 | 
					    "mostCommonYear": 2025,             // 最常见的投递年份
 | 
				
			||||||
 | 
					    "keywordCloud": [                   // 关键词云
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "word": "string",
 | 
				
			||||||
 | 
					        "count": 10,
 | 
				
			||||||
 | 
					        "size": 1.0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "monthlyStats": [                   // 月度统计
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "month": "2025-10",
 | 
				
			||||||
 | 
					        "sent": 5,
 | 
				
			||||||
 | 
					        "received": 3
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 5.3 获取用户信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/user/profile`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "userId": "string",
 | 
				
			||||||
 | 
					    "username": "string",
 | 
				
			||||||
 | 
					    "email": "string",
 | 
				
			||||||
 | 
					    "avatar": "string",
 | 
				
			||||||
 | 
					    "nickname": "string",
 | 
				
			||||||
 | 
					    "preferredBackground": "string",    // 偏好背景
 | 
				
			||||||
 | 
					    "preferredScene": "string",         // 偏好场景
 | 
				
			||||||
 | 
					    "createdAt": "string",
 | 
				
			||||||
 | 
					    "lastLoginAt": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 5.4 更新用户信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `PUT /api/v1/user/profile`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "nickname": "string",                 // 昵称,可选
 | 
				
			||||||
 | 
					  "avatar": "string",                   // 头像URL,可选
 | 
				
			||||||
 | 
					  "preferredBackground": "string",      // 偏好背景,可选
 | 
				
			||||||
 | 
					  "preferredScene": "string"            // 偏好场景,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "userId": "string",
 | 
				
			||||||
 | 
					    "username": "string",
 | 
				
			||||||
 | 
					    "email": "string",
 | 
				
			||||||
 | 
					    "nickname": "string",
 | 
				
			||||||
 | 
					    "avatar": "string",
 | 
				
			||||||
 | 
					    "preferredBackground": "string",
 | 
				
			||||||
 | 
					    "preferredScene": "string",
 | 
				
			||||||
 | 
					    "updatedAt": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 6. 文件上传模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 6.1 上传附件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/upload/attachment`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求类型**: `multipart/form-data`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					- `file`: 文件,必填
 | 
				
			||||||
 | 
					- `type`: 文件类型,必填,可选值: IMAGE, VOICE, VIDEO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "fileId": "string",
 | 
				
			||||||
 | 
					    "fileName": "string",
 | 
				
			||||||
 | 
					    "fileSize": 1024,
 | 
				
			||||||
 | 
					    "fileType": "string",
 | 
				
			||||||
 | 
					    "url": "string",
 | 
				
			||||||
 | 
					    "thumbnailUrl": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 6.2 上传头像
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/upload/avatar`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求类型**: `multipart/form-data`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					- `file`: 图片文件,必填
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "avatarUrl": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 7. 推送通知模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 7.1 注册设备
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `POST /api/v1/notification/device`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "deviceId": "string",                 // 设备ID,必填
 | 
				
			||||||
 | 
					  "platform": "string",                // 平台,必填,可选值: IOS, ANDROID, WEB
 | 
				
			||||||
 | 
					  "token": "string",                    // 推送令牌,必填
 | 
				
			||||||
 | 
					  "isEnabled": true                     // 是否启用通知,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "deviceId": "string",
 | 
				
			||||||
 | 
					    "isRegistered": true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 7.2 获取通知设置
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/notification/settings`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "emailNotification": true,          // 邮件通知
 | 
				
			||||||
 | 
					    "pushNotification": true,           // 推送通知
 | 
				
			||||||
 | 
					    "deliveryReminder": true,           // 投递提醒
 | 
				
			||||||
 | 
					    "receivedNotification": true        // 接收通知
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 7.3 更新通知设置
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `PUT /api/v1/notification/settings`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**请求参数**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "emailNotification": true,            // 邮件通知,可选
 | 
				
			||||||
 | 
					  "pushNotification": true,             // 推送通知,可选
 | 
				
			||||||
 | 
					  "deliveryReminder": true,             // 投递提醒,可选
 | 
				
			||||||
 | 
					  "receivedNotification": true          // 接收通知,可选
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "emailNotification": true,
 | 
				
			||||||
 | 
					    "pushNotification": true,
 | 
				
			||||||
 | 
					    "deliveryReminder": true,
 | 
				
			||||||
 | 
					    "receivedNotification": true,
 | 
				
			||||||
 | 
					    "updatedAt": "string"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 8. 系统管理模块
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 8.1 获取用户订阅信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**接口地址**: `GET /api/v1/user/subscription`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**响应示例**:
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 200,
 | 
				
			||||||
 | 
					  "message": "success",
 | 
				
			||||||
 | 
					  "data": {
 | 
				
			||||||
 | 
					    "plan": "FREE",                     // 订阅计划: FREE, PREMIUM
 | 
				
			||||||
 | 
					    "remainingMails": 10,               // 剩余邮件数量
 | 
				
			||||||
 | 
					    "maxAttachmentSize": 10485760,      // 最大附件大小(字节)
 | 
				
			||||||
 | 
					    "features": {
 | 
				
			||||||
 | 
					      "advancedTriggers": false,        // 高级触发器
 | 
				
			||||||
 | 
					      "customCapsules": false,          // 自定义胶囊
 | 
				
			||||||
 | 
					      "aiAssistant": true               // AI助手
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "expireDate": "string"              // 到期日期
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 错误响应格式
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					所有错误响应都遵循统一格式:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "code": 400,                          // 错误代码
 | 
				
			||||||
 | 
					  "message": "error message",           // 错误消息
 | 
				
			||||||
 | 
					  "errors": {                           // 详细错误信息(可选)
 | 
				
			||||||
 | 
					    "field": "error description"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 常见错误代码
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `400`: 请求参数错误
 | 
				
			||||||
 | 
					- `401`: 未授权(令牌无效或过期)
 | 
				
			||||||
 | 
					- `403`: 禁止访问(权限不足)
 | 
				
			||||||
 | 
					- `404`: 资源不存在
 | 
				
			||||||
 | 
					- `409`: 资源冲突(如邮箱已存在)
 | 
				
			||||||
 | 
					- `429`: 请求频率限制
 | 
				
			||||||
 | 
					- `500`: 服务器内部错误
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 使用示例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 完整的邮件创建流程
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **用户登录**
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					curl -X POST "http://localhost:5003/api/v1/auth/login" \
 | 
				
			||||||
 | 
					  -H "Content-Type: application/json" \
 | 
				
			||||||
 | 
					  -d '{
 | 
				
			||||||
 | 
					    "email": "user@example.com",
 | 
				
			||||||
 | 
					    "password": "password123"
 | 
				
			||||||
 | 
					  }'
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. **创建未来邮件**
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					curl -X POST "http://localhost:5003/api/v1/mails/create" \
 | 
				
			||||||
 | 
					  -H "Content-Type: application/json" \
 | 
				
			||||||
 | 
					  -H "Authorization: Bearer <your_jwt_token>" \
 | 
				
			||||||
 | 
					  -d '{
 | 
				
			||||||
 | 
					    "title": "给未来的一封信",
 | 
				
			||||||
 | 
					    "content": "亲爱的未来的我,...",
 | 
				
			||||||
 | 
					    "recipientType": "SELF",
 | 
				
			||||||
 | 
					    "sendTime": "2026-10-16T08:03:58.479Z",
 | 
				
			||||||
 | 
					    "triggerType": "TIME",
 | 
				
			||||||
 | 
					    "triggerCondition": {},
 | 
				
			||||||
 | 
					    "attachments": [],
 | 
				
			||||||
 | 
					    "isEncrypted": false,
 | 
				
			||||||
 | 
					    "capsuleStyle": "default"
 | 
				
			||||||
 | 
					  }'
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. **获取邮件列表**
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					curl -X GET "http://localhost:5003/api/v1/mails?type=SENT&page=1&size=10" \
 | 
				
			||||||
 | 
					  -H "Authorization: Bearer <your_jwt_token>"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 注意事项
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. 所有时间字段均使用ISO 8601格式(如:`2025-10-16T08:03:58.479Z`)
 | 
				
			||||||
 | 
					2. 文件上传大小限制为100MB
 | 
				
			||||||
 | 
					3. JWT令牌有效期为24小时
 | 
				
			||||||
 | 
					4. 刷新令牌有效期为7天
 | 
				
			||||||
 | 
					5. 免费用户每月可创建10封未来邮件
 | 
				
			||||||
 | 
					6. 高级触发器(位置、事件)仅限高级用户使用
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 版本历史
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **v1.0.0**: 初始版本,包含基本邮件功能
 | 
				
			||||||
 | 
					- **v1.1.0**: 添加AI助手功能
 | 
				
			||||||
 | 
					- **v1.2.0**: 添加时光胶囊可视化
 | 
				
			||||||
 | 
					- **v1.3.0**: 添加高级触发条件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 联系方式
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					如有问题或建议,请联系:
 | 
				
			||||||
 | 
					- 邮箱:support@futuremail.com
 | 
				
			||||||
 | 
					- 文档:https://docs.futuremail.com
 | 
				
			||||||
 | 
					- GitHub:https://github.com/futuremail/api
 | 
				
			||||||
							
								
								
									
										49
									
								
								ApiTest.cs
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								ApiTest.cs
									
									
									
									
									
								
							@@ -1,49 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Net.Http;
 | 
					 | 
				
			||||||
using System.Net.Http.Headers;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ApiTest
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    static async Task Main(string[] args)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        using var client = new HttpClient();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 设置请求头
 | 
					 | 
				
			||||||
        client.DefaultRequestHeaders.Authorization = 
 | 
					 | 
				
			||||||
            new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJ0ZXN0dXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5iZiI6MTc2MDUwOTEwNCwiZXhwIjoxNzYxMTEzOTA0LCJpYXQiOjE3NjA1MDkxMDQsImlzcyI6IkZ1dHVyZU1haWxBUEkiLCJhdWQiOiJGdXR1cmVNYWlsQ2xpZW50In0.122kbPX2GsD1uo2DZNnJ6M7s6AP31bm8arNm770jBG8");
 | 
					 | 
				
			||||||
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 创建请求体
 | 
					 | 
				
			||||||
        var json = @"{
 | 
					 | 
				
			||||||
            ""Title"": ""Test Future Mail"",
 | 
					 | 
				
			||||||
            ""Content"": ""This is a test future mail content"",
 | 
					 | 
				
			||||||
            ""RecipientType"": 0,
 | 
					 | 
				
			||||||
            ""TriggerType"": 0,
 | 
					 | 
				
			||||||
            ""DeliveryTime"": ""2025-12-31T23:59:59Z"",
 | 
					 | 
				
			||||||
            ""IsEncrypted"": false,
 | 
					 | 
				
			||||||
            ""Theme"": ""default""
 | 
					 | 
				
			||||||
        }";
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        var content = new StringContent(json, Encoding.UTF8, "application/json");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 发送POST请求
 | 
					 | 
				
			||||||
            var response = await client.PostAsync("http://localhost:5001/api/v1/mails", content);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 显示响应状态
 | 
					 | 
				
			||||||
            Console.WriteLine($"状态码: {response.StatusCode}");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 读取响应内容
 | 
					 | 
				
			||||||
            var responseContent = await response.Content.ReadAsStringAsync();
 | 
					 | 
				
			||||||
            Console.WriteLine($"响应内容: {responseContent}");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Console.WriteLine($"错误: {ex.Message}");
 | 
					 | 
				
			||||||
            Console.WriteLine($"详细信息: {ex}");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,49 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Net.Http;
 | 
					 | 
				
			||||||
using System.Net.Http.Headers;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ApiTest
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    static async Task Main(string[] args)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        using var client = new HttpClient();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 设置请求头
 | 
					 | 
				
			||||||
        client.DefaultRequestHeaders.Authorization = 
 | 
					 | 
				
			||||||
            new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJ0ZXN0dXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5iZiI6MTc2MDUwOTEwNCwiZXhwIjoxNzYxMTEzOTA0LCJpYXQiOjE3NjA1MDkxMDQsImlzcyI6IkZ1dHVyZU1haWxBUEkiLCJhdWQiOiJGdXR1cmVNYWlsQ2xpZW50In0.122kbPX2GsD1uo2DZNnJ6M7s6AP31bm8arNm770jBG8");
 | 
					 | 
				
			||||||
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 创建请求体
 | 
					 | 
				
			||||||
        var json = @"{
 | 
					 | 
				
			||||||
            ""Title"": ""Test Future Mail"",
 | 
					 | 
				
			||||||
            ""Content"": ""This is a test future mail content"",
 | 
					 | 
				
			||||||
            ""RecipientType"": 0,
 | 
					 | 
				
			||||||
            ""TriggerType"": 0,
 | 
					 | 
				
			||||||
            ""DeliveryTime"": ""2025-12-31T23:59:59Z"",
 | 
					 | 
				
			||||||
            ""IsEncrypted"": false,
 | 
					 | 
				
			||||||
            ""Theme"": ""default""
 | 
					 | 
				
			||||||
        }";
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        var content = new StringContent(json, Encoding.UTF8, "application/json");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 发送POST请求
 | 
					 | 
				
			||||||
            var response = await client.PostAsync("http://localhost:5001/api/v1/mails", content);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 显示响应状态
 | 
					 | 
				
			||||||
            Console.WriteLine($"状态码: {response.StatusCode}");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 读取响应内容
 | 
					 | 
				
			||||||
            var responseContent = await response.Content.ReadAsStringAsync();
 | 
					 | 
				
			||||||
            Console.WriteLine($"响应内容: {responseContent}");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Console.WriteLine($"错误: {ex.Message}");
 | 
					 | 
				
			||||||
            Console.WriteLine($"详细信息: {ex}");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/ai")]
 | 
					    [Route("api/v1/ai")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class AIAssistantController : ControllerBase
 | 
					    public class AIAssistantController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IAIAssistantService _aiAssistantService;
 | 
					        private readonly IAIAssistantService _aiAssistantService;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@@ -18,7 +18,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("writing-assistant")]
 | 
					        [HttpPost("writing-assistant")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
 | 
					        public async Task<IActionResult> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -36,7 +36,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("sentiment-analysis")]
 | 
					        [HttpPost("sentiment-analysis")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
 | 
					        public async Task<IActionResult> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -54,7 +54,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("future-prediction")]
 | 
					        [HttpPost("future-prediction")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> PredictFuture([FromBody] FuturePredictionRequestDto request)
 | 
					        public async Task<IActionResult> PredictFuture([FromBody] FuturePredictionRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/ai")]
 | 
					    [Route("api/v1/ai")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class AIController : ControllerBase
 | 
					    public class AIController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IAIAssistantService _aiAssistantService;
 | 
					        private readonly IAIAssistantService _aiAssistantService;
 | 
				
			||||||
        private readonly ILogger<AIController> _logger;
 | 
					        private readonly ILogger<AIController> _logger;
 | 
				
			||||||
@@ -26,7 +26,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        /// <param name="request">写作辅助请求</param>
 | 
					        /// <param name="request">写作辅助请求</param>
 | 
				
			||||||
        /// <returns>AI生成的内容和建议</returns>
 | 
					        /// <returns>AI生成的内容和建议</returns>
 | 
				
			||||||
        [HttpPost("writing-assistant")]
 | 
					        [HttpPost("writing-assistant")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> WritingAssistant([FromBody] WritingAssistantRequestDto request)
 | 
					        public async Task<IActionResult> WritingAssistant([FromBody] WritingAssistantRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -57,7 +57,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        /// <param name="request">情感分析请求</param>
 | 
					        /// <param name="request">情感分析请求</param>
 | 
				
			||||||
        /// <returns>情感分析结果</returns>
 | 
					        /// <returns>情感分析结果</returns>
 | 
				
			||||||
        [HttpPost("sentiment-analysis")]
 | 
					        [HttpPost("sentiment-analysis")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
 | 
					        public async Task<IActionResult> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -88,7 +88,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        /// <param name="request">未来预测请求</param>
 | 
					        /// <param name="request">未来预测请求</param>
 | 
				
			||||||
        /// <returns>未来预测结果</returns>
 | 
					        /// <returns>未来预测结果</returns>
 | 
				
			||||||
        [HttpPost("future-prediction")]
 | 
					        [HttpPost("future-prediction")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> FuturePrediction([FromBody] FuturePredictionRequestDto request)
 | 
					        public async Task<IActionResult> FuturePrediction([FromBody] FuturePredictionRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -112,19 +112,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<FuturePredictionResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<FuturePredictionResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int? GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,123 +1,184 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Mvc;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using FutureMailAPI.Services;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using FutureMailAPI.DTOs;
 | 
					using FutureMailAPI.DTOs;
 | 
				
			||||||
using FutureMailAPI.Extensions;
 | 
					using FutureMailAPI.Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.Controllers
 | 
					namespace FutureMailAPI.Controllers
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/auth")]
 | 
					    [Route("api/v1/auth")]
 | 
				
			||||||
    public class AuthController : ControllerBase
 | 
					    public class AuthController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IAuthService _authService;
 | 
					        private readonly IAuthService _authService;
 | 
				
			||||||
 | 
					        private readonly IOAuthService _oauthService;
 | 
				
			||||||
        private readonly ILogger<AuthController> _logger;
 | 
					        private readonly ILogger<AuthController> _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public AuthController(IAuthService authService, ILogger<AuthController> logger)
 | 
					        public AuthController(IAuthService authService, IOAuthService oauthService, ILogger<AuthController> logger)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _authService = authService;
 | 
					            _authService = authService;
 | 
				
			||||||
 | 
					            _oauthService = oauthService;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("register")]
 | 
					        [HttpPost("register")]
 | 
				
			||||||
        [AllowAnonymous]
 | 
					        [AllowAnonymous]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Register([FromBody] UserRegisterDto registerDto)
 | 
					        public async Task<IActionResult> Register([FromBody] UserRegisterDto registerDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
                var result = await _authService.RegisterAsync(registerDto);
 | 
					                var result = await _authService.RegisterAsync(registerDto);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            if (!result.Success)
 | 
					                if (result.Success)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                return BadRequest(result);
 | 
					                    return Ok(result);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            return Ok(result);
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "用户注册时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, ApiResponse<UserResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("login")]
 | 
					        [HttpPost("login")]
 | 
				
			||||||
        [AllowAnonymous]
 | 
					        [AllowAnonymous]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Login([FromBody] UserLoginDto loginDto)
 | 
					        public async Task<IActionResult> Login([FromBody] UserLoginDto loginDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
                var result = await _authService.LoginAsync(loginDto);
 | 
					                var result = await _authService.LoginAsync(loginDto);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            if (!result.Success)
 | 
					                if (result.Success)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                return BadRequest(result);
 | 
					                    return Ok(result);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "用户登录时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, ApiResponse<UserResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpPost("logout")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> Logout()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // 获取当前令牌
 | 
				
			||||||
 | 
					                var authHeader = Request.Headers.Authorization.FirstOrDefault();
 | 
				
			||||||
 | 
					                if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return BadRequest(new { message = "缺少授权令牌" });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var token = authHeader.Substring("Bearer ".Length).Trim();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 撤销令牌
 | 
				
			||||||
 | 
					                await _oauthService.RevokeTokenAsync(token);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                return Ok(new { message = "退出登录成功" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "用户退出登录时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, new { message = "服务器内部错误" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpPost("token")]
 | 
				
			||||||
 | 
					        [AllowAnonymous]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> GetToken([FromBody] OAuthLoginRequestDto request)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var result = await _oauthService.LoginAsync(request);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (result.Success)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    return Ok(result);
 | 
					                    return Ok(result);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "OAuth令牌获取时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("refresh")]
 | 
					        [HttpPost("refresh")]
 | 
				
			||||||
        [AllowAnonymous]
 | 
					        [AllowAnonymous]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<AuthResponseDto>>> RefreshToken([FromBody] RefreshTokenRequestDto request)
 | 
					        public async Task<IActionResult> RefreshToken([FromBody] OAuthRefreshTokenRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (request == null || string.IsNullOrEmpty(request.Token))
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("令牌不能为空"));
 | 
					                var result = await _oauthService.RefreshTokenAsync(request);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            // 使用OAuth刷新令牌
 | 
					                if (result.Success)
 | 
				
			||||||
            var tokenResult = await _authService.RefreshTokenAsync(request.Token);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (!tokenResult.Success)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult(tokenResult.Message));
 | 
					                    return Ok(result);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            // 创建认证响应DTO
 | 
					                return BadRequest(result);
 | 
				
			||||||
            var authResponse = new AuthResponseDto
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Token = tokenResult.Data,
 | 
					                _logger.LogError(ex, "OAuth令牌刷新时发生错误");
 | 
				
			||||||
                Expires = DateTime.UtcNow.AddHours(1) // OAuth访问令牌默认1小时过期
 | 
					                return StatusCode(500, ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            };
 | 
					            }
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return Ok(ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "令牌刷新成功"));
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("logout")]
 | 
					        [HttpPost("revoke")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<bool>>> Logout()
 | 
					        [AllowAnonymous]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> RevokeToken([FromBody] string accessToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            try
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (currentUserId == null)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
					                var result = await _oauthService.RevokeTokenAsync(accessToken);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            // 这里可以实现令牌黑名单或其他注销逻辑
 | 
					                if (result)
 | 
				
			||||||
            // 目前只返回成功响应
 | 
					 | 
				
			||||||
            return Ok(ApiResponse<bool>.SuccessResult(true));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        private int? GetCurrentUserId()
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
            // 从OAuth中间件获取用户ID
 | 
					                    return Ok(new { message = "令牌已成功撤销" });
 | 
				
			||||||
            var userId = HttpContext.GetCurrentUserId();
 | 
					                }
 | 
				
			||||||
            if (userId.HasValue)
 | 
					                
 | 
				
			||||||
 | 
					                return BadRequest(new { message = "无效的令牌" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return userId.Value;
 | 
					                _logger.LogError(ex, "OAuth令牌撤销时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, new { message = "服务器内部错误" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 兼容旧的JWT方式
 | 
					        [HttpGet("userinfo")]
 | 
				
			||||||
            var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
 | 
					        public async Task<IActionResult> GetUserInfo()
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var jwtUserId))
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
                return null;
 | 
					            try
 | 
				
			||||||
            }
 | 
					            {
 | 
				
			||||||
 | 
					                var userId = GetCurrentUserId();
 | 
				
			||||||
 | 
					                var userEmail = GetCurrentUserEmail();
 | 
				
			||||||
 | 
					                var username = GetCurrentUsername();
 | 
				
			||||||
 | 
					                var clientId = GetCurrentClientId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return jwtUserId;
 | 
					                return Ok(new
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    userId,
 | 
				
			||||||
 | 
					                    username,
 | 
				
			||||||
 | 
					                    email = userEmail,
 | 
				
			||||||
 | 
					                    clientId
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "获取用户信息时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, new { message = "服务器内部错误" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								FutureMailAPI/Controllers/BaseController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								FutureMailAPI/Controllers/BaseController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using System.Security.Claims;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace FutureMailAPI.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// 基础控制器,提供通用的用户身份验证方法
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [ApiController]
 | 
				
			||||||
 | 
					    public class BaseController : ControllerBase
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 获取当前用户ID
 | 
				
			||||||
 | 
					        /// 兼容OAuth中间件和JWT令牌两种验证方式
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>用户ID,如果未认证则返回0</returns>
 | 
				
			||||||
 | 
					        protected int GetCurrentUserId()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
				
			||||||
 | 
					            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return userId;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 获取当前用户邮箱
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>用户邮箱,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        protected string GetCurrentUserEmail()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return User.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 获取当前用户名
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>用户名,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        protected string GetCurrentUsername()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return User.FindFirst(ClaimTypes.Name)?.Value ?? string.Empty;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 获取当前客户端ID
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>客户端ID,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        protected string GetCurrentClientId()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return User.FindFirst("client_id")?.Value ?? string.Empty;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,9 +6,9 @@ using FutureMailAPI.DTOs;
 | 
				
			|||||||
namespace FutureMailAPI.Controllers
 | 
					namespace FutureMailAPI.Controllers
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/capsules")]
 | 
					    [Route("api/v1/[controller]")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class CapsulesController : ControllerBase
 | 
					    public class CapsulesController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly ITimeCapsuleService _timeCapsuleService;
 | 
					        private readonly ITimeCapsuleService _timeCapsuleService;
 | 
				
			||||||
        private readonly ILogger<CapsulesController> _logger;
 | 
					        private readonly ILogger<CapsulesController> _logger;
 | 
				
			||||||
@@ -20,17 +20,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetCapsules()
 | 
					        public async Task<IActionResult> GetCapsules()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
 | 
					            var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -41,7 +41,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPut("{capsuleId}/style")]
 | 
					        [HttpPut("{capsuleId}/style")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
 | 
					        public async Task<IActionResult> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -51,12 +51,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
 | 
					            var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId, capsuleId, updateDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -65,17 +65,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            
 | 
					            
 | 
				
			||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        private int? GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return userId;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/[controller]")]
 | 
					    [Route("api/v1/[controller]")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class FileUploadController : ControllerBase
 | 
					    public class FileUploadController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IFileUploadService _fileUploadService;
 | 
					        private readonly IFileUploadService _fileUploadService;
 | 
				
			||||||
        private readonly ILogger<FileUploadController> _logger;
 | 
					        private readonly ILogger<FileUploadController> _logger;
 | 
				
			||||||
@@ -171,19 +171,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从当前请求中获取用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,25 +1,25 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Authorization;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using FutureMailAPI.Services;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using FutureMailAPI.DTOs;
 | 
					using FutureMailAPI.DTOs;
 | 
				
			||||||
using System.Security.Claims;
 | 
					using FutureMailAPI.Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.Controllers
 | 
					namespace FutureMailAPI.Controllers
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/[controller]")]
 | 
					    [Route("api/v1/mails")]
 | 
				
			||||||
    [Authorize]
 | 
					    public class MailsController : BaseController
 | 
				
			||||||
    public class MailsController : ControllerBase
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IMailService _mailService;
 | 
					        private readonly IMailService _mailService;
 | 
				
			||||||
 | 
					        private readonly ILogger<MailsController> _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public MailsController(IMailService mailService)
 | 
					        public MailsController(IMailService mailService, ILogger<MailsController> logger)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _mailService = mailService;
 | 
					            _mailService = mailService;
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost]
 | 
					        [HttpPost]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> CreateMail([FromBody] SentMailCreateDto createDto)
 | 
					        public async Task<IActionResult> CreateMail([FromBody] SentMailCreateDto createDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -29,12 +29,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.CreateMailAsync(currentUserId.Value, createDto);
 | 
					            var result = await _mailService.CreateMailAsync(currentUserId, createDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -47,18 +47,153 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                result);
 | 
					                result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("{mailId}")]
 | 
					        // 兼容前端请求格式的创建邮件接口
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> GetMail(int mailId)
 | 
					        [HttpPost("create")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> CreateMailCompat([FromBody] SentMailCreateCompatDto createDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<SentMailResponseDto>.ErrorResult("输入数据无效"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.GetSentMailByIdAsync(currentUserId.Value, mailId);
 | 
					            // 转换为内部DTO
 | 
				
			||||||
 | 
					            var internalDto = createDto.ToInternalDto();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.CreateMailAsync(currentUserId, internalDto);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return CreatedAtAction(
 | 
				
			||||||
 | 
					                nameof(GetMail), 
 | 
				
			||||||
 | 
					                new { mailId = result.Data!.Id }, 
 | 
				
			||||||
 | 
					                result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 直接接收前端原始格式的创建邮件接口
 | 
				
			||||||
 | 
					        [HttpPost("create-raw")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> CreateMailRaw()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // 读取请求体
 | 
				
			||||||
 | 
					                var request = HttpContext.Request;
 | 
				
			||||||
 | 
					                using var reader = new StreamReader(request.Body);
 | 
				
			||||||
 | 
					                var body = await reader.ReadToEndAsync();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 解析JSON
 | 
				
			||||||
 | 
					                var rawMail = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(body);
 | 
				
			||||||
 | 
					                if (rawMail == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return BadRequest(ApiResponse<SentMailResponseDto>.ErrorResult("请求数据为空"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 创建兼容DTO
 | 
				
			||||||
 | 
					                var compatDto = new SentMailCreateCompatDto();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 解析各个字段
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("title") && rawMail["title"] != null)
 | 
				
			||||||
 | 
					                    compatDto.title = rawMail["title"].ToString() ?? string.Empty;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("content") && rawMail["content"] != null)
 | 
				
			||||||
 | 
					                    compatDto.content = rawMail["content"].ToString() ?? string.Empty;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("recipientType") && rawMail["recipientType"] != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var recipientTypeStr = rawMail["recipientType"].ToString();
 | 
				
			||||||
 | 
					                    if (Enum.TryParse<RecipientTypeEnum>(recipientTypeStr, true, out var recipientType))
 | 
				
			||||||
 | 
					                        compatDto.recipientType = recipientType;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("recipientEmail") && rawMail["recipientEmail"] != null)
 | 
				
			||||||
 | 
					                    compatDto.recipientEmail = rawMail["recipientEmail"].ToString();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("sendTime") && rawMail["sendTime"] != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (DateTime.TryParse(rawMail["sendTime"].ToString(), out var sendTime))
 | 
				
			||||||
 | 
					                        compatDto.sendTime = sendTime;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("triggerType") && rawMail["triggerType"] != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var triggerTypeStr = rawMail["triggerType"].ToString();
 | 
				
			||||||
 | 
					                    if (Enum.TryParse<TriggerTypeEnum>(triggerTypeStr, true, out var triggerType))
 | 
				
			||||||
 | 
					                        compatDto.triggerType = triggerType;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("triggerCondition"))
 | 
				
			||||||
 | 
					                    compatDto.triggerCondition = rawMail["triggerCondition"];
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("attachments"))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    try
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        compatDto.attachments = System.Text.Json.JsonSerializer.Deserialize<List<object>>(rawMail["attachments"].ToString());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        compatDto.attachments = new List<object>();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("isEncrypted"))
 | 
				
			||||||
 | 
					                    compatDto.isEncrypted = bool.Parse(rawMail["isEncrypted"].ToString());
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (rawMail.ContainsKey("capsuleStyle"))
 | 
				
			||||||
 | 
					                    compatDto.capsuleStyle = rawMail["capsuleStyle"].ToString() ?? "default";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
 | 
					                var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (currentUserId <= 0)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 转换为内部DTO
 | 
				
			||||||
 | 
					                var internalDto = compatDto.ToInternalDto();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                var result = await _mailService.CreateMailAsync(currentUserId, internalDto);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (!result.Success)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return BadRequest(result);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                return CreatedAtAction(
 | 
				
			||||||
 | 
					                    nameof(GetMail), 
 | 
				
			||||||
 | 
					                    new { mailId = result.Data!.Id }, 
 | 
				
			||||||
 | 
					                    result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "创建邮件时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, ApiResponse<SentMailResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [HttpGet("{mailId}")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> GetMail(int mailId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.GetSentMailByIdAsync(currentUserId, mailId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -69,23 +204,36 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<PagedResponse<SentMailResponseDto>>>> GetMails([FromQuery] MailListQueryDto queryDto)
 | 
					        public async Task<IActionResult> GetMails([FromQuery] MailListQueryDto queryDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // 从JWT令牌中获取当前用户ID
 | 
					                // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
                var currentUserId = GetCurrentUserId();
 | 
					                var currentUserId = GetCurrentUserId();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            if (currentUserId == null)
 | 
					                if (currentUserId <= 0)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
 | 
					                    return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            var result = await _mailService.GetSentMailsAsync(currentUserId.Value, queryDto);
 | 
					                var result = await _mailService.GetSentMailsAsync(currentUserId, queryDto);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                if (result.Success)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    return Ok(result);
 | 
					                    return Ok(result);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "获取邮件列表时发生错误");
 | 
				
			||||||
 | 
					                return StatusCode(500, ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        [HttpPut("{mailId}")]
 | 
					        [HttpPut("{mailId}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
 | 
					        public async Task<IActionResult> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -95,12 +243,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.UpdateMailAsync(currentUserId.Value, mailId, updateDto);
 | 
					            var result = await _mailService.UpdateMailAsync(currentUserId, mailId, updateDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -111,17 +259,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpDelete("{mailId}")]
 | 
					        [HttpDelete("{mailId}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<bool>>> DeleteMail(int mailId)
 | 
					        public async Task<IActionResult> DeleteMail(int mailId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.DeleteMailAsync(currentUserId.Value, mailId);
 | 
					            var result = await _mailService.DeleteMailAsync(currentUserId, mailId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -132,33 +280,33 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("received")]
 | 
					        [HttpGet("received")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<PagedResponse<ReceivedMailResponseDto>>>> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
 | 
					        public async Task<IActionResult> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<PagedResponse<ReceivedMailResponseDto>>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<PagedResponse<ReceivedMailResponseDto>>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.GetReceivedMailsAsync(currentUserId.Value, queryDto);
 | 
					            var result = await _mailService.GetReceivedMailsAsync(currentUserId, queryDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("received/{id}")]
 | 
					        [HttpGet("received/{id}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<ReceivedMailResponseDto>>> GetReceivedMail(int id)
 | 
					        public async Task<IActionResult> GetReceivedMail(int id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<ReceivedMailResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<ReceivedMailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.GetReceivedMailByIdAsync(currentUserId.Value, id);
 | 
					            var result = await _mailService.GetReceivedMailByIdAsync(currentUserId, id);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -169,17 +317,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("received/{id}/mark-read")]
 | 
					        [HttpPost("received/{id}/mark-read")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<bool>>> MarkReceivedMailAsRead(int id)
 | 
					        public async Task<IActionResult> MarkReceivedMailAsRead(int id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId.Value, id);
 | 
					            var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId, id);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -190,17 +338,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("{mailId}/revoke")]
 | 
					        [HttpPost("{mailId}/revoke")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<bool>>> RevokeMail(int mailId)
 | 
					        public async Task<IActionResult> RevokeMail(int mailId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _mailService.RevokeMailAsync(currentUserId.Value, mailId);
 | 
					            var result = await _mailService.RevokeMailAsync(currentUserId, mailId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -210,16 +358,197 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        private int? GetCurrentUserId()
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 存入胶囊 - 创建胶囊邮件
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="dto">存入胶囊请求</param>
 | 
				
			||||||
 | 
					        /// <returns>操作结果</returns>
 | 
				
			||||||
 | 
					        [HttpPost("capsule")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> SaveToCapsule([FromBody] SaveToCapsuleDto dto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return null;
 | 
					                return BadRequest(ApiResponse<SaveToCapsuleResponseDto>.ErrorResult("请求参数无效"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return userId;
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<SaveToCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.SaveToCapsuleAsync(currentUserId, dto);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return CreatedAtAction(
 | 
				
			||||||
 | 
					                nameof(GetCapsuleMail), 
 | 
				
			||||||
 | 
					                new { id = result.Data!.Id }, 
 | 
				
			||||||
 | 
					                result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 获取胶囊邮件列表
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="pageIndex">页码</param>
 | 
				
			||||||
 | 
					        /// <param name="pageSize">页大小</param>
 | 
				
			||||||
 | 
					        /// <param name="status">状态筛选</param>
 | 
				
			||||||
 | 
					        /// <param name="recipientType">收件人类型筛选</param>
 | 
				
			||||||
 | 
					        /// <param name="keyword">关键词搜索</param>
 | 
				
			||||||
 | 
					        /// <param name="startDate">开始日期</param>
 | 
				
			||||||
 | 
					        /// <param name="endDate">结束日期</param>
 | 
				
			||||||
 | 
					        /// <returns>胶囊邮件列表</returns>
 | 
				
			||||||
 | 
					        [HttpGet("capsule")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> GetCapsuleMails(
 | 
				
			||||||
 | 
					            [FromQuery] int pageIndex = 1,
 | 
				
			||||||
 | 
					            [FromQuery] int pageSize = 10,
 | 
				
			||||||
 | 
					            [FromQuery] int? status = null,
 | 
				
			||||||
 | 
					            [FromQuery] int? recipientType = null,
 | 
				
			||||||
 | 
					            [FromQuery] string? keyword = null,
 | 
				
			||||||
 | 
					            [FromQuery] DateTime? startDate = null,
 | 
				
			||||||
 | 
					            [FromQuery] DateTime? endDate = null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var queryDto = new MailListQueryDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                PageIndex = pageIndex,
 | 
				
			||||||
 | 
					                PageSize = pageSize,
 | 
				
			||||||
 | 
					                Status = status,
 | 
				
			||||||
 | 
					                RecipientType = recipientType,
 | 
				
			||||||
 | 
					                Keyword = keyword,
 | 
				
			||||||
 | 
					                StartDate = startDate,
 | 
				
			||||||
 | 
					                EndDate = endDate
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<PagedResponse<CapsuleMailListResponseDto>>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.GetCapsuleMailsAsync(currentUserId, queryDto);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return Ok(result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 获取胶囊邮件详情
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					        /// <returns>胶囊邮件详情</returns>
 | 
				
			||||||
 | 
					        [HttpGet("capsule/{id}")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> GetCapsuleMail(int id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.GetCapsuleMailByIdAsync(currentUserId, id);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return Ok(result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 更新胶囊邮件
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					        /// <param name="dto">更新请求</param>
 | 
				
			||||||
 | 
					        /// <returns>更新后的胶囊邮件详情</returns>
 | 
				
			||||||
 | 
					        [HttpPut("capsule/{id}")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> UpdateCapsuleMail(int id, [FromBody] UpdateCapsuleMailDto dto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("请求参数无效"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.UpdateCapsuleMailAsync(currentUserId, id, dto);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return Ok(result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 撤销胶囊邮件
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					        /// <returns>操作结果</returns>
 | 
				
			||||||
 | 
					        [HttpPost("capsule/{id}/revoke")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> RevokeCapsuleMail(int id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.RevokeCapsuleMailAsync(currentUserId, id);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return Ok(result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 发送至未来 - 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="sendToFutureDto">发送至未来请求DTO</param>
 | 
				
			||||||
 | 
					        /// <returns>发送至未来响应DTO</returns>
 | 
				
			||||||
 | 
					        [HttpPost("send-to-future")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> SendToFuture([FromBody] SendToFutureDto sendToFutureDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<SendToFutureResponseDto>.ErrorResult("请求参数无效"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Unauthorized(ApiResponse<SendToFutureResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var result = await _mailService.SendToFutureAsync(currentUserId, sendToFutureDto);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!result.Success)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest(result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/notification")]
 | 
					    [Route("api/v1/notification")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class NotificationController : ControllerBase
 | 
					    public class NotificationController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly INotificationService _notificationService;
 | 
					        private readonly INotificationService _notificationService;
 | 
				
			||||||
        private readonly ILogger<NotificationController> _logger;
 | 
					        private readonly ILogger<NotificationController> _logger;
 | 
				
			||||||
@@ -87,19 +87,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<NotificationSettingsDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<NotificationSettingsDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,295 +0,0 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Mvc;
 | 
					 | 
				
			||||||
using FutureMailAPI.Services;
 | 
					 | 
				
			||||||
using FutureMailAPI.DTOs;
 | 
					 | 
				
			||||||
using FutureMailAPI.Models;
 | 
					 | 
				
			||||||
using FutureMailAPI.Extensions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace FutureMailAPI.Controllers
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    [ApiController]
 | 
					 | 
				
			||||||
    [Route("api/v1/oauth")]
 | 
					 | 
				
			||||||
    public class OAuthController : ControllerBase
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IOAuthService _oauthService;
 | 
					 | 
				
			||||||
        private readonly ILogger<OAuthController> _logger;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public OAuthController(IOAuthService oauthService, ILogger<OAuthController> logger)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _oauthService = oauthService;
 | 
					 | 
				
			||||||
            _logger = logger;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// OAuth登录端点
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpPost("login")]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> Login([FromBody] OAuthLoginDto loginDto)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var result = await _oauthService.LoginAsync(loginDto);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (result.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return Ok(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return BadRequest(result);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "OAuth登录时发生错误");
 | 
					 | 
				
			||||||
                return StatusCode(500, new { message = "服务器内部错误" });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 创建OAuth客户端
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpPost("clients")]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> CreateClient([FromBody] OAuthClientCreateDto createDto)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // 从OAuth中间件获取当前用户ID
 | 
					 | 
				
			||||||
                var userId = HttpContext.GetCurrentUserId();
 | 
					 | 
				
			||||||
                if (!userId.HasValue)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return Unauthorized(new { message = "未授权访问" });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                var result = await _oauthService.CreateClientAsync(userId.Value, createDto);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (result.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return Ok(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return BadRequest(result);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "创建OAuth客户端时发生错误");
 | 
					 | 
				
			||||||
                return StatusCode(500, new { message = "服务器内部错误" });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 获取OAuth客户端信息
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpGet("clients/{clientId}")]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> GetClient(string clientId)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var result = await _oauthService.GetClientAsync(clientId);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (result.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return Ok(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return NotFound(result);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "获取OAuth客户端信息时发生错误");
 | 
					 | 
				
			||||||
                return StatusCode(500, new { message = "服务器内部错误" });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// OAuth授权端点
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpGet("authorize")]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> Authorize([FromQuery] OAuthAuthorizationRequestDto request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // 从OAuth中间件获取当前用户ID
 | 
					 | 
				
			||||||
                var userId = HttpContext.GetCurrentUserId();
 | 
					 | 
				
			||||||
                if (!userId.HasValue)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    // 如果用户未登录,重定向到登录页面
 | 
					 | 
				
			||||||
                    var loginRedirectUri = $"/api/v1/auth/login?redirect_uri={Uri.EscapeDataString(request.RedirectUri)}";
 | 
					 | 
				
			||||||
                    if (!string.IsNullOrEmpty(request.State))
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        loginRedirectUri += $"&state={request.State}";
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return Redirect(loginRedirectUri);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                var result = await _oauthService.AuthorizeAsync(userId.Value, request);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (result.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    // 重定向到客户端,携带授权码
 | 
					 | 
				
			||||||
                    var redirectUri = $"{request.RedirectUri}?code={result.Data.Code}";
 | 
					 | 
				
			||||||
                    if (!string.IsNullOrEmpty(request.State))
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        redirectUri += $"&state={request.State}";
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    return Redirect(redirectUri);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // 错误重定向
 | 
					 | 
				
			||||||
                var errorRedirectUri = $"{request.RedirectUri}?error={result.Message}";
 | 
					 | 
				
			||||||
                if (!string.IsNullOrEmpty(request.State))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    errorRedirectUri += $"&state={request.State}";
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return Redirect(errorRedirectUri);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "OAuth授权时发生错误");
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // 错误重定向
 | 
					 | 
				
			||||||
                var errorRedirectUri = $"{request.RedirectUri}?error=server_error";
 | 
					 | 
				
			||||||
                if (!string.IsNullOrEmpty(request.State))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    errorRedirectUri += $"&state={request.State}";
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return Redirect(errorRedirectUri);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// OAuth令牌端点
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpPost("token")]
 | 
					 | 
				
			||||||
        [Microsoft.AspNetCore.Authorization.AllowAnonymous]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> ExchangeToken([FromForm] OAuthTokenRequestDto request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _logger.LogInformation("OAuth令牌端点被调用");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogInformation("OAuth令牌交换请求: GrantType={GrantType}, ClientId={ClientId}, Username={Username}", 
 | 
					 | 
				
			||||||
                    request.GrantType, request.ClientId, request.Username);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (request.GrantType == "authorization_code")
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var result = await _oauthService.ExchangeCodeForTokenAsync(request);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    if (result.Success)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        return Ok(result);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    return BadRequest(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if (request.GrantType == "refresh_token")
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var result = await _oauthService.RefreshTokenAsync(request);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    if (result.Success)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        return Ok(result);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    return BadRequest(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if (request.GrantType == "password")
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    _logger.LogInformation("处理密码授权类型登录请求");
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    // 创建OAuth登录请求
 | 
					 | 
				
			||||||
                    var loginDto = new OAuthLoginDto
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        UsernameOrEmail = request.Username,
 | 
					 | 
				
			||||||
                        Password = request.Password,
 | 
					 | 
				
			||||||
                        ClientId = request.ClientId,
 | 
					 | 
				
			||||||
                        ClientSecret = request.ClientSecret,
 | 
					 | 
				
			||||||
                        Scope = request.Scope
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    var result = await _oauthService.LoginAsync(loginDto);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    if (result.Success)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        _logger.LogInformation("密码授权类型登录成功");
 | 
					 | 
				
			||||||
                        return Ok(result);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    _logger.LogWarning("密码授权类型登录失败: {Message}", result.Message);
 | 
					 | 
				
			||||||
                    return BadRequest(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    _logger.LogWarning("不支持的授权类型: {GrantType}", request.GrantType);
 | 
					 | 
				
			||||||
                    return BadRequest(new { message = "不支持的授权类型" });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "OAuth令牌交换时发生错误");
 | 
					 | 
				
			||||||
                return StatusCode(500, new { message = "服务器内部错误" });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 撤销令牌
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpPost("revoke")]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> RevokeToken([FromForm] string token, [FromForm] string token_type_hint = "access_token")
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var result = await _oauthService.RevokeTokenAsync(token);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (result.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return Ok(new { message = "令牌已撤销" });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return BadRequest(result);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "撤销令牌时发生错误");
 | 
					 | 
				
			||||||
                return StatusCode(500, new { message = "服务器内部错误" });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 验证令牌
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        [HttpPost("introspect")]
 | 
					 | 
				
			||||||
        public async Task<IActionResult> IntrospectToken([FromForm] string token)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var result = await _oauthService.ValidateTokenAsync(token);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (result.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var accessToken = await _oauthService.GetAccessTokenAsync(token);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    if (accessToken != null)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        return Ok(new
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            active = true,
 | 
					 | 
				
			||||||
                            scope = accessToken.Scopes,
 | 
					 | 
				
			||||||
                            client_id = accessToken.Client.ClientId,
 | 
					 | 
				
			||||||
                            username = accessToken.User.Email,
 | 
					 | 
				
			||||||
                            exp = ((DateTimeOffset)accessToken.ExpiresAt).ToUnixTimeSeconds(),
 | 
					 | 
				
			||||||
                            iat = ((DateTimeOffset)accessToken.CreatedAt).ToUnixTimeSeconds()
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                return Ok(new { active = false });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _logger.LogError(ex, "验证令牌时发生错误");
 | 
					 | 
				
			||||||
                return StatusCode(500, new { message = "服务器内部错误" });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/[controller]")]
 | 
					    [Route("api/v1/[controller]")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class PersonalSpaceController : ControllerBase
 | 
					    public class PersonalSpaceController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IPersonalSpaceService _personalSpaceService;
 | 
					        private readonly IPersonalSpaceService _personalSpaceService;
 | 
				
			||||||
        private readonly ILogger<PersonalSpaceController> _logger;
 | 
					        private readonly ILogger<PersonalSpaceController> _logger;
 | 
				
			||||||
@@ -156,19 +156,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从当前请求中获取用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/statistics")]
 | 
					    [Route("api/v1/statistics")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class StatisticsController : ControllerBase
 | 
					    public class StatisticsController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IPersonalSpaceService _personalSpaceService;
 | 
					        private readonly IPersonalSpaceService _personalSpaceService;
 | 
				
			||||||
        private readonly ILogger<StatisticsController> _logger;
 | 
					        private readonly ILogger<StatisticsController> _logger;
 | 
				
			||||||
@@ -50,19 +50,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<StatisticsResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<StatisticsResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/[controller]")]
 | 
					    [Route("api/v1/[controller]")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class TimeCapsulesController : ControllerBase
 | 
					    public class TimeCapsulesController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly ITimeCapsuleService _timeCapsuleService;
 | 
					        private readonly ITimeCapsuleService _timeCapsuleService;
 | 
				
			||||||
        private readonly ILogger<TimeCapsulesController> _logger;
 | 
					        private readonly ILogger<TimeCapsulesController> _logger;
 | 
				
			||||||
@@ -20,7 +20,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost]
 | 
					        [HttpPost]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
 | 
					        public async Task<IActionResult> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -30,12 +30,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId.Value, createDto);
 | 
					            var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId, createDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -49,17 +49,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("{capsuleId}")]
 | 
					        [HttpGet("{capsuleId}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> GetTimeCapsule(int capsuleId)
 | 
					        public async Task<IActionResult> GetTimeCapsule(int capsuleId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId.Value, capsuleId);
 | 
					            var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId, capsuleId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -70,23 +70,23 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
 | 
					        public async Task<IActionResult> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId.Value, queryDto);
 | 
					            var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId, queryDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPut("{capsuleId}")]
 | 
					        [HttpPut("{capsuleId}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
 | 
					        public async Task<IActionResult> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -96,12 +96,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId.Value, capsuleId, updateDto);
 | 
					            var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId, capsuleId, updateDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -112,17 +112,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpDelete("{capsuleId}")]
 | 
					        [HttpDelete("{capsuleId}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<bool>>> DeleteTimeCapsule(int capsuleId)
 | 
					        public async Task<IActionResult> DeleteTimeCapsule(int capsuleId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId.Value, capsuleId);
 | 
					            var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId, capsuleId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -134,7 +134,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("public")]
 | 
					        [HttpGet("public")]
 | 
				
			||||||
        [AllowAnonymous]
 | 
					        [AllowAnonymous]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
 | 
					        public async Task<IActionResult> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = await _timeCapsuleService.GetPublicTimeCapsulesAsync(queryDto);
 | 
					            var result = await _timeCapsuleService.GetPublicTimeCapsulesAsync(queryDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
@@ -142,17 +142,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("public/{capsuleId}/claim")]
 | 
					        [HttpPost("public/{capsuleId}/claim")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> ClaimPublicCapsule(int capsuleId)
 | 
					        public async Task<IActionResult> ClaimPublicCapsule(int capsuleId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId.Value, capsuleId);
 | 
					            var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId, capsuleId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -163,17 +163,17 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("view")]
 | 
					        [HttpGet("view")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetTimeCapsuleView()
 | 
					        public async Task<IActionResult> GetTimeCapsuleView()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
 | 
					            var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -184,7 +184,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPut("{capsuleId}/style")]
 | 
					        [HttpPut("{capsuleId}/style")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
 | 
					        public async Task<IActionResult> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -194,12 +194,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
 | 
					            var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId, capsuleId, updateDto);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!result.Success)
 | 
					            if (!result.Success)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -209,16 +209,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        private int? GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return userId;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/timeline")]
 | 
					    [Route("api/v1/timeline")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class TimelineController : ControllerBase
 | 
					    public class TimelineController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IPersonalSpaceService _personalSpaceService;
 | 
					        private readonly IPersonalSpaceService _personalSpaceService;
 | 
				
			||||||
        private readonly ILogger<TimelineController> _logger;
 | 
					        private readonly ILogger<TimelineController> _logger;
 | 
				
			||||||
@@ -63,19 +63,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<TimelineResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<TimelineResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/upload")]
 | 
					    [Route("api/v1/upload")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class UploadController : ControllerBase
 | 
					    public class UploadController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IFileUploadService _fileUploadService;
 | 
					        private readonly IFileUploadService _fileUploadService;
 | 
				
			||||||
        private readonly ILogger<UploadController> _logger;
 | 
					        private readonly ILogger<UploadController> _logger;
 | 
				
			||||||
@@ -97,19 +97,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/user")]
 | 
					    [Route("api/v1/user")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class UserController : ControllerBase
 | 
					    public class UserController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IPersonalSpaceService _personalSpaceService;
 | 
					        private readonly IPersonalSpaceService _personalSpaceService;
 | 
				
			||||||
        private readonly ILogger<UserController> _logger;
 | 
					        private readonly ILogger<UserController> _logger;
 | 
				
			||||||
@@ -81,19 +81,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
                return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
 | 
					                return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        private int GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    [ApiController]
 | 
					    [ApiController]
 | 
				
			||||||
    [Route("api/v1/users")]
 | 
					    [Route("api/v1/users")]
 | 
				
			||||||
    [Authorize]
 | 
					    
 | 
				
			||||||
    public class UsersController : ControllerBase
 | 
					    public class UsersController : BaseController
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IUserService _userService;
 | 
					        private readonly IUserService _userService;
 | 
				
			||||||
        private readonly ILogger<UsersController> _logger;
 | 
					        private readonly ILogger<UsersController> _logger;
 | 
				
			||||||
@@ -20,12 +20,12 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpGet("{id}")]
 | 
					        [HttpGet("{id}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<UserResponseDto>>> GetUser(int id)
 | 
					        public async Task<IActionResult> GetUser(int id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -47,7 +47,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPut("{id}")]
 | 
					        [HttpPut("{id}")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<UserResponseDto>>> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
 | 
					        public async Task<IActionResult> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -57,7 +57,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -79,7 +79,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [HttpPost("{id}/change-password")]
 | 
					        [HttpPost("{id}/change-password")]
 | 
				
			||||||
        public async Task<ActionResult<ApiResponse<bool>>> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
 | 
					        public async Task<IActionResult> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -89,7 +89,7 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            // 从JWT令牌中获取当前用户ID
 | 
					            // 从JWT令牌中获取当前用户ID
 | 
				
			||||||
            var currentUserId = GetCurrentUserId();
 | 
					            var currentUserId = GetCurrentUserId();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (currentUserId == null)
 | 
					            if (currentUserId <= 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
					                return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -109,17 +109,5 @@ namespace FutureMailAPI.Controllers
 | 
				
			|||||||
            
 | 
					            
 | 
				
			||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        private int? GetCurrentUserId()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return userId;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -52,9 +52,9 @@ namespace FutureMailAPI.DTOs
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    public class AuthResponseDto
 | 
					    public class AuthResponseDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public string Token { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string? RefreshToken { get; set; }
 | 
					 | 
				
			||||||
        public DateTime Expires { get; set; }
 | 
					 | 
				
			||||||
        public UserResponseDto User { get; set; } = new();
 | 
					        public UserResponseDto User { get; set; } = new();
 | 
				
			||||||
 | 
					        public string Token { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string RefreshToken { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public int ExpiresIn { get; set; } = 3600; // 默认1小时过期
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations;
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.DTOs
 | 
					namespace FutureMailAPI.DTOs
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -44,6 +45,82 @@ namespace FutureMailAPI.DTOs
 | 
				
			|||||||
        public string? Theme { get; set; }
 | 
					        public string? Theme { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    // 兼容前端请求格式的DTO
 | 
				
			||||||
 | 
					    public class SentMailCreateCompatDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "标题是必填项")]
 | 
				
			||||||
 | 
					        [StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
 | 
				
			||||||
 | 
					        public string title { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "内容是必填项")]
 | 
				
			||||||
 | 
					        public string content { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "收件人类型是必填项")]
 | 
				
			||||||
 | 
					        [JsonConverter(typeof(JsonStringEnumConverter))]
 | 
				
			||||||
 | 
					        public RecipientTypeEnum recipientType { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public string? recipientEmail { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "投递时间是必填项")]
 | 
				
			||||||
 | 
					        public DateTime sendTime { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "触发条件类型是必填项")]
 | 
				
			||||||
 | 
					        [JsonConverter(typeof(JsonStringEnumConverter))]
 | 
				
			||||||
 | 
					        public TriggerTypeEnum triggerType { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public object? triggerCondition { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public List<object>? attachments { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public bool isEncrypted { get; set; } = false;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public string capsuleStyle { get; set; } = "default";
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 转换为内部DTO
 | 
				
			||||||
 | 
					        public SentMailCreateDto ToInternalDto()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dto = new SentMailCreateDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Title = this.title,
 | 
				
			||||||
 | 
					                Content = this.content,
 | 
				
			||||||
 | 
					                RecipientType = (int)this.recipientType,
 | 
				
			||||||
 | 
					                RecipientEmail = this.recipientEmail,
 | 
				
			||||||
 | 
					                DeliveryTime = this.sendTime,
 | 
				
			||||||
 | 
					                TriggerType = (int)this.triggerType,
 | 
				
			||||||
 | 
					                IsEncrypted = this.isEncrypted,
 | 
				
			||||||
 | 
					                Theme = this.capsuleStyle
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 处理触发条件
 | 
				
			||||||
 | 
					            if (this.triggerCondition != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                dto.TriggerDetails = System.Text.Json.JsonSerializer.Serialize(this.triggerCondition);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 处理附件
 | 
				
			||||||
 | 
					            if (this.attachments != null && this.attachments.Count > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                dto.Attachments = System.Text.Json.JsonSerializer.Serialize(this.attachments);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return dto;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public enum RecipientTypeEnum
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        SELF = 0,
 | 
				
			||||||
 | 
					        SPECIFIC = 1,
 | 
				
			||||||
 | 
					        PUBLIC = 2
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public enum TriggerTypeEnum
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        TIME = 0,
 | 
				
			||||||
 | 
					        LOCATION = 1,
 | 
				
			||||||
 | 
					        EVENT = 2
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    public class SentMailUpdateDto
 | 
					    public class SentMailUpdateDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
 | 
					        [StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
 | 
				
			||||||
@@ -112,4 +189,130 @@ namespace FutureMailAPI.DTOs
 | 
				
			|||||||
        public DateTime? StartDate { get; set; }
 | 
					        public DateTime? StartDate { get; set; }
 | 
				
			||||||
        public DateTime? EndDate { get; set; }
 | 
					        public DateTime? EndDate { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 存入胶囊请求DTO
 | 
				
			||||||
 | 
					    public class SaveToCapsuleDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "标题是必填项")]
 | 
				
			||||||
 | 
					        [StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
 | 
				
			||||||
 | 
					        public string Title { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "内容是必填项")]
 | 
				
			||||||
 | 
					        public string Content { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "收件人类型是必填项")]
 | 
				
			||||||
 | 
					        [JsonConverter(typeof(JsonStringEnumConverter))]
 | 
				
			||||||
 | 
					        public RecipientTypeEnum RecipientType { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public string? RecipientEmail { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime? SendTime { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "触发条件类型是必填项")]
 | 
				
			||||||
 | 
					        [JsonConverter(typeof(JsonStringEnumConverter))]
 | 
				
			||||||
 | 
					        public TriggerTypeEnum TriggerType { get; set; } = TriggerTypeEnum.TIME;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public object? TriggerCondition { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public List<object>? Attachments { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public bool IsEncrypted { get; set; } = false;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "胶囊样式是必填项")]
 | 
				
			||||||
 | 
					        public string CapsuleStyle { get; set; } = "default";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 存入胶囊响应DTO
 | 
				
			||||||
 | 
					    public class SaveToCapsuleResponseDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public int Id { get; set; }
 | 
				
			||||||
 | 
					        public string MailId { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string CapsuleId { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Status { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public DateTime CreatedAt { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 胶囊邮件列表响应DTO
 | 
				
			||||||
 | 
					    public class CapsuleMailListResponseDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string MailId { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Title { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public UserInfoDto Sender { get; set; } = new();
 | 
				
			||||||
 | 
					        public UserInfoDto Recipient { get; set; } = new();
 | 
				
			||||||
 | 
					        public DateTime SendTime { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? DeliveryTime { get; set; }
 | 
				
			||||||
 | 
					        public string Status { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public bool HasAttachments { get; set; }
 | 
				
			||||||
 | 
					        public bool IsEncrypted { get; set; }
 | 
				
			||||||
 | 
					        public string CapsuleStyle { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public int? Countdown { get; set; } // 倒计时秒数(仅status=PENDING时返回)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 胶囊邮件详情响应DTO
 | 
				
			||||||
 | 
					    public class CapsuleMailDetailResponseDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string MailId { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Title { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Content { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public UserInfoDto Sender { get; set; } = new();
 | 
				
			||||||
 | 
					        public UserInfoDto Recipient { get; set; } = new();
 | 
				
			||||||
 | 
					        public DateTime SendTime { get; set; }
 | 
				
			||||||
 | 
					        public DateTime CreatedAt { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? DeliveryTime { get; set; }
 | 
				
			||||||
 | 
					        public string Status { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string TriggerType { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public object? TriggerCondition { get; set; }
 | 
				
			||||||
 | 
					        public List<AttachmentDto> Attachments { get; set; } = new();
 | 
				
			||||||
 | 
					        public bool IsEncrypted { get; set; }
 | 
				
			||||||
 | 
					        public string CapsuleStyle { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public bool CanEdit { get; set; } // 是否可编辑(仅草稿状态)
 | 
				
			||||||
 | 
					        public bool CanRevoke { get; set; } // 是否可撤销(仅待投递状态)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 附件DTO
 | 
				
			||||||
 | 
					    public class AttachmentDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string Id { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Type { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Url { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string? Thumbnail { get; set; }
 | 
				
			||||||
 | 
					        public long Size { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 更新胶囊邮件DTO
 | 
				
			||||||
 | 
					    public class UpdateCapsuleMailDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string? Title { get; set; }
 | 
				
			||||||
 | 
					        public string? Content { get; set; }
 | 
				
			||||||
 | 
					        public RecipientTypeEnum? RecipientType { get; set; }
 | 
				
			||||||
 | 
					        public string? RecipientEmail { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? SendTime { get; set; }
 | 
				
			||||||
 | 
					        public TriggerTypeEnum? TriggerType { get; set; }
 | 
				
			||||||
 | 
					        public object? TriggerCondition { get; set; }
 | 
				
			||||||
 | 
					        public List<object>? Attachments { get; set; }
 | 
				
			||||||
 | 
					        public bool? IsEncrypted { get; set; }
 | 
				
			||||||
 | 
					        public string? CapsuleStyle { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 发送至未来请求DTO
 | 
				
			||||||
 | 
					    public class SendToFutureDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "邮件ID是必填项")]
 | 
				
			||||||
 | 
					        public int MailId { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required(ErrorMessage = "投递时间是必填项")]
 | 
				
			||||||
 | 
					        public DateTime DeliveryTime { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 发送至未来响应DTO
 | 
				
			||||||
 | 
					    public class SendToFutureResponseDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public int MailId { get; set; }
 | 
				
			||||||
 | 
					        public string Title { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public DateTime DeliveryTime { get; set; }
 | 
				
			||||||
 | 
					        public string Status { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public DateTime SentAt { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,104 +2,42 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.DTOs
 | 
					namespace FutureMailAPI.DTOs
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class OAuthAuthorizationRequestDto
 | 
					    public class OAuthLoginRequestDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Required]
 | 
					        [Required(ErrorMessage = "客户端ID是必填项")]
 | 
				
			||||||
        public string ResponseType { get; set; } = "code"; // code for authorization code flow
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string ClientId { get; set; } = string.Empty;
 | 
					        public string ClientId { get; set; } = string.Empty;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Required]
 | 
					        [Required(ErrorMessage = "客户端密钥是必填项")]
 | 
				
			||||||
        public string RedirectUri { get; set; } = string.Empty;
 | 
					        public string ClientSecret { get; set; } = string.Empty;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public string Scope { get; set; } = "read write"; // Default scopes
 | 
					        [Required(ErrorMessage = "用户名或邮箱是必填项")]
 | 
				
			||||||
 | 
					        public string Username { get; set; } = string.Empty;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public string State { get; set; } = string.Empty; // CSRF protection
 | 
					        [Required(ErrorMessage = "密码是必填项")]
 | 
				
			||||||
    }
 | 
					        public string Password { get; set; } = string.Empty;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    public class OAuthTokenRequestDto
 | 
					        public string? Scope { get; set; }
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string GrantType { get; set; } = string.Empty; // authorization_code, refresh_token, client_credentials, password
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string Code { get; set; } = string.Empty; // For authorization_code grant
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string RefreshToken { get; set; } = string.Empty; // For refresh_token grant
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string Username { get; set; } = string.Empty; // For password grant
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string Password { get; set; } = string.Empty; // For password grant
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string ClientId { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string RedirectUri { get; set; } = string.Empty; // Required for authorization_code grant
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string Scope { get; set; } = string.Empty; // Optional, defaults to requested scopes
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public class OAuthTokenResponseDto
 | 
					    public class OAuthTokenResponseDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public string AccessToken { get; set; } = string.Empty;
 | 
					        public string AccessToken { get; set; } = string.Empty;
 | 
				
			||||||
        public string TokenType { get; set; } = "Bearer";
 | 
					 | 
				
			||||||
        public int ExpiresIn { get; set; } // Seconds until expiration
 | 
					 | 
				
			||||||
        public string RefreshToken { get; set; } = string.Empty;
 | 
					        public string RefreshToken { get; set; } = string.Empty;
 | 
				
			||||||
        public string Scope { get; set; } = string.Empty;
 | 
					        public string TokenType { get; set; } = "Bearer";
 | 
				
			||||||
 | 
					        public int ExpiresIn { get; set; }
 | 
				
			||||||
 | 
					        public string? Scope { get; set; }
 | 
				
			||||||
 | 
					        public UserResponseDto User { get; set; } = new();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public class OAuthAuthorizationResponseDto
 | 
					    public class OAuthRefreshTokenRequestDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public string Code { get; set; } = string.Empty;
 | 
					        [Required(ErrorMessage = "刷新令牌是必填项")]
 | 
				
			||||||
        public string State { get; set; } = string.Empty;
 | 
					        public string RefreshToken { get; set; } = string.Empty;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    public class OAuthClientDto
 | 
					        [Required(ErrorMessage = "客户端ID是必填项")]
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public int Id { get; set; }
 | 
					 | 
				
			||||||
        public string ClientId { get; set; } = string.Empty;
 | 
					        public string ClientId { get; set; } = string.Empty;
 | 
				
			||||||
        public string Name { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string[] RedirectUris { get; set; } = Array.Empty<string>();
 | 
					 | 
				
			||||||
        public string[] Scopes { get; set; } = Array.Empty<string>();
 | 
					 | 
				
			||||||
        public bool IsActive { get; set; }
 | 
					 | 
				
			||||||
        public DateTime CreatedAt { get; set; }
 | 
					 | 
				
			||||||
        public DateTime UpdatedAt { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    public class OAuthClientCreateDto
 | 
					        [Required(ErrorMessage = "客户端密钥是必填项")]
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        [StringLength(100)]
 | 
					 | 
				
			||||||
        public string Name { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string[] RedirectUris { get; set; } = Array.Empty<string>();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string[] Scopes { get; set; } = Array.Empty<string>();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public class OAuthClientSecretDto
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public string ClientId { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string ClientSecret { get; set; } = string.Empty;
 | 
					        public string ClientSecret { get; set; } = string.Empty;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public class OAuthLoginDto
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string UsernameOrEmail { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string Password { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        [Required]
 | 
					 | 
				
			||||||
        public string ClientId { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string Scope { get; set; } = "read write"; // Default scopes
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -60,6 +60,7 @@ namespace FutureMailAPI.DTOs
 | 
				
			|||||||
        public int UserId { get; set; }
 | 
					        public int UserId { get; set; }
 | 
				
			||||||
        public string Username { get; set; } = string.Empty;
 | 
					        public string Username { get; set; } = string.Empty;
 | 
				
			||||||
        public string? Avatar { get; set; }
 | 
					        public string? Avatar { get; set; }
 | 
				
			||||||
 | 
					        public string Email { get; set; } = string.Empty;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public class SubscriptionResponseDto
 | 
					    public class SubscriptionResponseDto
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,9 +50,5 @@ namespace FutureMailAPI.DTOs
 | 
				
			|||||||
        public string NewPassword { get; set; } = string.Empty;
 | 
					        public string NewPassword { get; set; } = string.Empty;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public class RefreshTokenRequestDto
 | 
					
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [Required(ErrorMessage = "令牌是必填项")]
 | 
					 | 
				
			||||||
        public string Token { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -14,9 +14,7 @@ namespace FutureMailAPI.Data
 | 
				
			|||||||
        public DbSet<ReceivedMail> ReceivedMails { get; set; }
 | 
					        public DbSet<ReceivedMail> ReceivedMails { get; set; }
 | 
				
			||||||
        public DbSet<TimeCapsule> TimeCapsules { get; set; }
 | 
					        public DbSet<TimeCapsule> TimeCapsules { get; set; }
 | 
				
			||||||
        public DbSet<OAuthClient> OAuthClients { get; set; }
 | 
					        public DbSet<OAuthClient> OAuthClients { get; set; }
 | 
				
			||||||
        public DbSet<OAuthAuthorizationCode> OAuthAuthorizationCodes { get; set; }
 | 
					        public DbSet<OAuthToken> OAuthTokens { get; set; }
 | 
				
			||||||
        public DbSet<OAuthAccessToken> OAuthAccessTokens { get; set; }
 | 
					 | 
				
			||||||
        public DbSet<OAuthRefreshToken> OAuthRefreshTokens { get; set; }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        protected override void OnModelCreating(ModelBuilder modelBuilder)
 | 
					        protected override void OnModelCreating(ModelBuilder modelBuilder)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -38,7 +36,7 @@ namespace FutureMailAPI.Data
 | 
				
			|||||||
                    .HasForeignKey(e => e.SenderId)
 | 
					                    .HasForeignKey(e => e.SenderId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Restrict);
 | 
					                    .OnDelete(DeleteBehavior.Restrict);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                entity.HasOne<User>()
 | 
					                entity.HasOne(e => e.Recipient)
 | 
				
			||||||
                    .WithMany()
 | 
					                    .WithMany()
 | 
				
			||||||
                    .HasForeignKey(e => e.RecipientId)
 | 
					                    .HasForeignKey(e => e.RecipientId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.SetNull);
 | 
					                    .OnDelete(DeleteBehavior.SetNull);
 | 
				
			||||||
@@ -54,8 +52,8 @@ namespace FutureMailAPI.Data
 | 
				
			|||||||
                    .HasForeignKey(e => e.SentMailId)
 | 
					                    .HasForeignKey(e => e.SentMailId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					                    .OnDelete(DeleteBehavior.Cascade);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                entity.HasOne<User>()
 | 
					                entity.HasOne(e => e.Recipient)
 | 
				
			||||||
                    .WithMany()
 | 
					                    .WithMany(u => u.ReceivedMails)
 | 
				
			||||||
                    .HasForeignKey(e => e.RecipientId)
 | 
					                    .HasForeignKey(e => e.RecipientId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					                    .OnDelete(DeleteBehavior.Cascade);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
@@ -70,9 +68,10 @@ namespace FutureMailAPI.Data
 | 
				
			|||||||
                    .HasForeignKey(e => e.UserId)
 | 
					                    .HasForeignKey(e => e.UserId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					                    .OnDelete(DeleteBehavior.Cascade);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                // 一对一关系配置
 | 
				
			||||||
                entity.HasOne<SentMail>()
 | 
					                entity.HasOne<SentMail>()
 | 
				
			||||||
                    .WithMany()
 | 
					                    .WithOne(m => m.TimeCapsule)
 | 
				
			||||||
                    .HasForeignKey(e => e.SentMailId)
 | 
					                    .HasForeignKey<TimeCapsule>(e => e.SentMailId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					                    .OnDelete(DeleteBehavior.Cascade);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					                entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
				
			||||||
@@ -86,51 +85,21 @@ namespace FutureMailAPI.Data
 | 
				
			|||||||
                entity.Property(e => e.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					                entity.Property(e => e.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 配置OAuthAuthorizationCode实体
 | 
					            // 配置OAuthToken实体
 | 
				
			||||||
            modelBuilder.Entity<OAuthAuthorizationCode>(entity =>
 | 
					            modelBuilder.Entity<OAuthToken>(entity =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                entity.HasOne(e => e.Client)
 | 
					 | 
				
			||||||
                    .WithMany()
 | 
					 | 
				
			||||||
                    .HasForeignKey(e => e.ClientId)
 | 
					 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                entity.HasOne(e => e.User)
 | 
					                entity.HasOne(e => e.User)
 | 
				
			||||||
                    .WithMany()
 | 
					                    .WithMany()
 | 
				
			||||||
                    .HasForeignKey(e => e.UserId)
 | 
					                    .HasForeignKey(e => e.UserId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					                    .OnDelete(DeleteBehavior.Cascade);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 配置OAuthAccessToken实体
 | 
					 | 
				
			||||||
            modelBuilder.Entity<OAuthAccessToken>(entity =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                entity.HasOne(e => e.Client)
 | 
					                entity.HasOne(e => e.Client)
 | 
				
			||||||
                    .WithMany()
 | 
					                    .WithMany(c => c.Tokens)
 | 
				
			||||||
                    .HasForeignKey(e => e.ClientId)
 | 
					                    .HasForeignKey(e => e.ClientId)
 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					                    .OnDelete(DeleteBehavior.Cascade);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                entity.HasOne(e => e.User)
 | 
					                entity.HasIndex(e => e.AccessToken).IsUnique();
 | 
				
			||||||
                    .WithMany()
 | 
					                entity.HasIndex(e => e.RefreshToken).IsUnique();
 | 
				
			||||||
                    .HasForeignKey(e => e.UserId)
 | 
					 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 配置OAuthRefreshToken实体
 | 
					 | 
				
			||||||
            modelBuilder.Entity<OAuthRefreshToken>(entity =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                entity.HasOne(e => e.Client)
 | 
					 | 
				
			||||||
                    .WithMany()
 | 
					 | 
				
			||||||
                    .HasForeignKey(e => e.ClientId)
 | 
					 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                entity.HasOne(e => e.User)
 | 
					 | 
				
			||||||
                    .WithMany()
 | 
					 | 
				
			||||||
                    .HasForeignKey(e => e.UserId)
 | 
					 | 
				
			||||||
                    .OnDelete(DeleteBehavior.Cascade);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					                entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,46 +1,23 @@
 | 
				
			|||||||
using FutureMailAPI.Models;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.Extensions
 | 
					namespace FutureMailAPI.Extensions
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static class HttpContextExtensions
 | 
					    public static class HttpContextExtensions
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// 获取当前用户ID
 | 
					        /// 获取当前用户ID(简化版本,不再依赖token)
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public static int? GetCurrentUserId(this HttpContext context)
 | 
					        public static int? GetCurrentUserId(this HttpContext context)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (context.Items.TryGetValue("UserId", out var userIdObj) && userIdObj is int userId)
 | 
					            // 简化实现:从查询参数或表单数据中获取用户ID
 | 
				
			||||||
 | 
					            // 在实际应用中,这里应该使用会话或其他认证机制
 | 
				
			||||||
 | 
					            if (context.Request.Query.TryGetValue("userId", out var userIdStr) && 
 | 
				
			||||||
 | 
					                int.TryParse(userIdStr, out var userId))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return userId;
 | 
					                return userId;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 获取当前用户邮箱
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        public static string? GetCurrentUserEmail(this HttpContext context)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (context.Items.TryGetValue("UserEmail", out var userEmailObj) && userEmailObj is string userEmail)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return userEmail;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// 获取当前访问令牌
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        public static OAuthAccessToken? GetCurrentAccessToken(this HttpContext context)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (context.Items.TryGetValue("AccessToken", out var accessTokenObj) && accessTokenObj is OAuthAccessToken accessToken)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return accessToken;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										138
									
								
								FutureMailAPI/Filters/OAuthAuthenticationFilter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								FutureMailAPI/Filters/OAuthAuthenticationFilter.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					using FutureMailAPI.Services;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc.Filters;
 | 
				
			||||||
 | 
					using System.IdentityModel.Tokens.Jwt;
 | 
				
			||||||
 | 
					using System.Security.Claims;
 | 
				
			||||||
 | 
					using Microsoft.IdentityModel.Tokens;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Configuration;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using FutureMailAPI.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace FutureMailAPI.Filters
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class OAuthAuthenticationFilter : IAsyncActionFilter
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IOAuthService _oauthService;
 | 
				
			||||||
 | 
					        private readonly ILogger<OAuthAuthenticationFilter> _logger;
 | 
				
			||||||
 | 
					        private readonly IConfiguration _configuration;
 | 
				
			||||||
 | 
					        private readonly FutureMailDbContext _context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public OAuthAuthenticationFilter(IOAuthService oauthService, ILogger<OAuthAuthenticationFilter> logger, IConfiguration configuration, FutureMailDbContext context)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _oauthService = oauthService;
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _configuration = configuration;
 | 
				
			||||||
 | 
					            _context = context;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // 跳过带有AllowAnonymous特性的操作
 | 
				
			||||||
 | 
					            var endpoint = context.HttpContext.GetEndpoint();
 | 
				
			||||||
 | 
					            if (endpoint?.Metadata?.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>() != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await next();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 从Authorization头获取令牌
 | 
				
			||||||
 | 
					            var authHeader = context.HttpContext.Request.Headers.Authorization.FirstOrDefault();
 | 
				
			||||||
 | 
					            if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                context.Result = new UnauthorizedObjectResult(new { error = "缺少授权令牌" });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var token = authHeader.Substring("Bearer ".Length).Trim();
 | 
				
			||||||
 | 
					            _logger.LogInformation("正在验证令牌: {Token}", token.Substring(0, Math.Min(50, token.Length)) + "...");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // 首先尝试验证JWT令牌
 | 
				
			||||||
 | 
					                var tokenHandler = new JwtSecurityTokenHandler();
 | 
				
			||||||
 | 
					                var jwtSettings = _configuration.GetSection("Jwt");
 | 
				
			||||||
 | 
					                var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                var validationParameters = new TokenValidationParameters
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ValidateIssuerSigningKey = true,
 | 
				
			||||||
 | 
					                    IssuerSigningKey = new SymmetricSecurityKey(key),
 | 
				
			||||||
 | 
					                    ValidateIssuer = true,
 | 
				
			||||||
 | 
					                    ValidIssuer = jwtSettings["Issuer"],
 | 
				
			||||||
 | 
					                    ValidateAudience = true,
 | 
				
			||||||
 | 
					                    ValidAudience = jwtSettings["Audience"],
 | 
				
			||||||
 | 
					                    ValidateLifetime = true,
 | 
				
			||||||
 | 
					                    ClockSkew = TimeSpan.Zero
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 验证JWT令牌
 | 
				
			||||||
 | 
					                var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 检查令牌类型
 | 
				
			||||||
 | 
					                var tokenType = principal.FindFirst("token_type")?.Value;
 | 
				
			||||||
 | 
					                _logger.LogInformation("令牌类型: {TokenType}", tokenType ?? "普通用户令牌");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (tokenType == "oauth")
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // OAuth令牌,检查数据库中是否存在
 | 
				
			||||||
 | 
					                    var oauthToken = await _oauthService.GetTokenAsync(token);
 | 
				
			||||||
 | 
					                    if (oauthToken == null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("OAuth令牌不存在或已过期");
 | 
				
			||||||
 | 
					                        context.Result = new UnauthorizedObjectResult(new { error = "OAuth令牌不存在或已过期" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // 普通用户令牌,检查用户表中是否有此用户
 | 
				
			||||||
 | 
					                    var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
 | 
				
			||||||
 | 
					                    _logger.LogInformation("用户ID: {UserId}", userId);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (string.IsNullOrEmpty(userId) || !int.TryParse(userId, out int uid))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("令牌中未包含有效的用户ID");
 | 
				
			||||||
 | 
					                        context.Result = new UnauthorizedObjectResult(new { error = "令牌无效" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 验证用户是否存在
 | 
				
			||||||
 | 
					                    var user = await _context.Users.FindAsync(uid);
 | 
				
			||||||
 | 
					                    if (user == null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("用户ID {UserId} 不存在", uid);
 | 
				
			||||||
 | 
					                        context.Result = new UnauthorizedObjectResult(new { error = "用户不存在" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    _logger.LogInformation("用户验证成功: {UserId}", uid);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 设置用户信息
 | 
				
			||||||
 | 
					                context.HttpContext.User = principal;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                await next();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (SecurityTokenExpiredException)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("令牌已过期");
 | 
				
			||||||
 | 
					                context.Result = new UnauthorizedObjectResult(new { error = "令牌已过期" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (SecurityTokenInvalidSignatureException)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("令牌签名无效");
 | 
				
			||||||
 | 
					                context.Result = new UnauthorizedObjectResult(new { error = "令牌签名无效" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (SecurityTokenException ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning(ex, "令牌验证失败");
 | 
				
			||||||
 | 
					                context.Result = new UnauthorizedObjectResult(new { error = "令牌验证失败" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "OAuth认证时发生错误");
 | 
				
			||||||
 | 
					                context.Result = new UnauthorizedObjectResult(new { error = "令牌验证失败" });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -6,9 +6,14 @@
 | 
				
			|||||||
    <ImplicitUsings>enable</ImplicitUsings>
 | 
					    <ImplicitUsings>enable</ImplicitUsings>
 | 
				
			||||||
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
					    <GenerateDocumentationFile>true</GenerateDocumentationFile>
 | 
				
			||||||
    <NoWarn>$(NoWarn);1591</NoWarn>
 | 
					    <NoWarn>$(NoWarn);1591</NoWarn>
 | 
				
			||||||
 | 
					    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
				
			||||||
 | 
					    <GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
 | 
				
			||||||
  </PropertyGroup>
 | 
					  </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
 | 
				
			||||||
@@ -20,11 +25,13 @@
 | 
				
			|||||||
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
					      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
				
			||||||
      <PrivateAssets>all</PrivateAssets>
 | 
					      <PrivateAssets>all</PrivateAssets>
 | 
				
			||||||
    </PackageReference>
 | 
					    </PackageReference>
 | 
				
			||||||
 | 
					    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
 | 
				
			||||||
    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
 | 
					    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
 | 
				
			||||||
    <PackageReference Include="Quartz" Version="3.15.0" />
 | 
					    <PackageReference Include="Quartz" Version="3.15.0" />
 | 
				
			||||||
    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.0" />
 | 
					    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.0" />
 | 
				
			||||||
    <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
 | 
					    <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
 | 
				
			||||||
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
 | 
					    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,97 +0,0 @@
 | 
				
			|||||||
using Microsoft.IdentityModel.Tokens;
 | 
					 | 
				
			||||||
using System.IdentityModel.Tokens.Jwt;
 | 
					 | 
				
			||||||
using System.Security.Claims;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using FutureMailAPI.Models;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace FutureMailAPI.Helpers
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public interface IJwtHelper
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        string GenerateToken(User user);
 | 
					 | 
				
			||||||
        string GenerateToken(int userId, string username, string email);
 | 
					 | 
				
			||||||
        ClaimsPrincipal? ValidateToken(string token);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public class JwtHelper : IJwtHelper
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IConfiguration _configuration;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public JwtHelper(IConfiguration configuration)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _configuration = configuration;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string GenerateToken(User user)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var tokenHandler = new JwtSecurityTokenHandler();
 | 
					 | 
				
			||||||
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
 | 
					 | 
				
			||||||
            var tokenDescriptor = new SecurityTokenDescriptor
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Subject = new ClaimsIdentity(new[]
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
 | 
					 | 
				
			||||||
                    new Claim(ClaimTypes.Name, user.Username),
 | 
					 | 
				
			||||||
                    new Claim(ClaimTypes.Email, user.Email)
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                Expires = DateTime.UtcNow.AddDays(7),
 | 
					 | 
				
			||||||
                Issuer = _configuration["Jwt:Issuer"],
 | 
					 | 
				
			||||||
                Audience = _configuration["Jwt:Audience"],
 | 
					 | 
				
			||||||
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var token = tokenHandler.CreateToken(tokenDescriptor);
 | 
					 | 
				
			||||||
            return tokenHandler.WriteToken(token);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public string GenerateToken(int userId, string username, string email)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var tokenHandler = new JwtSecurityTokenHandler();
 | 
					 | 
				
			||||||
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
 | 
					 | 
				
			||||||
            var tokenDescriptor = new SecurityTokenDescriptor
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Subject = new ClaimsIdentity(new[]
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
 | 
					 | 
				
			||||||
                    new Claim(ClaimTypes.Name, username),
 | 
					 | 
				
			||||||
                    new Claim(ClaimTypes.Email, email)
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
                Expires = DateTime.UtcNow.AddDays(7),
 | 
					 | 
				
			||||||
                Issuer = _configuration["Jwt:Issuer"],
 | 
					 | 
				
			||||||
                Audience = _configuration["Jwt:Audience"],
 | 
					 | 
				
			||||||
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var token = tokenHandler.CreateToken(tokenDescriptor);
 | 
					 | 
				
			||||||
            return tokenHandler.WriteToken(token);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public ClaimsPrincipal? ValidateToken(string token)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var tokenHandler = new JwtSecurityTokenHandler();
 | 
					 | 
				
			||||||
                var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                var validationParameters = new TokenValidationParameters
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ValidateIssuerSigningKey = true,
 | 
					 | 
				
			||||||
                    IssuerSigningKey = new SymmetricSecurityKey(key),
 | 
					 | 
				
			||||||
                    ValidateIssuer = true,
 | 
					 | 
				
			||||||
                    ValidIssuer = _configuration["Jwt:Issuer"],
 | 
					 | 
				
			||||||
                    ValidateAudience = true,
 | 
					 | 
				
			||||||
                    ValidAudience = _configuration["Jwt:Audience"],
 | 
					 | 
				
			||||||
                    ValidateLifetime = true,
 | 
					 | 
				
			||||||
                    ClockSkew = TimeSpan.Zero
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
 | 
					 | 
				
			||||||
                return principal;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -7,6 +7,7 @@ namespace FutureMailAPI.Helpers
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        string HashPassword(string password);
 | 
					        string HashPassword(string password);
 | 
				
			||||||
        bool VerifyPassword(string password, string hash);
 | 
					        bool VerifyPassword(string password, string hash);
 | 
				
			||||||
 | 
					        bool VerifyPassword(string password, string hash, string salt);
 | 
				
			||||||
        string HashPassword(string password, string salt);
 | 
					        string HashPassword(string password, string salt);
 | 
				
			||||||
        string GenerateSalt();
 | 
					        string GenerateSalt();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -59,8 +60,12 @@ namespace FutureMailAPI.Helpers
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // 从存储的哈希中提取盐值
 | 
					                // 检查哈希长度,判断是完整哈希(36字节)还是纯哈希(20字节)
 | 
				
			||||||
                byte[] hashBytes = Convert.FromBase64String(hash);
 | 
					                byte[] hashBytes = Convert.FromBase64String(hash);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (hashBytes.Length == 36)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // 完整哈希格式:盐值(16字节) + 哈希值(20字节)
 | 
				
			||||||
                    byte[] salt = new byte[16];
 | 
					                    byte[] salt = new byte[16];
 | 
				
			||||||
                    Array.Copy(hashBytes, 0, salt, 0, 16);
 | 
					                    Array.Copy(hashBytes, 0, salt, 0, 16);
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
@@ -77,6 +82,34 @@ namespace FutureMailAPI.Helpers
 | 
				
			|||||||
                    
 | 
					                    
 | 
				
			||||||
                    return true;
 | 
					                    return true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                else if (hashBytes.Length == 20)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // 纯哈希格式:只有哈希值(20字节)
 | 
				
			||||||
 | 
					                    // 这种情况下,我们需要从数据库中获取盐值
 | 
				
			||||||
 | 
					                    // 但由于VerifyPassword方法没有盐值参数,我们无法验证这种格式
 | 
				
			||||||
 | 
					                    // 返回false表示验证失败
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // 未知的哈希格式
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public bool VerifyPassword(string password, string hash, string salt)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // 使用提供的盐值计算密码的哈希
 | 
				
			||||||
 | 
					                var computedHash = HashPassword(password, salt);
 | 
				
			||||||
 | 
					                return hash == computedHash;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            catch
 | 
					            catch
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,62 +0,0 @@
 | 
				
			|||||||
using FutureMailAPI.Services;
 | 
					 | 
				
			||||||
using FutureMailAPI.Models;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace FutureMailAPI.Middleware
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public class OAuthAuthenticationMiddleware
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly RequestDelegate _next;
 | 
					 | 
				
			||||||
        private readonly ILogger<OAuthAuthenticationMiddleware> _logger;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public OAuthAuthenticationMiddleware(RequestDelegate next, ILogger<OAuthAuthenticationMiddleware> logger)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _next = next;
 | 
					 | 
				
			||||||
            _logger = logger;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task InvokeAsync(HttpContext context, IOAuthService oauthService)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 检查是否需要OAuth认证
 | 
					 | 
				
			||||||
            var endpoint = context.GetEndpoint();
 | 
					 | 
				
			||||||
            if (endpoint != null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // 如果端点标记为AllowAnonymous,则跳过认证
 | 
					 | 
				
			||||||
                var allowAnonymousAttribute = endpoint.Metadata.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>();
 | 
					 | 
				
			||||||
                if (allowAnonymousAttribute != null)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    await _next(context);
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 检查Authorization头
 | 
					 | 
				
			||||||
            var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
 | 
					 | 
				
			||||||
            if (authHeader != null && authHeader.StartsWith("Bearer "))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var token = authHeader.Substring("Bearer ".Length).Trim();
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // 验证令牌
 | 
					 | 
				
			||||||
                var validationResult = await oauthService.ValidateTokenAsync(token);
 | 
					 | 
				
			||||||
                if (validationResult.Success)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    // 获取访问令牌信息
 | 
					 | 
				
			||||||
                    var accessToken = await oauthService.GetAccessTokenAsync(token);
 | 
					 | 
				
			||||||
                    if (accessToken != null)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        // 将用户信息添加到HttpContext
 | 
					 | 
				
			||||||
                        context.Items["UserId"] = accessToken.UserId;
 | 
					 | 
				
			||||||
                        context.Items["UserEmail"] = accessToken.User.Email;
 | 
					 | 
				
			||||||
                        context.Items["AccessToken"] = accessToken;
 | 
					 | 
				
			||||||
                        
 | 
					 | 
				
			||||||
                        await _next(context);
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 如果没有有效的令牌,返回401未授权
 | 
					 | 
				
			||||||
            context.Response.StatusCode = 401;
 | 
					 | 
				
			||||||
            await context.Response.WriteAsync("未授权访问");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,180 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#nullable disable
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace FutureMailAPI.Migrations
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <inheritdoc />
 | 
					 | 
				
			||||||
    public partial class AddOAuthEntities : Migration
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <inheritdoc />
 | 
					 | 
				
			||||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            migrationBuilder.CreateTable(
 | 
					 | 
				
			||||||
                name: "OAuthClients",
 | 
					 | 
				
			||||||
                columns: table => new
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Id = table.Column<int>(type: "INTEGER", nullable: false)
 | 
					 | 
				
			||||||
                        .Annotation("Sqlite:Autoincrement", true),
 | 
					 | 
				
			||||||
                    ClientId = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    ClientSecret = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    Name = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    RedirectUris = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    Scopes = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    IsActive = table.Column<bool>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
 | 
					 | 
				
			||||||
                    UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                constraints: table =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    table.PrimaryKey("PK_OAuthClients", x => x.Id);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateTable(
 | 
					 | 
				
			||||||
                name: "OAuthAccessTokens",
 | 
					 | 
				
			||||||
                columns: table => new
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Id = table.Column<int>(type: "INTEGER", nullable: false)
 | 
					 | 
				
			||||||
                        .Annotation("Sqlite:Autoincrement", true),
 | 
					 | 
				
			||||||
                    Token = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    ClientId = table.Column<int>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    UserId = table.Column<int>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    Scopes = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    IsRevoked = table.Column<bool>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                constraints: table =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    table.PrimaryKey("PK_OAuthAccessTokens", x => x.Id);
 | 
					 | 
				
			||||||
                    table.ForeignKey(
 | 
					 | 
				
			||||||
                        name: "FK_OAuthAccessTokens_OAuthClients_ClientId",
 | 
					 | 
				
			||||||
                        column: x => x.ClientId,
 | 
					 | 
				
			||||||
                        principalTable: "OAuthClients",
 | 
					 | 
				
			||||||
                        principalColumn: "Id",
 | 
					 | 
				
			||||||
                        onDelete: ReferentialAction.Cascade);
 | 
					 | 
				
			||||||
                    table.ForeignKey(
 | 
					 | 
				
			||||||
                        name: "FK_OAuthAccessTokens_Users_UserId",
 | 
					 | 
				
			||||||
                        column: x => x.UserId,
 | 
					 | 
				
			||||||
                        principalTable: "Users",
 | 
					 | 
				
			||||||
                        principalColumn: "Id",
 | 
					 | 
				
			||||||
                        onDelete: ReferentialAction.Cascade);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateTable(
 | 
					 | 
				
			||||||
                name: "OAuthAuthorizationCodes",
 | 
					 | 
				
			||||||
                columns: table => new
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Id = table.Column<int>(type: "INTEGER", nullable: false)
 | 
					 | 
				
			||||||
                        .Annotation("Sqlite:Autoincrement", true),
 | 
					 | 
				
			||||||
                    Code = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    ClientId = table.Column<int>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    UserId = table.Column<int>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    RedirectUri = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    Scopes = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                constraints: table =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    table.PrimaryKey("PK_OAuthAuthorizationCodes", x => x.Id);
 | 
					 | 
				
			||||||
                    table.ForeignKey(
 | 
					 | 
				
			||||||
                        name: "FK_OAuthAuthorizationCodes_OAuthClients_ClientId",
 | 
					 | 
				
			||||||
                        column: x => x.ClientId,
 | 
					 | 
				
			||||||
                        principalTable: "OAuthClients",
 | 
					 | 
				
			||||||
                        principalColumn: "Id",
 | 
					 | 
				
			||||||
                        onDelete: ReferentialAction.Cascade);
 | 
					 | 
				
			||||||
                    table.ForeignKey(
 | 
					 | 
				
			||||||
                        name: "FK_OAuthAuthorizationCodes_Users_UserId",
 | 
					 | 
				
			||||||
                        column: x => x.UserId,
 | 
					 | 
				
			||||||
                        principalTable: "Users",
 | 
					 | 
				
			||||||
                        principalColumn: "Id",
 | 
					 | 
				
			||||||
                        onDelete: ReferentialAction.Cascade);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateTable(
 | 
					 | 
				
			||||||
                name: "OAuthRefreshTokens",
 | 
					 | 
				
			||||||
                columns: table => new
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Id = table.Column<int>(type: "INTEGER", nullable: false)
 | 
					 | 
				
			||||||
                        .Annotation("Sqlite:Autoincrement", true),
 | 
					 | 
				
			||||||
                    Token = table.Column<string>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    ClientId = table.Column<int>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    UserId = table.Column<int>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
 | 
					 | 
				
			||||||
                    ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
 | 
					 | 
				
			||||||
                    CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                constraints: table =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    table.PrimaryKey("PK_OAuthRefreshTokens", x => x.Id);
 | 
					 | 
				
			||||||
                    table.ForeignKey(
 | 
					 | 
				
			||||||
                        name: "FK_OAuthRefreshTokens_OAuthClients_ClientId",
 | 
					 | 
				
			||||||
                        column: x => x.ClientId,
 | 
					 | 
				
			||||||
                        principalTable: "OAuthClients",
 | 
					 | 
				
			||||||
                        principalColumn: "Id",
 | 
					 | 
				
			||||||
                        onDelete: ReferentialAction.Cascade);
 | 
					 | 
				
			||||||
                    table.ForeignKey(
 | 
					 | 
				
			||||||
                        name: "FK_OAuthRefreshTokens_Users_UserId",
 | 
					 | 
				
			||||||
                        column: x => x.UserId,
 | 
					 | 
				
			||||||
                        principalTable: "Users",
 | 
					 | 
				
			||||||
                        principalColumn: "Id",
 | 
					 | 
				
			||||||
                        onDelete: ReferentialAction.Cascade);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthAccessTokens_ClientId",
 | 
					 | 
				
			||||||
                table: "OAuthAccessTokens",
 | 
					 | 
				
			||||||
                column: "ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthAccessTokens_UserId",
 | 
					 | 
				
			||||||
                table: "OAuthAccessTokens",
 | 
					 | 
				
			||||||
                column: "UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthAuthorizationCodes_ClientId",
 | 
					 | 
				
			||||||
                table: "OAuthAuthorizationCodes",
 | 
					 | 
				
			||||||
                column: "ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthAuthorizationCodes_UserId",
 | 
					 | 
				
			||||||
                table: "OAuthAuthorizationCodes",
 | 
					 | 
				
			||||||
                column: "UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthClients_ClientId",
 | 
					 | 
				
			||||||
                table: "OAuthClients",
 | 
					 | 
				
			||||||
                column: "ClientId",
 | 
					 | 
				
			||||||
                unique: true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthRefreshTokens_ClientId",
 | 
					 | 
				
			||||||
                table: "OAuthRefreshTokens",
 | 
					 | 
				
			||||||
                column: "ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.CreateIndex(
 | 
					 | 
				
			||||||
                name: "IX_OAuthRefreshTokens_UserId",
 | 
					 | 
				
			||||||
                table: "OAuthRefreshTokens",
 | 
					 | 
				
			||||||
                column: "UserId");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <inheritdoc />
 | 
					 | 
				
			||||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            migrationBuilder.DropTable(
 | 
					 | 
				
			||||||
                name: "OAuthAccessTokens");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.DropTable(
 | 
					 | 
				
			||||||
                name: "OAuthAuthorizationCodes");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.DropTable(
 | 
					 | 
				
			||||||
                name: "OAuthRefreshTokens");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            migrationBuilder.DropTable(
 | 
					 | 
				
			||||||
                name: "OAuthClients");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
				
			|||||||
namespace FutureMailAPI.Migrations
 | 
					namespace FutureMailAPI.Migrations
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [DbContext(typeof(FutureMailDbContext))]
 | 
					    [DbContext(typeof(FutureMailDbContext))]
 | 
				
			||||||
    [Migration("20251016012504_AddSaltToUser")]
 | 
					    [Migration("20251018051334_AddSentMailCreatedAt")]
 | 
				
			||||||
    partial class AddSaltToUser
 | 
					    partial class AddSentMailCreatedAt
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <inheritdoc />
 | 
					        /// <inheritdoc />
 | 
				
			||||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
					        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
				
			||||||
@@ -20,90 +20,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
#pragma warning disable 612, 618
 | 
					#pragma warning disable 612, 618
 | 
				
			||||||
            modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
 | 
					            modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT")
 | 
					 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<bool>("IsRevoked")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Scopes")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Token")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasKey("Id");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.ToTable("OAuthAccessTokens");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Code")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT")
 | 
					 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<bool>("IsUsed")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("RedirectUri")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Scopes")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasKey("Id");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.ToTable("OAuthAuthorizationCodes");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
@@ -112,10 +28,12 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("ClientId")
 | 
					                    b.Property<string>("ClientId")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("ClientSecret")
 | 
					                    b.Property<string>("ClientSecret")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					                    b.Property<DateTime>("CreatedAt")
 | 
				
			||||||
@@ -128,6 +46,7 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Name")
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("RedirectUris")
 | 
					                    b.Property<string>("RedirectUris")
 | 
				
			||||||
@@ -151,12 +70,17 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.ToTable("OAuthClients");
 | 
					                    b.ToTable("OAuthClients");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("AccessToken")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					                    b.Property<int>("ClientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,11 +92,22 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					                    b.Property<DateTime>("ExpiresAt")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<bool>("IsUsed")
 | 
					                    b.Property<string>("RefreshToken")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Token")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("RevokedAt")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Scope")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("TokenType")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("UpdatedAt")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					                    b.Property<int>("UserId")
 | 
				
			||||||
@@ -180,11 +115,17 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("AccessToken")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					                    b.HasIndex("ClientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("RefreshToken")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					                    b.HasIndex("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("OAuthRefreshTokens");
 | 
					                    b.ToTable("OAuthTokens");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
				
			||||||
@@ -243,6 +184,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime>("CreatedAt")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime>("DeliveryTime")
 | 
					                    b.Property<DateTime>("DeliveryTime")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -313,6 +257,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .HasColumnType("TEXT")
 | 
					                        .HasColumnType("TEXT")
 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<double>("GlowIntensity")
 | 
				
			||||||
 | 
					                        .HasColumnType("REAL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<double>("Opacity")
 | 
					                    b.Property<double>("Opacity")
 | 
				
			||||||
                        .HasColumnType("REAL");
 | 
					                        .HasColumnType("REAL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -340,6 +287,10 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int>("Status")
 | 
					                    b.Property<int>("Status")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Style")
 | 
				
			||||||
 | 
					                        .HasMaxLength(50)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("Type")
 | 
					                    b.Property<int>("Type")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -348,7 +299,8 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("SentMailId");
 | 
					                    b.HasIndex("SentMailId")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("SentMailId1");
 | 
					                    b.HasIndex("SentMailId1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -400,6 +352,13 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .HasMaxLength(20)
 | 
					                        .HasMaxLength(20)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("RefreshToken")
 | 
				
			||||||
 | 
					                        .HasMaxLength(500)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("RefreshTokenExpiryTime")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Salt")
 | 
					                    b.Property<string>("Salt")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
                        .HasMaxLength(255)
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
@@ -421,48 +380,10 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.ToTable("Users");
 | 
					                    b.ToTable("Users");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany("Tokens")
 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "User")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("Client");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("User");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "User")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("Client");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("User");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					                        .HasForeignKey("ClientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
@@ -528,8 +449,8 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
            modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.SentMail", null)
 | 
					                    b.HasOne("FutureMailAPI.Models.SentMail", null)
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithOne("TimeCapsule")
 | 
				
			||||||
                        .HasForeignKey("SentMailId")
 | 
					                        .HasForeignKey("FutureMailAPI.Models.TimeCapsule", "SentMailId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -550,6 +471,16 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Navigation("User");
 | 
					                    b.Navigation("User");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Navigation("Tokens");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Navigation("TimeCapsule");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.User", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.User", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Navigation("ReceivedMails");
 | 
					                    b.Navigation("ReceivedMails");
 | 
				
			||||||
@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace FutureMailAPI.Migrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class AddSentMailCreatedAt : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_TimeCapsules_SentMailId",
 | 
				
			||||||
 | 
					                table: "TimeCapsules");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<double>(
 | 
				
			||||||
 | 
					                name: "GlowIntensity",
 | 
				
			||||||
 | 
					                table: "TimeCapsules",
 | 
				
			||||||
 | 
					                type: "REAL",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: 0.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<string>(
 | 
				
			||||||
 | 
					                name: "Style",
 | 
				
			||||||
 | 
					                table: "TimeCapsules",
 | 
				
			||||||
 | 
					                type: "TEXT",
 | 
				
			||||||
 | 
					                maxLength: 50,
 | 
				
			||||||
 | 
					                nullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<DateTime>(
 | 
				
			||||||
 | 
					                name: "CreatedAt",
 | 
				
			||||||
 | 
					                table: "SentMails",
 | 
				
			||||||
 | 
					                type: "TEXT",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_TimeCapsules_SentMailId",
 | 
				
			||||||
 | 
					                table: "TimeCapsules",
 | 
				
			||||||
 | 
					                column: "SentMailId",
 | 
				
			||||||
 | 
					                unique: true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_TimeCapsules_SentMailId",
 | 
				
			||||||
 | 
					                table: "TimeCapsules");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "GlowIntensity",
 | 
				
			||||||
 | 
					                table: "TimeCapsules");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "Style",
 | 
				
			||||||
 | 
					                table: "TimeCapsules");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "CreatedAt",
 | 
				
			||||||
 | 
					                table: "SentMails");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_TimeCapsules_SentMailId",
 | 
				
			||||||
 | 
					                table: "TimeCapsules",
 | 
				
			||||||
 | 
					                column: "SentMailId");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
				
			|||||||
namespace FutureMailAPI.Migrations
 | 
					namespace FutureMailAPI.Migrations
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [DbContext(typeof(FutureMailDbContext))]
 | 
					    [DbContext(typeof(FutureMailDbContext))]
 | 
				
			||||||
    [Migration("20251016011551_AddOAuthEntities")]
 | 
					    [Migration("20251018071917_FixDuplicateForeignKeys")]
 | 
				
			||||||
    partial class AddOAuthEntities
 | 
					    partial class FixDuplicateForeignKeys
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <inheritdoc />
 | 
					        /// <inheritdoc />
 | 
				
			||||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
					        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
				
			||||||
@@ -20,90 +20,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
#pragma warning disable 612, 618
 | 
					#pragma warning disable 612, 618
 | 
				
			||||||
            modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
 | 
					            modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT")
 | 
					 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<bool>("IsRevoked")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Scopes")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Token")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasKey("Id");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.ToTable("OAuthAccessTokens");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Code")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT")
 | 
					 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<bool>("IsUsed")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("RedirectUri")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Scopes")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasKey("Id");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.ToTable("OAuthAuthorizationCodes");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
@@ -112,10 +28,12 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("ClientId")
 | 
					                    b.Property<string>("ClientId")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("ClientSecret")
 | 
					                    b.Property<string>("ClientSecret")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					                    b.Property<DateTime>("CreatedAt")
 | 
				
			||||||
@@ -128,6 +46,7 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Name")
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("RedirectUris")
 | 
					                    b.Property<string>("RedirectUris")
 | 
				
			||||||
@@ -151,12 +70,17 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.ToTable("OAuthClients");
 | 
					                    b.ToTable("OAuthClients");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("AccessToken")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					                    b.Property<int>("ClientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,11 +92,22 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					                    b.Property<DateTime>("ExpiresAt")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<bool>("IsUsed")
 | 
					                    b.Property<string>("RefreshToken")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Token")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("RevokedAt")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Scope")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("TokenType")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("UpdatedAt")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					                    b.Property<int>("UserId")
 | 
				
			||||||
@@ -180,11 +115,17 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("AccessToken")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					                    b.HasIndex("ClientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("RefreshToken")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					                    b.HasIndex("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("OAuthRefreshTokens");
 | 
					                    b.ToTable("OAuthTokens");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
				
			||||||
@@ -210,9 +151,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int>("RecipientId")
 | 
					                    b.Property<int>("RecipientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("RecipientId1")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int?>("ReplyMailId")
 | 
					                    b.Property<int?>("ReplyMailId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -223,8 +161,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId");
 | 
					                    b.HasIndex("RecipientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("SentMailId");
 | 
					                    b.HasIndex("SentMailId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("ReceivedMails");
 | 
					                    b.ToTable("ReceivedMails");
 | 
				
			||||||
@@ -243,6 +179,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime>("CreatedAt")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime>("DeliveryTime")
 | 
					                    b.Property<DateTime>("DeliveryTime")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -255,9 +194,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int?>("RecipientId")
 | 
					                    b.Property<int?>("RecipientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int?>("RecipientId1")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("RecipientType")
 | 
					                    b.Property<int>("RecipientType")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -291,8 +227,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId");
 | 
					                    b.HasIndex("RecipientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("SenderId");
 | 
					                    b.HasIndex("SenderId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("SentMails");
 | 
					                    b.ToTable("SentMails");
 | 
				
			||||||
@@ -313,6 +247,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .HasColumnType("TEXT")
 | 
					                        .HasColumnType("TEXT")
 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<double>("GlowIntensity")
 | 
				
			||||||
 | 
					                        .HasColumnType("REAL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<double>("Opacity")
 | 
					                    b.Property<double>("Opacity")
 | 
				
			||||||
                        .HasColumnType("REAL");
 | 
					                        .HasColumnType("REAL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -340,6 +277,10 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int>("Status")
 | 
					                    b.Property<int>("Status")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Style")
 | 
				
			||||||
 | 
					                        .HasMaxLength(50)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("Type")
 | 
					                    b.Property<int>("Type")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -348,7 +289,8 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("SentMailId");
 | 
					                    b.HasIndex("SentMailId")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("SentMailId1");
 | 
					                    b.HasIndex("SentMailId1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -400,6 +342,18 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .HasMaxLength(20)
 | 
					                        .HasMaxLength(20)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("RefreshToken")
 | 
				
			||||||
 | 
					                        .HasMaxLength(500)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("RefreshTokenExpiryTime")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Salt")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Username")
 | 
					                    b.Property<string>("Username")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
                        .HasMaxLength(100)
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
@@ -416,48 +370,10 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.ToTable("Users");
 | 
					                    b.ToTable("Users");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany("Tokens")
 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "User")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("Client");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("User");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "User")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("Client");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("User");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					                        .HasForeignKey("ClientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
@@ -475,15 +391,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", null)
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("RecipientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
					                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
				
			||||||
                        .WithMany("ReceivedMails")
 | 
					                        .WithMany("ReceivedMails")
 | 
				
			||||||
                        .HasForeignKey("RecipientId1")
 | 
					                        .HasForeignKey("RecipientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -500,15 +410,11 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", null)
 | 
					                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany()
 | 
				
			||||||
                        .HasForeignKey("RecipientId")
 | 
					                        .HasForeignKey("RecipientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.SetNull);
 | 
					                        .OnDelete(DeleteBehavior.SetNull);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("RecipientId1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "Sender")
 | 
					                    b.HasOne("FutureMailAPI.Models.User", "Sender")
 | 
				
			||||||
                        .WithMany("SentMails")
 | 
					                        .WithMany("SentMails")
 | 
				
			||||||
                        .HasForeignKey("SenderId")
 | 
					                        .HasForeignKey("SenderId")
 | 
				
			||||||
@@ -523,8 +429,8 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
            modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.SentMail", null)
 | 
					                    b.HasOne("FutureMailAPI.Models.SentMail", null)
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithOne("TimeCapsule")
 | 
				
			||||||
                        .HasForeignKey("SentMailId")
 | 
					                        .HasForeignKey("FutureMailAPI.Models.TimeCapsule", "SentMailId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -545,6 +451,16 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Navigation("User");
 | 
					                    b.Navigation("User");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Navigation("Tokens");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Navigation("TimeCapsule");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.User", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.User", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Navigation("ReceivedMails");
 | 
					                    b.Navigation("ReceivedMails");
 | 
				
			||||||
@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace FutureMailAPI.Migrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class FixDuplicateForeignKeys : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_ReceivedMails_Users_RecipientId1",
 | 
				
			||||||
 | 
					                table: "ReceivedMails");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SentMails_Users_RecipientId1",
 | 
				
			||||||
 | 
					                table: "SentMails");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_SentMails_RecipientId1",
 | 
				
			||||||
 | 
					                table: "SentMails");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_ReceivedMails_RecipientId1",
 | 
				
			||||||
 | 
					                table: "ReceivedMails");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "RecipientId1",
 | 
				
			||||||
 | 
					                table: "SentMails");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "RecipientId1",
 | 
				
			||||||
 | 
					                table: "ReceivedMails");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<int>(
 | 
				
			||||||
 | 
					                name: "RecipientId1",
 | 
				
			||||||
 | 
					                table: "SentMails",
 | 
				
			||||||
 | 
					                type: "INTEGER",
 | 
				
			||||||
 | 
					                nullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<int>(
 | 
				
			||||||
 | 
					                name: "RecipientId1",
 | 
				
			||||||
 | 
					                table: "ReceivedMails",
 | 
				
			||||||
 | 
					                type: "INTEGER",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_SentMails_RecipientId1",
 | 
				
			||||||
 | 
					                table: "SentMails",
 | 
				
			||||||
 | 
					                column: "RecipientId1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_ReceivedMails_RecipientId1",
 | 
				
			||||||
 | 
					                table: "ReceivedMails",
 | 
				
			||||||
 | 
					                column: "RecipientId1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_ReceivedMails_Users_RecipientId1",
 | 
				
			||||||
 | 
					                table: "ReceivedMails",
 | 
				
			||||||
 | 
					                column: "RecipientId1",
 | 
				
			||||||
 | 
					                principalTable: "Users",
 | 
				
			||||||
 | 
					                principalColumn: "Id",
 | 
				
			||||||
 | 
					                onDelete: ReferentialAction.Cascade);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_SentMails_Users_RecipientId1",
 | 
				
			||||||
 | 
					                table: "SentMails",
 | 
				
			||||||
 | 
					                column: "RecipientId1",
 | 
				
			||||||
 | 
					                principalTable: "Users",
 | 
				
			||||||
 | 
					                principalColumn: "Id");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -17,90 +17,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
#pragma warning disable 612, 618
 | 
					#pragma warning disable 612, 618
 | 
				
			||||||
            modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
 | 
					            modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT")
 | 
					 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<bool>("IsRevoked")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Scopes")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Token")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasKey("Id");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.ToTable("OAuthAccessTokens");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Code")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT")
 | 
					 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<bool>("IsUsed")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("RedirectUri")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Scopes")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasKey("Id");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.ToTable("OAuthAuthorizationCodes");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
@@ -109,10 +25,12 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("ClientId")
 | 
					                    b.Property<string>("ClientId")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("ClientSecret")
 | 
					                    b.Property<string>("ClientSecret")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime>("CreatedAt")
 | 
					                    b.Property<DateTime>("CreatedAt")
 | 
				
			||||||
@@ -125,6 +43,7 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Name")
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(100)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("RedirectUris")
 | 
					                    b.Property<string>("RedirectUris")
 | 
				
			||||||
@@ -148,12 +67,17 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.ToTable("OAuthClients");
 | 
					                    b.ToTable("OAuthClients");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<int>("Id")
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
                        .ValueGeneratedOnAdd()
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("AccessToken")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("ClientId")
 | 
					                    b.Property<int>("ClientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,11 +89,22 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<DateTime>("ExpiresAt")
 | 
					                    b.Property<DateTime>("ExpiresAt")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<bool>("IsUsed")
 | 
					                    b.Property<string>("RefreshToken")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<string>("Token")
 | 
					 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("RevokedAt")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Scope")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("TokenType")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("UpdatedAt")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("UserId")
 | 
					                    b.Property<int>("UserId")
 | 
				
			||||||
@@ -177,11 +112,17 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("AccessToken")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("ClientId");
 | 
					                    b.HasIndex("ClientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("RefreshToken")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("UserId");
 | 
					                    b.HasIndex("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("OAuthRefreshTokens");
 | 
					                    b.ToTable("OAuthTokens");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
				
			||||||
@@ -207,9 +148,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int>("RecipientId")
 | 
					                    b.Property<int>("RecipientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("RecipientId1")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int?>("ReplyMailId")
 | 
					                    b.Property<int?>("ReplyMailId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -220,8 +158,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId");
 | 
					                    b.HasIndex("RecipientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("SentMailId");
 | 
					                    b.HasIndex("SentMailId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("ReceivedMails");
 | 
					                    b.ToTable("ReceivedMails");
 | 
				
			||||||
@@ -240,6 +176,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime>("CreatedAt")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime>("DeliveryTime")
 | 
					                    b.Property<DateTime>("DeliveryTime")
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -252,9 +191,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int?>("RecipientId")
 | 
					                    b.Property<int?>("RecipientId")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int?>("RecipientId1")
 | 
					 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Property<int>("RecipientType")
 | 
					                    b.Property<int>("RecipientType")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -288,8 +224,6 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId");
 | 
					                    b.HasIndex("RecipientId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("RecipientId1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasIndex("SenderId");
 | 
					                    b.HasIndex("SenderId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("SentMails");
 | 
					                    b.ToTable("SentMails");
 | 
				
			||||||
@@ -310,6 +244,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .HasColumnType("TEXT")
 | 
					                        .HasColumnType("TEXT")
 | 
				
			||||||
                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
					                        .HasDefaultValueSql("CURRENT_TIMESTAMP");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<double>("GlowIntensity")
 | 
				
			||||||
 | 
					                        .HasColumnType("REAL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<double>("Opacity")
 | 
					                    b.Property<double>("Opacity")
 | 
				
			||||||
                        .HasColumnType("REAL");
 | 
					                        .HasColumnType("REAL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -337,6 +274,10 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Property<int>("Status")
 | 
					                    b.Property<int>("Status")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Style")
 | 
				
			||||||
 | 
					                        .HasMaxLength(50)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<int>("Type")
 | 
					                    b.Property<int>("Type")
 | 
				
			||||||
                        .HasColumnType("INTEGER");
 | 
					                        .HasColumnType("INTEGER");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -345,7 +286,8 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("SentMailId");
 | 
					                    b.HasIndex("SentMailId")
 | 
				
			||||||
 | 
					                        .IsUnique();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("SentMailId1");
 | 
					                    b.HasIndex("SentMailId1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -397,6 +339,13 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                        .HasMaxLength(20)
 | 
					                        .HasMaxLength(20)
 | 
				
			||||||
                        .HasColumnType("TEXT");
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("RefreshToken")
 | 
				
			||||||
 | 
					                        .HasMaxLength(500)
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("RefreshTokenExpiryTime")
 | 
				
			||||||
 | 
					                        .HasColumnType("TEXT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Salt")
 | 
					                    b.Property<string>("Salt")
 | 
				
			||||||
                        .IsRequired()
 | 
					                        .IsRequired()
 | 
				
			||||||
                        .HasMaxLength(255)
 | 
					                        .HasMaxLength(255)
 | 
				
			||||||
@@ -418,48 +367,10 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.ToTable("Users");
 | 
					                    b.ToTable("Users");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany("Tokens")
 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "User")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("Client");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("User");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "User")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("UserId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("Client");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.Navigation("User");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("ClientId")
 | 
					                        .HasForeignKey("ClientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
@@ -477,15 +388,9 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", null)
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("RecipientId")
 | 
					 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					 | 
				
			||||||
                        .IsRequired();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
					                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
				
			||||||
                        .WithMany("ReceivedMails")
 | 
					                        .WithMany("ReceivedMails")
 | 
				
			||||||
                        .HasForeignKey("RecipientId1")
 | 
					                        .HasForeignKey("RecipientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -502,15 +407,11 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", null)
 | 
					                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany()
 | 
				
			||||||
                        .HasForeignKey("RecipientId")
 | 
					                        .HasForeignKey("RecipientId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.SetNull);
 | 
					                        .OnDelete(DeleteBehavior.SetNull);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "Recipient")
 | 
					 | 
				
			||||||
                        .WithMany()
 | 
					 | 
				
			||||||
                        .HasForeignKey("RecipientId1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.User", "Sender")
 | 
					                    b.HasOne("FutureMailAPI.Models.User", "Sender")
 | 
				
			||||||
                        .WithMany("SentMails")
 | 
					                        .WithMany("SentMails")
 | 
				
			||||||
                        .HasForeignKey("SenderId")
 | 
					                        .HasForeignKey("SenderId")
 | 
				
			||||||
@@ -525,8 +426,8 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
            modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("FutureMailAPI.Models.SentMail", null)
 | 
					                    b.HasOne("FutureMailAPI.Models.SentMail", null)
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithOne("TimeCapsule")
 | 
				
			||||||
                        .HasForeignKey("SentMailId")
 | 
					                        .HasForeignKey("FutureMailAPI.Models.TimeCapsule", "SentMailId")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -547,6 +448,16 @@ namespace FutureMailAPI.Migrations
 | 
				
			|||||||
                    b.Navigation("User");
 | 
					                    b.Navigation("User");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Navigation("Tokens");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Navigation("TimeCapsule");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("FutureMailAPI.Models.User", b =>
 | 
					            modelBuilder.Entity("FutureMailAPI.Models.User", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Navigation("ReceivedMails");
 | 
					                    b.Navigation("ReceivedMails");
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								FutureMailAPI/Models/OAuthClient.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								FutureMailAPI/Models/OAuthClient.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace FutureMailAPI.Models
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class OAuthClient
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [Key]
 | 
				
			||||||
 | 
					        public int Id { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        [MaxLength(100)]
 | 
				
			||||||
 | 
					        public string ClientId { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        [MaxLength(255)]
 | 
				
			||||||
 | 
					        public string ClientSecret { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        [MaxLength(100)]
 | 
				
			||||||
 | 
					        public string Name { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        public string RedirectUris { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        public string Scopes { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public bool IsActive { get; set; } = true;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 导航属性
 | 
				
			||||||
 | 
					        public virtual ICollection<OAuthToken> Tokens { get; set; } = new List<OAuthToken>();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,63 +0,0 @@
 | 
				
			|||||||
namespace FutureMailAPI.Models
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public class OAuthClient
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public int Id { get; set; }
 | 
					 | 
				
			||||||
        public string ClientId { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string ClientSecret { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string Name { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string RedirectUris { get; set; } = string.Empty; // JSON array
 | 
					 | 
				
			||||||
        public string Scopes { get; set; } = string.Empty; // JSON array
 | 
					 | 
				
			||||||
        public bool IsActive { get; set; } = true;
 | 
					 | 
				
			||||||
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
					 | 
				
			||||||
        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public class OAuthAuthorizationCode
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public int Id { get; set; }
 | 
					 | 
				
			||||||
        public string Code { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public int ClientId { get; set; }
 | 
					 | 
				
			||||||
        public int UserId { get; set; }
 | 
					 | 
				
			||||||
        public string RedirectUri { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public string Scopes { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public bool IsUsed { get; set; } = false;
 | 
					 | 
				
			||||||
        public DateTime ExpiresAt { get; set; }
 | 
					 | 
				
			||||||
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Navigation properties
 | 
					 | 
				
			||||||
        public virtual OAuthClient Client { get; set; } = null!;
 | 
					 | 
				
			||||||
        public virtual User User { get; set; } = null!;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public class OAuthRefreshToken
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public int Id { get; set; }
 | 
					 | 
				
			||||||
        public string Token { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public int ClientId { get; set; }
 | 
					 | 
				
			||||||
        public int UserId { get; set; }
 | 
					 | 
				
			||||||
        public bool IsUsed { get; set; } = false;
 | 
					 | 
				
			||||||
        public DateTime ExpiresAt { get; set; }
 | 
					 | 
				
			||||||
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Navigation properties
 | 
					 | 
				
			||||||
        public virtual OAuthClient Client { get; set; } = null!;
 | 
					 | 
				
			||||||
        public virtual User User { get; set; } = null!;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public class OAuthAccessToken
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public int Id { get; set; }
 | 
					 | 
				
			||||||
        public string Token { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public int ClientId { get; set; }
 | 
					 | 
				
			||||||
        public int UserId { get; set; }
 | 
					 | 
				
			||||||
        public string Scopes { get; set; } = string.Empty;
 | 
					 | 
				
			||||||
        public bool IsRevoked { get; set; } = false;
 | 
					 | 
				
			||||||
        public DateTime ExpiresAt { get; set; }
 | 
					 | 
				
			||||||
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Navigation properties
 | 
					 | 
				
			||||||
        public virtual OAuthClient Client { get; set; } = null!;
 | 
					 | 
				
			||||||
        public virtual User User { get; set; } = null!;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										44
									
								
								FutureMailAPI/Models/OAuthToken.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								FutureMailAPI/Models/OAuthToken.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace FutureMailAPI.Models
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class OAuthToken
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [Key]
 | 
				
			||||||
 | 
					        public int Id { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        [MaxLength(255)]
 | 
				
			||||||
 | 
					        public string AccessToken { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        [MaxLength(255)]
 | 
				
			||||||
 | 
					        public string RefreshToken { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [Required]
 | 
				
			||||||
 | 
					        public string TokenType { get; set; } = "Bearer";
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime ExpiresAt { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime? UpdatedAt { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime? RevokedAt { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public string? Scope { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 外键
 | 
				
			||||||
 | 
					        public int UserId { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public int ClientId { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 导航属性
 | 
				
			||||||
 | 
					        [ForeignKey("UserId")]
 | 
				
			||||||
 | 
					        public virtual User User { get; set; } = null!;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        [ForeignKey("ClientId")]
 | 
				
			||||||
 | 
					        public virtual OAuthClient Client { get; set; } = null!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -28,6 +28,9 @@ namespace FutureMailAPI.Models
 | 
				
			|||||||
        // 发送时间
 | 
					        // 发送时间
 | 
				
			||||||
        public DateTime SentAt { get; set; } = DateTime.UtcNow;
 | 
					        public DateTime SentAt { get; set; } = DateTime.UtcNow;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // 创建时间
 | 
				
			||||||
 | 
					        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        // 投递时间
 | 
					        // 投递时间
 | 
				
			||||||
        [Required]
 | 
					        [Required]
 | 
				
			||||||
        public DateTime DeliveryTime { get; set; }
 | 
					        public DateTime DeliveryTime { get; set; }
 | 
				
			||||||
@@ -61,5 +64,7 @@ namespace FutureMailAPI.Models
 | 
				
			|||||||
        public virtual User Sender { get; set; } = null!;
 | 
					        public virtual User Sender { get; set; } = null!;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public virtual User? Recipient { get; set; }
 | 
					        public virtual User? Recipient { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public virtual TimeCapsule? TimeCapsule { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -32,6 +32,13 @@ namespace FutureMailAPI.Models
 | 
				
			|||||||
        // 胶囊旋转角度
 | 
					        // 胶囊旋转角度
 | 
				
			||||||
        public double Rotation { get; set; } = 0;
 | 
					        public double Rotation { get; set; } = 0;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // 胶囊样式/皮肤
 | 
				
			||||||
 | 
					        [MaxLength(50)]
 | 
				
			||||||
 | 
					        public string? Style { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 发光强度
 | 
				
			||||||
 | 
					        public double GlowIntensity { get; set; } = 0.8;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        // 胶囊状态: 0-未激活, 1-漂浮中, 2-即将到达, 3-已开启
 | 
					        // 胶囊状态: 0-未激活, 1-漂浮中, 2-即将到达, 3-已开启
 | 
				
			||||||
        [Required]
 | 
					        [Required]
 | 
				
			||||||
        public int Status { get; set; } = 0;
 | 
					        public int Status { get; set; } = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,12 @@ namespace FutureMailAPI.Models
 | 
				
			|||||||
        [MaxLength(50)]
 | 
					        [MaxLength(50)]
 | 
				
			||||||
        public string? PreferredBackground { get; set; } = "default";
 | 
					        public string? PreferredBackground { get; set; } = "default";
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        [MaxLength(500)]
 | 
				
			||||||
 | 
					        public string? RefreshToken { get; set; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public DateTime? RefreshTokenExpiryTime { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        // 导航属性
 | 
					        // 导航属性
 | 
				
			||||||
        public virtual ICollection<SentMail> SentMails { get; set; } = new List<SentMail>();
 | 
					        public virtual ICollection<SentMail> SentMails { get; set; } = new List<SentMail>();
 | 
				
			||||||
        public virtual ICollection<ReceivedMail> ReceivedMails { get; set; } = new List<ReceivedMail>();
 | 
					        public virtual ICollection<ReceivedMail> ReceivedMails { get; set; } = new List<ReceivedMail>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
// 测试OAuth 2.0密码授权流程
 | 
					 | 
				
			||||||
// 1. 使用密码授权获取访问令牌
 | 
					 | 
				
			||||||
POST http://localhost:5001/api/v1/oauth/token
 | 
					 | 
				
			||||||
Content-Type: application/x-www-form-urlencoded
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
grant_type=password&username=testuser3&password=password123&client_id=futuremail-client&client_secret=futuremail-secret
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 2. 使用访问令牌访问受保护的API
 | 
					 | 
				
			||||||
GET http://localhost:5001/api/v1/mails
 | 
					 | 
				
			||||||
Authorization: Bearer YOUR_ACCESS_TOKEN
 | 
					 | 
				
			||||||
@@ -1,37 +0,0 @@
 | 
				
			|||||||
// 测试OAuth 2.0认证流程
 | 
					 | 
				
			||||||
// 1. 创建OAuth客户端
 | 
					 | 
				
			||||||
POST http://localhost:5001/api/v1/oauth/clients
 | 
					 | 
				
			||||||
Content-Type: application/json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "clientName": "TestClient",
 | 
					 | 
				
			||||||
  "redirectUris": ["http://localhost:3000/callback"],
 | 
					 | 
				
			||||||
  "scopes": ["read", "write"]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 2. 获取授权码(在浏览器中访问以下URL)
 | 
					 | 
				
			||||||
// http://localhost:5001/api/v1/oauth/authorize?response_type=code&client_id=test_client&redirect_uri=http://localhost:3000/callback&scope=read&state=xyz
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 3. 使用授权码获取访问令牌
 | 
					 | 
				
			||||||
POST http://localhost:5001/api/v1/oauth/token
 | 
					 | 
				
			||||||
Content-Type: application/x-www-form-urlencoded
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
grant_type=authorization_code&code=YOUR_AUTHORIZATION_CODE&redirect_uri=http://localhost:3000/callback&client_id=test_client&client_secret=YOUR_CLIENT_SECRET
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 4. 使用访问令牌访问受保护的API
 | 
					 | 
				
			||||||
GET http://localhost:5001/api/v1/mails
 | 
					 | 
				
			||||||
Authorization: Bearer YOUR_ACCESS_TOKEN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 5. 刷新访问令牌
 | 
					 | 
				
			||||||
POST http://localhost:5001/api/v1/oauth/token
 | 
					 | 
				
			||||||
Content-Type: application/x-www-form-urlencoded
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=test_client&client_secret=YOUR_CLIENT_SECRET
 | 
					 | 
				
			||||||
@@ -1,18 +1,20 @@
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authentication.JwtBearer;
 | 
				
			||||||
 | 
					using Microsoft.IdentityModel.Tokens;
 | 
				
			||||||
using Quartz;
 | 
					using Quartz;
 | 
				
			||||||
using FutureMailAPI.Data;
 | 
					using FutureMailAPI.Data;
 | 
				
			||||||
using FutureMailAPI.Helpers;
 | 
					using FutureMailAPI.Helpers;
 | 
				
			||||||
using FutureMailAPI.Services;
 | 
					using FutureMailAPI.Services;
 | 
				
			||||||
using FutureMailAPI.Middleware;
 | 
					using FutureMailAPI.Filters;
 | 
				
			||||||
using FutureMailAPI.Extensions;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.FileProviders;
 | 
					using Microsoft.Extensions.FileProviders;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var builder = WebApplication.CreateBuilder(args);
 | 
					var builder = WebApplication.CreateBuilder(args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 配置服务器监听所有网络接口的5001端口
 | 
					// 配置服务器监听所有网络接口的5003端口
 | 
				
			||||||
builder.WebHost.ConfigureKestrel(options =>
 | 
					builder.WebHost.ConfigureKestrel(options =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    options.ListenAnyIP(5001);
 | 
					    options.ListenAnyIP(5003);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 配置数据库连接
 | 
					// 配置数据库连接
 | 
				
			||||||
@@ -20,9 +22,6 @@ var connectionString = builder.Configuration.GetConnectionString("DefaultConnect
 | 
				
			|||||||
builder.Services.AddDbContext<FutureMailDbContext>(options =>
 | 
					builder.Services.AddDbContext<FutureMailDbContext>(options =>
 | 
				
			||||||
    options.UseSqlite(connectionString));
 | 
					    options.UseSqlite(connectionString));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 配置OAuth 2.0认证
 | 
					 | 
				
			||||||
// 注意:我们使用自定义中间件实现OAuth 2.0认证,而不是使用内置的JWT认证
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 配置Swagger/OpenAPI
 | 
					// 配置Swagger/OpenAPI
 | 
				
			||||||
builder.Services.AddEndpointsApiExplorer();
 | 
					builder.Services.AddEndpointsApiExplorer();
 | 
				
			||||||
builder.Services.AddSwaggerGen(c =>
 | 
					builder.Services.AddSwaggerGen(c =>
 | 
				
			||||||
@@ -40,13 +39,42 @@ builder.Services.AddScoped<IUserService, UserService>();
 | 
				
			|||||||
builder.Services.AddScoped<IMailService, MailService>();
 | 
					builder.Services.AddScoped<IMailService, MailService>();
 | 
				
			||||||
builder.Services.AddScoped<ITimeCapsuleService, TimeCapsuleService>();
 | 
					builder.Services.AddScoped<ITimeCapsuleService, TimeCapsuleService>();
 | 
				
			||||||
builder.Services.AddScoped<IAuthService, AuthService>();
 | 
					builder.Services.AddScoped<IAuthService, AuthService>();
 | 
				
			||||||
 | 
					builder.Services.AddScoped<IOAuthService, OAuthService>();
 | 
				
			||||||
builder.Services.AddScoped<IAIAssistantService, AIAssistantService>();
 | 
					builder.Services.AddScoped<IAIAssistantService, AIAssistantService>();
 | 
				
			||||||
builder.Services.AddScoped<IPersonalSpaceService, PersonalSpaceService>();
 | 
					builder.Services.AddScoped<IPersonalSpaceService, PersonalSpaceService>();
 | 
				
			||||||
builder.Services.AddScoped<IFileUploadService, FileUploadService>();
 | 
					builder.Services.AddScoped<IFileUploadService, FileUploadService>();
 | 
				
			||||||
builder.Services.AddScoped<INotificationService, NotificationService>();
 | 
					builder.Services.AddScoped<INotificationService, NotificationService>();
 | 
				
			||||||
builder.Services.AddScoped<IOAuthService, OAuthService>();
 | 
					
 | 
				
			||||||
builder.Services.AddScoped<IInitializationService, InitializationService>();
 | 
					builder.Services.AddScoped<IInitializationService, InitializationService>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 添加JWT认证
 | 
				
			||||||
 | 
					var jwtSettings = builder.Configuration.GetSection("Jwt");
 | 
				
			||||||
 | 
					var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.Services.AddAuthentication(options =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
 | 
				
			||||||
 | 
					    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					.AddJwtBearer(options =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    options.RequireHttpsMetadata = false;
 | 
				
			||||||
 | 
					    options.SaveToken = true;
 | 
				
			||||||
 | 
					    options.TokenValidationParameters = new TokenValidationParameters
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ValidateIssuerSigningKey = true,
 | 
				
			||||||
 | 
					        IssuerSigningKey = new SymmetricSecurityKey(key),
 | 
				
			||||||
 | 
					        ValidateIssuer = true,
 | 
				
			||||||
 | 
					        ValidIssuer = jwtSettings["Issuer"],
 | 
				
			||||||
 | 
					        ValidateAudience = true,
 | 
				
			||||||
 | 
					        ValidAudience = jwtSettings["Audience"],
 | 
				
			||||||
 | 
					        ValidateLifetime = true,
 | 
				
			||||||
 | 
					        ClockSkew = TimeSpan.Zero
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.Services.AddAuthorization();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 配置Quartz任务调度
 | 
					// 配置Quartz任务调度
 | 
				
			||||||
builder.Services.AddQuartz(q =>
 | 
					builder.Services.AddQuartz(q =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -64,8 +92,11 @@ builder.Services.AddQuartz(q =>
 | 
				
			|||||||
// 添加Quartz主机服务
 | 
					// 添加Quartz主机服务
 | 
				
			||||||
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
 | 
					builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 添加控制器
 | 
					// 添加控制器并注册OAuth认证过滤器为全局过滤器
 | 
				
			||||||
builder.Services.AddControllers();
 | 
					builder.Services.AddControllers(options =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    options.Filters.Add<OAuthAuthenticationFilter>();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 添加CORS
 | 
					// 添加CORS
 | 
				
			||||||
builder.Services.AddCors(options =>
 | 
					builder.Services.AddCors(options =>
 | 
				
			||||||
@@ -106,10 +137,7 @@ app.UseStaticFiles(new StaticFileOptions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
app.UseCors("AllowAll");
 | 
					app.UseCors("AllowAll");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 添加OAuth 2.0认证中间件
 | 
					 | 
				
			||||||
app.UseMiddleware<OAuthAuthenticationMiddleware>();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.UseAuthorization();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.MapControllers();
 | 
					app.MapControllers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
      "commandName": "Project",
 | 
					      "commandName": "Project",
 | 
				
			||||||
      "dotnetRunMessages": true,
 | 
					      "dotnetRunMessages": true,
 | 
				
			||||||
      "launchBrowser": false,
 | 
					      "launchBrowser": false,
 | 
				
			||||||
      "applicationUrl": "http://0.0.0.0:5001",
 | 
					      "applicationUrl": "http://0.0.0.0:5003",
 | 
				
			||||||
      "environmentVariables": {
 | 
					      "environmentVariables": {
 | 
				
			||||||
        "ASPNETCORE_ENVIRONMENT": "Development"
 | 
					        "ASPNETCORE_ENVIRONMENT": "Development"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -14,7 +14,7 @@
 | 
				
			|||||||
      "commandName": "Project",
 | 
					      "commandName": "Project",
 | 
				
			||||||
      "dotnetRunMessages": true,
 | 
					      "dotnetRunMessages": true,
 | 
				
			||||||
      "launchBrowser": false,
 | 
					      "launchBrowser": false,
 | 
				
			||||||
      "applicationUrl": "https://localhost:7236;http://0.0.0.0:5001",
 | 
					      "applicationUrl": "https://localhost:7236;http://0.0.0.0:5003",
 | 
				
			||||||
      "environmentVariables": {
 | 
					      "environmentVariables": {
 | 
				
			||||||
        "ASPNETCORE_ENVIRONMENT": "Development"
 | 
					        "ASPNETCORE_ENVIRONMENT": "Development"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,13 @@
 | 
				
			|||||||
using FutureMailAPI.Helpers;
 | 
					using FutureMailAPI.Helpers;
 | 
				
			||||||
using FutureMailAPI.DTOs;
 | 
					using FutureMailAPI.DTOs;
 | 
				
			||||||
 | 
					using FutureMailAPI.Models;
 | 
				
			||||||
 | 
					using FutureMailAPI.Data;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using Microsoft.IdentityModel.Tokens;
 | 
				
			||||||
 | 
					using System.IdentityModel.Tokens.Jwt;
 | 
				
			||||||
using System.Security.Claims;
 | 
					using System.Security.Claims;
 | 
				
			||||||
 | 
					using System.Security.Cryptography;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.Services
 | 
					namespace FutureMailAPI.Services
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -8,86 +15,73 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
 | 
					        Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
 | 
				
			||||||
        Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto);
 | 
					        Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto);
 | 
				
			||||||
        Task<ApiResponse<bool>> ValidateTokenAsync(string token);
 | 
					 | 
				
			||||||
        Task<ApiResponse<string>> RefreshTokenAsync(string token);
 | 
					 | 
				
			||||||
        Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public class AuthService : IAuthService
 | 
					    public class AuthService : IAuthService
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IUserService _userService;
 | 
					        private readonly IUserService _userService;
 | 
				
			||||||
        private readonly IPasswordHelper _passwordHelper;
 | 
					        private readonly IPasswordHelper _passwordHelper;
 | 
				
			||||||
        private readonly IOAuthService _oauthService;
 | 
					        private readonly FutureMailDbContext _context;
 | 
				
			||||||
 | 
					        private readonly IConfiguration _configuration;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public AuthService(
 | 
					        public AuthService(
 | 
				
			||||||
            IUserService userService, 
 | 
					            IUserService userService, 
 | 
				
			||||||
            IPasswordHelper passwordHelper,
 | 
					            IPasswordHelper passwordHelper,
 | 
				
			||||||
            IOAuthService oauthService)
 | 
					            FutureMailDbContext context,
 | 
				
			||||||
 | 
					            IConfiguration configuration)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _userService = userService;
 | 
					            _userService = userService;
 | 
				
			||||||
            _passwordHelper = passwordHelper;
 | 
					            _passwordHelper = passwordHelper;
 | 
				
			||||||
            _oauthService = oauthService;
 | 
					            _context = context;
 | 
				
			||||||
 | 
					            _configuration = configuration;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
 | 
					        public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 使用默认客户端ID和密钥进行OAuth登录
 | 
					 | 
				
			||||||
            // 在实际应用中,这些应该从配置中获取
 | 
					 | 
				
			||||||
            var defaultClientId = "futuremail_default_client";
 | 
					 | 
				
			||||||
            var defaultClientSecret = "futuremail_default_secret";
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return await LoginWithOAuthAsync(loginDto, defaultClientId, defaultClientSecret);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 创建OAuth登录请求
 | 
					 | 
				
			||||||
            var oauthLoginDto = new OAuthLoginDto
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                UsernameOrEmail = loginDto.UsernameOrEmail,
 | 
					 | 
				
			||||||
                Password = loginDto.Password,
 | 
					 | 
				
			||||||
                ClientId = clientId,
 | 
					 | 
				
			||||||
                ClientSecret = clientSecret,
 | 
					 | 
				
			||||||
                Scope = "read write" // 默认权限范围
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 使用OAuth服务进行登录
 | 
					 | 
				
			||||||
            var oauthResult = await _oauthService.LoginAsync(oauthLoginDto);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (!oauthResult.Success)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<AuthResponseDto>.ErrorResult(oauthResult.Message ?? "登录失败");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 获取用户信息
 | 
					            // 获取用户信息
 | 
				
			||||||
            var userResult = await _userService.GetUserByUsernameOrEmailAsync(loginDto.UsernameOrEmail);
 | 
					            var userResult = await _userService.GetUserByUsernameOrEmailAsync(loginDto.UsernameOrEmail);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!userResult.Success || userResult.Data == null)
 | 
					            if (!userResult.Success || userResult.Data == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return ApiResponse<AuthResponseDto>.ErrorResult("获取用户信息失败");
 | 
					                return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            var user = userResult.Data;
 | 
					            var userDto = userResult.Data;
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 创建用户响应DTO
 | 
					            // 获取原始用户信息用于密码验证
 | 
				
			||||||
            var userResponse = new UserResponseDto
 | 
					            var user = await _context.Users
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(u => u.Id == userDto.Id);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (user == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Id = user.Id,
 | 
					                return ApiResponse<AuthResponseDto>.ErrorResult("用户不存在");
 | 
				
			||||||
                Username = user.Username,
 | 
					            }
 | 
				
			||||||
                Email = user.Email,
 | 
					 | 
				
			||||||
                Nickname = user.Nickname,
 | 
					 | 
				
			||||||
                Avatar = user.Avatar,
 | 
					 | 
				
			||||||
                CreatedAt = user.CreatedAt,
 | 
					 | 
				
			||||||
                LastLoginAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 创建认证响应DTO,使用OAuth令牌
 | 
					            // 验证密码
 | 
				
			||||||
 | 
					            if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 更新用户响应DTO
 | 
				
			||||||
 | 
					            userDto.LastLoginAt = DateTime.UtcNow;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 生成JWT令牌
 | 
				
			||||||
 | 
					            var token = GenerateJwtToken(user);
 | 
				
			||||||
 | 
					            var refreshToken = GenerateRefreshToken();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 保存刷新令牌到用户表
 | 
				
			||||||
 | 
					            user.RefreshToken = refreshToken;
 | 
				
			||||||
 | 
					            user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7); // 刷新令牌7天过期
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 创建认证响应DTO
 | 
				
			||||||
            var authResponse = new AuthResponseDto
 | 
					            var authResponse = new AuthResponseDto
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Token = oauthResult.Data.AccessToken,
 | 
					                User = userDto,
 | 
				
			||||||
                RefreshToken = oauthResult.Data.RefreshToken,
 | 
					                Token = token,
 | 
				
			||||||
                Expires = DateTime.UtcNow.AddSeconds(oauthResult.Data.ExpiresIn),
 | 
					                RefreshToken = refreshToken,
 | 
				
			||||||
                User = userResponse
 | 
					                ExpiresIn = 3600 // 1小时,单位秒
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
 | 
					            return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
 | 
				
			||||||
@@ -119,7 +113,7 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
                return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
 | 
					                return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 注册成功后,自动使用OAuth登录
 | 
					            // 注册成功后,自动登录
 | 
				
			||||||
            var loginDto = new UserLoginDto
 | 
					            var loginDto = new UserLoginDto
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                UsernameOrEmail = registerDto.Username,
 | 
					                UsernameOrEmail = registerDto.Username,
 | 
				
			||||||
@@ -129,40 +123,35 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
            return await LoginAsync(loginDto);
 | 
					            return await LoginAsync(loginDto);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
 | 
					        private string GenerateJwtToken(User user)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 注意:在OAuth 2.0中,令牌验证应该由OAuth中间件处理
 | 
					            var jwtSettings = _configuration.GetSection("Jwt");
 | 
				
			||||||
            // 这里我们暂时返回成功,实际使用时应该通过OAuth 2.0的令牌验证流程
 | 
					            var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
 | 
				
			||||||
            return ApiResponse<bool>.SuccessResult(true);
 | 
					            var tokenDescriptor = new SecurityTokenDescriptor
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<string>> RefreshTokenAsync(string token)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
            // 在OAuth 2.0中,刷新令牌需要客户端ID和密钥
 | 
					                Subject = new ClaimsIdentity(new[]
 | 
				
			||||||
            // 这里我们使用默认客户端凭据
 | 
					 | 
				
			||||||
            var defaultClientId = "futuremail_default_client";
 | 
					 | 
				
			||||||
            var defaultClientSecret = "futuremail_default_secret";
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 创建OAuth刷新令牌请求
 | 
					 | 
				
			||||||
            var oauthTokenRequest = new OAuthTokenRequestDto
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                ClientId = defaultClientId,
 | 
					                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
 | 
				
			||||||
                ClientSecret = defaultClientSecret,
 | 
					                    new Claim(ClaimTypes.Name, user.Username),
 | 
				
			||||||
                RefreshToken = token,
 | 
					                    new Claim(ClaimTypes.Email, user.Email)
 | 
				
			||||||
                GrantType = "refresh_token",
 | 
					                }),
 | 
				
			||||||
                Scope = "read write"
 | 
					                Expires = DateTime.UtcNow.AddHours(1),
 | 
				
			||||||
 | 
					                Issuer = jwtSettings["Issuer"],
 | 
				
			||||||
 | 
					                Audience = jwtSettings["Audience"],
 | 
				
			||||||
 | 
					                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 使用OAuth服务刷新令牌
 | 
					            var tokenHandler = new JwtSecurityTokenHandler();
 | 
				
			||||||
            var oauthResult = await _oauthService.RefreshTokenAsync(oauthTokenRequest);
 | 
					            var token = tokenHandler.CreateToken(tokenDescriptor);
 | 
				
			||||||
 | 
					            return tokenHandler.WriteToken(token);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!oauthResult.Success)
 | 
					        private string GenerateRefreshToken()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
                return ApiResponse<string>.ErrorResult(oauthResult.Message ?? "刷新令牌失败");
 | 
					            var randomNumber = new byte[32];
 | 
				
			||||||
            }
 | 
					            using var rng = RandomNumberGenerator.Create();
 | 
				
			||||||
            
 | 
					            rng.GetBytes(randomNumber);
 | 
				
			||||||
            // 返回新的访问令牌
 | 
					            return Convert.ToBase64String(randomNumber);
 | 
				
			||||||
            return ApiResponse<string>.SuccessResult(oauthResult.Data.AccessToken, "令牌刷新成功");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -17,5 +17,15 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
        Task<ApiResponse<bool>> MarkReceivedMailAsReadAsync(int userId, int mailId);
 | 
					        Task<ApiResponse<bool>> MarkReceivedMailAsReadAsync(int userId, int mailId);
 | 
				
			||||||
        Task<ApiResponse<bool>> MarkAsReadAsync(int userId, int mailId);
 | 
					        Task<ApiResponse<bool>> MarkAsReadAsync(int userId, int mailId);
 | 
				
			||||||
        Task<ApiResponse<bool>> RevokeMailAsync(int userId, int mailId);
 | 
					        Task<ApiResponse<bool>> RevokeMailAsync(int userId, int mailId);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 存入胶囊相关方法
 | 
				
			||||||
 | 
					        Task<ApiResponse<SaveToCapsuleResponseDto>> SaveToCapsuleAsync(int userId, SaveToCapsuleDto saveToCapsuleDto);
 | 
				
			||||||
 | 
					        Task<ApiResponse<PagedResponse<CapsuleMailListResponseDto>>> GetCapsuleMailsAsync(int userId, MailListQueryDto queryDto);
 | 
				
			||||||
 | 
					        Task<ApiResponse<CapsuleMailDetailResponseDto>> GetCapsuleMailByIdAsync(int userId, int mailId);
 | 
				
			||||||
 | 
					        Task<ApiResponse<CapsuleMailDetailResponseDto>> UpdateCapsuleMailAsync(int userId, int mailId, UpdateCapsuleMailDto updateDto);
 | 
				
			||||||
 | 
					        Task<ApiResponse<bool>> RevokeCapsuleMailAsync(int userId, int mailId);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 发送至未来功能
 | 
				
			||||||
 | 
					        Task<ApiResponse<SendToFutureResponseDto>> SendToFutureAsync(int userId, SendToFutureDto sendToFutureDto);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -5,15 +5,9 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public interface IOAuthService
 | 
					    public interface IOAuthService
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto);
 | 
					        Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request);
 | 
				
			||||||
        Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId);
 | 
					        Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request);
 | 
				
			||||||
        Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request);
 | 
					        Task<bool> RevokeTokenAsync(string accessToken);
 | 
				
			||||||
        Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request);
 | 
					        Task<OAuthToken?> GetTokenAsync(string accessToken);
 | 
				
			||||||
        Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request);
 | 
					 | 
				
			||||||
        Task<ApiResponse<bool>> RevokeTokenAsync(string token);
 | 
					 | 
				
			||||||
        Task<ApiResponse<bool>> ValidateTokenAsync(string token);
 | 
					 | 
				
			||||||
        Task<OAuthAccessToken?> GetAccessTokenAsync(string token);
 | 
					 | 
				
			||||||
        Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret);
 | 
					 | 
				
			||||||
        Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -48,7 +48,7 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 创建默认OAuth客户端
 | 
					            // 创建默认OAuth客户端
 | 
				
			||||||
            var defaultClient = new OAuthClient
 | 
					            var defaultClient = new Models.OAuthClient
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                ClientId = defaultClientId,
 | 
					                ClientId = defaultClientId,
 | 
				
			||||||
                ClientSecret = "futuremail_default_secret",
 | 
					                ClientSecret = "futuremail_default_secret",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
 | 
				
			|||||||
using FutureMailAPI.Data;
 | 
					using FutureMailAPI.Data;
 | 
				
			||||||
using FutureMailAPI.Models;
 | 
					using FutureMailAPI.Models;
 | 
				
			||||||
using FutureMailAPI.DTOs;
 | 
					using FutureMailAPI.DTOs;
 | 
				
			||||||
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.Services
 | 
					namespace FutureMailAPI.Services
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -382,6 +383,393 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
            return ApiResponse<bool>.SuccessResult(true, "邮件已撤销");
 | 
					            return ApiResponse<bool>.SuccessResult(true, "邮件已撤销");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // 存入胶囊相关方法实现
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<SaveToCapsuleResponseDto>> SaveToCapsuleAsync(int userId, SaveToCapsuleDto saveToCapsuleDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // 验证收件人类型
 | 
				
			||||||
 | 
					            if (saveToCapsuleDto.RecipientType == RecipientTypeEnum.SPECIFIC && string.IsNullOrEmpty(saveToCapsuleDto.RecipientEmail))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<SaveToCapsuleResponseDto>.ErrorResult("指定收件人时,收件人邮箱是必填项");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 验证发送时间
 | 
				
			||||||
 | 
					            if (saveToCapsuleDto.SendTime.HasValue && saveToCapsuleDto.SendTime.Value <= DateTime.UtcNow)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<SaveToCapsuleResponseDto>.ErrorResult("发送时间必须是未来时间");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 创建邮件
 | 
				
			||||||
 | 
					            var mail = new SentMail
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Title = saveToCapsuleDto.Title,
 | 
				
			||||||
 | 
					                Content = saveToCapsuleDto.Content,
 | 
				
			||||||
 | 
					                SenderId = userId,
 | 
				
			||||||
 | 
					                RecipientType = (int)saveToCapsuleDto.RecipientType,
 | 
				
			||||||
 | 
					                SentAt = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                CreatedAt = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                DeliveryTime = saveToCapsuleDto.SendTime ?? DateTime.UtcNow.AddDays(1), // 默认一天后发送
 | 
				
			||||||
 | 
					                Status = 0, // 草稿状态
 | 
				
			||||||
 | 
					                TriggerType = (int)saveToCapsuleDto.TriggerType,
 | 
				
			||||||
 | 
					                TriggerDetails = saveToCapsuleDto.TriggerCondition != null ? JsonSerializer.Serialize(saveToCapsuleDto.TriggerCondition) : null,
 | 
				
			||||||
 | 
					                Attachments = saveToCapsuleDto.Attachments != null ? JsonSerializer.Serialize(saveToCapsuleDto.Attachments) : null,
 | 
				
			||||||
 | 
					                IsEncrypted = saveToCapsuleDto.IsEncrypted,
 | 
				
			||||||
 | 
					                Theme = saveToCapsuleDto.CapsuleStyle
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 如果是指定收件人,查找收件人ID
 | 
				
			||||||
 | 
					            if (saveToCapsuleDto.RecipientType == RecipientTypeEnum.SPECIFIC && !string.IsNullOrEmpty(saveToCapsuleDto.RecipientEmail))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var recipient = await _context.Users
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(u => u.Email == saveToCapsuleDto.RecipientEmail);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                if (recipient == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return ApiResponse<SaveToCapsuleResponseDto>.ErrorResult("收件人不存在");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                mail.RecipientId = recipient.Id;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _context.SentMails.Add(mail);
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 创建时间胶囊
 | 
				
			||||||
 | 
					            var timeCapsule = new TimeCapsule
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                UserId = userId,
 | 
				
			||||||
 | 
					                SentMailId = mail.Id,
 | 
				
			||||||
 | 
					                Status = 0, // 草稿状态
 | 
				
			||||||
 | 
					                CreatedAt = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                PositionX = 0.5f, // 默认位置
 | 
				
			||||||
 | 
					                PositionY = 0.5f,
 | 
				
			||||||
 | 
					                PositionZ = 0.5f,
 | 
				
			||||||
 | 
					                Style = saveToCapsuleDto.CapsuleStyle,
 | 
				
			||||||
 | 
					                GlowIntensity = 0.8f // 默认发光强度
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _context.TimeCapsules.Add(timeCapsule);
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var response = new SaveToCapsuleResponseDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Id = mail.Id,
 | 
				
			||||||
 | 
					                MailId = mail.Id.ToString(),
 | 
				
			||||||
 | 
					                CapsuleId = timeCapsule.Id.ToString(),
 | 
				
			||||||
 | 
					                Status = "DRAFT",
 | 
				
			||||||
 | 
					                CreatedAt = mail.CreatedAt
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return ApiResponse<SaveToCapsuleResponseDto>.SuccessResult(response, "邮件已存入胶囊");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<PagedResponse<CapsuleMailListResponseDto>>> GetCapsuleMailsAsync(int userId, MailListQueryDto queryDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var query = _context.SentMails
 | 
				
			||||||
 | 
					                .Where(m => m.SenderId == userId && m.Status == 0) // 只查询草稿状态的邮件
 | 
				
			||||||
 | 
					                .Include(m => m.Sender)
 | 
				
			||||||
 | 
					                .Include(m => m.Recipient)
 | 
				
			||||||
 | 
					                .Include(m => m.TimeCapsule)
 | 
				
			||||||
 | 
					                .AsQueryable();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 应用筛选条件
 | 
				
			||||||
 | 
					            if (queryDto.Status.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query = query.Where(m => m.Status == queryDto.Status.Value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (queryDto.RecipientType.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query = query.Where(m => m.RecipientType == queryDto.RecipientType.Value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(queryDto.Keyword))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query = query.Where(m => m.Title.Contains(queryDto.Keyword) || m.Content.Contains(queryDto.Keyword));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (queryDto.StartDate.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query = query.Where(m => m.SentAt >= queryDto.StartDate.Value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (queryDto.EndDate.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query = query.Where(m => m.SentAt <= queryDto.EndDate.Value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 排序
 | 
				
			||||||
 | 
					            query = query.OrderByDescending(m => m.SentAt);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 分页
 | 
				
			||||||
 | 
					            var totalCount = await query.CountAsync();
 | 
				
			||||||
 | 
					            var mails = await query
 | 
				
			||||||
 | 
					                .Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
 | 
				
			||||||
 | 
					                .Take(queryDto.PageSize)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var mailDtos = mails.Select(MapToCapsuleMailListResponseDto).ToList();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var pagedResponse = new PagedResponse<CapsuleMailListResponseDto>(
 | 
				
			||||||
 | 
					                mailDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return ApiResponse<PagedResponse<CapsuleMailListResponseDto>>.SuccessResult(pagedResponse);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<CapsuleMailDetailResponseDto>> GetCapsuleMailByIdAsync(int userId, int mailId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var mail = await _context.SentMails
 | 
				
			||||||
 | 
					                .Include(m => m.Sender)
 | 
				
			||||||
 | 
					                .Include(m => m.Recipient)
 | 
				
			||||||
 | 
					                .Include(m => m.TimeCapsule)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if (mail == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("邮件不存在");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var mailDto = MapToCapsuleMailDetailResponseDto(mail);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return ApiResponse<CapsuleMailDetailResponseDto>.SuccessResult(mailDto);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<CapsuleMailDetailResponseDto>> UpdateCapsuleMailAsync(int userId, int mailId, UpdateCapsuleMailDto updateDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var mail = await _context.SentMails
 | 
				
			||||||
 | 
					                .Include(m => m.Sender)
 | 
				
			||||||
 | 
					                .Include(m => m.Recipient)
 | 
				
			||||||
 | 
					                .Include(m => m.TimeCapsule)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if (mail == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("邮件不存在");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 检查邮件状态,只有草稿状态的邮件才能编辑
 | 
				
			||||||
 | 
					            if (mail.Status != 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("只有草稿状态的邮件才能编辑");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 更新邮件信息
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(updateDto.Title))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.Title = updateDto.Title;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(updateDto.Content))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.Content = updateDto.Content;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (updateDto.RecipientType.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.RecipientType = (int)updateDto.RecipientType.Value;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 如果是指定收件人,查找收件人ID
 | 
				
			||||||
 | 
					                if (updateDto.RecipientType.Value == RecipientTypeEnum.SPECIFIC && !string.IsNullOrEmpty(updateDto.RecipientEmail))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var recipient = await _context.Users
 | 
				
			||||||
 | 
					                        .FirstOrDefaultAsync(u => u.Email == updateDto.RecipientEmail);
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                    if (recipient == null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("收件人不存在");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    mail.RecipientId = recipient.Id;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (updateDto.SendTime.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (updateDto.SendTime.Value <= DateTime.UtcNow)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return ApiResponse<CapsuleMailDetailResponseDto>.ErrorResult("发送时间必须是未来时间");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                mail.DeliveryTime = updateDto.SendTime.Value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (updateDto.TriggerType.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.TriggerType = (int)updateDto.TriggerType.Value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (updateDto.TriggerCondition != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.TriggerDetails = JsonSerializer.Serialize(updateDto.TriggerCondition);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (updateDto.Attachments != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.Attachments = JsonSerializer.Serialize(updateDto.Attachments);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (updateDto.IsEncrypted.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.IsEncrypted = updateDto.IsEncrypted.Value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(updateDto.CapsuleStyle))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.Theme = updateDto.CapsuleStyle;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 更新时间胶囊样式
 | 
				
			||||||
 | 
					                if (mail.TimeCapsule != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    mail.TimeCapsule.Style = updateDto.CapsuleStyle;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var mailResponse = MapToCapsuleMailDetailResponseDto(mail);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return ApiResponse<CapsuleMailDetailResponseDto>.SuccessResult(mailResponse, "胶囊邮件更新成功");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<bool>> RevokeCapsuleMailAsync(int userId, int mailId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var mail = await _context.SentMails
 | 
				
			||||||
 | 
					                .Include(m => m.TimeCapsule)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            if (mail == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<bool>.ErrorResult("邮件不存在");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 检查邮件状态,只有草稿或待投递状态的邮件才能撤销
 | 
				
			||||||
 | 
					            if (mail.Status > 1)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<bool>.ErrorResult("已投递的邮件不能撤销");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 更新邮件状态为已撤销
 | 
				
			||||||
 | 
					            mail.Status = 4; // 4-已撤销
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 更新相关的时间胶囊状态
 | 
				
			||||||
 | 
					            if (mail.TimeCapsule != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mail.TimeCapsule.Status = 3; // 3-已撤销
 | 
				
			||||||
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return ApiResponse<bool>.SuccessResult(true, "胶囊邮件已撤销");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        private static CapsuleMailListResponseDto MapToCapsuleMailListResponseDto(SentMail mail)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return new CapsuleMailListResponseDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                MailId = mail.Id.ToString(),
 | 
				
			||||||
 | 
					                Title = mail.Title,
 | 
				
			||||||
 | 
					                Sender = MapToUserInfoDto(mail.Sender),
 | 
				
			||||||
 | 
					                Recipient = mail.Recipient != null ? MapToUserInfoDto(mail.Recipient) : new UserInfoDto(),
 | 
				
			||||||
 | 
					                SendTime = mail.SentAt,
 | 
				
			||||||
 | 
					                DeliveryTime = mail.DeliveryTime,
 | 
				
			||||||
 | 
					                Status = mail.Status switch
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    0 => "DRAFT",
 | 
				
			||||||
 | 
					                    1 => "PENDING",
 | 
				
			||||||
 | 
					                    2 => "DELIVERING",
 | 
				
			||||||
 | 
					                    3 => "DELIVERED",
 | 
				
			||||||
 | 
					                    _ => "UNKNOWN"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                HasAttachments = !string.IsNullOrEmpty(mail.Attachments),
 | 
				
			||||||
 | 
					                IsEncrypted = mail.IsEncrypted,
 | 
				
			||||||
 | 
					                CapsuleStyle = mail.Theme ?? "default",
 | 
				
			||||||
 | 
					                Countdown = mail.Status == 1 ? (int)(mail.DeliveryTime - DateTime.UtcNow).TotalSeconds : null
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        private static CapsuleMailDetailResponseDto MapToCapsuleMailDetailResponseDto(SentMail mail)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            List<AttachmentDto> attachments = new List<AttachmentDto>();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(mail.Attachments))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var attachmentList = JsonSerializer.Deserialize<List<object>>(mail.Attachments);
 | 
				
			||||||
 | 
					                    if (attachmentList != null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        attachments = attachmentList.Select(a => new AttachmentDto
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Id = Guid.NewGuid().ToString(),
 | 
				
			||||||
 | 
					                            Type = "IMAGE", // 默认类型,实际应根据数据解析
 | 
				
			||||||
 | 
					                            Url = a.ToString() ?? "",
 | 
				
			||||||
 | 
					                            Size = 0 // 默认大小,实际应根据数据解析
 | 
				
			||||||
 | 
					                        }).ToList();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // 解析失败时忽略附件
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            object? triggerCondition = null;
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(mail.TriggerDetails))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    triggerCondition = JsonSerializer.Deserialize<object>(mail.TriggerDetails);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // 解析失败时忽略触发条件
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return new CapsuleMailDetailResponseDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                MailId = mail.Id.ToString(),
 | 
				
			||||||
 | 
					                Title = mail.Title,
 | 
				
			||||||
 | 
					                Content = mail.Content,
 | 
				
			||||||
 | 
					                Sender = MapToUserInfoDto(mail.Sender),
 | 
				
			||||||
 | 
					                Recipient = mail.Recipient != null ? MapToUserInfoDto(mail.Recipient) : new UserInfoDto(),
 | 
				
			||||||
 | 
					                SendTime = mail.SentAt,
 | 
				
			||||||
 | 
					                CreatedAt = mail.CreatedAt,
 | 
				
			||||||
 | 
					                DeliveryTime = mail.DeliveryTime,
 | 
				
			||||||
 | 
					                Status = mail.Status switch
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    0 => "DRAFT",
 | 
				
			||||||
 | 
					                    1 => "PENDING",
 | 
				
			||||||
 | 
					                    2 => "DELIVERING",
 | 
				
			||||||
 | 
					                    3 => "DELIVERED",
 | 
				
			||||||
 | 
					                    _ => "UNKNOWN"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                TriggerType = mail.TriggerType switch
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    0 => "TIME",
 | 
				
			||||||
 | 
					                    1 => "LOCATION",
 | 
				
			||||||
 | 
					                    2 => "EVENT",
 | 
				
			||||||
 | 
					                    _ => "UNKNOWN"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                TriggerCondition = triggerCondition,
 | 
				
			||||||
 | 
					                Attachments = attachments,
 | 
				
			||||||
 | 
					                IsEncrypted = mail.IsEncrypted,
 | 
				
			||||||
 | 
					                CapsuleStyle = mail.Theme ?? "default",
 | 
				
			||||||
 | 
					                CanEdit = mail.Status == 0, // 只有草稿状态可编辑
 | 
				
			||||||
 | 
					                CanRevoke = mail.Status <= 1 // 草稿或待投递状态可撤销
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        private static UserInfoDto MapToUserInfoDto(User user)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return new UserInfoDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                UserId = user.Id,
 | 
				
			||||||
 | 
					                Username = user.Username,
 | 
				
			||||||
 | 
					                Avatar = user.Avatar ?? "",
 | 
				
			||||||
 | 
					                Email = user.Email
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        private async Task<SentMailResponseDto> GetSentMailWithDetailsAsync(int mailId)
 | 
					        private async Task<SentMailResponseDto> GetSentMailWithDetailsAsync(int mailId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var mail = await _context.SentMails
 | 
					            var mail = await _context.SentMails
 | 
				
			||||||
@@ -471,5 +859,70 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
                _ => "未知"
 | 
					                _ => "未知"
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// 发送至未来 - 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">用户ID</param>
 | 
				
			||||||
 | 
					        /// <param name="sendToFutureDto">发送至未来请求DTO</param>
 | 
				
			||||||
 | 
					        /// <returns>发送至未来响应DTO</returns>
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<SendToFutureResponseDto>> SendToFutureAsync(int userId, SendToFutureDto sendToFutureDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // 检查投递时间是否在未来
 | 
				
			||||||
 | 
					            if (sendToFutureDto.DeliveryTime <= DateTime.UtcNow)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<SendToFutureResponseDto>.ErrorResult("投递时间必须是未来时间");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 查找邮件
 | 
				
			||||||
 | 
					            var mail = await _context.SentMails
 | 
				
			||||||
 | 
					                .Include(m => m.Sender)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(m => m.Id == sendToFutureDto.MailId && m.SenderId == userId);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (mail == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<SendToFutureResponseDto>.ErrorResult("邮件不存在");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 检查邮件是否为草稿状态
 | 
				
			||||||
 | 
					            if (mail.Status != 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return ApiResponse<SendToFutureResponseDto>.ErrorResult("只有草稿状态的邮件可以设置为发送至未来");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 更新邮件状态和投递时间
 | 
				
			||||||
 | 
					            mail.DeliveryTime = sendToFutureDto.DeliveryTime;
 | 
				
			||||||
 | 
					            mail.Status = 1; // 设置为已发送(待投递)状态
 | 
				
			||||||
 | 
					            mail.SentAt = DateTime.UtcNow; // 设置发送时间为当前时间
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 创建时间胶囊
 | 
				
			||||||
 | 
					            var timeCapsule = new TimeCapsule
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                UserId = userId,
 | 
				
			||||||
 | 
					                SentMailId = mail.Id,
 | 
				
			||||||
 | 
					                PositionX = 0,
 | 
				
			||||||
 | 
					                PositionY = 0,
 | 
				
			||||||
 | 
					                PositionZ = 0,
 | 
				
			||||||
 | 
					                Status = 1, // 漂浮中
 | 
				
			||||||
 | 
					                Type = 0 // 普通
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _context.TimeCapsules.Add(timeCapsule);
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 构建响应
 | 
				
			||||||
 | 
					            var response = new SendToFutureResponseDto
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                MailId = mail.Id,
 | 
				
			||||||
 | 
					                Title = mail.Title,
 | 
				
			||||||
 | 
					                DeliveryTime = mail.DeliveryTime,
 | 
				
			||||||
 | 
					                Status = GetStatusText(mail.Status),
 | 
				
			||||||
 | 
					                SentAt = mail.SentAt
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return ApiResponse<SendToFutureResponseDto>.SuccessResult(response, "邮件已设置为发送至未来");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,343 +1,36 @@
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using FutureMailAPI.Data;
 | 
					using Microsoft.IdentityModel.Tokens;
 | 
				
			||||||
using FutureMailAPI.Models;
 | 
					using System.IdentityModel.Tokens.Jwt;
 | 
				
			||||||
using FutureMailAPI.DTOs;
 | 
					using System.Security.Claims;
 | 
				
			||||||
using FutureMailAPI.Helpers;
 | 
					 | 
				
			||||||
using System.Security.Cryptography;
 | 
					using System.Security.Cryptography;
 | 
				
			||||||
using System.Text.Json;
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using FutureMailAPI.Data;
 | 
				
			||||||
 | 
					using FutureMailAPI.DTOs;
 | 
				
			||||||
 | 
					using FutureMailAPI.Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace FutureMailAPI.Services
 | 
					namespace FutureMailAPI.Services
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class OAuthService : IOAuthService
 | 
					    public class OAuthService : IOAuthService
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly FutureMailDbContext _context;
 | 
					        private readonly FutureMailDbContext _context;
 | 
				
			||||||
 | 
					        private readonly IConfiguration _configuration;
 | 
				
			||||||
        private readonly ILogger<OAuthService> _logger;
 | 
					        private readonly ILogger<OAuthService> _logger;
 | 
				
			||||||
        private readonly IPasswordHelper _passwordHelper;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public OAuthService(FutureMailDbContext context, ILogger<OAuthService> logger, IPasswordHelper passwordHelper)
 | 
					        public OAuthService(FutureMailDbContext context, IConfiguration configuration, ILogger<OAuthService> logger)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
 | 
					            _configuration = configuration;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
            _passwordHelper = passwordHelper;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto)
 | 
					        public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var clientId = GenerateRandomString(32);
 | 
					            try
 | 
				
			||||||
            var clientSecret = GenerateRandomString(64);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var client = new OAuthClient
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ClientId = clientId,
 | 
					 | 
				
			||||||
                ClientSecret = clientSecret,
 | 
					 | 
				
			||||||
                Name = createDto.Name,
 | 
					 | 
				
			||||||
                RedirectUris = JsonSerializer.Serialize(createDto.RedirectUris),
 | 
					 | 
				
			||||||
                Scopes = JsonSerializer.Serialize(createDto.Scopes),
 | 
					 | 
				
			||||||
                IsActive = true,
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow,
 | 
					 | 
				
			||||||
                UpdatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            _context.OAuthClients.Add(client);
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var result = new OAuthClientSecretDto
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ClientId = clientId,
 | 
					 | 
				
			||||||
                ClientSecret = clientSecret
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<OAuthClientSecretDto>.SuccessResult(result, "OAuth客户端创建成功");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                // 验证OAuth客户端
 | 
				
			||||||
                var client = await _context.OAuthClients
 | 
					                var client = await _context.OAuthClients
 | 
				
			||||||
                .FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
 | 
					                    .FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.ClientSecret == request.ClientSecret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (client == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthClientDto>.ErrorResult("客户端不存在");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
 | 
					 | 
				
			||||||
            var scopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var result = new OAuthClientDto
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Id = client.Id,
 | 
					 | 
				
			||||||
                ClientId = client.ClientId,
 | 
					 | 
				
			||||||
                Name = client.Name,
 | 
					 | 
				
			||||||
                RedirectUris = redirectUris,
 | 
					 | 
				
			||||||
                Scopes = scopes,
 | 
					 | 
				
			||||||
                IsActive = client.IsActive,
 | 
					 | 
				
			||||||
                CreatedAt = client.CreatedAt,
 | 
					 | 
				
			||||||
                UpdatedAt = client.UpdatedAt
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<OAuthClientDto>.SuccessResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 验证客户端
 | 
					 | 
				
			||||||
            var client = await _context.OAuthClients
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.IsActive);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            if (client == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的客户端ID");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 验证重定向URI
 | 
					 | 
				
			||||||
            var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
 | 
					 | 
				
			||||||
            if (!redirectUris.Contains(request.RedirectUri))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的重定向URI");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 验证范围
 | 
					 | 
				
			||||||
            var clientScopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
 | 
					 | 
				
			||||||
            var requestedScopes = request.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            foreach (var scope in requestedScopes)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (!clientScopes.Contains(scope))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult($"无效的范围: {scope}");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 生成授权码
 | 
					 | 
				
			||||||
            var code = GenerateRandomString(64);
 | 
					 | 
				
			||||||
            var authorizationCode = new OAuthAuthorizationCode
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Code = code,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = userId,
 | 
					 | 
				
			||||||
                RedirectUri = request.RedirectUri,
 | 
					 | 
				
			||||||
                Scopes = request.Scope,
 | 
					 | 
				
			||||||
                IsUsed = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddMinutes(10), // 授权码10分钟后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            _context.OAuthAuthorizationCodes.Add(authorizationCode);
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var result = new OAuthAuthorizationResponseDto
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Code = code,
 | 
					 | 
				
			||||||
                State = request.State
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<OAuthAuthorizationResponseDto>.SuccessResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 验证客户端
 | 
					 | 
				
			||||||
            var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
 | 
					 | 
				
			||||||
            if (client == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 验证授权码
 | 
					 | 
				
			||||||
            var authCode = await _context.OAuthAuthorizationCodes
 | 
					 | 
				
			||||||
                .Include(c => c.Client)
 | 
					 | 
				
			||||||
                .Include(c => c.User)
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(c => c.Code == request.Code && c.ClientId == client.Id);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            if (authCode == null || authCode.IsUsed || authCode.ExpiresAt < DateTime.UtcNow)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的授权码");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 验证重定向URI
 | 
					 | 
				
			||||||
            if (authCode.RedirectUri != request.RedirectUri)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("重定向URI不匹配");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 标记授权码为已使用
 | 
					 | 
				
			||||||
            authCode.IsUsed = true;
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 生成访问令牌和刷新令牌
 | 
					 | 
				
			||||||
            var accessToken = GenerateRandomString(64);
 | 
					 | 
				
			||||||
            var refreshToken = GenerateRandomString(64);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : authCode.Scopes;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var oauthAccessToken = new OAuthAccessToken
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Token = accessToken,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = authCode.UserId,
 | 
					 | 
				
			||||||
                Scopes = scopes,
 | 
					 | 
				
			||||||
                IsRevoked = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var oauthRefreshToken = new OAuthRefreshToken
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Token = refreshToken,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = authCode.UserId,
 | 
					 | 
				
			||||||
                IsUsed = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            _context.OAuthAccessTokens.Add(oauthAccessToken);
 | 
					 | 
				
			||||||
            _context.OAuthRefreshTokens.Add(oauthRefreshToken);
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var result = new OAuthTokenResponseDto
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                AccessToken = accessToken,
 | 
					 | 
				
			||||||
                TokenType = "Bearer",
 | 
					 | 
				
			||||||
                ExpiresIn = 3600, // 1小时
 | 
					 | 
				
			||||||
                RefreshToken = refreshToken,
 | 
					 | 
				
			||||||
                Scope = scopes
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 验证客户端
 | 
					 | 
				
			||||||
            var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
 | 
					 | 
				
			||||||
            if (client == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 验证刷新令牌
 | 
					 | 
				
			||||||
            var refreshToken = await _context.OAuthRefreshTokens
 | 
					 | 
				
			||||||
                .Include(t => t.Client)
 | 
					 | 
				
			||||||
                .Include(t => t.User)
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(t => t.Token == request.RefreshToken && t.ClientId == client.Id);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            if (refreshToken == null || refreshToken.IsUsed || refreshToken.ExpiresAt < DateTime.UtcNow)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 标记旧刷新令牌为已使用
 | 
					 | 
				
			||||||
            refreshToken.IsUsed = true;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 撤销旧访问令牌
 | 
					 | 
				
			||||||
            var oldAccessTokens = await _context.OAuthAccessTokens
 | 
					 | 
				
			||||||
                .Where(t => t.UserId == refreshToken.UserId && t.ClientId == client.Id && !t.IsRevoked)
 | 
					 | 
				
			||||||
                .ToListAsync();
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            foreach (var token in oldAccessTokens)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                token.IsRevoked = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 生成新的访问令牌和刷新令牌
 | 
					 | 
				
			||||||
            var newAccessToken = GenerateRandomString(64);
 | 
					 | 
				
			||||||
            var newRefreshToken = GenerateRandomString(64);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : "";
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var newOAuthAccessToken = new OAuthAccessToken
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Token = newAccessToken,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = refreshToken.UserId,
 | 
					 | 
				
			||||||
                Scopes = scopes,
 | 
					 | 
				
			||||||
                IsRevoked = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var newOAuthRefreshToken = new OAuthRefreshToken
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Token = newRefreshToken,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = refreshToken.UserId,
 | 
					 | 
				
			||||||
                IsUsed = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            _context.OAuthAccessTokens.Add(newOAuthAccessToken);
 | 
					 | 
				
			||||||
            _context.OAuthRefreshTokens.Add(newOAuthRefreshToken);
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var result = new OAuthTokenResponseDto
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                AccessToken = newAccessToken,
 | 
					 | 
				
			||||||
                TokenType = "Bearer",
 | 
					 | 
				
			||||||
                ExpiresIn = 3600, // 1小时
 | 
					 | 
				
			||||||
                RefreshToken = newRefreshToken,
 | 
					 | 
				
			||||||
                Scope = scopes
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<bool>> RevokeTokenAsync(string token)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var accessToken = await _context.OAuthAccessTokens
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(t => t.Token == token);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            if (accessToken == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<bool>.ErrorResult("令牌不存在");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            accessToken.IsRevoked = true;
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<bool>.SuccessResult(true, "令牌已撤销");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var accessToken = await _context.OAuthAccessTokens
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            if (accessToken == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return ApiResponse<bool>.ErrorResult("无效的令牌");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return ApiResponse<bool>.SuccessResult(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<OAuthAccessToken?> GetAccessTokenAsync(string token)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return await _context.OAuthAccessTokens
 | 
					 | 
				
			||||||
                .Include(t => t.Client)
 | 
					 | 
				
			||||||
                .Include(t => t.User)
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (string.IsNullOrEmpty(clientSecret))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // 公开客户端,只验证客户端ID
 | 
					 | 
				
			||||||
                return await _context.OAuthClients
 | 
					 | 
				
			||||||
                    .FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // 机密客户端,验证客户端ID和密钥
 | 
					 | 
				
			||||||
            return await _context.OAuthClients
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(c => c.ClientId == clientId && c.ClientSecret == clientSecret && c.IsActive);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 验证客户端
 | 
					 | 
				
			||||||
            var client = await GetClientByCredentialsAsync(loginDto.ClientId, loginDto.ClientSecret);
 | 
					 | 
				
			||||||
                if (client == null)
 | 
					                if (client == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
 | 
					                    return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
 | 
				
			||||||
@@ -345,72 +38,167 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // 验证用户凭据
 | 
					                // 验证用户凭据
 | 
				
			||||||
                var user = await _context.Users
 | 
					                var user = await _context.Users
 | 
				
			||||||
                .FirstOrDefaultAsync(u => (u.Email == loginDto.UsernameOrEmail || u.Nickname == loginDto.UsernameOrEmail));
 | 
					                    .FirstOrDefaultAsync(u => u.Email == request.Username);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (user == null)
 | 
					                if (user == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
 | 
					                    return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户不存在");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // 验证密码
 | 
					                // 验证密码
 | 
				
			||||||
            if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
 | 
					                if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
 | 
					                    return ApiResponse<OAuthTokenResponseDto>.ErrorResult("密码错误");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 生成访问令牌和刷新令牌
 | 
					                // 生成访问令牌
 | 
				
			||||||
            var accessToken = GenerateRandomString(64);
 | 
					                var accessToken = GenerateJwtToken(user, client);
 | 
				
			||||||
            var refreshToken = GenerateRandomString(64);
 | 
					                var refreshToken = GenerateRefreshToken();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var oauthAccessToken = new OAuthAccessToken
 | 
					                // 保存令牌到数据库
 | 
				
			||||||
            {
 | 
					                var oauthToken = new OAuthToken
 | 
				
			||||||
                Token = accessToken,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = user.Id,
 | 
					 | 
				
			||||||
                Scopes = loginDto.Scope,
 | 
					 | 
				
			||||||
                IsRevoked = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var oauthRefreshToken = new OAuthRefreshToken
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Token = refreshToken,
 | 
					 | 
				
			||||||
                ClientId = client.Id,
 | 
					 | 
				
			||||||
                UserId = user.Id,
 | 
					 | 
				
			||||||
                IsUsed = false,
 | 
					 | 
				
			||||||
                ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
 | 
					 | 
				
			||||||
                CreatedAt = DateTime.UtcNow
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            _context.OAuthAccessTokens.Add(oauthAccessToken);
 | 
					 | 
				
			||||||
            _context.OAuthRefreshTokens.Add(oauthRefreshToken);
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var result = new OAuthTokenResponseDto
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    AccessToken = accessToken,
 | 
					                    AccessToken = accessToken,
 | 
				
			||||||
                TokenType = "Bearer",
 | 
					 | 
				
			||||||
                ExpiresIn = 3600, // 1小时
 | 
					 | 
				
			||||||
                    RefreshToken = refreshToken,
 | 
					                    RefreshToken = refreshToken,
 | 
				
			||||||
                Scope = loginDto.Scope
 | 
					                    UserId = user.Id,
 | 
				
			||||||
 | 
					                    ClientId = client.Id,
 | 
				
			||||||
 | 
					                    ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时过期
 | 
				
			||||||
 | 
					                    CreatedAt = DateTime.UtcNow
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
 | 
					                _context.OAuthTokens.Add(oauthToken);
 | 
				
			||||||
        }
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private string GenerateRandomString(int length)
 | 
					                var response = new OAuthTokenResponseDto
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 | 
					                    AccessToken = accessToken,
 | 
				
			||||||
            var random = new Random();
 | 
					                    RefreshToken = refreshToken,
 | 
				
			||||||
            var result = new char[length];
 | 
					                    TokenType = "Bearer",
 | 
				
			||||||
 | 
					                    ExpiresIn = 3600 // 1小时,单位秒
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (int i = 0; i < length; i++)
 | 
					                return ApiResponse<OAuthTokenResponseDto>.SuccessResult(response);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                result[i] = chars[random.Next(chars.Length)];
 | 
					                _logger.LogError(ex, "OAuth登录时发生错误");
 | 
				
			||||||
 | 
					                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return new string(result);
 | 
					        public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // 查找刷新令牌
 | 
				
			||||||
 | 
					                var token = await _context.OAuthTokens
 | 
				
			||||||
 | 
					                    .Include(t => t.User)
 | 
				
			||||||
 | 
					                    .Include(t => t.Client)
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(t => t.RefreshToken == request.RefreshToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (token == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 生成新的访问令牌
 | 
				
			||||||
 | 
					                var accessToken = GenerateJwtToken(token.User, token.Client);
 | 
				
			||||||
 | 
					                var refreshToken = GenerateRefreshToken();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 更新令牌
 | 
				
			||||||
 | 
					                token.AccessToken = accessToken;
 | 
				
			||||||
 | 
					                token.RefreshToken = refreshToken;
 | 
				
			||||||
 | 
					                token.ExpiresAt = DateTime.UtcNow.AddHours(1);
 | 
				
			||||||
 | 
					                token.UpdatedAt = DateTime.UtcNow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var response = new OAuthTokenResponseDto
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    AccessToken = accessToken,
 | 
				
			||||||
 | 
					                    RefreshToken = refreshToken,
 | 
				
			||||||
 | 
					                    TokenType = "Bearer",
 | 
				
			||||||
 | 
					                    ExpiresIn = 3600 // 1小时,单位秒
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return ApiResponse<OAuthTokenResponseDto>.SuccessResult(response);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "OAuth刷新令牌时发生错误");
 | 
				
			||||||
 | 
					                return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task<bool> RevokeTokenAsync(string accessToken)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var token = await _context.OAuthTokens
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(t => t.AccessToken == accessToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (token != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _context.OAuthTokens.Remove(token);
 | 
				
			||||||
 | 
					                    await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "OAuth撤销令牌时发生错误");
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task<OAuthToken?> GetTokenAsync(string accessToken)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return await _context.OAuthTokens
 | 
				
			||||||
 | 
					                    .Include(t => t.User)
 | 
				
			||||||
 | 
					                    .Include(t => t.Client)
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(t => t.AccessToken == accessToken && t.ExpiresAt > DateTime.UtcNow);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "获取OAuth令牌时发生错误");
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private string GenerateJwtToken(User user, OAuthClient client)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var jwtSettings = _configuration.GetSection("Jwt");
 | 
				
			||||||
 | 
					            var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
 | 
				
			||||||
 | 
					            var tokenDescriptor = new SecurityTokenDescriptor
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Subject = new ClaimsIdentity(new[]
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
 | 
				
			||||||
 | 
					                    new Claim(ClaimTypes.Name, user.Username),
 | 
				
			||||||
 | 
					                    new Claim(ClaimTypes.Email, user.Email),
 | 
				
			||||||
 | 
					                    new Claim("client_id", client.ClientId)
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                Expires = DateTime.UtcNow.AddHours(1),
 | 
				
			||||||
 | 
					                Issuer = jwtSettings["Issuer"],
 | 
				
			||||||
 | 
					                Audience = jwtSettings["Audience"],
 | 
				
			||||||
 | 
					                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var tokenHandler = new JwtSecurityTokenHandler();
 | 
				
			||||||
 | 
					            var token = tokenHandler.CreateToken(tokenDescriptor);
 | 
				
			||||||
 | 
					            return tokenHandler.WriteToken(token);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private string GenerateRefreshToken()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var randomNumber = new byte[32];
 | 
				
			||||||
 | 
					            using var rng = RandomNumberGenerator.Create();
 | 
				
			||||||
 | 
					            rng.GetBytes(randomNumber);
 | 
				
			||||||
 | 
					            return Convert.ToBase64String(randomNumber);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -50,10 +50,10 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
                return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
 | 
					                return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 生成随机盐值
 | 
					            // 生成盐值
 | 
				
			||||||
            var salt = _passwordHelper.GenerateSalt();
 | 
					            var salt = _passwordHelper.GenerateSalt();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 创建新用户
 | 
					            // 创建新用户(使用正确的密码哈希方法)
 | 
				
			||||||
            var user = new User
 | 
					            var user = new User
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Username = registerDto.Username,
 | 
					                Username = registerDto.Username,
 | 
				
			||||||
@@ -94,7 +94,7 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 验证密码
 | 
					            // 验证密码
 | 
				
			||||||
            if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
 | 
					            if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
 | 
					                return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -103,13 +103,9 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
            user.LastLoginAt = DateTime.UtcNow;
 | 
					            user.LastLoginAt = DateTime.UtcNow;
 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 注意:这里不再生成JWT令牌,因为我们将使用OAuth 2.0
 | 
					            // 创建认证响应(无token版本)
 | 
				
			||||||
            // 在OAuth 2.0流程中,令牌是通过OAuth端点生成的
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            var authResponse = new AuthResponseDto
 | 
					            var authResponse = new AuthResponseDto
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Token = "", // 临时空字符串,实际使用OAuth 2.0令牌
 | 
					 | 
				
			||||||
                Expires = DateTime.UtcNow.AddDays(7),
 | 
					 | 
				
			||||||
                User = MapToUserResponseDto(user)
 | 
					                User = MapToUserResponseDto(user)
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
@@ -225,7 +221,7 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 验证当前密码
 | 
					            // 验证当前密码
 | 
				
			||||||
            if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash))
 | 
					            if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash, user.Salt))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return ApiResponse<bool>.ErrorResult("当前密码错误");
 | 
					                return ApiResponse<bool>.ErrorResult("当前密码错误");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -259,10 +255,10 @@ namespace FutureMailAPI.Services
 | 
				
			|||||||
                return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
 | 
					                return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 生成随机盐值
 | 
					            // 生成盐值
 | 
				
			||||||
            var salt = _passwordHelper.GenerateSalt();
 | 
					            var salt = _passwordHelper.GenerateSalt();
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 创建新用户
 | 
					            // 创建新用户(使用正确的密码哈希方法)
 | 
				
			||||||
            var user = new User
 | 
					            var user = new User
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Username = registerDto.Username,
 | 
					                Username = registerDto.Username,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,53 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Net.Http;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using System.Text.Json;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TestRegisterProgram
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    static async Task Main(string[] args)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var client = new HttpClient();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 生成随机用户名
 | 
					 | 
				
			||||||
        var random = new Random();
 | 
					 | 
				
			||||||
        var username = $"testuser{random.Next(1000, 9999)}";
 | 
					 | 
				
			||||||
        var email = $"{username}@example.com";
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        Console.WriteLine($"尝试注册用户: {username}, 邮箱: {email}");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 创建注册请求
 | 
					 | 
				
			||||||
        var registerData = new
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Username = username,
 | 
					 | 
				
			||||||
            Email = email,
 | 
					 | 
				
			||||||
            Password = "password123"
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        var json = JsonSerializer.Serialize(registerData);
 | 
					 | 
				
			||||||
        var content = new StringContent(json, Encoding.UTF8, "application/json");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // 发送注册请求
 | 
					 | 
				
			||||||
            var response = await client.PostAsync("http://localhost:5001/api/v1/auth/register", content);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (response.IsSuccessStatusCode)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var responseContent = await response.Content.ReadAsStringAsync();
 | 
					 | 
				
			||||||
                Console.WriteLine($"注册成功! 响应内容: {responseContent}");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var errorContent = await response.Content.ReadAsStringAsync();
 | 
					 | 
				
			||||||
                Console.WriteLine($"注册失败! 状态码: {response.StatusCode}");
 | 
					 | 
				
			||||||
                Console.WriteLine($"错误内容: {errorContent}");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Console.WriteLine($"发生异常: {ex.Message}");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -9,20 +9,14 @@
 | 
				
			|||||||
  "ConnectionStrings": {
 | 
					  "ConnectionStrings": {
 | 
				
			||||||
    "DefaultConnection": "Data Source=FutureMail.db"
 | 
					    "DefaultConnection": "Data Source=FutureMail.db"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "JwtSettings": {
 | 
					 | 
				
			||||||
    "SecretKey": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
 | 
					 | 
				
			||||||
    "Issuer": "FutureMailAPI",
 | 
					 | 
				
			||||||
    "Audience": "FutureMailClient",
 | 
					 | 
				
			||||||
    "ExpirationInMinutes": 1440
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "Jwt": {
 | 
					 | 
				
			||||||
    "Key": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
 | 
					 | 
				
			||||||
    "Issuer": "FutureMailAPI",
 | 
					 | 
				
			||||||
    "Audience": "FutureMailClient"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "FileUpload": {
 | 
					  "FileUpload": {
 | 
				
			||||||
    "UploadPath": "uploads",
 | 
					    "UploadPath": "uploads",
 | 
				
			||||||
    "BaseUrl": "http://localhost:5054/uploads",
 | 
					    "BaseUrl": "http://localhost:5054/uploads",
 | 
				
			||||||
    "MaxFileSize": 104857600
 | 
					    "MaxFileSize": 104857600
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "Jwt": {
 | 
				
			||||||
 | 
					    "Key": "ThisIsASecretKeyForJWTTokenGenerationAndValidation123456789",
 | 
				
			||||||
 | 
					    "Issuer": "FutureMailAPI",
 | 
				
			||||||
 | 
					    "Audience": "FutureMailClient"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								FutureMailAPI/bin/Debug/net9.0/BCrypt.Net-Next.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								FutureMailAPI/bin/Debug/net9.0/BCrypt.Net-Next.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -8,20 +8,30 @@
 | 
				
			|||||||
    ".NETCoreApp,Version=v9.0": {
 | 
					    ".NETCoreApp,Version=v9.0": {
 | 
				
			||||||
      "FutureMailAPI/1.0.0": {
 | 
					      "FutureMailAPI/1.0.0": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
 | 
					          "BCrypt.Net-Next": "4.0.3",
 | 
				
			||||||
          "Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.9",
 | 
					          "Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.9",
 | 
				
			||||||
          "Microsoft.AspNetCore.OpenApi": "9.0.9",
 | 
					          "Microsoft.AspNetCore.OpenApi": "9.0.9",
 | 
				
			||||||
          "Microsoft.EntityFrameworkCore.Design": "9.0.9",
 | 
					          "Microsoft.EntityFrameworkCore.Design": "9.0.9",
 | 
				
			||||||
          "Microsoft.EntityFrameworkCore.Sqlite": "9.0.9",
 | 
					          "Microsoft.EntityFrameworkCore.Sqlite": "9.0.9",
 | 
				
			||||||
 | 
					          "Microsoft.IdentityModel.Tokens": "8.3.0",
 | 
				
			||||||
          "Pomelo.EntityFrameworkCore.MySql": "9.0.0",
 | 
					          "Pomelo.EntityFrameworkCore.MySql": "9.0.0",
 | 
				
			||||||
          "Quartz": "3.15.0",
 | 
					          "Quartz": "3.15.0",
 | 
				
			||||||
          "Quartz.Extensions.Hosting": "3.15.0",
 | 
					          "Quartz.Extensions.Hosting": "3.15.0",
 | 
				
			||||||
          "Swashbuckle.AspNetCore": "9.0.6",
 | 
					          "Swashbuckle.AspNetCore": "9.0.6",
 | 
				
			||||||
          "System.IdentityModel.Tokens.Jwt": "8.14.0"
 | 
					          "System.IdentityModel.Tokens.Jwt": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "FutureMailAPI.dll": {}
 | 
					          "FutureMailAPI.dll": {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      "BCrypt.Net-Next/4.0.3": {
 | 
				
			||||||
 | 
					        "runtime": {
 | 
				
			||||||
 | 
					          "lib/net6.0/BCrypt.Net-Next.dll": {
 | 
				
			||||||
 | 
					            "assemblyVersion": "4.0.3.0",
 | 
				
			||||||
 | 
					            "fileVersion": "4.0.3.0"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      "Humanizer.Core/2.14.1": {
 | 
					      "Humanizer.Core/2.14.1": {
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net6.0/Humanizer.dll": {
 | 
					          "lib/net6.0/Humanizer.dll": {
 | 
				
			||||||
@@ -539,39 +549,39 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Abstractions/8.14.0": {
 | 
					      "Microsoft.IdentityModel.Abstractions/8.3.0": {
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
 | 
				
			||||||
            "assemblyVersion": "8.14.0.0",
 | 
					            "assemblyVersion": "8.3.0.0",
 | 
				
			||||||
            "fileVersion": "8.14.0.60815"
 | 
					            "fileVersion": "8.3.0.51204"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
 | 
					      "Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.Tokens": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Tokens": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
 | 
				
			||||||
            "assemblyVersion": "8.14.0.0",
 | 
					            "assemblyVersion": "8.3.0.0",
 | 
				
			||||||
            "fileVersion": "8.14.0.60815"
 | 
					            "fileVersion": "8.3.0.51204"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Logging/8.14.0": {
 | 
					      "Microsoft.IdentityModel.Logging/8.3.0": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.Abstractions": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Abstractions": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
 | 
				
			||||||
            "assemblyVersion": "8.14.0.0",
 | 
					            "assemblyVersion": "8.3.0.0",
 | 
				
			||||||
            "fileVersion": "8.14.0.60815"
 | 
					            "fileVersion": "8.3.0.51204"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Protocols/8.0.1": {
 | 
					      "Microsoft.IdentityModel.Protocols/8.0.1": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.Tokens": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Tokens": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Protocols.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Protocols.dll": {
 | 
				
			||||||
@@ -583,7 +593,7 @@
 | 
				
			|||||||
      "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
 | 
					      "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.Protocols": "8.0.1",
 | 
					          "Microsoft.IdentityModel.Protocols": "8.0.1",
 | 
				
			||||||
          "System.IdentityModel.Tokens.Jwt": "8.14.0"
 | 
					          "System.IdentityModel.Tokens.Jwt": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
 | 
				
			||||||
@@ -592,15 +602,14 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Tokens/8.14.0": {
 | 
					      "Microsoft.IdentityModel.Tokens/8.3.0": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.9",
 | 
					          "Microsoft.IdentityModel.Logging": "8.3.0"
 | 
				
			||||||
          "Microsoft.IdentityModel.Logging": "8.14.0"
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
 | 
				
			||||||
            "assemblyVersion": "8.14.0.0",
 | 
					            "assemblyVersion": "8.3.0.0",
 | 
				
			||||||
            "fileVersion": "8.14.0.60815"
 | 
					            "fileVersion": "8.3.0.51204"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -927,15 +936,15 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "System.IdentityModel.Tokens.Jwt/8.14.0": {
 | 
					      "System.IdentityModel.Tokens.Jwt/8.3.0": {
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.JsonWebTokens": "8.14.0",
 | 
					          "Microsoft.IdentityModel.JsonWebTokens": "8.3.0",
 | 
				
			||||||
          "Microsoft.IdentityModel.Tokens": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Tokens": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "runtime": {
 | 
					        "runtime": {
 | 
				
			||||||
          "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
 | 
					          "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
 | 
				
			||||||
            "assemblyVersion": "8.14.0.0",
 | 
					            "assemblyVersion": "8.3.0.0",
 | 
				
			||||||
            "fileVersion": "8.14.0.60815"
 | 
					            "fileVersion": "8.3.0.51204"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -947,6 +956,13 @@
 | 
				
			|||||||
      "serviceable": false,
 | 
					      "serviceable": false,
 | 
				
			||||||
      "sha512": ""
 | 
					      "sha512": ""
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "BCrypt.Net-Next/4.0.3": {
 | 
				
			||||||
 | 
					      "type": "package",
 | 
				
			||||||
 | 
					      "serviceable": true,
 | 
				
			||||||
 | 
					      "sha512": "sha512-W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==",
 | 
				
			||||||
 | 
					      "path": "bcrypt.net-next/4.0.3",
 | 
				
			||||||
 | 
					      "hashPath": "bcrypt.net-next.4.0.3.nupkg.sha512"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Humanizer.Core/2.14.1": {
 | 
					    "Humanizer.Core/2.14.1": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "serviceable": true,
 | 
					      "serviceable": true,
 | 
				
			||||||
@@ -1136,26 +1152,26 @@
 | 
				
			|||||||
      "path": "microsoft.extensions.primitives/9.0.9",
 | 
					      "path": "microsoft.extensions.primitives/9.0.9",
 | 
				
			||||||
      "hashPath": "microsoft.extensions.primitives.9.0.9.nupkg.sha512"
 | 
					      "hashPath": "microsoft.extensions.primitives.9.0.9.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Abstractions/8.14.0": {
 | 
					    "Microsoft.IdentityModel.Abstractions/8.3.0": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "serviceable": true,
 | 
					      "serviceable": true,
 | 
				
			||||||
      "sha512": "sha512-iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==",
 | 
					      "sha512": "sha512-jNin7yvWZu+K3U24q+6kD+LmGSRfbkHl9Px8hN1XrGwq6ZHgKGi/zuTm5m08G27fwqKfVXIWuIcUeq4Y1VQUOg==",
 | 
				
			||||||
      "path": "microsoft.identitymodel.abstractions/8.14.0",
 | 
					      "path": "microsoft.identitymodel.abstractions/8.3.0",
 | 
				
			||||||
      "hashPath": "microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512"
 | 
					      "hashPath": "microsoft.identitymodel.abstractions.8.3.0.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
 | 
					    "Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "serviceable": true,
 | 
					      "serviceable": true,
 | 
				
			||||||
      "sha512": "sha512-4jOpiA4THdtpLyMdAb24dtj7+6GmvhOhxf5XHLYWmPKF8ApEnApal1UnJsKO4HxUWRXDA6C4WQVfYyqsRhpNpQ==",
 | 
					      "sha512": "sha512-4SVXLT8sDG7CrHiszEBrsDYi+aDW0W9d+fuWUGdZPBdan56aM6fGXJDjbI0TVGEDjJhXbACQd8F/BnC7a+m2RQ==",
 | 
				
			||||||
      "path": "microsoft.identitymodel.jsonwebtokens/8.14.0",
 | 
					      "path": "microsoft.identitymodel.jsonwebtokens/8.3.0",
 | 
				
			||||||
      "hashPath": "microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512"
 | 
					      "hashPath": "microsoft.identitymodel.jsonwebtokens.8.3.0.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Logging/8.14.0": {
 | 
					    "Microsoft.IdentityModel.Logging/8.3.0": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "serviceable": true,
 | 
					      "serviceable": true,
 | 
				
			||||||
      "sha512": "sha512-eqqnemdW38CKZEHS6diA50BV94QICozDZEvSrsvN3SJXUFwVB9gy+/oz76gldP7nZliA16IglXjXTCTdmU/Ejg==",
 | 
					      "sha512": "sha512-4w4pSIGHhCCLTHqtVNR2Cc/zbDIUWIBHTZCu/9ZHm2SVwrXY3RJMcZ7EFGiKqmKZMQZJzA0bpwCZ6R8Yb7i5VQ==",
 | 
				
			||||||
      "path": "microsoft.identitymodel.logging/8.14.0",
 | 
					      "path": "microsoft.identitymodel.logging/8.3.0",
 | 
				
			||||||
      "hashPath": "microsoft.identitymodel.logging.8.14.0.nupkg.sha512"
 | 
					      "hashPath": "microsoft.identitymodel.logging.8.3.0.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Protocols/8.0.1": {
 | 
					    "Microsoft.IdentityModel.Protocols/8.0.1": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
@@ -1171,12 +1187,12 @@
 | 
				
			|||||||
      "path": "microsoft.identitymodel.protocols.openidconnect/8.0.1",
 | 
					      "path": "microsoft.identitymodel.protocols.openidconnect/8.0.1",
 | 
				
			||||||
      "hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512"
 | 
					      "hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Tokens/8.14.0": {
 | 
					    "Microsoft.IdentityModel.Tokens/8.3.0": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "serviceable": true,
 | 
					      "serviceable": true,
 | 
				
			||||||
      "sha512": "sha512-lKIZiBiGd36k02TCdMHp1KlNWisyIvQxcYJvIkz7P4gSQ9zi8dgh6S5Grj8NNG7HWYIPfQymGyoZ6JB5d1Lo1g==",
 | 
					      "sha512": "sha512-yGzqmk+kInH50zeSEH/L1/J0G4/yqTQNq4YmdzOhpE7s/86tz37NS2YbbY2ievbyGjmeBI1mq26QH+yBR6AK3Q==",
 | 
				
			||||||
      "path": "microsoft.identitymodel.tokens/8.14.0",
 | 
					      "path": "microsoft.identitymodel.tokens/8.3.0",
 | 
				
			||||||
      "hashPath": "microsoft.identitymodel.tokens.8.14.0.nupkg.sha512"
 | 
					      "hashPath": "microsoft.identitymodel.tokens.8.3.0.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.OpenApi/1.6.25": {
 | 
					    "Microsoft.OpenApi/1.6.25": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
@@ -1332,12 +1348,12 @@
 | 
				
			|||||||
      "path": "system.composition.typedparts/7.0.0",
 | 
					      "path": "system.composition.typedparts/7.0.0",
 | 
				
			||||||
      "hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512"
 | 
					      "hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "System.IdentityModel.Tokens.Jwt/8.14.0": {
 | 
					    "System.IdentityModel.Tokens.Jwt/8.3.0": {
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "serviceable": true,
 | 
					      "serviceable": true,
 | 
				
			||||||
      "sha512": "sha512-EYGgN/S+HK7S6F3GaaPLFAfK0UzMrkXFyWCvXpQWFYmZln3dqtbyIO7VuTM/iIIPMzkelg8ZLlBPvMhxj6nOAA==",
 | 
					      "sha512": "sha512-9GESpDG0Zb17HD5mBW/uEWi2yz/uKPmCthX2UhyLnk42moGH2FpMgXA2Y4l2Qc7P75eXSUTA6wb/c9D9GSVkzw==",
 | 
				
			||||||
      "path": "system.identitymodel.tokens.jwt/8.14.0",
 | 
					      "path": "system.identitymodel.tokens.jwt/8.3.0",
 | 
				
			||||||
      "hashPath": "system.identitymodel.tokens.jwt.8.14.0.nupkg.sha512"
 | 
					      "hashPath": "system.identitymodel.tokens.jwt.8.3.0.nupkg.sha512"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -25,11 +25,35 @@
 | 
				
			|||||||
            <param name="request">未来预测请求</param>
 | 
					            <param name="request">未来预测请求</param>
 | 
				
			||||||
            <returns>未来预测结果</returns>
 | 
					            <returns>未来预测结果</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.AIController.GetCurrentUserId">
 | 
					        <member name="T:FutureMailAPI.Controllers.BaseController">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					            基础控制器,提供通用的用户身份验证方法
 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserId">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前用户ID
 | 
				
			||||||
 | 
					            兼容OAuth中间件和JWT令牌两种验证方式
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>用户ID,如果未认证则返回0</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserEmail">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前用户邮箱
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>用户邮箱,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUsername">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前用户名
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>用户名,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentClientId">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前客户端ID
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>客户端ID,如果未认证则返回空字符串</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
					        <member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
@@ -59,11 +83,54 @@
 | 
				
			|||||||
            <param name="fileId">文件ID</param>
 | 
					            <param name="fileId">文件ID</param>
 | 
				
			||||||
            <returns>文件信息</returns>
 | 
					            <returns>文件信息</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.FileUploadController.GetCurrentUserId">
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.SaveToCapsule(FutureMailAPI.DTOs.SaveToCapsuleDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            从当前请求中获取用户ID
 | 
					            存入胶囊 - 创建胶囊邮件
 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					            <param name="dto">存入胶囊请求</param>
 | 
				
			||||||
 | 
					            <returns>操作结果</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.GetCapsuleMails(System.Int32,System.Int32,System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取胶囊邮件列表
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="pageIndex">页码</param>
 | 
				
			||||||
 | 
					            <param name="pageSize">页大小</param>
 | 
				
			||||||
 | 
					            <param name="status">状态筛选</param>
 | 
				
			||||||
 | 
					            <param name="recipientType">收件人类型筛选</param>
 | 
				
			||||||
 | 
					            <param name="keyword">关键词搜索</param>
 | 
				
			||||||
 | 
					            <param name="startDate">开始日期</param>
 | 
				
			||||||
 | 
					            <param name="endDate">结束日期</param>
 | 
				
			||||||
 | 
					            <returns>胶囊邮件列表</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.GetCapsuleMail(System.Int32)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取胶囊邮件详情
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					            <returns>胶囊邮件详情</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.UpdateCapsuleMail(System.Int32,FutureMailAPI.DTOs.UpdateCapsuleMailDto)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            更新胶囊邮件
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					            <param name="dto">更新请求</param>
 | 
				
			||||||
 | 
					            <returns>更新后的胶囊邮件详情</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.RevokeCapsuleMail(System.Int32)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            撤销胶囊邮件
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					            <returns>操作结果</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.SendToFuture(FutureMailAPI.DTOs.SendToFutureDto)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            发送至未来 - 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="sendToFutureDto">发送至未来请求DTO</param>
 | 
				
			||||||
 | 
					            <returns>发送至未来响应DTO</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
 | 
					        <member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
@@ -78,47 +145,6 @@
 | 
				
			|||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>通知设置</returns>
 | 
					            <returns>通知设置</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.NotificationController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.Login(FutureMailAPI.DTOs.OAuthLoginDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            OAuth登录端点
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.CreateClient(FutureMailAPI.DTOs.OAuthClientCreateDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            创建OAuth客户端
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.GetClient(System.String)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            获取OAuth客户端信息
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.Authorize(FutureMailAPI.DTOs.OAuthAuthorizationRequestDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            OAuth授权端点
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.ExchangeToken(FutureMailAPI.DTOs.OAuthTokenRequestDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            OAuth令牌端点
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.RevokeToken(System.String,System.String)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            撤销令牌
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.IntrospectToken(System.String)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            验证令牌
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
					        <member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户时间线
 | 
					            获取用户时间线
 | 
				
			||||||
@@ -146,24 +172,12 @@
 | 
				
			|||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户资料</returns>
 | 
					            <returns>用户资料</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从当前请求中获取用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
 | 
					        <member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户统计数据
 | 
					            获取用户统计数据
 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户统计数据</returns>
 | 
					            <returns>用户统计数据</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.StatisticsController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
					        <member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户时间线
 | 
					            获取用户时间线
 | 
				
			||||||
@@ -173,12 +187,6 @@
 | 
				
			|||||||
            <param name="endDate">结束日期</param>
 | 
					            <param name="endDate">结束日期</param>
 | 
				
			||||||
            <returns>用户时间线</returns>
 | 
					            <returns>用户时间线</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.TimelineController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
					        <member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            上传附件
 | 
					            上传附件
 | 
				
			||||||
@@ -193,12 +201,6 @@
 | 
				
			|||||||
            <param name="request">文件上传请求</param>
 | 
					            <param name="request">文件上传请求</param>
 | 
				
			||||||
            <returns>上传结果</returns>
 | 
					            <returns>上传结果</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UploadController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
 | 
					        <member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户订阅信息
 | 
					            获取用户订阅信息
 | 
				
			||||||
@@ -211,25 +213,9 @@
 | 
				
			|||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户资料</returns>
 | 
					            <returns>用户资料</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UserController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
 | 
					        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取当前用户ID
 | 
					            获取当前用户ID(简化版本,不再依赖token)
 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserEmail(Microsoft.AspNetCore.Http.HttpContext)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            获取当前用户邮箱
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentAccessToken(Microsoft.AspNetCore.Http.HttpContext)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            获取当前访问令牌
 | 
					 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
 | 
					        <member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
 | 
				
			||||||
@@ -261,18 +247,6 @@
 | 
				
			|||||||
        <member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
					        <member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="T:FutureMailAPI.Migrations.AddOAuthEntities">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddOAuthEntities.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="T:FutureMailAPI.Migrations.AddSaltToUser">
 | 
					        <member name="T:FutureMailAPI.Migrations.AddSaltToUser">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
@@ -282,8 +256,37 @@
 | 
				
			|||||||
        <member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddSaltToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
					        <member name="T:FutureMailAPI.Migrations.AddSentMailCreatedAt">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSentMailCreatedAt.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSentMailCreatedAt.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSentMailCreatedAt.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="T:FutureMailAPI.Migrations.FixDuplicateForeignKeys">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.FixDuplicateForeignKeys.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.FixDuplicateForeignKeys.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.FixDuplicateForeignKeys.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Services.MailService.SendToFutureAsync(System.Int32,FutureMailAPI.DTOs.SendToFutureDto)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            发送至未来 - 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="userId">用户ID</param>
 | 
				
			||||||
 | 
					            <param name="sendToFutureDto">发送至未来请求DTO</param>
 | 
				
			||||||
 | 
					            <returns>发送至未来响应DTO</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
    </members>
 | 
					    </members>
 | 
				
			||||||
</doc>
 | 
					</doc>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -9,20 +9,14 @@
 | 
				
			|||||||
  "ConnectionStrings": {
 | 
					  "ConnectionStrings": {
 | 
				
			||||||
    "DefaultConnection": "Data Source=FutureMail.db"
 | 
					    "DefaultConnection": "Data Source=FutureMail.db"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "JwtSettings": {
 | 
					 | 
				
			||||||
    "SecretKey": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
 | 
					 | 
				
			||||||
    "Issuer": "FutureMailAPI",
 | 
					 | 
				
			||||||
    "Audience": "FutureMailClient",
 | 
					 | 
				
			||||||
    "ExpirationInMinutes": 1440
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "Jwt": {
 | 
					 | 
				
			||||||
    "Key": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
 | 
					 | 
				
			||||||
    "Issuer": "FutureMailAPI",
 | 
					 | 
				
			||||||
    "Audience": "FutureMailClient"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "FileUpload": {
 | 
					  "FileUpload": {
 | 
				
			||||||
    "UploadPath": "uploads",
 | 
					    "UploadPath": "uploads",
 | 
				
			||||||
    "BaseUrl": "http://localhost:5054/uploads",
 | 
					    "BaseUrl": "http://localhost:5054/uploads",
 | 
				
			||||||
    "MaxFileSize": 104857600
 | 
					    "MaxFileSize": 104857600
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "Jwt": {
 | 
				
			||||||
 | 
					    "Key": "ThisIsASecretKeyForJWTTokenGenerationAndValidation123456789",
 | 
				
			||||||
 | 
					    "Issuer": "FutureMailAPI",
 | 
				
			||||||
 | 
					    "Audience": "FutureMailClient"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "createDto": {
 | 
					 | 
				
			||||||
    "title": "我的第一封未来邮件",
 | 
					 | 
				
			||||||
    "content": "这是一封测试邮件,将在未来某个时间点发送。",
 | 
					 | 
				
			||||||
    "recipientType": 0,
 | 
					 | 
				
			||||||
    "deliveryTime": "2025-12-31T23:59:59Z",
 | 
					 | 
				
			||||||
    "triggerType": 0,
 | 
					 | 
				
			||||||
    "isEncrypted": false,
 | 
					 | 
				
			||||||
    "theme": "default"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
{"title":"My First Future Mail","content":"This is a test email that will be sent in the future.","recipientType":0,"deliveryTime":"2025-12-31T23:59:59Z","triggerType":0,"isEncrypted":false,"theme":"default"}
 | 
					 | 
				
			||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
{"createDto":{"title":"My First Future Mail","content":"This is a test email that will be sent in the future.","recipientType":0,"deliveryTime":"2025-12-31T23:59:59Z","triggerType":0,"isEncrypted":false,"theme":"default"}}
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
// <autogenerated />
 | 
					 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.Reflection;
 | 
					 | 
				
			||||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]
 | 
					 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
//------------------------------------------------------------------------------
 | 
					 | 
				
			||||||
// <auto-generated>
 | 
					 | 
				
			||||||
//     This code was generated by a tool.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//     Changes to this file may cause incorrect behavior and will be lost if
 | 
					 | 
				
			||||||
//     the code is regenerated.
 | 
					 | 
				
			||||||
// </auto-generated>
 | 
					 | 
				
			||||||
//------------------------------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.Reflection;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("FutureMailAPI")]
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyProductAttribute("FutureMailAPI")]
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyTitleAttribute("FutureMailAPI")]
 | 
					 | 
				
			||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 由 MSBuild WriteCodeFragment 类生成。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
7d8082c67f2b527c41664fc0e94b2e397670b265f791e6f31537001d2e72ae7a
 | 
					 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1 +1 @@
 | 
				
			|||||||
16e2bf065c2a49612f8136abef96ed0c83b838d6f5f3e20e08516e88e72d0914
 | 
					96b95197304e11878aba7d6a0ad52cc38a6b9883e011012e3da5e389d5e93831
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,19 @@
 | 
				
			|||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.csproj.AssemblyReference.cache
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.csproj.AssemblyReference.cache
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rpswa.dswa.cache.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rpswa.dswa.cache.json
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.GeneratedMSBuildEditorConfig.editorconfig
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.GeneratedMSBuildEditorConfig.editorconfig
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.AssemblyInfoInputs.cache
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.AssemblyInfo.cs
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.csproj.CoreCompileInputs.cache
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.csproj.CoreCompileInputs.cache
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.MvcApplicationPartsAssemblyInfo.cs
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.MvcApplicationPartsAssemblyInfo.cs
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.MvcApplicationPartsAssemblyInfo.cache
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.MvcApplicationPartsAssemblyInfo.cache
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\appsettings.Development.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\appsettings.Development.json
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\appsettings.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\appsettings.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.runtime.json
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.endpoints.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.endpoints.json
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.exe
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.exe
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.deps.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.deps.json
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.runtimeconfig.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.runtimeconfig.json
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.pdb
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.pdb
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.xml
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Humanizer.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Humanizer.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.AspNetCore.OpenApi.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.AspNetCore.OpenApi.dll
 | 
				
			||||||
@@ -25,10 +25,12 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
 | 
				
			|||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Data.Sqlite.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Abstractions.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Abstractions.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Design.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Design.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Relational.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Relational.dll
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Sqlite.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Caching.Abstractions.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Caching.Abstractions.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Caching.Memory.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Caching.Memory.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Configuration.Abstractions.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Configuration.Abstractions.dll
 | 
				
			||||||
@@ -52,6 +54,9 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
 | 
				
			|||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.Extensions.DependencyInjection.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.Extensions.DependencyInjection.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.Extensions.Hosting.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.Extensions.Hosting.dll
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.batteries_v2.dll
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.core.dll
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.provider.e_sqlite3.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.Swagger.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.Swagger.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.SwaggerGen.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.SwaggerGen.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.SwaggerUI.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.SwaggerUI.dll
 | 
				
			||||||
@@ -127,27 +132,6 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
 | 
				
			|||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjimswa.dswa.cache.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmrazor.dswa.cache.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmcshtml.dswa.cache.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\scopedcss\bundle\FutureMailAPI.styles.css
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json.cache
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.development.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.endpoints.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\swae.build.ex.cache
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMa.9A5350ED.Up2Date
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\refint\FutureMailAPI.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.pdb
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.genruntimeconfig.cache
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\ref\FutureMailAPI.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.runtime.json
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Data.Sqlite.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Sqlite.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.batteries_v2.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.core.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.provider.e_sqlite3.dll
 | 
					 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\browser-wasm\nativeassets\net9.0\e_sqlite3.a
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\browser-wasm\nativeassets\net9.0\e_sqlite3.a
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\linux-arm\native\libe_sqlite3.so
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\linux-arm\native\libe_sqlite3.so
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\linux-arm64\native\libe_sqlite3.so
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\linux-arm64\native\libe_sqlite3.so
 | 
				
			||||||
@@ -169,9 +153,20 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
 | 
				
			|||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-arm64\native\e_sqlite3.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-arm64\native\e_sqlite3.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-x64\native\e_sqlite3.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-x64\native\e_sqlite3.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-x86\native\e_sqlite3.dll
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-x86\native\e_sqlite3.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.xml
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjimswa.dswa.cache.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmrazor.dswa.cache.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmcshtml.dswa.cache.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\scopedcss\bundle\FutureMailAPI.styles.css
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json.cache
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.development.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.endpoints.json
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\swae.build.ex.cache
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMa.9A5350ED.Up2Date
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.dll
 | 
				
			||||||
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\refint\FutureMailAPI.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.xml
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.xml
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\temp_register.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.pdb
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\test_mail.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.genruntimeconfig.cache
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\test_mail_direct.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\ref\FutureMailAPI.dll
 | 
				
			||||||
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\test_mail_simple.json
 | 
					C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\BCrypt.Net-Next.dll
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -25,11 +25,35 @@
 | 
				
			|||||||
            <param name="request">未来预测请求</param>
 | 
					            <param name="request">未来预测请求</param>
 | 
				
			||||||
            <returns>未来预测结果</returns>
 | 
					            <returns>未来预测结果</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.AIController.GetCurrentUserId">
 | 
					        <member name="T:FutureMailAPI.Controllers.BaseController">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					            基础控制器,提供通用的用户身份验证方法
 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserId">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前用户ID
 | 
				
			||||||
 | 
					            兼容OAuth中间件和JWT令牌两种验证方式
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>用户ID,如果未认证则返回0</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserEmail">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前用户邮箱
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>用户邮箱,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUsername">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前用户名
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>用户名,如果未认证则返回空字符串</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentClientId">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取当前客户端ID
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <returns>客户端ID,如果未认证则返回空字符串</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
					        <member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
@@ -59,11 +83,54 @@
 | 
				
			|||||||
            <param name="fileId">文件ID</param>
 | 
					            <param name="fileId">文件ID</param>
 | 
				
			||||||
            <returns>文件信息</returns>
 | 
					            <returns>文件信息</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.FileUploadController.GetCurrentUserId">
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.SaveToCapsule(FutureMailAPI.DTOs.SaveToCapsuleDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            从当前请求中获取用户ID
 | 
					            存入胶囊 - 创建胶囊邮件
 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					            <param name="dto">存入胶囊请求</param>
 | 
				
			||||||
 | 
					            <returns>操作结果</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.GetCapsuleMails(System.Int32,System.Int32,System.Nullable{System.Int32},System.Nullable{System.Int32},System.String,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取胶囊邮件列表
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="pageIndex">页码</param>
 | 
				
			||||||
 | 
					            <param name="pageSize">页大小</param>
 | 
				
			||||||
 | 
					            <param name="status">状态筛选</param>
 | 
				
			||||||
 | 
					            <param name="recipientType">收件人类型筛选</param>
 | 
				
			||||||
 | 
					            <param name="keyword">关键词搜索</param>
 | 
				
			||||||
 | 
					            <param name="startDate">开始日期</param>
 | 
				
			||||||
 | 
					            <param name="endDate">结束日期</param>
 | 
				
			||||||
 | 
					            <returns>胶囊邮件列表</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.GetCapsuleMail(System.Int32)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            获取胶囊邮件详情
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					            <returns>胶囊邮件详情</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.UpdateCapsuleMail(System.Int32,FutureMailAPI.DTOs.UpdateCapsuleMailDto)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            更新胶囊邮件
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					            <param name="dto">更新请求</param>
 | 
				
			||||||
 | 
					            <returns>更新后的胶囊邮件详情</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.RevokeCapsuleMail(System.Int32)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            撤销胶囊邮件
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="id">邮件ID</param>
 | 
				
			||||||
 | 
					            <returns>操作结果</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Controllers.MailsController.SendToFuture(FutureMailAPI.DTOs.SendToFutureDto)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            发送至未来 - 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="sendToFutureDto">发送至未来请求DTO</param>
 | 
				
			||||||
 | 
					            <returns>发送至未来响应DTO</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
 | 
					        <member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
@@ -78,47 +145,6 @@
 | 
				
			|||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>通知设置</returns>
 | 
					            <returns>通知设置</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.NotificationController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.Login(FutureMailAPI.DTOs.OAuthLoginDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            OAuth登录端点
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.CreateClient(FutureMailAPI.DTOs.OAuthClientCreateDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            创建OAuth客户端
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.GetClient(System.String)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            获取OAuth客户端信息
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.Authorize(FutureMailAPI.DTOs.OAuthAuthorizationRequestDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            OAuth授权端点
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.ExchangeToken(FutureMailAPI.DTOs.OAuthTokenRequestDto)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            OAuth令牌端点
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.RevokeToken(System.String,System.String)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            撤销令牌
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.OAuthController.IntrospectToken(System.String)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            验证令牌
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
					        <member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户时间线
 | 
					            获取用户时间线
 | 
				
			||||||
@@ -146,24 +172,12 @@
 | 
				
			|||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户资料</returns>
 | 
					            <returns>用户资料</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从当前请求中获取用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
 | 
					        <member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户统计数据
 | 
					            获取用户统计数据
 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户统计数据</returns>
 | 
					            <returns>用户统计数据</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.StatisticsController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
					        <member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户时间线
 | 
					            获取用户时间线
 | 
				
			||||||
@@ -173,12 +187,6 @@
 | 
				
			|||||||
            <param name="endDate">结束日期</param>
 | 
					            <param name="endDate">结束日期</param>
 | 
				
			||||||
            <returns>用户时间线</returns>
 | 
					            <returns>用户时间线</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.TimelineController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
					        <member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            上传附件
 | 
					            上传附件
 | 
				
			||||||
@@ -193,12 +201,6 @@
 | 
				
			|||||||
            <param name="request">文件上传请求</param>
 | 
					            <param name="request">文件上传请求</param>
 | 
				
			||||||
            <returns>上传结果</returns>
 | 
					            <returns>上传结果</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UploadController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
 | 
					        <member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取用户订阅信息
 | 
					            获取用户订阅信息
 | 
				
			||||||
@@ -211,25 +213,9 @@
 | 
				
			|||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
            <returns>用户资料</returns>
 | 
					            <returns>用户资料</returns>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Controllers.UserController.GetCurrentUserId">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            从JWT令牌中获取当前用户ID
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
            <returns>用户ID</returns>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
 | 
					        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
 | 
				
			||||||
            <summary>
 | 
					            <summary>
 | 
				
			||||||
            获取当前用户ID
 | 
					            获取当前用户ID(简化版本,不再依赖token)
 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserEmail(Microsoft.AspNetCore.Http.HttpContext)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            获取当前用户邮箱
 | 
					 | 
				
			||||||
            </summary>
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentAccessToken(Microsoft.AspNetCore.Http.HttpContext)">
 | 
					 | 
				
			||||||
            <summary>
 | 
					 | 
				
			||||||
            获取当前访问令牌
 | 
					 | 
				
			||||||
            </summary>
 | 
					            </summary>
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
 | 
					        <member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
 | 
				
			||||||
@@ -261,18 +247,6 @@
 | 
				
			|||||||
        <member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
					        <member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="T:FutureMailAPI.Migrations.AddOAuthEntities">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddOAuthEntities.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
					 | 
				
			||||||
            <inheritdoc />
 | 
					 | 
				
			||||||
        </member>
 | 
					 | 
				
			||||||
        <member name="T:FutureMailAPI.Migrations.AddSaltToUser">
 | 
					        <member name="T:FutureMailAPI.Migrations.AddSaltToUser">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
@@ -282,8 +256,37 @@
 | 
				
			|||||||
        <member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
        <member name="M:FutureMailAPI.Migrations.AddSaltToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
					        <member name="T:FutureMailAPI.Migrations.AddSentMailCreatedAt">
 | 
				
			||||||
            <inheritdoc />
 | 
					            <inheritdoc />
 | 
				
			||||||
        </member>
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSentMailCreatedAt.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSentMailCreatedAt.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.AddSentMailCreatedAt.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="T:FutureMailAPI.Migrations.FixDuplicateForeignKeys">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.FixDuplicateForeignKeys.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.FixDuplicateForeignKeys.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Migrations.FixDuplicateForeignKeys.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
 | 
				
			||||||
 | 
					            <inheritdoc />
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
 | 
					        <member name="M:FutureMailAPI.Services.MailService.SendToFutureAsync(System.Int32,FutureMailAPI.DTOs.SendToFutureDto)">
 | 
				
			||||||
 | 
					            <summary>
 | 
				
			||||||
 | 
					            发送至未来 - 将草稿状态的邮件设置为在未来特定时间自动发送
 | 
				
			||||||
 | 
					            </summary>
 | 
				
			||||||
 | 
					            <param name="userId">用户ID</param>
 | 
				
			||||||
 | 
					            <param name="sendToFutureDto">发送至未来请求DTO</param>
 | 
				
			||||||
 | 
					            <returns>发送至未来响应DTO</returns>
 | 
				
			||||||
 | 
					        </member>
 | 
				
			||||||
    </members>
 | 
					    </members>
 | 
				
			||||||
</doc>
 | 
					</doc>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1 +1 @@
 | 
				
			|||||||
{"GlobalPropertiesHash":"1nyXR9zdL54Badakr4zt6ZsTCwUunwdqRSmf7XLLUwI=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","Dh2M8KitOfKPR8IeSNkaC81VB\u002BMSAUycC8vgnJB96As=","t2RF\u002B1UZM5Hw6aYb0b251h1IYJ0pLy2XznEprAc7\u002Bok=","2FbYKHJBLTvh9uGYDfvrsS/W7A7tAnJew9pbpj5fD0c=","2w8tqUWtgtJR3X6RHUFwY6ycOgc9but1QXTeF3XoSAs=","1UDkwWB80qnO\u002B89ANbOkMura0UnCvX\u002B8qPfDZHovrt4=","pIoP9frnT632kkjB7SjrifWUQVG7c11SzIkVZRRvB50=","w/ytggSRqR/oDVkicUa\u002B0V7yzDjGSt0QMxWeKcz9IZM=","KtJa1U2aUQv2tuOjiicNgBLGEaKYqPhKaVpzqk4t85k=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","MbGDnaS5Z1urQXC0aeolLZu50a5W0ICU1IGUKW06M0s=","Qf4B5yCiEiASjhutpOPj/Oq2gQPQj6e4MCKc90vHMnw=","/muOT05SlUwDGWNG078cXDov8tMwl5k9wKSKT8dDlqk="],"CachedAssets":{},"CachedCopyCandidates":{}}
 | 
					{"GlobalPropertiesHash":"1nyXR9zdL54Badakr4zt6ZsTCwUunwdqRSmf7XLLUwI=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","FLPLXKwQVK16UZsWKzZrjH5kq4sMEtCZqAIkpUwa2pU=","ed7if\u002BhRbLX5eYApJ6Bg4oF5be3kjR0VKVKgf\u002BgmyVo=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","talZRfyIQIv4aZc27HTn01\u002B12VbY6LMFOy3\u002B3r448jo=","R7hxHyYox4qeYIBY\u002Bo3l0eo8ZIIfGZeN7PS3V61Q7Ks="],"CachedAssets":{},"CachedCopyCandidates":{}}
 | 
				
			||||||
@@ -1 +1 @@
 | 
				
			|||||||
{"GlobalPropertiesHash":"hRzLFfhtQD0jC2zNthCXf5A5W0LGZjQdHMs0v5Enof8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","Dh2M8KitOfKPR8IeSNkaC81VB\u002BMSAUycC8vgnJB96As=","t2RF\u002B1UZM5Hw6aYb0b251h1IYJ0pLy2XznEprAc7\u002Bok=","2FbYKHJBLTvh9uGYDfvrsS/W7A7tAnJew9pbpj5fD0c=","2w8tqUWtgtJR3X6RHUFwY6ycOgc9but1QXTeF3XoSAs=","1UDkwWB80qnO\u002B89ANbOkMura0UnCvX\u002B8qPfDZHovrt4=","pIoP9frnT632kkjB7SjrifWUQVG7c11SzIkVZRRvB50=","w/ytggSRqR/oDVkicUa\u002B0V7yzDjGSt0QMxWeKcz9IZM=","KtJa1U2aUQv2tuOjiicNgBLGEaKYqPhKaVpzqk4t85k=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","MbGDnaS5Z1urQXC0aeolLZu50a5W0ICU1IGUKW06M0s=","Qf4B5yCiEiASjhutpOPj/Oq2gQPQj6e4MCKc90vHMnw=","/muOT05SlUwDGWNG078cXDov8tMwl5k9wKSKT8dDlqk="],"CachedAssets":{},"CachedCopyCandidates":{}}
 | 
					{"GlobalPropertiesHash":"hRzLFfhtQD0jC2zNthCXf5A5W0LGZjQdHMs0v5Enof8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","FLPLXKwQVK16UZsWKzZrjH5kq4sMEtCZqAIkpUwa2pU=","ed7if\u002BhRbLX5eYApJ6Bg4oF5be3kjR0VKVKgf\u002BgmyVo=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","talZRfyIQIv4aZc27HTn01\u002B12VbY6LMFOy3\u002B3r448jo=","R7hxHyYox4qeYIBY\u002Bo3l0eo8ZIIfGZeN7PS3V61Q7Ks="],"CachedAssets":{},"CachedCopyCandidates":{}}
 | 
				
			||||||
@@ -1 +1 @@
 | 
				
			|||||||
{"GlobalPropertiesHash":"Bto0zkaTl4M6gb1C4K2QiQDhFhr9nJky761xwMrocfc=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","Dh2M8KitOfKPR8IeSNkaC81VB\u002BMSAUycC8vgnJB96As=","t2RF\u002B1UZM5Hw6aYb0b251h1IYJ0pLy2XznEprAc7\u002Bok=","2FbYKHJBLTvh9uGYDfvrsS/W7A7tAnJew9pbpj5fD0c=","2w8tqUWtgtJR3X6RHUFwY6ycOgc9but1QXTeF3XoSAs=","1UDkwWB80qnO\u002B89ANbOkMura0UnCvX\u002B8qPfDZHovrt4="],"CachedAssets":{},"CachedCopyCandidates":{}}
 | 
					{"GlobalPropertiesHash":"Bto0zkaTl4M6gb1C4K2QiQDhFhr9nJky761xwMrocfc=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","FLPLXKwQVK16UZsWKzZrjH5kq4sMEtCZqAIkpUwa2pU="],"CachedAssets":{},"CachedCopyCandidates":{}}
 | 
				
			||||||
@@ -51,6 +51,10 @@
 | 
				
			|||||||
        "net9.0": {
 | 
					        "net9.0": {
 | 
				
			||||||
          "targetAlias": "net9.0",
 | 
					          "targetAlias": "net9.0",
 | 
				
			||||||
          "dependencies": {
 | 
					          "dependencies": {
 | 
				
			||||||
 | 
					            "BCrypt.Net-Next": {
 | 
				
			||||||
 | 
					              "target": "Package",
 | 
				
			||||||
 | 
					              "version": "[4.0.3, )"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            "Microsoft.AspNetCore.Authentication.JwtBearer": {
 | 
					            "Microsoft.AspNetCore.Authentication.JwtBearer": {
 | 
				
			||||||
              "target": "Package",
 | 
					              "target": "Package",
 | 
				
			||||||
              "version": "[9.0.9, )"
 | 
					              "version": "[9.0.9, )"
 | 
				
			||||||
@@ -75,6 +79,10 @@
 | 
				
			|||||||
              "target": "Package",
 | 
					              "target": "Package",
 | 
				
			||||||
              "version": "[9.0.9, )"
 | 
					              "version": "[9.0.9, )"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            "Microsoft.IdentityModel.Tokens": {
 | 
				
			||||||
 | 
					              "target": "Package",
 | 
				
			||||||
 | 
					              "version": "[8.3.0, )"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            "Pomelo.EntityFrameworkCore.MySql": {
 | 
					            "Pomelo.EntityFrameworkCore.MySql": {
 | 
				
			||||||
              "target": "Package",
 | 
					              "target": "Package",
 | 
				
			||||||
              "version": "[9.0.0, )"
 | 
					              "version": "[9.0.0, )"
 | 
				
			||||||
@@ -93,7 +101,7 @@
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            "System.IdentityModel.Tokens.Jwt": {
 | 
					            "System.IdentityModel.Tokens.Jwt": {
 | 
				
			||||||
              "target": "Package",
 | 
					              "target": "Package",
 | 
				
			||||||
              "version": "[8.14.0, )"
 | 
					              "version": "[8.3.0, )"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "imports": [
 | 
					          "imports": [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,9 @@
 | 
				
			|||||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 | 
					<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 | 
				
			||||||
  <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
 | 
					  <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)system.text.json\9.0.9\buildTransitive\net8.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\9.0.9\buildTransitive\net8.0\System.Text.Json.targets')" />
 | 
					    <Import Project="$(NuGetPackageRoot)system.text.json\9.0.9\buildTransitive\net8.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\9.0.9\buildTransitive\net8.0\System.Text.Json.targets')" />
 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
 | 
					 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\9.0.0\build\Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\9.0.0\build\Microsoft.Extensions.ApiDescription.Server.targets')" />
 | 
					    <Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\9.0.0\build\Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\9.0.0\build\Microsoft.Extensions.ApiDescription.Server.targets')" />
 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3\2.1.10\buildTransitive\net9.0\SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3\2.1.10\buildTransitive\net9.0\SQLitePCLRaw.lib.e_sqlite3.targets')" />
 | 
					    <Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3\2.1.10\buildTransitive\net9.0\SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3\2.1.10\buildTransitive\net9.0\SQLitePCLRaw.lib.e_sqlite3.targets')" />
 | 
				
			||||||
 | 
					    <Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)microsoft.extensions.options\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
 | 
					    <Import Project="$(NuGetPackageRoot)microsoft.extensions.options\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)mono.texttemplating\3.0.0\buildTransitive\Mono.TextTemplating.targets" Condition="Exists('$(NuGetPackageRoot)mono.texttemplating\3.0.0\buildTransitive\Mono.TextTemplating.targets')" />
 | 
					    <Import Project="$(NuGetPackageRoot)mono.texttemplating\3.0.0\buildTransitive\Mono.TextTemplating.targets" Condition="Exists('$(NuGetPackageRoot)mono.texttemplating\3.0.0\buildTransitive\Mono.TextTemplating.targets')" />
 | 
				
			||||||
    <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.3.4\buildTransitive\Microsoft.CodeAnalysis.Analyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.3.4\buildTransitive\Microsoft.CodeAnalysis.Analyzers.targets')" />
 | 
					    <Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.3.4\buildTransitive\Microsoft.CodeAnalysis.Analyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.3.4\buildTransitive\Microsoft.CodeAnalysis.Analyzers.targets')" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,19 @@
 | 
				
			|||||||
  "version": 3,
 | 
					  "version": 3,
 | 
				
			||||||
  "targets": {
 | 
					  "targets": {
 | 
				
			||||||
    "net9.0": {
 | 
					    "net9.0": {
 | 
				
			||||||
 | 
					      "BCrypt.Net-Next/4.0.3": {
 | 
				
			||||||
 | 
					        "type": "package",
 | 
				
			||||||
 | 
					        "compile": {
 | 
				
			||||||
 | 
					          "lib/net6.0/BCrypt.Net-Next.dll": {
 | 
				
			||||||
 | 
					            "related": ".xml"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "runtime": {
 | 
				
			||||||
 | 
					          "lib/net6.0/BCrypt.Net-Next.dll": {
 | 
				
			||||||
 | 
					            "related": ".xml"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      "Humanizer.Core/2.14.1": {
 | 
					      "Humanizer.Core/2.14.1": {
 | 
				
			||||||
        "type": "package",
 | 
					        "type": "package",
 | 
				
			||||||
        "compile": {
 | 
					        "compile": {
 | 
				
			||||||
@@ -813,7 +826,7 @@
 | 
				
			|||||||
          "buildTransitive/net8.0/_._": {}
 | 
					          "buildTransitive/net8.0/_._": {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Abstractions/8.14.0": {
 | 
					      "Microsoft.IdentityModel.Abstractions/8.3.0": {
 | 
				
			||||||
        "type": "package",
 | 
					        "type": "package",
 | 
				
			||||||
        "compile": {
 | 
					        "compile": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
 | 
				
			||||||
@@ -826,10 +839,10 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
 | 
					      "Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
 | 
				
			||||||
        "type": "package",
 | 
					        "type": "package",
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.Tokens": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Tokens": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "compile": {
 | 
					        "compile": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
 | 
				
			||||||
@@ -842,10 +855,10 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Logging/8.14.0": {
 | 
					      "Microsoft.IdentityModel.Logging/8.3.0": {
 | 
				
			||||||
        "type": "package",
 | 
					        "type": "package",
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.Abstractions": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Abstractions": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "compile": {
 | 
					        "compile": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
 | 
				
			||||||
@@ -891,11 +904,10 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "Microsoft.IdentityModel.Tokens/8.14.0": {
 | 
					      "Microsoft.IdentityModel.Tokens/8.3.0": {
 | 
				
			||||||
        "type": "package",
 | 
					        "type": "package",
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
 | 
					          "Microsoft.IdentityModel.Logging": "8.3.0"
 | 
				
			||||||
          "Microsoft.IdentityModel.Logging": "8.14.0"
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "compile": {
 | 
					        "compile": {
 | 
				
			||||||
          "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
 | 
					          "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
 | 
				
			||||||
@@ -1364,11 +1376,11 @@
 | 
				
			|||||||
          "buildTransitive/net6.0/_._": {}
 | 
					          "buildTransitive/net6.0/_._": {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "System.IdentityModel.Tokens.Jwt/8.14.0": {
 | 
					      "System.IdentityModel.Tokens.Jwt/8.3.0": {
 | 
				
			||||||
        "type": "package",
 | 
					        "type": "package",
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
          "Microsoft.IdentityModel.JsonWebTokens": "8.14.0",
 | 
					          "Microsoft.IdentityModel.JsonWebTokens": "8.3.0",
 | 
				
			||||||
          "Microsoft.IdentityModel.Tokens": "8.14.0"
 | 
					          "Microsoft.IdentityModel.Tokens": "8.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "compile": {
 | 
					        "compile": {
 | 
				
			||||||
          "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
 | 
					          "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
 | 
				
			||||||
@@ -1476,6 +1488,37 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "libraries": {
 | 
					  "libraries": {
 | 
				
			||||||
 | 
					    "BCrypt.Net-Next/4.0.3": {
 | 
				
			||||||
 | 
					      "sha512": "W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==",
 | 
				
			||||||
 | 
					      "type": "package",
 | 
				
			||||||
 | 
					      "path": "bcrypt.net-next/4.0.3",
 | 
				
			||||||
 | 
					      "files": [
 | 
				
			||||||
 | 
					        ".nupkg.metadata",
 | 
				
			||||||
 | 
					        ".signature.p7s",
 | 
				
			||||||
 | 
					        "bcrypt.net-next.4.0.3.nupkg.sha512",
 | 
				
			||||||
 | 
					        "bcrypt.net-next.nuspec",
 | 
				
			||||||
 | 
					        "ico.png",
 | 
				
			||||||
 | 
					        "lib/net20/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net20/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/net35/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net35/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/net462/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net462/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/net472/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net472/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/net48/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net48/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/net5.0/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net5.0/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/net6.0/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/net6.0/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/netstandard2.0/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/netstandard2.0/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "lib/netstandard2.1/BCrypt.Net-Next.dll",
 | 
				
			||||||
 | 
					        "lib/netstandard2.1/BCrypt.Net-Next.xml",
 | 
				
			||||||
 | 
					        "readme.md"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Humanizer.Core/2.14.1": {
 | 
					    "Humanizer.Core/2.14.1": {
 | 
				
			||||||
      "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==",
 | 
					      "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==",
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
@@ -3345,10 +3388,10 @@
 | 
				
			|||||||
        "useSharedDesignerContext.txt"
 | 
					        "useSharedDesignerContext.txt"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Abstractions/8.14.0": {
 | 
					    "Microsoft.IdentityModel.Abstractions/8.3.0": {
 | 
				
			||||||
      "sha512": "iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==",
 | 
					      "sha512": "jNin7yvWZu+K3U24q+6kD+LmGSRfbkHl9Px8hN1XrGwq6ZHgKGi/zuTm5m08G27fwqKfVXIWuIcUeq4Y1VQUOg==",
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "path": "microsoft.identitymodel.abstractions/8.14.0",
 | 
					      "path": "microsoft.identitymodel.abstractions/8.3.0",
 | 
				
			||||||
      "files": [
 | 
					      "files": [
 | 
				
			||||||
        ".nupkg.metadata",
 | 
					        ".nupkg.metadata",
 | 
				
			||||||
        ".signature.p7s",
 | 
					        ".signature.p7s",
 | 
				
			||||||
@@ -3365,14 +3408,14 @@
 | 
				
			|||||||
        "lib/net9.0/Microsoft.IdentityModel.Abstractions.xml",
 | 
					        "lib/net9.0/Microsoft.IdentityModel.Abstractions.xml",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.xml",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.xml",
 | 
				
			||||||
        "microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512",
 | 
					        "microsoft.identitymodel.abstractions.8.3.0.nupkg.sha512",
 | 
				
			||||||
        "microsoft.identitymodel.abstractions.nuspec"
 | 
					        "microsoft.identitymodel.abstractions.nuspec"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
 | 
					    "Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
 | 
				
			||||||
      "sha512": "4jOpiA4THdtpLyMdAb24dtj7+6GmvhOhxf5XHLYWmPKF8ApEnApal1UnJsKO4HxUWRXDA6C4WQVfYyqsRhpNpQ==",
 | 
					      "sha512": "4SVXLT8sDG7CrHiszEBrsDYi+aDW0W9d+fuWUGdZPBdan56aM6fGXJDjbI0TVGEDjJhXbACQd8F/BnC7a+m2RQ==",
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "path": "microsoft.identitymodel.jsonwebtokens/8.14.0",
 | 
					      "path": "microsoft.identitymodel.jsonwebtokens/8.3.0",
 | 
				
			||||||
      "files": [
 | 
					      "files": [
 | 
				
			||||||
        ".nupkg.metadata",
 | 
					        ".nupkg.metadata",
 | 
				
			||||||
        ".signature.p7s",
 | 
					        ".signature.p7s",
 | 
				
			||||||
@@ -3389,14 +3432,14 @@
 | 
				
			|||||||
        "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.xml",
 | 
					        "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.xml",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.dll",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.dll",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.xml",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.xml",
 | 
				
			||||||
        "microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512",
 | 
					        "microsoft.identitymodel.jsonwebtokens.8.3.0.nupkg.sha512",
 | 
				
			||||||
        "microsoft.identitymodel.jsonwebtokens.nuspec"
 | 
					        "microsoft.identitymodel.jsonwebtokens.nuspec"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Logging/8.14.0": {
 | 
					    "Microsoft.IdentityModel.Logging/8.3.0": {
 | 
				
			||||||
      "sha512": "eqqnemdW38CKZEHS6diA50BV94QICozDZEvSrsvN3SJXUFwVB9gy+/oz76gldP7nZliA16IglXjXTCTdmU/Ejg==",
 | 
					      "sha512": "4w4pSIGHhCCLTHqtVNR2Cc/zbDIUWIBHTZCu/9ZHm2SVwrXY3RJMcZ7EFGiKqmKZMQZJzA0bpwCZ6R8Yb7i5VQ==",
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "path": "microsoft.identitymodel.logging/8.14.0",
 | 
					      "path": "microsoft.identitymodel.logging/8.3.0",
 | 
				
			||||||
      "files": [
 | 
					      "files": [
 | 
				
			||||||
        ".nupkg.metadata",
 | 
					        ".nupkg.metadata",
 | 
				
			||||||
        ".signature.p7s",
 | 
					        ".signature.p7s",
 | 
				
			||||||
@@ -3413,7 +3456,7 @@
 | 
				
			|||||||
        "lib/net9.0/Microsoft.IdentityModel.Logging.xml",
 | 
					        "lib/net9.0/Microsoft.IdentityModel.Logging.xml",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.Logging.dll",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.Logging.dll",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.Logging.xml",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.Logging.xml",
 | 
				
			||||||
        "microsoft.identitymodel.logging.8.14.0.nupkg.sha512",
 | 
					        "microsoft.identitymodel.logging.8.3.0.nupkg.sha512",
 | 
				
			||||||
        "microsoft.identitymodel.logging.nuspec"
 | 
					        "microsoft.identitymodel.logging.nuspec"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -3463,10 +3506,10 @@
 | 
				
			|||||||
        "microsoft.identitymodel.protocols.openidconnect.nuspec"
 | 
					        "microsoft.identitymodel.protocols.openidconnect.nuspec"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Microsoft.IdentityModel.Tokens/8.14.0": {
 | 
					    "Microsoft.IdentityModel.Tokens/8.3.0": {
 | 
				
			||||||
      "sha512": "lKIZiBiGd36k02TCdMHp1KlNWisyIvQxcYJvIkz7P4gSQ9zi8dgh6S5Grj8NNG7HWYIPfQymGyoZ6JB5d1Lo1g==",
 | 
					      "sha512": "yGzqmk+kInH50zeSEH/L1/J0G4/yqTQNq4YmdzOhpE7s/86tz37NS2YbbY2ievbyGjmeBI1mq26QH+yBR6AK3Q==",
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "path": "microsoft.identitymodel.tokens/8.14.0",
 | 
					      "path": "microsoft.identitymodel.tokens/8.3.0",
 | 
				
			||||||
      "files": [
 | 
					      "files": [
 | 
				
			||||||
        ".nupkg.metadata",
 | 
					        ".nupkg.metadata",
 | 
				
			||||||
        ".signature.p7s",
 | 
					        ".signature.p7s",
 | 
				
			||||||
@@ -3483,7 +3526,7 @@
 | 
				
			|||||||
        "lib/net9.0/Microsoft.IdentityModel.Tokens.xml",
 | 
					        "lib/net9.0/Microsoft.IdentityModel.Tokens.xml",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.dll",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.dll",
 | 
				
			||||||
        "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.xml",
 | 
					        "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.xml",
 | 
				
			||||||
        "microsoft.identitymodel.tokens.8.14.0.nupkg.sha512",
 | 
					        "microsoft.identitymodel.tokens.8.3.0.nupkg.sha512",
 | 
				
			||||||
        "microsoft.identitymodel.tokens.nuspec"
 | 
					        "microsoft.identitymodel.tokens.nuspec"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -3992,10 +4035,10 @@
 | 
				
			|||||||
        "useSharedDesignerContext.txt"
 | 
					        "useSharedDesignerContext.txt"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "System.IdentityModel.Tokens.Jwt/8.14.0": {
 | 
					    "System.IdentityModel.Tokens.Jwt/8.3.0": {
 | 
				
			||||||
      "sha512": "EYGgN/S+HK7S6F3GaaPLFAfK0UzMrkXFyWCvXpQWFYmZln3dqtbyIO7VuTM/iIIPMzkelg8ZLlBPvMhxj6nOAA==",
 | 
					      "sha512": "9GESpDG0Zb17HD5mBW/uEWi2yz/uKPmCthX2UhyLnk42moGH2FpMgXA2Y4l2Qc7P75eXSUTA6wb/c9D9GSVkzw==",
 | 
				
			||||||
      "type": "package",
 | 
					      "type": "package",
 | 
				
			||||||
      "path": "system.identitymodel.tokens.jwt/8.14.0",
 | 
					      "path": "system.identitymodel.tokens.jwt/8.3.0",
 | 
				
			||||||
      "files": [
 | 
					      "files": [
 | 
				
			||||||
        ".nupkg.metadata",
 | 
					        ".nupkg.metadata",
 | 
				
			||||||
        ".signature.p7s",
 | 
					        ".signature.p7s",
 | 
				
			||||||
@@ -4012,7 +4055,7 @@
 | 
				
			|||||||
        "lib/net9.0/System.IdentityModel.Tokens.Jwt.xml",
 | 
					        "lib/net9.0/System.IdentityModel.Tokens.Jwt.xml",
 | 
				
			||||||
        "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.dll",
 | 
					        "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.dll",
 | 
				
			||||||
        "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.xml",
 | 
					        "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.xml",
 | 
				
			||||||
        "system.identitymodel.tokens.jwt.8.14.0.nupkg.sha512",
 | 
					        "system.identitymodel.tokens.jwt.8.3.0.nupkg.sha512",
 | 
				
			||||||
        "system.identitymodel.tokens.jwt.nuspec"
 | 
					        "system.identitymodel.tokens.jwt.nuspec"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -4220,16 +4263,18 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "projectFileDependencyGroups": {
 | 
					  "projectFileDependencyGroups": {
 | 
				
			||||||
    "net9.0": [
 | 
					    "net9.0": [
 | 
				
			||||||
 | 
					      "BCrypt.Net-Next >= 4.0.3",
 | 
				
			||||||
      "Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.9",
 | 
					      "Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.9",
 | 
				
			||||||
      "Microsoft.AspNetCore.OpenApi >= 9.0.9",
 | 
					      "Microsoft.AspNetCore.OpenApi >= 9.0.9",
 | 
				
			||||||
      "Microsoft.EntityFrameworkCore.Design >= 9.0.9",
 | 
					      "Microsoft.EntityFrameworkCore.Design >= 9.0.9",
 | 
				
			||||||
      "Microsoft.EntityFrameworkCore.Sqlite >= 9.0.9",
 | 
					      "Microsoft.EntityFrameworkCore.Sqlite >= 9.0.9",
 | 
				
			||||||
      "Microsoft.EntityFrameworkCore.Tools >= 9.0.9",
 | 
					      "Microsoft.EntityFrameworkCore.Tools >= 9.0.9",
 | 
				
			||||||
 | 
					      "Microsoft.IdentityModel.Tokens >= 8.3.0",
 | 
				
			||||||
      "Pomelo.EntityFrameworkCore.MySql >= 9.0.0",
 | 
					      "Pomelo.EntityFrameworkCore.MySql >= 9.0.0",
 | 
				
			||||||
      "Quartz >= 3.15.0",
 | 
					      "Quartz >= 3.15.0",
 | 
				
			||||||
      "Quartz.Extensions.Hosting >= 3.15.0",
 | 
					      "Quartz.Extensions.Hosting >= 3.15.0",
 | 
				
			||||||
      "Swashbuckle.AspNetCore >= 9.0.6",
 | 
					      "Swashbuckle.AspNetCore >= 9.0.6",
 | 
				
			||||||
      "System.IdentityModel.Tokens.Jwt >= 8.14.0"
 | 
					      "System.IdentityModel.Tokens.Jwt >= 8.3.0"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "packageFolders": {
 | 
					  "packageFolders": {
 | 
				
			||||||
@@ -4283,6 +4328,10 @@
 | 
				
			|||||||
      "net9.0": {
 | 
					      "net9.0": {
 | 
				
			||||||
        "targetAlias": "net9.0",
 | 
					        "targetAlias": "net9.0",
 | 
				
			||||||
        "dependencies": {
 | 
					        "dependencies": {
 | 
				
			||||||
 | 
					          "BCrypt.Net-Next": {
 | 
				
			||||||
 | 
					            "target": "Package",
 | 
				
			||||||
 | 
					            "version": "[4.0.3, )"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          "Microsoft.AspNetCore.Authentication.JwtBearer": {
 | 
					          "Microsoft.AspNetCore.Authentication.JwtBearer": {
 | 
				
			||||||
            "target": "Package",
 | 
					            "target": "Package",
 | 
				
			||||||
            "version": "[9.0.9, )"
 | 
					            "version": "[9.0.9, )"
 | 
				
			||||||
@@ -4307,6 +4356,10 @@
 | 
				
			|||||||
            "target": "Package",
 | 
					            "target": "Package",
 | 
				
			||||||
            "version": "[9.0.9, )"
 | 
					            "version": "[9.0.9, )"
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          "Microsoft.IdentityModel.Tokens": {
 | 
				
			||||||
 | 
					            "target": "Package",
 | 
				
			||||||
 | 
					            "version": "[8.3.0, )"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          "Pomelo.EntityFrameworkCore.MySql": {
 | 
					          "Pomelo.EntityFrameworkCore.MySql": {
 | 
				
			||||||
            "target": "Package",
 | 
					            "target": "Package",
 | 
				
			||||||
            "version": "[9.0.0, )"
 | 
					            "version": "[9.0.0, )"
 | 
				
			||||||
@@ -4325,7 +4378,7 @@
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
          "System.IdentityModel.Tokens.Jwt": {
 | 
					          "System.IdentityModel.Tokens.Jwt": {
 | 
				
			||||||
            "target": "Package",
 | 
					            "target": "Package",
 | 
				
			||||||
            "version": "[8.14.0, )"
 | 
					            "version": "[8.3.0, )"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "imports": [
 | 
					        "imports": [
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user