feat: Initial commit

This commit is contained in:
engigu
2025-12-20 09:30:16 +08:00
commit 362237241e
189 changed files with 12035 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
package utils
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"io"
"os"
"path/filepath"
"strings"
)
func ExtractZip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
// 安全检查:防止路径遍历
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
continue
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, 0755)
continue
}
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
func ExtractTar(src, dest string) error {
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
return extractTarReader(tar.NewReader(file), dest)
}
func ExtractTarGz(src, dest string) error {
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
return extractTarReader(tar.NewReader(gzr), dest)
}
func extractTarReader(tr *tar.Reader, dest string) error {
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
fpath := filepath.Join(dest, header.Name)
// 安全检查:防止路径遍历
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
continue
}
switch header.Typeflag {
case tar.TypeDir:
os.MkdirAll(fpath, 0755)
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return err
}
outFile, err := os.Create(fpath)
if err != nil {
return err
}
if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close()
return err
}
outFile.Close()
os.Chmod(fpath, os.FileMode(header.Mode))
}
}
return nil
}
+39
View File
@@ -0,0 +1,39 @@
package utils
import (
"bytes"
"compress/gzip"
"encoding/base64"
"io"
)
// CompressToBase64 compresses data using gzip and encodes to base64
func CompressToBase64(data string) (string, error) {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
if _, err := gz.Write([]byte(data)); err != nil {
return "", err
}
if err := gz.Close(); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
// DecompressFromBase64 decodes base64 and decompresses gzip data
func DecompressFromBase64(data string) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return "", err
}
gz, err := gzip.NewReader(bytes.NewReader(decoded))
if err != nil {
return "", err
}
defer gz.Close()
result, err := io.ReadAll(gz)
if err != nil {
return "", err
}
return string(result), nil
}
+45
View File
@@ -0,0 +1,45 @@
package utils
import (
"strconv"
"baihu/internal/constant"
"github.com/gin-gonic/gin"
)
// Pagination 分页参数
type Pagination struct {
Page int
PageSize int
}
// ParsePagination 从请求中解析分页参数
func ParsePagination(c *gin.Context) Pagination {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", strconv.Itoa(constant.DefaultPageSize)))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = constant.DefaultPageSize
}
return Pagination{Page: page, PageSize: pageSize}
}
// Offset 计算偏移量
func (p Pagination) Offset() int {
return (p.Page - 1) * p.PageSize
}
// PaginatedResponse 分页响应
func PaginatedResponse(c *gin.Context, data interface{}, total int64, p Pagination) {
Success(c, gin.H{
"data": data,
"total": total,
"page": p.Page,
"page_size": p.PageSize,
})
}
+55
View File
@@ -0,0 +1,55 @@
package utils
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: 200,
Msg: "success",
Data: data,
})
}
func SuccessMsg(c *gin.Context, msg string) {
c.JSON(http.StatusOK, Response{
Code: 200,
Msg: msg,
})
}
func Error(c *gin.Context, code int, msg string) {
c.JSON(http.StatusOK, Response{
Code: code,
Msg: msg,
})
}
func BadRequest(c *gin.Context, msg string) {
Error(c, 400, msg)
}
func Unauthorized(c *gin.Context, msg string) {
Error(c, 401, msg)
}
func Forbidden(c *gin.Context, msg string) {
Error(c, 403, msg)
}
func NotFound(c *gin.Context, msg string) {
Error(c, 404, msg)
}
func ServerError(c *gin.Context, msg string) {
Error(c, 500, msg)
}
+54
View File
@@ -0,0 +1,54 @@
package utils
import (
"os"
"os/exec"
"runtime"
)
// GetShell 返回当前操作系统的 shell 和参数
func GetShell() (shell string, args []string) {
if runtime.GOOS == "windows" {
return "cmd", []string{}
}
// 优先使用环境变量中的 SHELL
if envShell := os.Getenv("SHELL"); envShell != "" {
return envShell, []string{}
}
// macOS 默认使用 zsh
if runtime.GOOS == "darwin" {
if _, err := exec.LookPath("/bin/zsh"); err == nil {
return "/bin/zsh", []string{}
}
}
// Linux 默认使用 bash
return "/bin/bash", []string{}
}
// GetShellCommand 返回执行命令的 shell 和参数
func GetShellCommand(command string) (shell string, args []string) {
shell, _ = GetShell()
if runtime.GOOS == "windows" {
return shell, []string{"/c", command}
}
return shell, []string{"-c", command}
}
// NewShellCmd 创建一个交互式 shell 命令
func NewShellCmd() *exec.Cmd {
shell, _ := GetShell()
if runtime.GOOS == "windows" {
return exec.Command(shell)
}
// Unix 系统使用 -i 启用交互模式
return exec.Command(shell, "-i")
}
// NewShellCommandCmd 创建一个执行指定命令的 shell 命令
func NewShellCommandCmd(command string) *exec.Cmd {
shell, args := GetShellCommand(command)
return exec.Command(shell, args...)
}
+48
View File
@@ -0,0 +1,48 @@
package utils
import (
"errors"
"time"
"baihu/internal/constant"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
// GenerateToken 生成 JWT token
func GenerateToken(userID uint, username string) (string, error) {
claims := Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(constant.TokenExpireDays) * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(constant.JWTSecret))
}
// ParseToken 解析 JWT token
func ParseToken(tokenString string) (uint, string, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (any, error) {
return []byte(constant.JWTSecret), nil
})
if err != nil {
return 0, "", err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims.UserID, claims.Username, nil
}
return 0, "", errors.New("invalid token")
}