diff --git a/cmd/server/main.go b/cmd/server/main.go
index 3447bfc..879f0b9 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -44,6 +44,7 @@ func main() {
uploadPartHandler := handlers.NewUploadPartHandler(s3Repo)
completeMultipartHandler := handlers.NewCompleteMultipartHandler(s3Repo)
deleteFileHandler := handlers.NewDeleteFileHandler(s3Repo)
+ loginHandler := handlers.NewLoginHandler(middleware.API_KEY_VALUE)
// Register Handlers
mediator.Register[handlers.UploadFileCommand, string](m, uploadHandler)
@@ -58,6 +59,7 @@ func main() {
mediator.Register[handlers.UploadPartCommand, string](m, uploadPartHandler)
mediator.Register[handlers.CompleteMultipartCommand, string](m, completeMultipartHandler)
mediator.Register[handlers.DeleteFileCommand, string](m, deleteFileHandler)
+ mediator.Register[handlers.LoginQuery, handlers.LoginResult](m, loginHandler)
// Validators
uploadValidator := validators.NewUploadFileValidator()
@@ -68,6 +70,7 @@ func main() {
// Endpoints
fileEndpoint := endpoints.NewFileEndpoint(m, uploadValidator, downloadValidator, newFeaturesValidator)
bucketEndpoint := endpoints.NewBucketEndpoint(m, createBucketValidator)
+ authEndpoint := endpoints.NewAuthEndpoint(m)
// Router
r := gin.Default()
@@ -85,6 +88,9 @@ func main() {
c.Next()
})
+ // 公开接口(无需授权)
+ r.POST("/auth/login", authEndpoint.Login)
+
// API授权中间件组
api := r.Group("/")
api.Use(middleware.AuthMiddleware())
diff --git a/docs/LOGIN_GUIDE.md b/docs/LOGIN_GUIDE.md
new file mode 100644
index 0000000..fd991ad
--- /dev/null
+++ b/docs/LOGIN_GUIDE.md
@@ -0,0 +1,174 @@
+# Web 登录功能说明
+
+## 功能概述
+
+Web UI 现已添加密钥登录功能,用户必须先登录才能访问文件管理系统。
+
+## 使用流程
+
+### 1. 访问登录页面
+
+打开浏览器访问:
+```
+http://localhost:8080/web/login.html
+```
+
+### 2. 输入 API 密钥
+
+在登录页面输入 API 密钥:
+```
+xn001624.
+```
+
+### 3. 登录成功
+
+登录成功后,系统会:
+- 将密钥保存到浏览器的 localStorage
+- 自动跳转到主页面 `/web/`
+- 所有后续 API 请求都会自动携带密钥
+
+### 4. 退出登录
+
+点击右上角的"退出"按钮可以:
+- 清除本地存储的密钥
+- 跳转回登录页面
+
+## 技术实现
+
+### 后端 (遵循 CQRS 模式)
+
+1. **登录查询** (`internal/api/handlers/auth_handlers.go`)
+ ```go
+ type LoginQuery struct {
+ APIKey string `json:"api_key" binding:"required"`
+ }
+ ```
+
+2. **登录处理器**
+ ```go
+ type LoginHandler struct {
+ apiKey string
+ }
+
+ func (h *LoginHandler) Handle(ctx context.Context, query LoginQuery) (LoginResult, error)
+ ```
+
+3. **认证端点** (`internal/api/endpoints/auth_endpoints.go`)
+ ```go
+ func (e *AuthEndpoint) Login(c *gin.Context)
+ ```
+
+4. **路由配置** (`cmd/server/main.go`)
+ - `/auth/login` - 公开接口,无需授权
+ - 所有其他 API 接口都需要 `X-API-Key` 请求头
+
+### 前端
+
+1. **登录页面** (`web/login.html`)
+ - 美观的渐变背景登录界面
+ - 密钥输入框(密码类型)
+ - 表单验证和错误提示
+ - 登录后自动跳转
+
+2. **主页面** (`web/index.html`)
+ - 检查 localStorage 中的 token
+ - 未登录显示提示信息
+ - 已登录正常显示文件管理界面
+ - 所有 API 请求自动携带 `X-API-Key` 头
+ - 401 错误自动跳转回登录页
+
+## 安全特性
+
+1. **前端存储**: 使用 localStorage 存储密钥(适合单用户场景)
+2. **自动过期**: 当 API 返回 401 时自动清除密钥并跳转登录
+3. **密钥保护**: 密钥输入框使用 password 类型,屏幕上不可见
+4. **退出功能**: 支持主动退出登录
+
+## 接口说明
+
+### POST /auth/login
+
+**请求体**:
+```json
+{
+ "api_key": "xn001624."
+}
+```
+
+**成功响应** (200):
+```json
+{
+ "success": true,
+ "message": "登录成功",
+ "token": "xn001624."
+}
+```
+
+**失败响应** (200):
+```json
+{
+ "success": false,
+ "message": "API密钥无效"
+}
+```
+
+## 使用示例
+
+### 使用 cURL 测试登录接口
+
+```bash
+curl -X POST http://localhost:8080/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{"api_key": "xn001624."}'
+```
+
+### 前端 API 调用示例
+
+登录后,所有 API 请求会自动携带密钥:
+
+```javascript
+// 登录
+const res = await axios.post('/auth/login', {
+ api_key: 'xn001624.'
+});
+
+// 保存 token
+localStorage.setItem('rustfs_token', res.data.token);
+
+// 后续请求自动携带密钥
+const api = axios.create({
+ baseURL: window.location.origin,
+ headers: {
+ 'X-API-Key': localStorage.getItem('rustfs_token')
+ }
+});
+
+// 调用 API
+const buckets = await api.get('/buckets');
+```
+
+## 文件结构
+
+```
+internal/
+├── api/
+│ ├── endpoints/
+│ │ └── auth_endpoints.go # 认证端点
+│ └── handlers/
+│ └── auth_handlers.go # 登录处理器
+└── middleware/
+ └── auth.go # API 授权中间件
+
+web/
+├── login.html # 登录页面
+└── index.html # 主页面(含登录检查)
+```
+
+## 更新日志
+
+### v1.2
+- ✨ 添加 Web 登录功能
+- ✨ 创建登录页面 UI
+- ✨ 实现 CQRS 模式的登录验证
+- 🔒 所有 Web 操作需要先登录
+- 📝 添加登录文档
diff --git a/internal/api/endpoints/auth_endpoints.go b/internal/api/endpoints/auth_endpoints.go
new file mode 100644
index 0000000..38a8e85
--- /dev/null
+++ b/internal/api/endpoints/auth_endpoints.go
@@ -0,0 +1,45 @@
+package endpoints
+
+import (
+ "file-system/internal/api/handlers"
+ "file-system/internal/infrastructure/mediator"
+
+ "github.com/gin-gonic/gin"
+)
+
+// AuthEndpoint 认证端点
+type AuthEndpoint struct {
+ mediator *mediator.Mediator
+}
+
+// NewAuthEndpoint 创建认证端点
+func NewAuthEndpoint(m *mediator.Mediator) *AuthEndpoint {
+ return &AuthEndpoint{
+ mediator: m,
+ }
+}
+
+// Login 用户登录
+// @Summary 用户登录
+// @Description 使用 API 密钥登录
+// @Tags 认证
+// @Accept json
+// @Produce json
+// @Param request body handlers.LoginQuery true "登录信息"
+// @Success 200 {object} handlers.LoginResult
+// @Router /auth/login [post]
+func (e *AuthEndpoint) Login(c *gin.Context) {
+ var query handlers.LoginQuery
+ if err := c.ShouldBindJSON(&query); err != nil {
+ c.JSON(400, gin.H{"error": "请求参数错误"})
+ return
+ }
+
+ result, err := mediator.Send[handlers.LoginQuery, handlers.LoginResult](e.mediator, c.Request.Context(), query)
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(200, result)
+}
diff --git a/internal/api/handlers/auth_handlers.go b/internal/api/handlers/auth_handlers.go
new file mode 100644
index 0000000..8150eaf
--- /dev/null
+++ b/internal/api/handlers/auth_handlers.go
@@ -0,0 +1,45 @@
+package handlers
+
+import (
+ "context"
+)
+
+// LoginQuery 登录查询
+type LoginQuery struct {
+ APIKey string `json:"api_key" binding:"required"`
+}
+
+// LoginResult 登录结果
+type LoginResult struct {
+ Success bool `json:"success"`
+ Message string `json:"message"`
+ Token string `json:"token,omitempty"`
+}
+
+// LoginHandler 登录处理器
+type LoginHandler struct {
+ apiKey string
+}
+
+// NewLoginHandler 创建登录处理器
+func NewLoginHandler(apiKey string) *LoginHandler {
+ return &LoginHandler{
+ apiKey: apiKey,
+ }
+}
+
+// Handle 处理登录查询
+func (h *LoginHandler) Handle(ctx context.Context, query LoginQuery) (LoginResult, error) {
+ if query.APIKey == h.apiKey {
+ return LoginResult{
+ Success: true,
+ Message: "登录成功",
+ Token: query.APIKey,
+ }, nil
+ }
+
+ return LoginResult{
+ Success: false,
+ Message: "API密钥无效",
+ }, nil
+}
diff --git a/web/index.html b/web/index.html
index c83fc15..a4861af 100644
--- a/web/index.html
+++ b/web/index.html
@@ -26,196 +26,214 @@