初始化
This commit is contained in:
107
server/utils/file.go
Normal file
107
server/utils/file.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetExtension 获取文件扩展名(不含点号, 全小写)
|
||||
// 例如: "photo.JPG" -> "jpg"
|
||||
func GetExtension(filename string) string {
|
||||
ext := filepath.Ext(filename)
|
||||
if ext == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(ext[1:]) // 去掉开头的点号
|
||||
}
|
||||
|
||||
// GetMimeType 根据文件扩展名获取MIME类型
|
||||
// 简单映射, 覆盖常见文件类型
|
||||
func GetMimeType(extension string) string {
|
||||
mimeMap := map[string]string{
|
||||
// 图片
|
||||
"jpg": "image/jpeg",
|
||||
"jpeg": "image/jpeg",
|
||||
"png": "image/png",
|
||||
"gif": "image/gif",
|
||||
"bmp": "image/bmp",
|
||||
"webp": "image/webp",
|
||||
"svg": "image/svg+xml",
|
||||
"ico": "image/x-icon",
|
||||
|
||||
// 文档
|
||||
"pdf": "application/pdf",
|
||||
"doc": "application/msword",
|
||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"xls": "application/vnd.ms-excel",
|
||||
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"ppt": "application/vnd.ms-powerpoint",
|
||||
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
|
||||
// 文本
|
||||
"txt": "text/plain",
|
||||
"html": "text/html",
|
||||
"htm": "text/html",
|
||||
"css": "text/css",
|
||||
"js": "application/javascript",
|
||||
"json": "application/json",
|
||||
"xml": "application/xml",
|
||||
"csv": "text/csv",
|
||||
"md": "text/markdown",
|
||||
|
||||
// 压缩包
|
||||
"zip": "application/zip",
|
||||
"rar": "application/x-rar-compressed",
|
||||
"7z": "application/x-7z-compressed",
|
||||
"tar": "application/x-tar",
|
||||
"gz": "application/gzip",
|
||||
|
||||
// 音视频
|
||||
"mp3": "audio/mpeg",
|
||||
"wav": "audio/wav",
|
||||
"mp4": "video/mp4",
|
||||
"avi": "video/x-msvideo",
|
||||
"mov": "video/quicktime",
|
||||
"wmv": "video/x-ms-wmv",
|
||||
"flv": "video/x-flv",
|
||||
"mkv": "video/x-matroska",
|
||||
}
|
||||
|
||||
if mime, ok := mimeMap[extension]; ok {
|
||||
return mime
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
// IsImageFile 判断是否为图片文件
|
||||
func IsImageFile(extension string) bool {
|
||||
imageExts := []string{"jpg", "jpeg", "png", "gif", "bmp", "webp", "svg", "ico"}
|
||||
for _, ext := range imageExts {
|
||||
if extension == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsVideoFile 判断是否为视频文件
|
||||
func IsVideoFile(extension string) bool {
|
||||
videoExts := []string{"mp4", "avi", "mov", "wmv", "flv", "mkv"}
|
||||
for _, ext := range videoExts {
|
||||
if extension == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDocumentFile 判断是否为文档文件
|
||||
func IsDocumentFile(extension string) bool {
|
||||
docExts := []string{"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "md", "csv"}
|
||||
for _, ext := range docExts {
|
||||
if extension == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
62
server/utils/hash.go
Normal file
62
server/utils/hash.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// HashPassword 使用bcrypt对密码进行哈希
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// CheckPassword 验证密码是否匹配
|
||||
func CheckPassword(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// MD5Hash 计算字符串的MD5值
|
||||
func MD5Hash(text string) string {
|
||||
hash := md5.Sum([]byte(text))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// FileMD5 计算文件的MD5值
|
||||
// 用于秒传判断: 相同MD5的文件只需存储一份
|
||||
func FileMD5(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := md5.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// FileSHA256 计算文件的SHA256值
|
||||
func FileSHA256(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
50
server/utils/jwt.go
Normal file
50
server/utils/jwt.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// JWTClaims 自定义JWT声明
|
||||
type JWTClaims struct {
|
||||
UserID uint64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// GenerateToken 生成JWT Token
|
||||
// userID: 用户ID, username: 用户名, role: 角色, secret: 密钥, expirationHours: 过期时间(小时)
|
||||
func GenerateToken(userID uint64, username, role, secret string, expirationHours int) (string, error) {
|
||||
claims := JWTClaims{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Role: role,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expirationHours) * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "seeyon-filesystem",
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(secret))
|
||||
}
|
||||
|
||||
// ParseToken 解析JWT Token
|
||||
func ParseToken(tokenString, secret string) (*JWTClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
89
server/utils/response.go
Normal file
89
server/utils/response.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Package utils 提供通用工具函数
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Response 统一API响应结构
|
||||
type Response struct {
|
||||
Code int `json:"code"` // 业务状态码, 0=成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
Data interface{} `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// PageResponse 分页响应结构
|
||||
type PageResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
Page int `json:"page"` // 当前页码
|
||||
Size int `json:"size"` // 每页大小
|
||||
}
|
||||
|
||||
// Success 返回成功响应
|
||||
func Success(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// SuccessWithMessage 返回带消息的成功响应
|
||||
func SuccessWithMessage(c *gin.Context, message string, data interface{}) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: 0,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// Error 返回错误响应
|
||||
func Error(c *gin.Context, httpCode int, bizCode int, message string) {
|
||||
c.JSON(httpCode, Response{
|
||||
Code: bizCode,
|
||||
Message: message,
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// BadRequest 返回400错误
|
||||
func BadRequest(c *gin.Context, message string) {
|
||||
Error(c, http.StatusBadRequest, 400, message)
|
||||
}
|
||||
|
||||
// Unauthorized 返回401错误
|
||||
func Unauthorized(c *gin.Context, message string) {
|
||||
Error(c, http.StatusUnauthorized, 401, message)
|
||||
}
|
||||
|
||||
// Forbidden 返回403错误
|
||||
func Forbidden(c *gin.Context, message string) {
|
||||
Error(c, http.StatusForbidden, 403, message)
|
||||
}
|
||||
|
||||
// NotFound 返回404错误
|
||||
func NotFound(c *gin.Context, message string) {
|
||||
Error(c, http.StatusNotFound, 404, message)
|
||||
}
|
||||
|
||||
// ServerError 返回500错误
|
||||
func ServerError(c *gin.Context, message string) {
|
||||
Error(c, http.StatusInternalServerError, 500, message)
|
||||
}
|
||||
|
||||
// PageSuccess 返回分页成功响应
|
||||
func PageSuccess(c *gin.Context, data interface{}, total int64, page, size int) {
|
||||
c.JSON(http.StatusOK, PageResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: data,
|
||||
Total: total,
|
||||
Page: page,
|
||||
Size: size,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user