refactor: 提取 avatar handler 魔法数字为具名常量
- maxAvatarSize = 5 * 1024 * 1024 (5MB) - magicBytesBufSize = 512 - avatarTokenLen = 8 - dirPerm = 0o755 - filePerm = 0o644
This commit is contained in:
@@ -32,6 +32,14 @@ func NewAvatarHandler(userRepo avatarUserRepository) *AvatarHandler {
|
|||||||
return &AvatarHandler{userRepo: userRepo}
|
return &AvatarHandler{userRepo: userRepo}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxAvatarSize = 5 * 1024 * 1024 // 5MB
|
||||||
|
magicBytesBufSize = 512
|
||||||
|
avatarTokenLen = 8
|
||||||
|
dirPerm = 0o755
|
||||||
|
filePerm = 0o644
|
||||||
|
)
|
||||||
|
|
||||||
// generateSecureToken generates a secure random token
|
// generateSecureToken generates a secure random token
|
||||||
func generateSecureToken(length int) string {
|
func generateSecureToken(length int) string {
|
||||||
bytes := make([]byte, length)
|
bytes := make([]byte, length)
|
||||||
@@ -93,7 +101,7 @@ func (h *AvatarHandler) UploadAvatar(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate file size (max 5MB)
|
// Validate file size (max 5MB)
|
||||||
if file.Size > 5*1024*1024 {
|
if file.Size > maxAvatarSize {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "file size exceeds 5MB limit"})
|
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "file size exceeds 5MB limit"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -115,7 +123,7 @@ func (h *AvatarHandler) UploadAvatar(c *gin.Context) {
|
|||||||
defer src.Close()
|
defer src.Close()
|
||||||
|
|
||||||
// Validate Magic Bytes to detect actual file type (prevents file extension spoofing)
|
// Validate Magic Bytes to detect actual file type (prevents file extension spoofing)
|
||||||
buf := make([]byte, 512)
|
buf := make([]byte, magicBytesBufSize)
|
||||||
n, err := src.Read(buf)
|
n, err := src.Read(buf)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "failed to read file"})
|
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "failed to read file"})
|
||||||
@@ -140,11 +148,11 @@ func (h *AvatarHandler) UploadAvatar(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate unique filename
|
// Generate unique filename
|
||||||
avatarFilename := fmt.Sprintf("avatar_%d_%s%s", userID, generateSecureToken(8), ext)
|
avatarFilename := fmt.Sprintf("avatar_%d_%s%s", userID, generateSecureToken(avatarTokenLen), ext)
|
||||||
uploadDir := "./uploads/avatars"
|
uploadDir := "./uploads/avatars"
|
||||||
|
|
||||||
// Create upload directory if not exists
|
// Create upload directory if not exists
|
||||||
if err := os.MkdirAll(uploadDir, 0o755); err != nil {
|
if err := os.MkdirAll(uploadDir, dirPerm); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to create upload directory"})
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to create upload directory"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -156,7 +164,7 @@ func (h *AvatarHandler) UploadAvatar(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to read uploaded file"})
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to read uploaded file"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(dstPath, data, 0o644); err != nil {
|
if err := os.WriteFile(dstPath, data, filePerm); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to save avatar file"})
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to save avatar file"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user