This commit is contained in:
oneao committed 2025-11-05 22:25:09 +08:00
1 parent c7a1f83c67
commit 6849579652
19 files changed
+600 -249

No files matched your search

@@ -15,7 +15,8 @@ db:
jwt:
secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
expiry: -1
accessExpiry: 30m
refreshExpiry: 720h
log:
dir: "logs" # 日志根目录
@@ -4,9 +4,9 @@ import (
"base-go-v2/internal/db"
response "base-go-v2/internal/reponse"
"base-go-v2/internal/router"
"base-go-v2/internal/utils/jwtx"
"base-go-v2/internal/utils/mapx"
"base-go-v2/internal/utils/uid"
"fmt"
)
func InitAuthRouter(r *router.Router) {
@@ -15,20 +15,71 @@ func InitAuthRouter(r *router.Router) {
group.POST("/register/default", registerDefault)
}
func RefreshToken(c *router.Context) error {
bodyData, err := c.GetBodyWithRequired("accessToken", "refreshToken")
//func RefreshToken(c *router.Context) error {
// _, err := c.GetBodyWithRequired("accessToken", "refreshToken")
// if err != nil {
// return err
// }
// //accessToken := bodyData.GetString("accessToken")
// //refreshToken := bodyData.GetString("refreshToken")
//}
// 默认登录
// 默认登录
func loginDefault(c *router.Context) error {
// 获取请求体并校验必填字段
bodyData, err := c.GetBodyWithRequired("account", "password")
if err != nil {
return err
}
accessToken := bodyData.GetString("accessToken")
refreshToken := bodyData.GetString("refreshToken")
}
// 默认登录
func loginDefault(c *router.Context) error {
fmt.Println("loginDefault 被调用")
account := bodyData.GetString("account")
password := bodyData.GetString("password")
return response.Success(c).Send()
// 查询用户
userQuery := mapx.New().SetKV(
"account", account,
"password", password,
)
user, err := db.FindOne("user_info", userQuery)
if err != nil {
return response.Success(c).Message("查询用户失败,请稍后重试").IsSuccess(false).Send()
}
if user == nil {
// 账号或密码错误
return response.Success(c).
IsSuccess(false).
Message("账号或密码错误").
Send()
}
userId := user.GetInt("id")
// 创建 Access Token 和 Refresh Token
accessToken, err := jwtx.CreateAccessToken(userId)
if err != nil {
return err
}
refreshToken, err := jwtx.CreateRefreshToken(userId)
if err != nil {
return err
}
// 返回数据
respData := mapx.New().
SetIntToString("userId", userId).
SetKV(
"username", user.GetString("username"),
"seqNo", user.GetInt("seq_no"),
"gender", user.GetInt("gender"),
"accessToken", accessToken,
"refreshToken", refreshToken,
)
return response.Success(c).
Data(respData).
Send()
}
func registerDefault(c *router.Context) error {
@@ -48,7 +99,7 @@ func registerDefault(c *router.Context) error {
}
if len(existingUsers) > 0 {
return response.Error(c).Message("该账号已被注册").Send()
return response.Error(c).IsSuccess(false).Message("该账号已被注册").Send()
}
// 构造新用户数据
@@ -64,7 +115,7 @@ func registerDefault(c *router.Context) error {
return err
}
if insertedRows != 1 {
return response.Error(c).Message("注册失败").Send()
return response.Success(c).IsSuccess(false).Message("注册失败").Send()
}
return response.Success(c).Message("注册成功").Send()
@@ -18,8 +18,9 @@ func init() {
Port: 8080,
},
JWT: types.JWTConfig{
Secret: "default_secret",
Expiry: time.Hour,
Secret: "default_secret",
RefreshExpiry: time.Hour,
AccessExpiry: time.Hour,
},
Db: types.DbConfig{
MaxOpenConns: 10,
@@ -1,17 +1,18 @@
package db
import (
"base-go-v2/internal/utils/mapx"
"fmt"
"strings"
)
// ---------------- 通用查询方法 ----------------
// GetOne 根据主键查询单条记录,返回 map[string]interface{}
func GetOne(table string, pkColumn string, pkValue interface{}) (map[string]interface{}, error) {
// GetOne 根据主键查询单条记录,返回 mapx.M
func GetOne(table string, pkColumn string, pkValue interface{}) (mapx.M, error) {
query := fmt.Sprintf("SELECT * FROM %s WHERE %s=$1", table, pkColumn)
row := make(map[string]interface{})
row := mapx.New()
err := DB.Get(&row, query, pkValue)
if err != nil {
return nil, err
@@ -19,13 +20,13 @@ func GetOne(table string, pkColumn string, pkValue interface{}) (map[string]inte
return row, nil
}
// GetBatch 根据主键批量查询,返回 []map[string]interface{}
func GetBatch(table string, pkColumn string, pkValues []interface{}) ([]map[string]interface{}, error) {
// GetBatch 根据主键批量查询,返回 []mapx.M
func GetBatch(table string, pkColumn string, pkValues []interface{}) ([]mapx.M, error) {
if len(pkValues) == 0 {
return nil, nil
}
var rows []map[string]interface{}
var rows []mapx.M
// 构建 IN 占位符
placeholders := make([]string, len(pkValues))
@@ -42,10 +43,8 @@ func GetBatch(table string, pkColumn string, pkValues []interface{}) ([]map[stri
return rows, nil
}
// Find 根据条件查询多条记录,可选排序
// conditions: map[column]value, 将以 AND 拼接
// orderBy: 可选排序字段,如 "created_at ASC",为空默认 "b_id DESC"
func Find(table string, conditions map[string]interface{}, orderBy ...string) ([]map[string]interface{}, error) {
// Find 根据条件查询多条记录,可选排序,返回 []mapx.M
func Find(table string, conditions map[string]interface{}, orderBy ...string) ([]mapx.M, error) {
if len(conditions) == 0 {
return nil, fmt.Errorf("查询条件不能为空")
}
@@ -67,7 +66,7 @@ func Find(table string, conditions map[string]interface{}, orderBy ...string) ([
}
query += " ORDER BY " + order
var rows []map[string]interface{}
var rows []mapx.M
err := DB.Select(&rows, query, values...)
if err != nil {
return nil, err
@@ -76,8 +75,8 @@ func Find(table string, conditions map[string]interface{}, orderBy ...string) ([
return rows, nil
}
// FindAll 查询整个表,可选排序
func FindAll(table string, orderBy ...string) ([]map[string]interface{}, error) {
// FindAll 查询整个表,可选排序,返回 []mapx.M
func FindAll(table string, orderBy ...string) ([]mapx.M, error) {
query := fmt.Sprintf("SELECT * FROM %s", table)
order := "b_id DESC"
@@ -86,7 +85,7 @@ func FindAll(table string, orderBy ...string) ([]map[string]interface{}, error)
}
query += " ORDER BY " + order
var rows []map[string]interface{}
var rows []mapx.M
err := DB.Select(&rows, query)
if err != nil {
return nil, err
@@ -94,3 +93,35 @@ func FindAll(table string, orderBy ...string) ([]map[string]interface{}, error)
return rows, nil
}
// FindOne 根据条件查询单条记录,返回 mapx.M
func FindOne(table string, conditions map[string]interface{}, orderBy ...string) (mapx.M, error) {
if len(conditions) == 0 {
return nil, fmt.Errorf("查询条件不能为空")
}
var whereParts []string
var values []interface{}
i := 1
for k, v := range conditions {
whereParts = append(whereParts, fmt.Sprintf("%s=$%d", k, i))
values = append(values, v)
i++
}
query := fmt.Sprintf("SELECT * FROM %s WHERE %s", table, strings.Join(whereParts, " AND "))
order := "b_id DESC"
if len(orderBy) > 0 && orderBy[0] != "" {
order = orderBy[0]
}
query += " ORDER BY " + order + " LIMIT 1" // 只取一条记录
row := mapx.New()
err := DB.Get(&row, query, values...)
if err != nil {
return nil, err
}
return row, nil
}
@@ -1,19 +1,34 @@
package errs
import "fmt"
import (
"fmt"
"strings"
)
// ValidationError 用于收集多个字段错误,同时实现 error 接口
type ValidationError struct {
field string
message string
Errors map[string]string // key: 字段名, value: 错误信息
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s: %s", e.field, e.message)
// Error 实现 error 接口
func (v *ValidationError) Error() string {
if len(v.Errors) == 0 {
return ""
}
msgs := make([]string, 0, len(v.Errors))
for field, msg := range v.Errors {
msgs = append(msgs, fmt.Sprintf("%s: %s", field, msg))
}
return strings.Join(msgs, "; ")
}
func NewValidationError(field, message string) error {
// NewValidationError 创建多字段的 ValidationError
func NewValidationError(errors map[string]string) error {
if len(errors) == 0 {
return nil
}
return &ValidationError{
field: field,
message: message,
Errors: errors,
}
}
@@ -8,18 +8,22 @@ import (
"net/http"
)
// Result 是最终响应结构体
type Result struct {
Code string `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
TrackId string `json:"trackId"`
IsSuccess bool `json:"isSuccess"` // 是否成功
Code string `json:"code"` // 状态码
Message string `json:"message"` // 提示信息
Data interface{} `json:"data,omitempty"` // 数据
TrackId string `json:"trackId,omitempty"` // 请求追踪ID
}
// Code 定义状态码
type Code struct {
Value string
Message string
}
// 常用状态码
var (
CodeSuccess = Code{"0000", "请求成功"}
CodeFail = Code{"0001", "请求失败"}
@@ -31,53 +35,75 @@ var (
const defaultMessage = "未知错误"
// Builder 用于构建响应
type Builder struct {
c *router.Context
result Result
}
// ---------------- 构造函数 ----------------
// Success 创建成功响应
func Success(c *router.Context) *Builder {
return &Builder{
c: c,
result: Result{
Code: CodeSuccess.Value,
Message: CodeSuccess.Message,
IsSuccess: true,
Code: CodeSuccess.Value,
Message: CodeSuccess.Message,
},
}
}
// Error 创建失败响应
func Error(c *router.Context) *Builder {
return &Builder{
c: c,
result: Result{
Code: CodeFail.Value,
Message: CodeFail.Message,
IsSuccess: false,
Code: CodeFail.Value,
Message: CodeFail.Message,
},
}
}
// ---------------- 链式方法 ----------------
// 设置 IsSuccess 字段
func (b *Builder) IsSuccess(IsSuccess bool) *Builder {
b.result.IsSuccess = IsSuccess
return b
}
// 设置 Code,并根据 Code 自动设置 IsSuccess
func (b *Builder) Code(code string) *Builder {
b.result.Code = code
b.result.Message = GetMessage(code)
b.result.IsSuccess = code == CodeSuccess.Value
return b
}
// 设置 Code 对象,并自动设置 IsSuccess
func (b *Builder) CodeObj(code Code) *Builder {
b.result.Code = code.Value
b.result.Message = code.Message
b.result.IsSuccess = code.Value == CodeSuccess.Value
return b
}
// 自定义提示信息
func (b *Builder) Message(msg string) *Builder {
b.result.Message = msg
return b
}
// 设置返回数据
func (b *Builder) Data(data interface{}) *Builder {
b.result.Data = data
return b
}
// 发送响应
func (b *Builder) Send() error {
// 默认 Data 为空
if b.result.Data == nil {
@@ -90,10 +116,12 @@ func (b *Builder) Send() error {
b.result.TrackId = strutil.ToString(trackId)
}
// 发送响应
return b.c.JSON(http.StatusOK, b.result)
}
// ---------------- 辅助函数 ----------------
// 根据 Code 获取默认提示信息
func GetMessage(value string) string {
for _, c := range []Code{
CodeSuccess, CodeFail, CodeNoLogin,
@@ -97,7 +97,7 @@ func (c *Context) GetBodyWithRequired(keys ...string) (mapx.M, error) {
}
// 校验必填字段
if err := validate.ValidateNotEmpty(req, keys...); err != nil {
if err := validate.NotEmpty(req, keys...); err != nil {
return nil, err
}
@@ -17,8 +17,9 @@ type ServerConfig struct {
}
type JWTConfig struct {
Secret string
Expiry time.Duration
Secret string
AccessExpiry time.Duration
RefreshExpiry time.Duration
}
type DbConfig struct {
@@ -1,77 +0,0 @@
package jwt
import (
"base-go-v2/internal/config"
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
ErrTokenExpired = errors.New("token expired")
ErrTokenInvalid = errors.New("token invalid")
)
// CustomClaims 定义自己的 payload 结构
type CustomClaims struct {
UserID string `json:"user_id"`
jwt.RegisteredClaims
}
// CreateToken 创建一个 JWT token
// 支持 config.App.JWT.Expiry == -1 永不过期
func CreateToken(orgID, userID string) (string, error) {
claims := CustomClaims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
expiry := config.App.JWT.Expiry
switch {
case expiry > 0:
expireAt := time.Now().Add(expiry)
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
case expiry == -1:
// 永不过期,ExpiresAt 设置为 nil
claims.ExpiresAt = nil
default:
// 默认过期时间,例如 1 小时
expireAt := time.Now().Add(time.Hour)
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(config.App.JWT.Secret))
}
// VerifyToken 验证并解析 JWT token
func VerifyToken(tokenString string) (*CustomClaims, error) {
signKey := []byte(config.App.JWT.Secret)
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
// 校验签名算法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, ErrTokenInvalid
}
return signKey, nil
})
if err != nil {
return nil, ErrTokenInvalid
}
claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
return nil, ErrTokenInvalid
}
// 检查过期:ExpiresAt 为 nil 时永不过期
if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
return nil, ErrTokenExpired
}
return claims, nil
}
@@ -0,0 +1,82 @@
package jwtx
import (
"base-go-v2/internal/config"
"time"
"github.com/golang-jwt/jwt/v5"
)
// CustomClaims 自定义 payload,可扩展其他信息
type CustomClaims struct {
UserID int `json:"user_id"` // 改为 int
jwt.RegisteredClaims
}
// TokenResult 用于返回验证结果
type TokenResult struct {
Claims *CustomClaims
Valid bool
Expired bool
}
// CreateAccessToken 创建 Access Token,只返回 token 和 error
func CreateAccessToken(userID int) (string, error) {
return createToken(userID, config.App.JWT.AccessExpiry)
}
// CreateRefreshToken 创建 Refresh Token,只返回 token 和 error
func CreateRefreshToken(userID int) (string, error) {
return createToken(userID, config.App.JWT.RefreshExpiry)
}
// 内部方法,根据过期时间生成 token
func createToken(userID int, expiry time.Duration) (string, error) {
claims := &CustomClaims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
switch {
case expiry > 0:
expireAt := time.Now().Add(expiry)
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
case expiry == -1:
claims.ExpiresAt = nil // 永不过期
default:
expireAt := time.Now().Add(time.Hour)
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString([]byte(config.App.JWT.Secret))
if err != nil {
return "", err
}
return signed, nil
}
// VerifyToken 验证 token 并返回结构化结果
func VerifyToken(tokenString string) TokenResult {
result := TokenResult{Valid: false}
token, _ := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(config.App.JWT.Secret), nil
})
claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
return result // token 无效
}
result.Claims = claims
result.Valid = true
if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
result.Expired = true
}
return result
}
@@ -1,5 +1,10 @@
package mapx
import (
"encoding/json"
"strconv"
)
// M 是 map[string]interface{} 的别名
type M map[string]interface{}
@@ -29,6 +34,35 @@ func (m M) SetKV(kvs ...interface{}) M {
return m
}
// ---------------- 新增方法 ----------------
// SetIntToString 将 int 或 int64 直接转为 string 存储,用于前端安全传输
func (m M) SetIntToString(key string, val interface{}) M {
var s string
switch v := val.(type) {
case int:
s = strconv.Itoa(v) // 直接转为 string
case int64:
s = strconv.FormatInt(v, 10)
default:
panic("SetIntToString: unsupported type")
}
m[key] = s
return m
}
// GetIntFromString 将前端传来的字符串解析为 int64
func (m M) GetIntFromString(key string) int64 {
if v, ok := m[key]; ok {
if s, ok := v.(string); ok {
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
return i
}
}
}
return 0
}
// Get 获取值
func (m M) Get(key string) interface{} {
return m[key]
@@ -62,44 +96,15 @@ func (m M) GetInt(key string) int {
return int(val)
case float64:
return int(val)
case string:
if i, err := strconv.ParseInt(val, 10, 64); err == nil {
return int(i)
}
}
}
return 0
}
// GetFloat 获取浮点数类型
func (m M) GetFloat(key string) float64 {
if v, ok := m[key]; ok {
switch val := v.(type) {
case float32:
return float64(val)
case float64:
return val
case int:
return float64(val)
case int8:
return float64(val)
case int16:
return float64(val)
case int32:
return float64(val)
case int64:
return float64(val)
}
}
return 0
}
// GetBool 获取布尔类型
func (m M) GetBool(key string) bool {
if v, ok := m[key]; ok {
if b, ok := v.(bool); ok {
return b
}
}
return false
}
// Merge 合并另一个 M,后者会覆盖前者同名字段
func (m M) Merge(other M) M {
for k, v := range other {
@@ -107,3 +112,8 @@ func (m M) Merge(other M) M {
}
return m
}
// MarshalJSON 支持 M 自定义 JSON 序列化
func (m M) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}(m))
}
@@ -1,42 +1,65 @@
package validate
import (
"base-go-v2/internal/errs"
"errors"
"reflect"
"fmt"
"strings"
)
// ValidateNotEmpty 校验结构体中指定字段不能为空
func ValidateNotEmpty(s interface{}, fields ...string) error {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
v = v.Elem()
// ValidationError 用于收集多个字段错误,同时实现 error 接口
type ValidationError struct {
Errors map[string]string
}
// Error 实现 error 接口
func (v *ValidationError) Error() string {
msgs := make([]string, 0, len(v.Errors))
for field, err := range v.Errors {
msgs = append(msgs, fmt.Sprintf("%s: %s", field, err))
}
return strings.Join(msgs, "; ")
}
// NotEmpty 校验 map[string]interface{} 中指定字段不能为空
func NotEmpty(data map[string]interface{}, fields ...string) error {
errs := make(map[string]string)
for _, field := range fields {
f := v.FieldByName(field)
if !f.IsValid() {
return errors.New("field " + field + " does not exist")
}
switch f.Kind() {
case reflect.String:
if strings.TrimSpace(f.String()) == "" {
return errs.NewValidationError(field, "不能为空")
}
case reflect.Slice, reflect.Array, reflect.Map:
if f.Len() == 0 {
return errs.NewValidationError(field, "不能为空")
}
case reflect.Ptr, reflect.Interface:
if f.IsNil() {
return errs.NewValidationError(field, "不能为空")
}
default:
continue
}
collectErrors(data[field], field, errs)
}
if len(errs) > 0 {
return &ValidationError{Errors: errs}
}
return nil
}
// collectErrors 递归收集字段错误
func collectErrors(v interface{}, path string, errs map[string]string) {
if v == nil {
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
return
}
switch val := v.(type) {
case string:
if strings.TrimSpace(val) == "" {
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
}
case []interface{}:
if len(val) == 0 {
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
}
for i, elem := range val {
collectErrors(elem, fmt.Sprintf("%s[%d]", path, i), errs)
}
case map[string]interface{}:
if len(val) == 0 {
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
}
for k, elem := range val {
collectErrors(elem, fmt.Sprintf("%s.%s", path, k), errs)
}
default:
// 其他类型暂不校验
}
}
@@ -1,3 +1,35 @@
{"level":"INFO","ts":"2025-11-05 16:59:43.475","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0009773,"trackId":"376656818492932096"}
{"level":"INFO","ts":"2025-11-05 17:00:17.955","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0009184,"trackId":"376656963112534016"}
{"level":"INFO","ts":"2025-11-05 17:00:21.027","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376656976001630208"}
{"level":"INFO","ts":"2025-11-05 21:35:40.062","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0005206,"trackId":"376726261852012544"}
{"level":"INFO","ts":"2025-11-05 21:35:47.481","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0008274,"trackId":"376726292969553920"}
{"level":"INFO","ts":"2025-11-05 21:35:48.665","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726297939804160"}
{"level":"INFO","ts":"2025-11-05 21:35:48.907","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726298954825728"}
{"level":"INFO","ts":"2025-11-05 21:35:49.171","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0009767,"trackId":"376726300057927680"}
{"level":"INFO","ts":"2025-11-05 21:37:38.008","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0005298,"trackId":"376726756557586432"}
{"level":"INFO","ts":"2025-11-05 21:38:18.445","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726926162657280"}
{"level":"INFO","ts":"2025-11-05 21:38:20.520","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0004533,"trackId":"376726934865838080"}
{"level":"INFO","ts":"2025-11-05 21:38:21.140","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726937466306560"}
{"level":"INFO","ts":"2025-11-05 21:38:21.288","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0004953,"trackId":"376726938082869248"}
{"level":"INFO","ts":"2025-11-05 21:38:21.609","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726939433435136"}
{"level":"INFO","ts":"2025-11-05 21:38:21.828","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726940351987712"}
{"level":"INFO","ts":"2025-11-05 21:38:22.088","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.000925,"trackId":"376726941438312448"}
{"level":"INFO","ts":"2025-11-05 21:38:22.281","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002126,"trackId":"376726942252007424"}
{"level":"INFO","ts":"2025-11-05 21:38:22.476","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002409,"trackId":"376726943069896704"}
{"level":"INFO","ts":"2025-11-05 21:38:22.663","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726943854231552"}
{"level":"INFO","ts":"2025-11-05 21:38:22.883","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726944776978432"}
{"level":"INFO","ts":"2025-11-05 21:38:23.100","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0007084,"trackId":"376726945682948096"}
{"level":"INFO","ts":"2025-11-05 21:38:23.324","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726946626666496"}
{"level":"INFO","ts":"2025-11-05 21:38:23.548","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726947566190592"}
{"level":"INFO","ts":"2025-11-05 21:38:23.796","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0007185,"trackId":"376726948606377984"}
{"level":"INFO","ts":"2025-11-05 21:38:23.992","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002713,"trackId":"376726949424267264"}
{"level":"INFO","ts":"2025-11-05 21:38:24.230","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0006165,"trackId":"376726950422511616"}
{"level":"INFO","ts":"2025-11-05 21:38:24.432","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726951273955328"}
{"level":"INFO","ts":"2025-11-05 21:38:24.628","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726952096038912"}
{"level":"INFO","ts":"2025-11-05 21:38:25.076","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726953975087104"}
{"level":"INFO","ts":"2025-11-05 21:38:25.276","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0000358,"trackId":"376726954813947904"}
{"level":"INFO","ts":"2025-11-05 21:38:25.459","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726955581505536"}
{"level":"INFO","ts":"2025-11-05 21:38:25.664","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0004416,"trackId":"376726956437143552"}
{"level":"INFO","ts":"2025-11-05 21:38:25.864","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0005154,"trackId":"376726957276004352"}
{"level":"INFO","ts":"2025-11-05 21:38:26.084","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002596,"trackId":"376726958202945536"}
{"level":"INFO","ts":"2025-11-05 21:38:26.319","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726959188606976"}