首次提交:初始化项目代码
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"cloudnest/internal/application/auth"
|
||||
"cloudnest/internal/application/captcha"
|
||||
"cloudnest/internal/application/email"
|
||||
"cloudnest/internal/pkg/errors"
|
||||
"cloudnest/internal/pkg/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AuthHandler handles HTTP requests for authentication operations.
|
||||
// AuthHandler 处理认证操作的 HTTP 请求。
|
||||
//
|
||||
// This struct acts as the controller layer for authentication endpoints,
|
||||
// converting HTTP requests to service calls and formatting responses.
|
||||
//
|
||||
// 此结构体作为认证端点的控制器层,将 HTTP 请求转换为服务调用并格式化响应。
|
||||
type AuthHandler struct {
|
||||
service *auth.AuthService
|
||||
captchaService *captcha.CaptchaService
|
||||
emailService *email.EmailService
|
||||
}
|
||||
|
||||
// NewAuthHandler creates a new AuthHandler.
|
||||
// NewAuthHandler 创建一个新的 AuthHandler。
|
||||
//
|
||||
// Parameters:
|
||||
// - service: The authentication service to use.
|
||||
// - captchaService: The captcha service to use.
|
||||
// - emailService: The email service to use.
|
||||
//
|
||||
// Returns:
|
||||
// - *AuthHandler: A pointer to the new AuthHandler instance.
|
||||
//
|
||||
// 参数:
|
||||
// - service: 要使用的认证服务。
|
||||
// - captchaService: 要使用的验证码服务。
|
||||
// - emailService: 要使用的邮件服务。
|
||||
//
|
||||
// 返回值:
|
||||
// - *AuthHandler: 新创建的 AuthHandler 实例指针。
|
||||
func NewAuthHandler(service *auth.AuthService, captchaService *captcha.CaptchaService, emailService *email.EmailService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
service: service,
|
||||
captchaService: captchaService,
|
||||
emailService: emailService,
|
||||
}
|
||||
}
|
||||
|
||||
// Register handles user registration requests.
|
||||
// Register 处理用户注册请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/auth/register
|
||||
// - Body: {"username": "string", "password": "string"}
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "注册成功"}
|
||||
// - Conflict: 409 {"code": 409, "message": "用户名已存在"}
|
||||
// - Bad Request: 400 {"code": 400, "message": "参数错误"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: POST
|
||||
// - 路径: /api/v1/auth/register
|
||||
// - 请求体: {"username": "string", "password": "string"}
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "注册成功"}
|
||||
// - 冲突: 409 {"code": 409, "message": "用户名已存在"}
|
||||
// - 请求错误: 400 {"code": 400, "message": "参数错误"}
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req auth.RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.captchaService.Verify(req.CaptchaID, req.CaptchaAnswer) {
|
||||
response.BadRequest(c, "图形验证码错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.Register(req.Username, req.Password, req.Email, req.Nickname, req.EmailCode); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "注册成功")
|
||||
}
|
||||
|
||||
// Login handles user login requests.
|
||||
// Login 处理用户登录请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/auth/login
|
||||
// - Body: {"username": "string", "password": "string"}
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"token": "string"}}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "用户名或密码错误"}
|
||||
// - Bad Request: 400 {"code": 400, "message": "参数错误"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: POST
|
||||
// - 路径: /api/v1/auth/login
|
||||
// - 请求体: {"username": "string", "password": "string"}
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "success", "data": {"token": "string"}}
|
||||
// - 未授权: 401 {"code": 401, "message": "用户名或密码错误"}
|
||||
// - 请求错误: 400 {"code": 400, "message": "参数错误"}
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req auth.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.captchaService.Verify(req.CaptchaID, req.CaptchaAnswer) {
|
||||
response.BadRequest(c, "图形验证码错误")
|
||||
return
|
||||
}
|
||||
|
||||
token, _, err := h.service.Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, auth.TokenResponse{Token: token})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Profile(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if userCode == "" {
|
||||
response.Unauthorized(c, "用户码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.service.GetUserInfo(userCode)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, map[string]interface{}{
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"email": user.Email,
|
||||
"phone": user.Phone,
|
||||
"storage_used": user.StorageUsed,
|
||||
"storage_limit": user.StorageLimit,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) UpdateProfile(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if userCode == "" {
|
||||
response.Unauthorized(c, "用户码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var req auth.UpdateProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.UpdateProfile(userCode, req.Email, req.Phone, req.Nickname); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "更新成功")
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ChangePassword(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if userCode == "" {
|
||||
response.Unauthorized(c, "用户码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var req auth.ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.ChangePassword(userCode, req.CurrentPassword, req.NewPassword); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "密码修改成功")
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cloudnest/internal/application/captcha"
|
||||
"cloudnest/internal/pkg/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CaptchaHandler handles HTTP requests for captcha operations.
|
||||
// CaptchaHandler 处理验证码操作的 HTTP 请求。
|
||||
type CaptchaHandler struct {
|
||||
service *captcha.CaptchaService
|
||||
}
|
||||
|
||||
// NewCaptchaHandler creates a new CaptchaHandler.
|
||||
// NewCaptchaHandler 创建一个新的 CaptchaHandler。
|
||||
func NewCaptchaHandler(service *captcha.CaptchaService) *CaptchaHandler {
|
||||
return &CaptchaHandler{service: service}
|
||||
}
|
||||
|
||||
// GenerateCaptcha handles GET /api/v1/captcha.
|
||||
// GenerateCaptcha 处理 GET /api/v1/captcha。
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"captcha_id": "string", "image_url": "string"}}
|
||||
func (h *CaptchaHandler) GenerateCaptcha(c *gin.Context) {
|
||||
captchaID, imageBytes, err := h.service.Generate()
|
||||
if err != nil {
|
||||
response.InternalError(c, "验证码生成失败")
|
||||
return
|
||||
}
|
||||
|
||||
imageUrl := "/api/v1/captcha/" + captchaID + "/image"
|
||||
// Store image bytes in context for potential direct use, but we return URL here
|
||||
_ = imageBytes
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"captcha_id": captchaID,
|
||||
"image_url": imageUrl,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCaptchaImage handles GET /api/v1/captcha/:id/image.
|
||||
// GetCaptchaImage 处理 GET /api/v1/captcha/:id/image。
|
||||
//
|
||||
// Returns the captcha PNG image directly.
|
||||
func (h *CaptchaHandler) GetCaptchaImage(c *gin.Context) {
|
||||
captchaID := c.Param("id")
|
||||
if captchaID == "" {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
imageBytes, err := h.service.GetImage(captchaID)
|
||||
if err != nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, "image/png", imageBytes)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"cloudnest/internal/application/captcha"
|
||||
"cloudnest/internal/application/email"
|
||||
"cloudnest/internal/pkg/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// EmailHandler handles HTTP requests for email verification code operations.
|
||||
// EmailHandler 处理邮箱验证码操作的 HTTP 请求。
|
||||
type EmailHandler struct {
|
||||
emailService *email.EmailService
|
||||
captchaService *captcha.CaptchaService
|
||||
}
|
||||
|
||||
// NewEmailHandler creates a new EmailHandler.
|
||||
// NewEmailHandler 创建一个新的 EmailHandler。
|
||||
func NewEmailHandler(emailService *email.EmailService, captchaService *captcha.CaptchaService) *EmailHandler {
|
||||
return &EmailHandler{
|
||||
emailService: emailService,
|
||||
captchaService: captchaService,
|
||||
}
|
||||
}
|
||||
|
||||
// SendEmailCodeRequest represents the request body for sending email verification code.
|
||||
// SendEmailCodeRequest 表示发送邮箱验证码的请求体。
|
||||
type SendEmailCodeRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
CaptchaID string `json:"captcha_id" binding:"required"`
|
||||
CaptchaAnswer string `json:"captcha_answer" binding:"required"`
|
||||
}
|
||||
|
||||
// SendEmailCode handles POST /api/v1/auth/email-code.
|
||||
// SendEmailCode 处理 POST /api/v1/auth/email-code。
|
||||
//
|
||||
// It first verifies the captcha, then sends the email verification code.
|
||||
// 先验证图形验证码,通过后才发送邮箱验证码。
|
||||
func (h *EmailHandler) SendEmailCode(c *gin.Context) {
|
||||
var req SendEmailCodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.captchaService.Verify(req.CaptchaID, req.CaptchaAnswer) {
|
||||
response.Error(c, 400, "图形验证码错误")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.emailService.SendVerifyCode(req.Email)
|
||||
if err != nil {
|
||||
if err.Error() == "邮件服务未配置" {
|
||||
response.Error(c, 503, "邮件服务未配置")
|
||||
return
|
||||
}
|
||||
response.InternalError(c, "邮件发送失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "验证码已发送")
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloudnest/internal/application/auth"
|
||||
"cloudnest/internal/application/file"
|
||||
"cloudnest/internal/pkg/errors"
|
||||
"cloudnest/internal/pkg/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type FileHandler struct {
|
||||
service *file.FileService
|
||||
authService *auth.AuthService
|
||||
}
|
||||
|
||||
func NewFileHandler(service *file.FileService, authService *auth.AuthService) *FileHandler {
|
||||
return &FileHandler{service: service, authService: authService}
|
||||
}
|
||||
|
||||
// Upload handles file upload requests.
|
||||
// Upload 处理文件上传请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/files/upload
|
||||
// - Content-Type: multipart/form-data
|
||||
// - Body: file=<file>
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"message": "上传成功", "file": "string", "size": int}}
|
||||
// - Bad Request: 400 {"code": 400, "message": "文件上传失败"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: POST
|
||||
// - 路径: /api/v1/files/upload
|
||||
// - 内容类型: multipart/form-data
|
||||
// - 请求体: file=<file>
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "success", "data": {"message": "上传成功", "file": "string", "size": int}}
|
||||
// - 请求错误: 400 {"code": 400, "message": "文件上传失败"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Upload(c *gin.Context) {
|
||||
f, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
response.BadRequest(c, "文件上传失败")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
userCode := c.GetString("user_code")
|
||||
|
||||
if err := h.authService.CheckStorageLimit(userCode, header.Size); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.Upload(userCode, header.Filename, f, header.Size, header.Header.Get("Content-Type")); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authService.AddStorageUsed(userCode, header.Size); err != nil {
|
||||
response.InternalError(c, "更新存储空间失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, file.UploadResponse{
|
||||
Message: "上传成功",
|
||||
File: userCode + "/" + header.Filename,
|
||||
Size: header.Size,
|
||||
})
|
||||
}
|
||||
|
||||
// List handles file listing requests.
|
||||
// List 处理文件列表请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"files": ["string"]}}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: GET
|
||||
// - 路径: /api/v1/files
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "success", "data": {"files": ["string"]}}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) List(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
fileInfos, err := h.service.List(userCode)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]file.FileItem, len(fileInfos))
|
||||
for i, info := range fileInfos {
|
||||
items[i] = file.FileItem{
|
||||
Key: info.Key,
|
||||
Size: info.Size,
|
||||
LastModified: info.LastModified,
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, file.ListResponse{Files: items})
|
||||
}
|
||||
|
||||
// Download handles file download requests by streaming the file through the backend.
|
||||
// Download 处理文件下载请求,通过后端流式传输文件。
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files/download/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK (file content streamed with Content-Disposition header)
|
||||
// - Not Found: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: GET
|
||||
// - 路径: /api/v1/files/download/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK(文件内容流式传输,带 Content-Disposition 头部)
|
||||
// - 未找到: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Download(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
filename := c.Param("name")
|
||||
|
||||
reader, size, contentType, err := h.service.GetFileStream(userCode, filename)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Set headers for file download / 设置文件下载头部
|
||||
encodedName := url.PathEscape(filename)
|
||||
c.Header("Content-Disposition", "attachment; filename=\""+filename+"\"; filename*=UTF-8''"+encodedName)
|
||||
c.Header("Content-Type", contentType)
|
||||
c.Header("Content-Length", strconv.FormatInt(size, 10))
|
||||
|
||||
// Stream the file content to the response / 将文件内容流式传输到响应
|
||||
c.Status(200)
|
||||
io.Copy(c.Writer, reader)
|
||||
}
|
||||
|
||||
// Delete handles file deletion requests.
|
||||
// Delete 处理文件删除请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: DELETE
|
||||
// - Path: /api/v1/files/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "删除成功"}
|
||||
// - Not Found: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: DELETE
|
||||
// - 路径: /api/v1/files/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "删除成功"}
|
||||
// - 未找到: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Delete(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if err := h.service.Delete(userCode, c.Param("name")); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "删除成功")
|
||||
}
|
||||
|
||||
// SoftDelete handles soft delete requests, moving file to recycle bin.
|
||||
// SoftDelete 处理软删除请求,将文件移动到回收站。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/files/soft-delete/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "已移到回收站"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: POST
|
||||
// - 路径: /api/v1/files/soft-delete/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "已移到回收站"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) SoftDelete(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
userID := c.GetUint("user_id")
|
||||
filename := c.Param("name")
|
||||
var req struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
c.ShouldBindJSON(&req)
|
||||
|
||||
if err := h.service.SoftDelete(userID, userCode, filename, req.Size); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "已移到回收站")
|
||||
}
|
||||
|
||||
// Favorite handles file favorite requests.
|
||||
// Favorite 处理文件收藏请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/files/favorite/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "收藏成功"}
|
||||
// - Bad Request: 400 {"code": 400, "message": "文件已在收藏夹中"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: POST
|
||||
// - 路径: /api/v1/files/favorite/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "收藏成功"}
|
||||
// - 请求错误: 400 {"code": 400, "message": "文件已在收藏夹中"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Favorite(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
userID := c.GetUint("user_id")
|
||||
if err := h.service.Favorite(userID, userCode, c.Param("name")); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "收藏成功")
|
||||
}
|
||||
|
||||
// Unfavorite handles file unfavorite requests.
|
||||
// Unfavorite 处理文件取消收藏请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: DELETE
|
||||
// - Path: /api/v1/files/favorite/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "取消收藏成功"}
|
||||
// - Not Found: 404 {"code": 404, "message": "收藏记录不存在"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: DELETE
|
||||
// - 路径: /api/v1/files/favorite/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "取消收藏成功"}
|
||||
// - 未找到: 404 {"code": 404, "message": "收藏记录不存在"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Unfavorite(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if err := h.service.Unfavorite(userCode, c.Param("name")); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "取消收藏成功")
|
||||
}
|
||||
|
||||
// ListFavorites handles favorite listing requests.
|
||||
// ListFavorites 处理收藏列表请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files/favorites
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"favorites": [...]}}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: GET
|
||||
// - 路径: /api/v1/files/favorites
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "success", "data": {"favorites": [...]}}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) ListFavorites(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
favorites, err := h.service.GetFavorites(userCode)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]file.FavoriteItem, len(favorites))
|
||||
for i, f := range favorites {
|
||||
items[i] = file.FavoriteItem{
|
||||
FileKey: f.FileKey,
|
||||
FileName: strings.TrimPrefix(f.FileKey, userCode+"/"),
|
||||
Size: f.Size,
|
||||
CreatedAt: f.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, file.FavoriteListResponse{Favorites: items})
|
||||
}
|
||||
|
||||
// RecycleBin handles recycle bin listing requests.
|
||||
// RecycleBin 处理回收站列表请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files/recycle-bin
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"files": [...]}}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: GET
|
||||
// - 路径: /api/v1/files/recycle-bin
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "success", "data": {"files": [...]}}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) RecycleBin(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
deletedFiles, err := h.service.GetRecycleBin(userCode)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]file.DeletedFileItem, len(deletedFiles))
|
||||
for i, df := range deletedFiles {
|
||||
items[i] = file.DeletedFileItem{
|
||||
FileKey: df.FileKey,
|
||||
FileName: df.FileName,
|
||||
Size: df.Size,
|
||||
DeletedAt: df.DeletedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, file.RecycleBinResponse{Files: items})
|
||||
}
|
||||
|
||||
// Restore handles file restore requests from recycle bin.
|
||||
// Restore 处理从回收站恢复文件的请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/files/restore/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "恢复成功"}
|
||||
// - Not Found: 404 {"code": 404, "message": "回收站中不存在该文件"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: POST
|
||||
// - 路径: /api/v1/files/restore/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "恢复成功"}
|
||||
// - 未找到: 404 {"code": 404, "message": "回收站中不存在该文件"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Restore(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if _, err := h.service.RestoreFromRecycleBin(userCode, c.Param("name")); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "恢复成功")
|
||||
}
|
||||
|
||||
// DeleteFromRecycleBin handles permanent deletion requests from recycle bin.
|
||||
// DeleteFromRecycleBin 处理从回收站永久删除文件的请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: DELETE
|
||||
// - Path: /api/v1/files/recycle-bin/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "永久删除成功"}
|
||||
// - Not Found: 404 {"code": 404, "message": "回收站中不存在该文件"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: DELETE
|
||||
// - 路径: /api/v1/files/recycle-bin/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "永久删除成功"}
|
||||
// - 未找到: 404 {"code": 404, "message": "回收站中不存在该文件"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) DeleteFromRecycleBin(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
if err := h.service.DeleteFromRecycleBin(userCode, c.Param("name")); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "永久删除成功")
|
||||
}
|
||||
|
||||
// Preview handles file preview requests.
|
||||
// Preview 处理文件预览请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files/preview/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"file_type": "string"}}
|
||||
// - Not Found: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: GET
|
||||
// - 路径: /api/v1/files/preview/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK {"code": 0, "message": "success", "data": {"file_type": "string"}}
|
||||
// - 未找到: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Preview(c *gin.Context) {
|
||||
filename := c.Param("name")
|
||||
|
||||
fileType := "other"
|
||||
if h.service.IsImageFile(filename) {
|
||||
fileType = "image"
|
||||
} else if h.service.IsTextFile(filename) {
|
||||
fileType = "text"
|
||||
} else if h.service.IsPDFFile(filename) {
|
||||
fileType = "pdf"
|
||||
}
|
||||
|
||||
response.Success(c, file.PreviewResponse{FileType: fileType})
|
||||
}
|
||||
|
||||
// Content handles file content requests by streaming the file inline through the backend.
|
||||
// Content 处理文件内容请求,通过后端内联流式传输文件。
|
||||
//
|
||||
// This endpoint is used for file preview (images, PDFs, text) by streaming
|
||||
// the file content directly from MinIO through the Go backend, avoiding
|
||||
// CORS issues and keeping MinIO internal.
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files/content/:name
|
||||
// - Header: Authorization: Bearer <token>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK (file content streamed inline)
|
||||
// - Not Found: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - Unauthorized: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
//
|
||||
// 此端点用于文件预览(图片、PDF、文本),通过 Go 后端直接从 MinIO
|
||||
// 流式传输文件内容,避免 CORS 问题并保持 MinIO 内部可见。
|
||||
//
|
||||
// 请求:
|
||||
// - 方法: GET
|
||||
// - 路径: /api/v1/files/content/:name
|
||||
// - 头部: Authorization: Bearer <token>
|
||||
//
|
||||
// 响应:
|
||||
// - 成功: 200 OK(文件内容内联流式传输)
|
||||
// - 未找到: 404 {"code": 404, "message": "文件不存在"}
|
||||
// - 未授权: 401 {"code": 401, "message": "未提供认证令牌"}
|
||||
func (h *FileHandler) Content(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
filename := c.Param("name")
|
||||
|
||||
reader, size, contentType, err := h.service.GetFileStream(userCode, filename)
|
||||
if err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Set headers for inline preview / 设置内联预览头部
|
||||
c.Header("Content-Disposition", "inline")
|
||||
c.Header("Content-Type", contentType)
|
||||
c.Header("Content-Length", strconv.FormatInt(size, 10))
|
||||
|
||||
// Stream the file content to the response / 将文件内容流式传输到响应
|
||||
c.Status(200)
|
||||
io.Copy(c.Writer, reader)
|
||||
}
|
||||
|
||||
// CheckUpload handles upload check requests for resumable uploads.
|
||||
// CheckUpload 处理断点续传的上传检查请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: GET
|
||||
// - Path: /api/v1/files/upload/check
|
||||
// - Query: hash=<file_hash>&filename=<filename>
|
||||
//
|
||||
// Response:
|
||||
// - Success: 200 OK {"code": 0, "message": "success", "data": {"uploaded_chunks": [0, 1, 2]}}
|
||||
func (h *FileHandler) CheckUpload(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
hash := c.Query("hash")
|
||||
if hash == "" {
|
||||
response.BadRequest(c, "缺少文件哈希")
|
||||
return
|
||||
}
|
||||
|
||||
chunks, err := h.service.GetUploadedChunks(userCode, hash)
|
||||
if err != nil {
|
||||
response.InternalError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, map[string]interface{}{"uploaded_chunks": chunks})
|
||||
}
|
||||
|
||||
// UploadChunk handles chunk upload requests for resumable uploads.
|
||||
// UploadChunk 处理断点续传的分片上传请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/files/upload/chunk
|
||||
// - Content-Type: multipart/form-data
|
||||
// - Body: file=<chunk_data>&hash=<file_hash>&chunk_index=<index>&chunk_count=<count>&filename=<filename>&file_size=<size>
|
||||
func (h *FileHandler) UploadChunk(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
|
||||
f, _, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
response.BadRequest(c, "分片上传失败")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
hash := c.PostForm("hash")
|
||||
chunkIndexStr := c.PostForm("chunk_index")
|
||||
fileSizeStr := c.PostForm("file_size")
|
||||
|
||||
if hash == "" || chunkIndexStr == "" || fileSizeStr == "" {
|
||||
response.BadRequest(c, "缺少必要参数")
|
||||
return
|
||||
}
|
||||
|
||||
chunkIndex, err := strconv.Atoi(chunkIndexStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "分片索引无效")
|
||||
return
|
||||
}
|
||||
|
||||
fileSize, err := strconv.ParseInt(fileSizeStr, 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "文件大小无效")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authService.CheckStorageLimit(userCode, fileSize); err != nil {
|
||||
if appErr, ok := errors.AsAppError(err); ok {
|
||||
response.Error(c, appErr.Code, appErr.Message)
|
||||
} else {
|
||||
response.InternalError(c, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "读取分片数据失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.SaveChunk(userCode, hash, chunkIndex, data); err != nil {
|
||||
response.InternalError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "分片上传成功")
|
||||
}
|
||||
|
||||
// CompleteUpload handles upload completion requests for resumable uploads.
|
||||
// CompleteUpload 处理断点续传的上传完成请求。
|
||||
//
|
||||
// Request:
|
||||
// - Method: POST
|
||||
// - Path: /api/v1/files/upload/complete
|
||||
// - Body: {"hash": "string", "filename": "string", "chunk_count": int, "file_size": int}
|
||||
func (h *FileHandler) CompleteUpload(c *gin.Context) {
|
||||
userCode := c.GetString("user_code")
|
||||
|
||||
var req struct {
|
||||
Hash string `json:"hash"`
|
||||
Filename string `json:"filename"`
|
||||
ChunkCount int `json:"chunk_count"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Hash == "" || req.Filename == "" || req.ChunkCount <= 0 {
|
||||
response.BadRequest(c, "缺少必要参数")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.MergeChunks(userCode, req.Hash, req.Filename, req.ChunkCount, req.FileSize); err != nil {
|
||||
response.InternalError(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authService.AddStorageUsed(userCode, req.FileSize); err != nil {
|
||||
response.InternalError(c, "更新存储空间失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessMsg(c, "上传完成")
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"cloudnest/internal/interface/http/handler"
|
||||
"cloudnest/internal/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterAuthRoutes registers authentication-related routes.
|
||||
// RegisterAuthRoutes 注册认证相关的路由。
|
||||
//
|
||||
// Parameters:
|
||||
// - r: The router group to register routes on.
|
||||
// - h: The authentication handler.
|
||||
// - emailHandler: The email verification handler.
|
||||
//
|
||||
// Routes:
|
||||
// - POST /register: Register a new user.
|
||||
// - POST /login: Authenticate and get JWT token.
|
||||
// - POST /email-code: Send email verification code.
|
||||
// - GET /profile: Get user profile (requires JWT).
|
||||
// - PUT /profile: Update user profile (requires JWT).
|
||||
// - PUT /password: Change password (requires JWT).
|
||||
//
|
||||
// 参数:
|
||||
// - r: 要注册路由的路由器组。
|
||||
// - h: 认证处理器。
|
||||
// - emailHandler: 邮箱验证码处理器。
|
||||
//
|
||||
// 路由:
|
||||
// - POST /register: 注册新用户。
|
||||
// - POST /login: 认证并获取 JWT 令牌。
|
||||
// - POST /email-code: 发送邮箱验证码。
|
||||
// - GET /profile: 获取用户个人信息(需要 JWT)。
|
||||
// - PUT /profile: 更新用户个人信息(需要 JWT)。
|
||||
// - PUT /password: 修改密码(需要 JWT)。
|
||||
func RegisterAuthRoutes(r *gin.RouterGroup, h *handler.AuthHandler, emailHandler *handler.EmailHandler, jwtSecret string) {
|
||||
r.POST("/register", h.Register)
|
||||
r.POST("/login", h.Login)
|
||||
r.POST("/email-code", emailHandler.SendEmailCode)
|
||||
|
||||
protected := r.Group("")
|
||||
protected.Use(middleware.JWT(jwtSecret))
|
||||
protected.GET("/profile", h.Profile)
|
||||
protected.PUT("/profile", h.UpdateProfile)
|
||||
protected.PUT("/password", h.ChangePassword)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"cloudnest/internal/interface/http/handler"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterCaptchaRoutes registers captcha-related routes.
|
||||
// RegisterCaptchaRoutes 注册验证码相关的路由。
|
||||
//
|
||||
// Routes:
|
||||
// - GET /captcha: Generate a new captcha (returns captcha_id and image_url).
|
||||
// - GET /captcha/:id/image: Get captcha image by ID.
|
||||
func RegisterCaptchaRoutes(r *gin.RouterGroup, h *handler.CaptchaHandler) {
|
||||
r.GET("/captcha", h.GenerateCaptcha)
|
||||
r.GET("/captcha/:id/image", h.GetCaptchaImage)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"cloudnest/internal/interface/http/handler"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterFileRoutes registers file management routes.
|
||||
// RegisterFileRoutes 注册文件管理路由。
|
||||
//
|
||||
// Parameters:
|
||||
// - r: The router group to register routes on.
|
||||
// - h: The file handler.
|
||||
//
|
||||
// Routes:
|
||||
// - POST /upload: Upload a file.
|
||||
// - GET /: List all files for the authenticated user.
|
||||
// - GET /download/:name: Get a pre-signed download URL.
|
||||
// - DELETE /:name: Delete a file.
|
||||
//
|
||||
// Note:
|
||||
// - These routes require JWT authentication.
|
||||
//
|
||||
// 参数:
|
||||
// - r: 要注册路由的路由器组。
|
||||
// - h: 文件处理器。
|
||||
//
|
||||
// 路由:
|
||||
// - POST /upload: 上传文件。
|
||||
// - GET /: 获取认证用户的所有文件列表。
|
||||
// - GET /download/:name: 获取预签名下载 URL。
|
||||
// - DELETE /:name: 删除文件。
|
||||
//
|
||||
// 注意:
|
||||
// - 这些路由需要 JWT 认证。
|
||||
func RegisterFileRoutes(r *gin.RouterGroup, h *handler.FileHandler) {
|
||||
r.POST("/upload", h.Upload)
|
||||
r.GET("/upload/check", h.CheckUpload)
|
||||
r.POST("/upload/chunk", h.UploadChunk)
|
||||
r.POST("/upload/complete", h.CompleteUpload)
|
||||
r.GET("/", h.List)
|
||||
r.GET("/download/:name", h.Download)
|
||||
r.GET("/content/:name", h.Content)
|
||||
r.DELETE("/:name", h.Delete)
|
||||
r.POST("/soft-delete/:name", h.SoftDelete)
|
||||
r.POST("/favorite/:name", h.Favorite)
|
||||
r.DELETE("/favorite/:name", h.Unfavorite)
|
||||
r.GET("/favorites", h.ListFavorites)
|
||||
r.GET("/recycle-bin", h.RecycleBin)
|
||||
r.POST("/restore/:name", h.Restore)
|
||||
r.DELETE("/recycle-bin/:name", h.DeleteFromRecycleBin)
|
||||
r.GET("/preview/:name", h.Preview)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"cloudnest/internal/interface/http/handler"
|
||||
"cloudnest/internal/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ServiceType defines the type of service that is running.
|
||||
// ServiceType 定义运行的服务类型。
|
||||
type ServiceType string
|
||||
|
||||
const (
|
||||
// ServiceTypeAuth is the authentication service.
|
||||
// ServiceTypeAuth 是认证服务。
|
||||
ServiceTypeAuth ServiceType = "auth"
|
||||
|
||||
// ServiceTypeFile is the file management service.
|
||||
// ServiceTypeFile 是文件管理服务。
|
||||
ServiceTypeFile ServiceType = "file"
|
||||
|
||||
// ServiceTypeAll is for testing purposes, registers all routes.
|
||||
// ServiceTypeAll 用于测试目的,注册所有路由。
|
||||
ServiceTypeAll ServiceType = "all"
|
||||
)
|
||||
|
||||
// SetupRoutes registers API routes based on the service type.
|
||||
// SetupRoutes 根据服务类型注册 API 路由。
|
||||
//
|
||||
// Parameters:
|
||||
// - engine: The Gin engine instance.
|
||||
// - serviceType: The type of service (auth, file, or all).
|
||||
// - authHandler: The authentication handler (required for auth service).
|
||||
// - fileHandler: The file handler (required for file service).
|
||||
// - jwtSecret: The secret key for JWT token validation.
|
||||
//
|
||||
// Note:
|
||||
// - Authentication routes are public and don't require JWT.
|
||||
// - File routes require JWT authentication via middleware.JWT.
|
||||
// - Each service should only register its own routes to avoid conflicts.
|
||||
//
|
||||
// 参数:
|
||||
// - engine: Gin 引擎实例。
|
||||
// - serviceType: 服务类型 (auth, file, 或 all)。
|
||||
// - authHandler: 认证处理器(认证服务必需)。
|
||||
// - fileHandler: 文件处理器(文件服务必需)。
|
||||
// - jwtSecret: JWT 令牌验证的密钥。
|
||||
//
|
||||
// 注意:
|
||||
// - 认证路由是公开的,不需要 JWT。
|
||||
// - 文件路由需要通过 middleware.JWT 进行 JWT 认证。
|
||||
// - 每个服务应该只注册自己的路由以避免冲突。
|
||||
func SetupRoutes(engine *gin.Engine, serviceType ServiceType, authHandler *handler.AuthHandler, fileHandler *handler.FileHandler, captchaHandler *handler.CaptchaHandler, emailHandler *handler.EmailHandler, jwtSecret string) {
|
||||
api := engine.Group("/api/v1")
|
||||
|
||||
RegisterCaptchaRoutes(api, captchaHandler)
|
||||
|
||||
switch serviceType {
|
||||
case ServiceTypeAuth:
|
||||
auth := api.Group("/auth")
|
||||
RegisterAuthRoutes(auth, authHandler, emailHandler, jwtSecret)
|
||||
case ServiceTypeFile:
|
||||
files := api.Group("/files")
|
||||
files.Use(middleware.JWT(jwtSecret))
|
||||
RegisterFileRoutes(files, fileHandler)
|
||||
case ServiceTypeAll:
|
||||
auth := api.Group("/auth")
|
||||
RegisterAuthRoutes(auth, authHandler, emailHandler, jwtSecret)
|
||||
files := api.Group("/files")
|
||||
files.Use(middleware.JWT(jwtSecret))
|
||||
RegisterFileRoutes(files, fileHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// SetupAuthRoutes registers only authentication routes.
|
||||
// SetupAuthRoutes 只注册认证路由。
|
||||
//
|
||||
// This function is used by the authentication service to register
|
||||
// only its own routes, avoiding conflicts with the file service.
|
||||
//
|
||||
// 此函数用于认证服务注册自己的路由,避免与文件服务冲突。
|
||||
func SetupAuthRoutes(engine *gin.Engine, authHandler *handler.AuthHandler, captchaHandler *handler.CaptchaHandler, emailHandler *handler.EmailHandler, jwtSecret string) {
|
||||
api := engine.Group("/api/v1")
|
||||
RegisterCaptchaRoutes(api, captchaHandler)
|
||||
auth := api.Group("/auth")
|
||||
RegisterAuthRoutes(auth, authHandler, emailHandler, jwtSecret)
|
||||
}
|
||||
|
||||
// SetupFileRoutes registers only file management routes.
|
||||
// SetupFileRoutes 只注册文件管理路由。
|
||||
//
|
||||
// This function is used by the file service to register
|
||||
// only its own routes, avoiding conflicts with the auth service.
|
||||
//
|
||||
// 此函数用于文件服务注册自己的路由,避免与认证服务冲突。
|
||||
func SetupFileRoutes(engine *gin.Engine, fileHandler *handler.FileHandler, captchaHandler *handler.CaptchaHandler, jwtSecret string) {
|
||||
api := engine.Group("/api/v1")
|
||||
RegisterCaptchaRoutes(api, captchaHandler)
|
||||
files := api.Group("/files")
|
||||
files.Use(middleware.JWT(jwtSecret))
|
||||
RegisterFileRoutes(files, fileHandler)
|
||||
}
|
||||
Reference in New Issue
Block a user