feat: update template
This commit is contained in:
7
internal/pkg/cache/cachekey/key.go
vendored
7
internal/pkg/cache/cachekey/key.go
vendored
@@ -5,6 +5,9 @@ import "fmt"
|
||||
const (
|
||||
UserApiPermissionsPattern = "user:api:permissions:*"
|
||||
UserInfoPattern = "user:info:*"
|
||||
UserAuthStatePattern = "user:auth_state:*"
|
||||
|
||||
AuthRefreshPattern = "auth:refresh:*" // 新增:覆盖 <hash> 和 user:<id> 两种 key
|
||||
)
|
||||
|
||||
func UserApiPermissions(id int32) string {
|
||||
@@ -23,3 +26,7 @@ func AuthRefresh(hash string) string {
|
||||
func AuthRefreshUser(userID int32) string {
|
||||
return fmt.Sprintf("auth:refresh:user:%d", userID)
|
||||
}
|
||||
|
||||
func UserAuthState(id int32) string {
|
||||
return fmt.Sprintf("user:auth_state:%d", id)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ var (
|
||||
ErrInvalidToken = New(http.StatusUnauthorized, "登录凭证无效")
|
||||
ErrInvalidTokenClaims = New(http.StatusUnauthorized, "登录凭证解析失败")
|
||||
ErrInvalidRefreshToken = New(http.StatusBadRequest, "invalid_grant")
|
||||
ErrExpiredRefreshToken = New(http.StatusBadRequest, "invalid_grant")
|
||||
ErrUserNotFound = New(http.StatusNotFound, "用户数据不存在")
|
||||
ErrCategoryNotFound = New(http.StatusNotFound, "分类数据不存在")
|
||||
ErrTagNotFound = New(http.StatusNotFound, "标签数据不存在")
|
||||
@@ -49,5 +48,7 @@ var (
|
||||
ErrTagCodeAlreadyExists = New(http.StatusBadRequest, "标签编码不允许重复")
|
||||
ErrMenusPathUniqueIdx = New(http.StatusBadRequest, "菜单路径不允许重复")
|
||||
ErrApiMethodPathAlreadyExists = New(http.StatusBadRequest, "接口方法(method)路径(path)不允许重复")
|
||||
ErrInvalidApiPath = New(http.StatusBadRequest, "接口路径格式不正确,必须以/开头,参数段需为{name}格式")
|
||||
ErrFileTypeNotAllowed = New(http.StatusBadRequest, "该文件类型不允许上传")
|
||||
ErrBodyTooLarge = New(http.StatusRequestEntityTooLarge, "请求体过大")
|
||||
ErrInternalServer = New(http.StatusInternalServerError, "内部服务器错误")
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"server/internal/config"
|
||||
"server/internal/model/common"
|
||||
@@ -58,8 +59,19 @@ func URLParamInt32(r *http.Request, key string) (int32, error) {
|
||||
return int32(n), nil
|
||||
}
|
||||
|
||||
func BindJson(r *http.Request, dest any) error {
|
||||
// maxJSONBodySize JSON 请求体上限(1MB)
|
||||
const maxJSONBodySize = 1 << 20
|
||||
|
||||
func BindJson(w http.ResponseWriter, r *http.Request, dest any) error {
|
||||
// 超限时 Decode 返回 *http.MaxBytesError → 413
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize)
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(dest); err != nil {
|
||||
var maxBytesErr *http.MaxBytesError
|
||||
if errors.As(err, &maxBytesErr) {
|
||||
return errs.ErrBodyTooLarge
|
||||
}
|
||||
|
||||
if errors.Is(err, io.EOF) {
|
||||
return errs.ErrEmptyBody
|
||||
}
|
||||
@@ -73,17 +85,28 @@ func BindJson(r *http.Request, dest any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientIP 获取客户端 IP,依次从 X-Forwarded-For、X-Real-IP、RemoteAddr 取值
|
||||
func ClientIP(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.Split(xff, ",")
|
||||
if len(parts) > 0 {
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
}
|
||||
// ClientIP 获取客户端 IP;trust_proxy=true 时信任代理头,否则用 RemoteAddr(防伪造)
|
||||
func ClientIP(r *http.Request) netip.Addr {
|
||||
addr, _ := netip.ParseAddr(clientIPString(r))
|
||||
return addr
|
||||
}
|
||||
|
||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
func clientIPString(r *http.Request) string {
|
||||
// 只在 trust_proxy=true 时信任代理头(防伪造)
|
||||
if config.GetBool("server.trust_proxy") {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.Split(xff, ",")
|
||||
for _, v := range parts {
|
||||
if ip := strings.TrimSpace(v); ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 再取 X-Real-IP(nginx 单层代理覆盖时最准)
|
||||
if realIP := strings.TrimSpace(r.Header.Get("X-Real-IP")); realIP != "" {
|
||||
return realIP
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAddr: IP:port
|
||||
@@ -91,7 +114,6 @@ func ClientIP(r *http.Request) string {
|
||||
if err == nil {
|
||||
return host
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
|
||||
18
internal/pkg/safego/safego.go
Normal file
18
internal/pkg/safego/safego.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package safego
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// Go 以协程执行 fn,并捕获 panic 记录日志,防止协程崩溃杀死整个进程
|
||||
func Go(fn func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
slog.Error("goroutine panic recovered", "panic", r, "stack", string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user