feat(auth): 添加完整的用户认证API项目
- 实现用户注册、登录、JWT令牌认证功能 - 集成Gin、GORM、Viper、Zap等框架 - 添加密码加密、数据库操作、中间件等完整功能 - 配置多环境支持、日志轮转、CORS处理 - 创建完整的项目结构和配置文件体系
This commit is contained in:
37
Web开发/01go-gin-demo/context_demo.go
Normal file
37
Web开发/01go-gin-demo/context_demo.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.GET("/user", func(c *gin.Context) {
|
||||
// 获取请求信息
|
||||
method := c.Request.Method // GET
|
||||
path := c.Request.URL.Path // /user
|
||||
|
||||
//打印
|
||||
fmt.Printf("Method: %s, Path: %s", method, path)
|
||||
|
||||
// 响应数据
|
||||
c.JSON(200, gin.H{"name": "Alice"})
|
||||
})
|
||||
|
||||
// 支持多种响应格式
|
||||
r.GET("/text", func(c *gin.Context) {
|
||||
c.String(200, "Hello, Gin!") // 纯文本
|
||||
})
|
||||
|
||||
r.GET("/yaml", func(c *gin.Context) {
|
||||
c.YAML(200, gin.H{
|
||||
"name": "Bob",
|
||||
"age": 30,
|
||||
}) // YAML 格式
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
31
Web开发/01go-gin-demo/go.mod
Normal file
31
Web开发/01go-gin-demo/go.mod
Normal file
@@ -0,0 +1,31 @@
|
||||
module go-gin-demo
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
78
Web开发/01go-gin-demo/go.sum
Normal file
78
Web开发/01go-gin-demo/go.sum
Normal file
@@ -0,0 +1,78 @@
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
51
Web开发/01go-gin-demo/handler_demo.go
Normal file
51
Web开发/01go-gin-demo/handler_demo.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 方式 2:独立函数(推荐,便于单元测试)
|
||||
func getProduct(c *gin.Context) {
|
||||
productID := c.Param("id")
|
||||
c.JSON(200, gin.H{
|
||||
"id": productID,
|
||||
"name": "Product",
|
||||
})
|
||||
}
|
||||
|
||||
// 方式 3:结构体方法(便于依赖注入)
|
||||
type ProductService struct {
|
||||
name string
|
||||
}
|
||||
|
||||
type ProductHandler struct {
|
||||
service *ProductService
|
||||
}
|
||||
|
||||
func (h *ProductHandler) Get(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// 使用 h.service 调用业务逻辑
|
||||
c.JSON(200, gin.H{
|
||||
"id": id,
|
||||
"service_name": h.service.name,
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 方式 1:直接定义
|
||||
r.GET("/method1", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"method": "inline"})
|
||||
})
|
||||
|
||||
// 方式 2:使用独立函数
|
||||
r.GET("/products/:id", getProduct)
|
||||
|
||||
// 方式 3:使用结构体方法
|
||||
service := &ProductService{name: "ProductService"}
|
||||
handler := &ProductHandler{service: service}
|
||||
r.GET("/products-struct/:id", handler.Get)
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
20
Web开发/01go-gin-demo/main.go
Normal file
20
Web开发/01go-gin-demo/main.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建 Gin 引擎(类似 Spring 的 ApplicationContext)
|
||||
r := gin.Default()
|
||||
|
||||
// 注册路由和处理器
|
||||
r.GET("/hello", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"message": "Hello, Gin!",
|
||||
})
|
||||
})
|
||||
|
||||
// 启动服务器,监听 8080 端口
|
||||
r.Run(":8080")
|
||||
}
|
||||
27
Web开发/01go-gin-demo/params_form_demo.go
Normal file
27
Web开发/01go-gin-demo/params_form_demo.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.POST("/login", func(c *gin.Context) {
|
||||
username := c.PostForm("username") // 获取表单参数
|
||||
password := c.PostForm("password")
|
||||
|
||||
// 简单验证
|
||||
if username == "" || password == "" {
|
||||
c.JSON(400, gin.H{"error": "username and password required"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"message": "Login successful",
|
||||
"username": username,
|
||||
})
|
||||
})
|
||||
|
||||
r.Run(":8080")
|
||||
}
|
||||
34
Web开发/01go-gin-demo/params_json_demo.go
Normal file
34
Web开发/01go-gin-demo/params_json_demo.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 定义数据结构体
|
||||
type UserJSON struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.POST("/users", func(c *gin.Context) {
|
||||
var user UserJSON
|
||||
|
||||
// 方式 1:ShouldBindJSON(推荐,错误时不会中断)
|
||||
if err := c.ShouldBindJSON(&user); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 业务逻辑
|
||||
c.JSON(201, gin.H{
|
||||
"message": "User created",
|
||||
"data": user,
|
||||
})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
24
Web开发/01go-gin-demo/params_path_demo.go
Normal file
24
Web开发/01go-gin-demo/params_path_demo.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 单个参数
|
||||
r.GET("/users/:id", func(c *gin.Context) {
|
||||
id := c.Param("id") // 获取参数
|
||||
c.JSON(200, gin.H{"user_id": id})
|
||||
})
|
||||
|
||||
// 多个参数
|
||||
r.GET("/users/:uid/posts/:pid", func(c *gin.Context) {
|
||||
uid := c.Param("uid")
|
||||
pid := c.Param("pid")
|
||||
c.JSON(200, gin.H{"uid": uid, "pid": pid})
|
||||
})
|
||||
|
||||
r.Run(":8080")
|
||||
}
|
||||
20
Web开发/01go-gin-demo/params_query_demo.go
Normal file
20
Web开发/01go-gin-demo/params_query_demo.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.GET("/search", func(c *gin.Context) {
|
||||
keyword := c.Query("q") // 获取查询参数
|
||||
limit := c.DefaultQuery("limit", "10") // 带默认值
|
||||
c.JSON(200, gin.H{
|
||||
"keyword": keyword,
|
||||
"limit": limit,
|
||||
})
|
||||
})
|
||||
|
||||
r.Run(":8080")
|
||||
}
|
||||
56
Web开发/01go-gin-demo/routes_demo.go
Normal file
56
Web开发/01go-gin-demo/routes_demo.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 基础 CRUD 路由
|
||||
r.GET("/products", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Get all products"})
|
||||
})
|
||||
|
||||
r.POST("/products", func(c *gin.Context) {
|
||||
c.JSON(201, gin.H{"message": "Product created"})
|
||||
})
|
||||
|
||||
r.PUT("/products/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
c.JSON(200, gin.H{"message": "Product updated", "id": id})
|
||||
})
|
||||
|
||||
r.DELETE("/products/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
c.JSON(200, gin.H{"message": "Product deleted", "id": id})
|
||||
})
|
||||
|
||||
// 路由分组(推荐)
|
||||
api := r.Group("/api/v1")
|
||||
{
|
||||
api.GET("/users", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Get all users"})
|
||||
})
|
||||
|
||||
api.POST("/users", func(c *gin.Context) {
|
||||
c.JSON(201, gin.H{"message": "User created"})
|
||||
})
|
||||
|
||||
// 嵌套分组
|
||||
users := api.Group("/users")
|
||||
{
|
||||
users.GET("/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
c.JSON(200, gin.H{"message": "Get user", "id": id})
|
||||
})
|
||||
|
||||
users.PUT("/:id", func(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
c.JSON(200, gin.H{"message": "User updated", "id": id})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
114
Web开发/01go-gin-demo/user_manager.go
Normal file
114
Web开发/01go-gin-demo/user_manager.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 用户结构体
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
}
|
||||
|
||||
// 内存存储(实战应使用数据库)
|
||||
var users = []User{
|
||||
{ID: 1, Name: "Alice", Email: "alice@example.com"},
|
||||
{ID: 2, Name: "Bob", Email: "bob@example.com"},
|
||||
}
|
||||
|
||||
var nextID = 3
|
||||
|
||||
// 获取所有用户
|
||||
func listUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
// 获取单个用户
|
||||
func getUser(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
for _, user := range users {
|
||||
if user.ID == intParam(id) {
|
||||
c.JSON(http.StatusOK, user)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
func createUser(c *gin.Context) {
|
||||
var user User
|
||||
if err := c.ShouldBindJSON(&user); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user.ID = nextID
|
||||
nextID++
|
||||
users = append(users, user)
|
||||
c.JSON(http.StatusCreated, user)
|
||||
}
|
||||
|
||||
// 更新用户
|
||||
func updateUser(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var user User
|
||||
|
||||
if err := c.ShouldBindJSON(&user); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for i, u := range users {
|
||||
if u.ID == intParam(id) {
|
||||
user.ID = u.ID
|
||||
users[i] = user
|
||||
c.JSON(http.StatusOK, user)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
func deleteUser(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
for i, user := range users {
|
||||
if user.ID == intParam(id) {
|
||||
users = append(users[:i], users[i+1:]...)
|
||||
c.JSON(http.StatusNoContent, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func intParam(s string) int {
|
||||
var i int
|
||||
fmt.Sscanf(s, "%d", &i)
|
||||
return i
|
||||
}
|
||||
|
||||
func main() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.Default()
|
||||
|
||||
// API 分组
|
||||
api := r.Group("/api")
|
||||
{
|
||||
users := api.Group("/users")
|
||||
{
|
||||
users.GET("", listUsers)
|
||||
users.POST("", createUser)
|
||||
users.GET("/:id", getUser)
|
||||
users.PUT("/:id", updateUser)
|
||||
users.DELETE("/:id", deleteUser)
|
||||
}
|
||||
}
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
32
Web开发/02go-gin-middleware/go.mod
Normal file
32
Web开发/02go-gin-middleware/go.mod
Normal file
@@ -0,0 +1,32 @@
|
||||
module go-gin-middleware
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/cors v1.4.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
118
Web开发/02go-gin-middleware/go.sum
Normal file
118
Web开发/02go-gin-middleware/go.sum
Normal file
@@ -0,0 +1,118 @@
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
31
Web开发/02go-gin-middleware/middleware_abort.go
Normal file
31
Web开发/02go-gin-middleware/middleware_abort.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TokenAuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 检查 token
|
||||
if c.Query("token") == "" {
|
||||
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||||
c.Abort() // 中止后续处理
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 使用认证中间件
|
||||
r.Use(TokenAuthMiddleware())
|
||||
|
||||
r.GET("/protected", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Access granted"})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
39
Web开发/02go-gin-middleware/middleware_auth.go
Normal file
39
Web开发/02go-gin-middleware/middleware_auth.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 基础认证中间件
|
||||
func BasicAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
username, password, ok := c.Request.BasicAuth()
|
||||
|
||||
if !ok || username != "admin" || password != "password123" {
|
||||
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 认证成功,继续
|
||||
c.Set("username", username) // 将用户信息存储在 context 中
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 公开路由
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Public endpoint"})
|
||||
})
|
||||
|
||||
// 需要认证的路由
|
||||
r.GET("/protected", BasicAuth(), func(c *gin.Context) {
|
||||
username := c.GetString("username")
|
||||
c.JSON(200, gin.H{"message": "Hello " + username})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
35
Web开发/02go-gin-middleware/middleware_basic.go
Normal file
35
Web开发/02go-gin-middleware/middleware_basic.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 最小化中间件
|
||||
func MyMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 请求前处理
|
||||
fmt.Println("Before request")
|
||||
|
||||
// 继续下一个中间件/Handler
|
||||
c.Next()
|
||||
|
||||
// 响应后处理
|
||||
fmt.Println("After request2")
|
||||
fmt.Println("After request1")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 使用中间件
|
||||
r.Use(MyMiddleware())
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Hello"})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
73
Web开发/02go-gin-middleware/middleware_complete.go
Normal file
73
Web开发/02go-gin-middleware/middleware_complete.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 日志中间件
|
||||
func Logger() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
c.Next()
|
||||
duration := time.Since(start)
|
||||
fmt.Printf("[%s] %s %d (%v)\n",
|
||||
c.Request.Method, c.Request.URL.Path, c.Writer.Status(), duration)
|
||||
}
|
||||
}
|
||||
|
||||
// 认证中间件
|
||||
func Auth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token != "Bearer secret" {
|
||||
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Recovery 中间件
|
||||
func Recovery() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
c.JSON(500, gin.H{"error": "Internal Server Error"})
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.New()
|
||||
|
||||
// 全局中间件
|
||||
r.Use(Logger())
|
||||
r.Use(Recovery())
|
||||
|
||||
// 公开路由
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Public"})
|
||||
})
|
||||
|
||||
// 需要认证的路由组
|
||||
auth := r.Group("/api")
|
||||
auth.Use(Auth())
|
||||
{
|
||||
auth.GET("/users", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"users": []string{"Alice", "Bob"}})
|
||||
})
|
||||
|
||||
auth.POST("/users", func(c *gin.Context) {
|
||||
c.JSON(201, gin.H{"message": "User created"})
|
||||
})
|
||||
}
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
28
Web开发/02go-gin-middleware/middleware_cors.go
Normal file
28
Web开发/02go-gin-middleware/middleware_cors.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 配置 CORS
|
||||
r.Use(cors.New(cors.Config{
|
||||
AllowOrigins: []string{"http://localhost:3000"},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 12 * time.Hour,
|
||||
}))
|
||||
|
||||
r.GET("/api/data", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "CORS enabled"})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
33
Web开发/02go-gin-middleware/middleware_cors_custom.go
Normal file
33
Web开发/02go-gin-middleware/middleware_cors_custom.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(CORSMiddleware())
|
||||
|
||||
r.GET("/api/data", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Custom CORS enabled"})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
46
Web开发/02go-gin-middleware/middleware_logger.go
Normal file
46
Web开发/02go-gin-middleware/middleware_logger.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 日志中间件
|
||||
func LoggerMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 记录请求信息
|
||||
startTime := time.Now()
|
||||
method := c.Request.Method
|
||||
url := c.Request.URL.Path
|
||||
|
||||
// 继续处理
|
||||
c.Next()
|
||||
|
||||
// 记录响应信息
|
||||
statusCode := c.Writer.Status()
|
||||
duration := time.Since(startTime)
|
||||
|
||||
fmt.Printf("[%s] %s %d ( %v)\n", method, url, statusCode, duration)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.New() // 不使用默认中间件
|
||||
|
||||
// 使用自定义日志中间件
|
||||
r.Use(LoggerMiddleware())
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Hello"})
|
||||
})
|
||||
|
||||
r.GET("/slow", func(c *gin.Context) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
c.JSON(200, gin.H{"message": "Slow endpoint"})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
50
Web开发/02go-gin-middleware/middleware_recovery.go
Normal file
50
Web开发/02go-gin-middleware/middleware_recovery.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 自定义 Recovery 中间件
|
||||
func CustomRecovery() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// 记录错误
|
||||
fmt.Printf("Panic: %v\n", err)
|
||||
|
||||
// 返回友好的错误响应
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Internal Server Error",
|
||||
"message": fmt.Sprintf("%v", err),
|
||||
})
|
||||
|
||||
// 中止处理链
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.New()
|
||||
|
||||
// 使用自定义 Recovery
|
||||
r.Use(CustomRecovery())
|
||||
r.Use(gin.Logger())
|
||||
|
||||
// 正常路由
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "OK"})
|
||||
})
|
||||
|
||||
// 故意触发 panic
|
||||
r.GET("/panic", func(c *gin.Context) {
|
||||
panic("Something went wrong!")
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
69
Web开发/02go-gin-middleware/middleware_token.go
Normal file
69
Web开发/02go-gin-middleware/middleware_token.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Token 认证中间件
|
||||
func TokenAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从请求头获取 token
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
|
||||
if authHeader == "" {
|
||||
c.JSON(401, gin.H{"error": "Missing Authorization header"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 验证 token 格式(Bearer xxx)
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.JSON(401, gin.H{"error": "Invalid Authorization header"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
// 验证 token 有效性(简化版,实际应使用 JWT)
|
||||
if !isValidToken(token) {
|
||||
c.JSON(401, gin.H{"error": "Invalid token"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 提取用户信息(从 token)
|
||||
userID := extractUserID(token)
|
||||
c.Set("user_id", userID)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 token(示例)
|
||||
func isValidToken(token string) bool {
|
||||
return token == "valid_token_123"
|
||||
}
|
||||
|
||||
// 提取用户ID(示例)
|
||||
func extractUserID(token string) string {
|
||||
return "user123"
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// 需要 token 的路由
|
||||
r.GET("/profile", TokenAuth(), func(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
c.JSON(200, gin.H{
|
||||
"user_id": userID,
|
||||
"profile": "User profile data",
|
||||
})
|
||||
})
|
||||
|
||||
r.Run(":9999")
|
||||
}
|
||||
71
Web开发/03go-gorm-demo/association_methods.go
Normal file
71
Web开发/03go-gorm-demo/association_methods.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Orders []Order
|
||||
//}
|
||||
//
|
||||
//type Order struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// UserID uint
|
||||
// Product string `gorm:"size:100"`
|
||||
// Amount int
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{}, &Order{})
|
||||
|
||||
//user := User{Name: "Alice"}
|
||||
//db.Create(&user)
|
||||
|
||||
//查询用户根据名称
|
||||
var user User
|
||||
db.Where("name = ?", "Alice").First(&user)
|
||||
|
||||
//order1 := Order{Product: "Laptop"}
|
||||
//order2 := Order{Product: "Mouse"}
|
||||
//order3 := Order{Product: "Keyboard"}
|
||||
|
||||
// 1. Append - 添加关联
|
||||
//db.Model(&user).Association("Orders").Append(&order1, &order2)
|
||||
//fmt.Println("Orders appended")
|
||||
|
||||
// 2. Count - 统计关联数量
|
||||
//count := db.Model(&user).Association("Orders").Count()
|
||||
//fmt.Printf("Order count: %d\n", count)
|
||||
|
||||
// 4. Replace - 替换所有关联
|
||||
//db.Model(&user).Association("Orders").Replace(&order3)
|
||||
|
||||
//查询订单 Keyboard
|
||||
var order3 Order
|
||||
db.Where("product = ?", "Keyboard").First(&order3)
|
||||
|
||||
// 5. Delete - 删除关联(不删除记录本身)
|
||||
//db.Model(&user).Association("Orders").Delete(&order3)
|
||||
//fmt.Println("Association deleted")
|
||||
|
||||
//fmt.Println("Orders replaced with Keyboard")
|
||||
|
||||
// 6. Clear - 清空所有关联
|
||||
//db.Model(&user).Association("Orders").Append(&order3)
|
||||
db.Model(&user).Association("Orders").Clear()
|
||||
fmt.Println("All associations cleared")
|
||||
|
||||
// 3. Find - 查找关联
|
||||
var orders []Order
|
||||
db.Model(&user).Association("Orders").Find(&orders)
|
||||
fmt.Println("Orders found:")
|
||||
for _, o := range orders {
|
||||
fmt.Printf(" - %s\n", o.Product)
|
||||
}
|
||||
}
|
||||
51
Web开发/03go-gorm-demo/crud_create.go
Normal file
51
Web开发/03go-gorm-demo/crud_create.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
// CreatedAt time.Time
|
||||
// }
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
// 1. 单条插入
|
||||
user := User{
|
||||
Name: "Alice",
|
||||
Email: "alice@example.com",
|
||||
Age: 30,
|
||||
}
|
||||
result := db.Create(&user)
|
||||
|
||||
if result.Error != nil {
|
||||
panic(result.Error)
|
||||
}
|
||||
|
||||
fmt.Printf("New user ID: %d\n", user.ID)
|
||||
fmt.Printf("Rows affected: %d\n", result.RowsAffected)
|
||||
|
||||
// 2. 批量插入
|
||||
users := []User{
|
||||
{Name: "Bob", Email: "bob@example.com", Age: 25},
|
||||
{Name: "Charlie", Email: "charlie@example.com", Age: 35},
|
||||
{Name: "David", Email: "david@example.com", Age: 28},
|
||||
}
|
||||
|
||||
db.Create(users)
|
||||
fmt.Println("Batch insert completed!")
|
||||
|
||||
// 3. 指定字段插入
|
||||
user2 := User{Name: "Eve", Email: "eve@example.com", Age: 22}
|
||||
db.Select("name", "email").Create(&user2) // 仅插入 name 和 email
|
||||
|
||||
fmt.Println("All users created successfully!")
|
||||
}
|
||||
56
Web开发/03go-gorm-demo/crud_delete.go
Normal file
56
Web开发/03go-gorm-demo/crud_delete.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
// DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除字段
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
//// 插入测试数据
|
||||
//users := []User{
|
||||
// {Name: "Alice", Email: "alice@example.com", Age: 30},
|
||||
// {Name: "Bob", Email: "bob@example.com", Age: 25},
|
||||
// {Name: "Charlie", Email: "charlie@example.com", Age: 35},
|
||||
//}
|
||||
//db.Create(users)
|
||||
|
||||
// 1. 软删除(标记 deleted_at)
|
||||
db.Delete(&User{}, 11) // 删除 ID=1 的用户
|
||||
fmt.Println("Soft deleted user ID=1")
|
||||
|
||||
// 2. 查询时默认不包括已删除的
|
||||
var activeUsers []User
|
||||
db.Find(&activeUsers)
|
||||
fmt.Printf("Active users: %d\n", len(activeUsers))
|
||||
|
||||
// 3. 查询所有记录(包括已删除的)
|
||||
var allUsers []User
|
||||
db.Unscoped().Find(&allUsers)
|
||||
fmt.Printf("All users (including deleted): %d\n", len(allUsers))
|
||||
|
||||
// 4. 永久删除(硬删除)
|
||||
db.Unscoped().Delete(&User{}, 12)
|
||||
fmt.Println("Permanently deleted user ID=2")
|
||||
|
||||
// 5. 批量删除
|
||||
db.Where("age < ?", 30).Delete(&User{})
|
||||
fmt.Println("Batch delete completed")
|
||||
|
||||
// 最终统计
|
||||
var finalCount int64
|
||||
db.Model(&User{}).Count(&finalCount)
|
||||
fmt.Printf("Final active users: %d\n", finalCount)
|
||||
}
|
||||
72
Web开发/03go-gorm-demo/crud_read.go
Normal file
72
Web开发/03go-gorm-demo/crud_read.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
// CreatedAt time.Time
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
|
||||
// 先插入测试数据
|
||||
db.AutoMigrate(&User{})
|
||||
db.Create(&User{Name: "Alice", Email: "alice@example.com", Age: 30})
|
||||
db.Create(&User{Name: "Bob", Email: "bob@example.com", Age: 25})
|
||||
db.Create(&User{Name: "Charlie", Email: "charlie@example.com", Age: 35})
|
||||
|
||||
// 1. 根据 ID 查询
|
||||
var user User
|
||||
db.First(&user, 1) // 查询 ID=1 的用户
|
||||
fmt.Printf("User ID=1: %s (%s)\n", user.Name, user.Email)
|
||||
|
||||
// 2. 按条件查询单条
|
||||
var user2 User
|
||||
db.Where("email = ?", "bob@example.com").First(&user2)
|
||||
fmt.Printf("User by email: %s (age=%d)\n", user2.Name, user2.Age)
|
||||
|
||||
// 3. 查询所有
|
||||
var users []User
|
||||
db.Find(&users)
|
||||
fmt.Printf("Total users: %d\n", len(users))
|
||||
|
||||
// 4. 按条件查询多条
|
||||
var adults []User
|
||||
db.Where("age >= ?", 30).Find(&adults)
|
||||
fmt.Printf("Users age >= 30: %d\n", len(adults))
|
||||
|
||||
// 5. 使用 IN 查询
|
||||
var selectedUsers []User
|
||||
db.Where("id IN ?", []int{1, 2}).Find(&selectedUsers)
|
||||
fmt.Printf("Selected users: %d\n", len(selectedUsers))
|
||||
|
||||
// 6. 排序查询
|
||||
var sortedUsers []User
|
||||
db.Order("age DESC").Find(&sortedUsers)
|
||||
fmt.Println("Users sorted by age (DESC):")
|
||||
for _, u := range sortedUsers {
|
||||
fmt.Printf(" - %s (age=%d)\n", u.Name, u.Age)
|
||||
}
|
||||
|
||||
// 7. 分页查询
|
||||
var pageUsers []User
|
||||
db.Offset(0).Limit(2).Find(&pageUsers)
|
||||
fmt.Printf("Page 1 (limit 2): %d users\n", len(pageUsers))
|
||||
|
||||
// 8. 组合查询
|
||||
var combinedUsers []User
|
||||
db.Where("age > ?", 25).
|
||||
Order("age DESC").
|
||||
Limit(2).
|
||||
Find(&combinedUsers)
|
||||
fmt.Printf("Combined query: %d users\n", len(combinedUsers))
|
||||
}
|
||||
56
Web开发/03go-gorm-demo/crud_update.go
Normal file
56
Web开发/03go-gorm-demo/crud_update.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
// UpdatedAt time.Time
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
// 插入测试数据
|
||||
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
|
||||
db.Create(&user)
|
||||
|
||||
// 1. 更新单个字段
|
||||
db.Model(&User{}).Where("id = ?", user.ID).Update("name", "Alice Updated")
|
||||
fmt.Println("Updated single field")
|
||||
|
||||
// 2. 更新多个字段(使用 map)
|
||||
db.Model(&User{}).Where("id = ?", user.ID).Updates(map[string]interface{}{
|
||||
"name": "Alice Smith",
|
||||
"age": 31,
|
||||
})
|
||||
fmt.Println("Updated multiple fields (map)")
|
||||
|
||||
// 3. 更新多个字段(使用 struct,仅更新非零值)
|
||||
db.Model(&User{}).Where("id = ?", user.ID).Updates(User{
|
||||
Name: "Alice Johnson",
|
||||
Age: 32,
|
||||
})
|
||||
fmt.Println("Updated multiple fields (struct)")
|
||||
|
||||
// 4. 强制更新零值字段
|
||||
db.Model(&User{}).Where("id = ?", user.ID).Update("age", 0)
|
||||
fmt.Println("Updated age to zero")
|
||||
|
||||
// 5. 批量更新
|
||||
db.Model(&User{}).Where("age > ?", 25).Update("age", gorm.Expr("age + ?", 1))
|
||||
fmt.Println("Batch update completed")
|
||||
|
||||
// 查询最终结果
|
||||
var finalUser User
|
||||
db.First(&finalUser, user.ID)
|
||||
fmt.Printf("Final user: %s (age=%d)\n", finalUser.Name, finalUser.Age)
|
||||
}
|
||||
29
Web开发/03go-gorm-demo/db_connect.go
Normal file
29
Web开发/03go-gorm-demo/db_connect.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 连接 SQLite(文件数据库)
|
||||
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("failed to connect database")
|
||||
}
|
||||
|
||||
fmt.Println("Database connected successfully!")
|
||||
|
||||
// 获取底层的 *sql.DB
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 设置连接池
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
|
||||
fmt.Println("Connection pool configured")
|
||||
}
|
||||
11
Web开发/03go-gorm-demo/go.mod
Normal file
11
Web开发/03go-gorm-demo/go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module go-gorm-demo
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
gorm.io/driver/sqlite v1.5.4 // indirect
|
||||
gorm.io/gorm v1.25.5 // indirect
|
||||
)
|
||||
10
Web开发/03go-gorm-demo/go.sum
Normal file
10
Web开发/03go-gorm-demo/go.sum
Normal file
@@ -0,0 +1,10 @@
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
28
Web开发/03go-gorm-demo/model_basic.go
Normal file
28
Web开发/03go-gorm-demo/model_basic.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// // User 模型
|
||||
//
|
||||
// type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100;not null"`
|
||||
// Email string `gorm:"size:100;unique;not null"`
|
||||
// Age int `gorm:"default:0"`
|
||||
// Active bool `gorm:"default:true"`
|
||||
// CreatedAt time.Time
|
||||
// UpdatedAt time.Time
|
||||
// }
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
|
||||
// 自动迁移(创建表)
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
fmt.Println("Table created successfully!")
|
||||
}
|
||||
63
Web开发/03go-gorm-demo/preload_strategies.go
Normal file
63
Web开发/03go-gorm-demo/preload_strategies.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"size:100"`
|
||||
Profile Profile
|
||||
Orders []Order
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
UserID uint
|
||||
Bio string `gorm:"size:200"`
|
||||
}
|
||||
|
||||
type Order struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
UserID uint
|
||||
Product string `gorm:"size:100"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{}, &Profile{}, &Order{})
|
||||
|
||||
// 创建测试数据
|
||||
//user := User{
|
||||
// Name: "Alice",
|
||||
// Profile: Profile{Bio: "Software Engineer"},
|
||||
// Orders: []Order{
|
||||
// {Product: "Laptop"},
|
||||
// {Product: "Mouse"},
|
||||
// },
|
||||
//}
|
||||
//db.Create(&user)
|
||||
|
||||
// 1. 预加载单个关联
|
||||
//var user1 User
|
||||
//db.Preload("Profile").First(&user1, 1)
|
||||
//fmt.Printf("User: %s, Bio: %s\n", user1.Name, user1.Profile.Bio)
|
||||
|
||||
//// 2. 预加载多个关联
|
||||
//var user2 User
|
||||
//db.Preload("Profile").Preload("Orders").First(&user2, 1)
|
||||
//fmt.Printf("User: %s, Orders: %d\n", user2.Name, len(user2.Orders))
|
||||
|
||||
// 3. 预加载所有关联
|
||||
//var user3 User
|
||||
//db.Preload("Profile").Preload("Orders").First(&user3, 1)
|
||||
//fmt.Printf("Fully loaded user: %s\n", user3.Name)
|
||||
|
||||
//// 4. 条件预加载
|
||||
var user4 User
|
||||
db.Preload("Orders", "product = ?", "Laptop").First(&user4, 1)
|
||||
fmt.Printf("Filtered orders: %d\n", len(user4.Orders))
|
||||
}
|
||||
76
Web开发/03go-gorm-demo/query_advanced.go
Normal file
76
Web开发/03go-gorm-demo/query_advanced.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
// Active bool `gorm:"default:true"`
|
||||
// CreatedAt time.Time
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
// 插入测试数据
|
||||
users := []User{
|
||||
{Name: "Alice", Email: "alice@example.com", Age: 30, Active: true},
|
||||
{Name: "Bob", Email: "bob@example.com", Age: 25, Active: false},
|
||||
{Name: "Charlie", Email: "charlie@example.com", Age: 35, Active: true},
|
||||
{Name: "David", Email: "david@example.com", Age: 28, Active: true},
|
||||
}
|
||||
db.Create(users)
|
||||
|
||||
// 1. 多条件查询
|
||||
var result1 []User
|
||||
db.Where("age > ? AND active = ?", 25, true).Find(&result1)
|
||||
fmt.Printf("Age > 25 AND active: %d users\n", len(result1))
|
||||
|
||||
// 2. OR 查询
|
||||
var result2 []User
|
||||
db.Where("age < ? OR active = ?", 30, false).Find(&result2)
|
||||
fmt.Printf("Age < 30 OR inactive: %d users\n", len(result2))
|
||||
|
||||
// 3. 模糊查询
|
||||
var result3 []User
|
||||
db.Where("name LIKE ?", "%li%").Find(&result3)
|
||||
fmt.Printf("Name contains 'li': %d users\n", len(result3))
|
||||
|
||||
// 4. 仅查询指定字段
|
||||
var result4 []User
|
||||
db.Select("name", "email").Find(&result4)
|
||||
fmt.Printf("Selected fields: %d users\n", len(result4))
|
||||
for _, u := range result4 {
|
||||
fmt.Printf(" - %s (%s)\n", u.Name, u.Email)
|
||||
}
|
||||
|
||||
// 5. 统计查询
|
||||
var count int64
|
||||
db.Model(&User{}).Where("active = ?", true).Count(&count)
|
||||
fmt.Printf("Active users count: %d\n", count)
|
||||
|
||||
// 6. 聚合查询
|
||||
var avgAge float64
|
||||
db.Model(&User{}).Select("AVG(age)").Row().Scan(&avgAge)
|
||||
fmt.Printf("Average age: %.2f\n", avgAge)
|
||||
|
||||
// 7. 分组查询
|
||||
type Result struct {
|
||||
Active bool
|
||||
Count int64
|
||||
}
|
||||
var groupResult []Result
|
||||
db.Model(&User{}).Select("active, count(*) as count").Group("active").Scan(&groupResult)
|
||||
fmt.Println("Group by active:")
|
||||
for _, r := range groupResult {
|
||||
fmt.Printf(" Active=%v: %d users\n", r.Active, r.Count)
|
||||
}
|
||||
}
|
||||
47
Web开发/03go-gorm-demo/raw_sql.go
Normal file
47
Web开发/03go-gorm-demo/raw_sql.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
// Active bool `gorm:"default:true"`
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
// 插入测试数据
|
||||
db.Create(&User{Name: "Alice", Email: "alice@example.com", Age: 30})
|
||||
db.Create(&User{Name: "Bob", Email: "bob@example.com", Age: 25})
|
||||
|
||||
// 1. 原生查询
|
||||
var users []User
|
||||
db.Raw("SELECT * FROM users WHERE age > ?", 20).Scan(&users)
|
||||
fmt.Printf("Raw query result: %d users\n", len(users))
|
||||
|
||||
// 2. 原生更新
|
||||
db.Exec("UPDATE users SET age = age + 1 WHERE name = ?", "Alice")
|
||||
fmt.Println("Raw update executed")
|
||||
|
||||
// 3. 原生统计
|
||||
var count int64
|
||||
db.Raw("SELECT COUNT(*) FROM users").Scan(&count)
|
||||
fmt.Printf("Total users: %d\n", count)
|
||||
|
||||
// 4. 查询单行
|
||||
var result struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
db.Raw("SELECT name, age FROM users WHERE id = ?", 1).Scan(&result)
|
||||
fmt.Printf("User: %s (age=%d)\n", result.Name, result.Age)
|
||||
}
|
||||
61
Web开发/03go-gorm-demo/relation_has_many.go
Normal file
61
Web开发/03go-gorm-demo/relation_has_many.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 用户拥有多个订单
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Orders []Order // 一对多关联
|
||||
//}
|
||||
//
|
||||
//type Order struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// UserID uint // 外键
|
||||
// Product string `gorm:"size:100"`
|
||||
// Amount int
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{}, &Order{})
|
||||
|
||||
// 1. 创建用户和订单
|
||||
user := User{
|
||||
Name: "Alice",
|
||||
Orders: []Order{
|
||||
{Product: "Laptop", Amount: 1},
|
||||
{Product: "Mouse", Amount: 2},
|
||||
{Product: "Keyboard", Amount: 1},
|
||||
},
|
||||
}
|
||||
db.Create(&user)
|
||||
fmt.Println("User and orders created")
|
||||
|
||||
// 2. 预加载查询
|
||||
var result User
|
||||
db.Preload("Orders").First(&result, user.ID)
|
||||
fmt.Printf("User: %s, Orders: %d\n", result.Name, len(result.Orders))
|
||||
for _, order := range result.Orders {
|
||||
fmt.Printf(" - %s (x%d)\n", order.Product, order.Amount)
|
||||
}
|
||||
|
||||
// 3. 添加新订单
|
||||
newOrder := Order{UserID: user.ID, Product: "Monitor", Amount: 1}
|
||||
db.Create(&newOrder)
|
||||
fmt.Println("New order added")
|
||||
|
||||
// 4. 查询用户的所有订单
|
||||
var orders []Order
|
||||
db.Where("user_id = ?", user.ID).Find(&orders)
|
||||
fmt.Printf("Total orders: %d\n", len(orders))
|
||||
|
||||
// 5. 删除某个订单
|
||||
db.Delete(&Order{}, orders[0].ID)
|
||||
fmt.Println("First order deleted")
|
||||
}
|
||||
51
Web开发/03go-gorm-demo/relation_has_one.go
Normal file
51
Web开发/03go-gorm-demo/relation_has_one.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 用户拥有一个账户
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Account Account // 一对一关联
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
//}
|
||||
//
|
||||
//type Account struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// UserID uint // 外键
|
||||
// Balance int
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{}, &Account{})
|
||||
|
||||
// 1. 创建用户和账户
|
||||
user := User{
|
||||
Name: "Alice",
|
||||
Account: Account{Balance: 1000},
|
||||
}
|
||||
db.Create(&user)
|
||||
fmt.Println("User and account created")
|
||||
|
||||
// 2. 预加载查询
|
||||
var result User
|
||||
db.Preload("Account").First(&result, user.ID)
|
||||
fmt.Printf("User: %s, Balance: %d\n", result.Name, result.Account.Balance)
|
||||
|
||||
// 3. 更新关联数据
|
||||
db.Model(&result.Account).Update("balance", 2000)
|
||||
fmt.Println("Account balance updated")
|
||||
|
||||
// 4. 删除关联(不会自动删除 Account)
|
||||
db.Delete(&result)
|
||||
|
||||
var accountCount int64
|
||||
db.Model(&Account{}).Count(&accountCount)
|
||||
fmt.Printf("Account still exists: %d\n", accountCount)
|
||||
}
|
||||
96
Web开发/03go-gorm-demo/relation_many_to_many.go
Normal file
96
Web开发/03go-gorm-demo/relation_many_to_many.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 学生和课程:多对多关系
|
||||
type Student struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"size:100"`
|
||||
Courses []Course `gorm:"many2many:student_courses;"` // 多对多
|
||||
}
|
||||
|
||||
type Course struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"size:100"`
|
||||
Students []Student `gorm:"many2many:student_courses;"` // 多对多
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
//db.AutoMigrate(&Student{}, &Course{})
|
||||
|
||||
// 1. 创建课程
|
||||
//math := Course{Name: "Math"}
|
||||
//physics := Course{Name: "Physics"}
|
||||
//chemistry := Course{Name: "Chemistry"}
|
||||
//db.Create(&math)
|
||||
//db.Create(&physics)
|
||||
//db.Create(&chemistry)
|
||||
|
||||
// 2. 创建学生并关联课程
|
||||
//alice := Student{
|
||||
// Name: "Alice",
|
||||
// Courses: []Course{math, physics},
|
||||
//}
|
||||
//bob := Student{
|
||||
// Name: "Bob",
|
||||
// Courses: []Course{physics, chemistry},
|
||||
//}
|
||||
//db.Create(&alice)
|
||||
//db.Create(&bob)
|
||||
//fmt.Println("Students and courses created")
|
||||
|
||||
//查询数据库的alice的学生
|
||||
var alice Student
|
||||
db.Where("name = ?", "Alice").First(&alice)
|
||||
|
||||
//查询数据库的Bob学生
|
||||
var bob Student
|
||||
db.Where("name = ?", "Bob").First(&bob)
|
||||
|
||||
//查询课程
|
||||
var math Course
|
||||
db.Where("name = ?", "Math").First(&math)
|
||||
|
||||
//// 5. 添加关联
|
||||
//db.Model(&alice).Association("Courses").Append(&chemistry)
|
||||
//fmt.Println("Alice enrolled in Chemistry")
|
||||
|
||||
//// 7. 替换所有关联
|
||||
//db.Model(&alice).Association("Courses").Replace(&math)
|
||||
//fmt.Println("Alice now only takes Math")
|
||||
|
||||
// 8. 清空关联
|
||||
db.Model(&bob).Association("Courses").Clear()
|
||||
fmt.Println("Bob dropped all courses")
|
||||
|
||||
// 3. 查询学生的课程
|
||||
var student1 Student
|
||||
db.Preload("Courses").First(&student1, 1)
|
||||
fmt.Printf("%s's courses:\n", student1.Name)
|
||||
for _, course := range student1.Courses {
|
||||
fmt.Printf(" - %s\n", course.Name)
|
||||
}
|
||||
|
||||
// 4. 查询课程的学生
|
||||
var course1 Course
|
||||
db.Preload("Students").First(&course1, 1)
|
||||
fmt.Printf("%s course students:\n", course1.Name)
|
||||
for _, student := range course1.Students {
|
||||
fmt.Printf(" - %s\n", student.Name)
|
||||
}
|
||||
|
||||
//查询bob的课程
|
||||
var student2 Student
|
||||
db.Preload("Courses").First(&student2, 2)
|
||||
fmt.Printf("%s's courses:\n", student2.Name)
|
||||
for _, course := range student2.Courses {
|
||||
fmt.Printf(" - %s\n", course.Name)
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Web开发/03go-gorm-demo/test.db
Normal file
BIN
Web开发/03go-gorm-demo/test.db
Normal file
Binary file not shown.
70
Web开发/03go-gorm-demo/transaction_basic.go
Normal file
70
Web开发/03go-gorm-demo/transaction_basic.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// Age int
|
||||
//}
|
||||
//
|
||||
//type Account struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// UserID uint
|
||||
// Balance int
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{}, &Account{})
|
||||
|
||||
// 1. 手动事务
|
||||
tx := db.Begin()
|
||||
|
||||
// 创建用户
|
||||
user := User{Name: "Alice", Email: "alice@example.com"}
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
tx.Rollback()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 创建账户
|
||||
account := Account{UserID: user.ID, Balance: 1000}
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
tx.Rollback()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
tx.Commit()
|
||||
fmt.Println("Transaction committed successfully!")
|
||||
|
||||
// 2. 自动事务(推荐)
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
// 在事务中执行操作
|
||||
user2 := User{Name: "Bob", Email: "bob@example.com"}
|
||||
if err := tx.Create(&user2).Error; err != nil {
|
||||
return err // 自动回滚
|
||||
}
|
||||
|
||||
account2 := Account{UserID: user2.ID, Balance: 2000}
|
||||
if err := tx.Create(&account2).Error; err != nil {
|
||||
return err // 自动回滚
|
||||
}
|
||||
|
||||
// 返回 nil 自动提交
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Transaction failed:", err)
|
||||
} else {
|
||||
fmt.Println("Auto transaction committed successfully!")
|
||||
}
|
||||
}
|
||||
47
Web开发/03go-gorm-demo/transaction_rollback.go
Normal file
47
Web开发/03go-gorm-demo/transaction_rollback.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
// }
|
||||
//type Account struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// UserID uint
|
||||
// Balance int
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{}, &Account{})
|
||||
|
||||
// 故意触发错误,测试回滚
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
user := User{Name: "Charlie", Email: "charlie@example.com"}
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("User created, ID:", user.ID)
|
||||
|
||||
// 故意返回错误,触发回滚
|
||||
return errors.New("something went wrong")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Transaction rolled back:", err)
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
var count int64
|
||||
db.Model(&User{}).Where("name = ?", "Charlie").Count(&count)
|
||||
fmt.Printf("User 'Charlie' count: %d (should be 0)\n", count)
|
||||
}
|
||||
37
Web开发/03go-gorm-demo/transaction_savepoint.go
Normal file
37
Web开发/03go-gorm-demo/transaction_savepoint.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//type User struct {
|
||||
// ID uint `gorm:"primaryKey"`
|
||||
// Name string `gorm:"size:100"`
|
||||
// Email string `gorm:"size:100;unique"`
|
||||
//}
|
||||
|
||||
func main() {
|
||||
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
db.Transaction(func(tx *gorm.DB) error {
|
||||
tx.Create(&User{Name: "Alice", Email: "alice@example.com"})
|
||||
|
||||
// 创建 SavePoint
|
||||
tx.SavePoint("sp1")
|
||||
tx.Create(&User{Name: "Bob", Email: "bob@example.com"})
|
||||
|
||||
// 回滚到 SavePoint
|
||||
tx.RollbackTo("sp1")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// 检查结果
|
||||
var count int64
|
||||
db.Model(&User{}).Count(&count)
|
||||
fmt.Printf("Total users: %d (should be 1, only Alice)\n", count)
|
||||
}
|
||||
36
Web开发/04go-viper-demo/01viper_basic.go
Normal file
36
Web开发/04go-viper-demo/01viper_basic.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 设置配置文件名和路径
|
||||
viper.SetConfigName("config") // 配置文件名(不含扩展名)
|
||||
viper.SetConfigType("yaml") // 配置文件类型
|
||||
viper.AddConfigPath(".") // 配置文件路径
|
||||
|
||||
// 2. 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(fmt.Errorf("Fatal error config file: %s", err))
|
||||
}
|
||||
|
||||
fmt.Println("Config file loaded successfully!")
|
||||
|
||||
// 3. 读取单个配置项
|
||||
appName := viper.GetString("app.name")
|
||||
appPort := viper.GetInt("app.port")
|
||||
fmt.Printf("App Name: %s\n", appName)
|
||||
fmt.Printf("App Port: %d\n", appPort)
|
||||
|
||||
// 4. 读取嵌套配置
|
||||
dbHost := viper.GetString("database.host")
|
||||
dbPort := viper.GetInt("database.port")
|
||||
fmt.Printf("Database: %s:%d\n", dbHost, dbPort)
|
||||
|
||||
// 5. 读取所有配置
|
||||
allSettings := viper.AllSettings()
|
||||
fmt.Printf("All settings: %v\n", allSettings)
|
||||
}
|
||||
67
Web开发/04go-viper-demo/02viper_struct.go
Normal file
67
Web开发/04go-viper-demo/02viper_struct.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// 配置结构体
|
||||
type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Version string `mapstructure:"version"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"dbname"`
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 读取配置文件
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 将配置绑定到结构体
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 使用配置
|
||||
fmt.Printf("App: %s v%s\n", config.App.Name, config.App.Version)
|
||||
fmt.Printf("Port: %d\n", config.App.Port)
|
||||
fmt.Printf("Database: %s@%s:%d/%s\n",
|
||||
config.Database.Username,
|
||||
config.Database.Host,
|
||||
config.Database.Port,
|
||||
config.Database.DBName,
|
||||
)
|
||||
fmt.Printf("Redis: %s:%d (DB %d)\n",
|
||||
config.Redis.Host,
|
||||
config.Redis.Port,
|
||||
config.Redis.DB,
|
||||
)
|
||||
}
|
||||
100
Web开发/04go-viper-demo/03viper_env.go
Normal file
100
Web开发/04go-viper-demo/03viper_env.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
//
|
||||
// "fmt"
|
||||
// "os"
|
||||
//
|
||||
// "github.com/spf13/viper"
|
||||
//
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// // 方案:先设置环境变量,再设置前缀和绑定
|
||||
// // 1. 先设置环境变量(模拟)
|
||||
// os.Setenv("MYAPP_PORT", "9000")
|
||||
// os.Setenv("MYAPP_DEBUG", "true")
|
||||
// os.Setenv("MYAPP_DB_HOST", "192.168.1.100")
|
||||
//
|
||||
// // 2. 设置环境变量前缀
|
||||
// viper.SetEnvPrefix("MYAPP")
|
||||
//
|
||||
// // 3. 设置默认值
|
||||
// viper.SetDefault("port", 8080)
|
||||
// viper.SetDefault("debug", false)
|
||||
// viper.SetDefault("db.host", "localhost")
|
||||
//
|
||||
// // 4. 绑定环境变量 - 显式指定完整的环境变量名(不加前缀)
|
||||
// viper.BindEnv("port", "MYAPP_PORT")
|
||||
// viper.BindEnv("debug", "MYAPP_DEBUG")
|
||||
// viper.BindEnv("db.host", "MYAPP_DB_HOST") // 直接指定完整的环境变量名
|
||||
//
|
||||
// // 5. 读取配置
|
||||
// port := viper.GetInt("port")
|
||||
// debug := viper.GetBool("debug")
|
||||
// dbHost := viper.GetString("db.host")
|
||||
//
|
||||
// fmt.Printf("Port: %d (from env)\n", port)
|
||||
// fmt.Printf("Debug: %v (from env)\n", debug)
|
||||
// fmt.Printf("DB Host: %s (from env)\n", dbHost)
|
||||
//
|
||||
// // 6. 启用自动环境变量绑定
|
||||
// viper.AutomaticEnv()
|
||||
//
|
||||
// // 设置自定义环境变量后获取
|
||||
// os.Setenv("MYAPP_CUSTOM_KEY", "custom_value")
|
||||
// // 注意:AutomaticEnv 后,MYAPP_CUSTOM_KEY 会映射到 custom_key
|
||||
// customKey := viper.GetString("custom_key")
|
||||
// fmt.Printf("Custom Key: %s\n", customKey)
|
||||
//
|
||||
// // 验证环境变量是否真的设置成功
|
||||
// fmt.Println("\n=== 验证环境变量 ===")
|
||||
// fmt.Printf("MYAPP_PORT: %s\n", os.Getenv("MYAPP_PORT"))
|
||||
// fmt.Printf("MYAPP_DEBUG: %s\n", os.Getenv("MYAPP_DEBUG"))
|
||||
// fmt.Printf("MYAPP_DB_HOST: %s\n", os.Getenv("MYAPP_DB_HOST"))
|
||||
// }
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 设置环境变量前缀
|
||||
viper.SetEnvPrefix("MYAPP") // 自动添加前缀
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// 2. 绑定环境变量
|
||||
viper.BindEnv("port") // 绑定 MYAPP_PORT
|
||||
viper.BindEnv("debug") // 绑定 MYAPP_DEBUG
|
||||
viper.BindEnv("db.host") // 绑定 MYAPP_DB_HOST
|
||||
|
||||
// 3. 设置默认值
|
||||
viper.SetDefault("port", 8080)
|
||||
viper.SetDefault("debug", false)
|
||||
|
||||
// 4. 设置环境变量(模拟)
|
||||
os.Setenv("MYAPP_PORT", "9000")
|
||||
os.Setenv("MYAPP_DEBUG", "true")
|
||||
os.Setenv("MYAPP_DB_HOST", "192.168.1.100")
|
||||
|
||||
// 5. 读取配置(优先级:环境变量 > 配置文件 > 默认值)
|
||||
port := viper.GetInt("port")
|
||||
debug := viper.GetBool("debug")
|
||||
dbHost := viper.GetString("db.host")
|
||||
|
||||
fmt.Printf("Port: %d (from env)\n", port)
|
||||
fmt.Printf("Debug: %v (from env)\n", debug)
|
||||
fmt.Printf("DB Host: %s (from env)\n", dbHost)
|
||||
|
||||
// 6. 自动绑定所有环境变量
|
||||
viper.AutomaticEnv()
|
||||
|
||||
os.Setenv("MYAPP_CUSTOM_KEY", "custom_value")
|
||||
customKey := viper.GetString("custom.key") // 自动转换 CUSTOM_KEY -> custom.key
|
||||
fmt.Printf("Custom Key: %s\n", customKey)
|
||||
}
|
||||
67
Web开发/04go-viper-demo/04viper_multi_env.go
Normal file
67
Web开发/04go-viper-demo/04viper_multi_env.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Port int `mapstructure:"port"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
} `mapstructure:"app"`
|
||||
Database struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"dbname"`
|
||||
} `mapstructure:"database"`
|
||||
}
|
||||
|
||||
func LoadConfig(env string) (*Config, error) {
|
||||
// 根据环境加载配置文件
|
||||
configName := fmt.Sprintf("config.%s", env)
|
||||
|
||||
viper.SetConfigName(configName)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 从环境变量读取环境名称
|
||||
env := os.Getenv("APP_ENV")
|
||||
if env == "" {
|
||||
env = "dev" // 默认开发环境
|
||||
}
|
||||
|
||||
fmt.Printf("Loading config for environment: %s\n", env)
|
||||
|
||||
config, err := LoadConfig(env)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("App: %s\n", config.App.Name)
|
||||
fmt.Printf("Port: %d\n", config.App.Port)
|
||||
fmt.Printf("Debug: %v\n", config.App.Debug)
|
||||
fmt.Printf("Database: %s@%s:%d/%s\n",
|
||||
config.Database.Username,
|
||||
config.Database.Host,
|
||||
config.Database.Port,
|
||||
config.Database.DBName,
|
||||
)
|
||||
}
|
||||
39
Web开发/04go-viper-demo/05viper_watch.go
Normal file
39
Web开发/04go-viper-demo/05viper_watch.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 读取配置文件
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("Initial config loaded")
|
||||
fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
|
||||
fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
|
||||
|
||||
// 监听配置文件变化
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(in fsnotify.Event) {
|
||||
fmt.Println("\n--- Config file changed! ---")
|
||||
fmt.Printf("Event: %s\n", in.Op.String())
|
||||
fmt.Printf("File: %s\n", in.Name)
|
||||
fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
|
||||
fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
|
||||
})
|
||||
|
||||
fmt.Println("\nWatching for config changes... (modify config.yaml to see changes)")
|
||||
fmt.Println("Press Ctrl+C to exit")
|
||||
|
||||
// 保持程序运行
|
||||
select {}
|
||||
}
|
||||
20
Web开发/04go-viper-demo/06viper_json.go
Normal file
20
Web开发/04go-viper-demo/06viper_json.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("json")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
|
||||
fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
|
||||
}
|
||||
22
Web开发/04go-viper-demo/07viper_toml.go
Normal file
22
Web开发/04go-viper-demo/07viper_toml.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 直接指定完整文件路径(避免和其他 config.* 文件冲突)
|
||||
viper.SetConfigFile("./myconfig.toml")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 打印实际读取的配置文件
|
||||
fmt.Printf("Using config file: %s\n\n", viper.ConfigFileUsed())
|
||||
|
||||
fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
|
||||
fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
|
||||
}
|
||||
11
Web开发/04go-viper-demo/config.dev.yaml
Normal file
11
Web开发/04go-viper-demo/config.dev.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
app:
|
||||
name: MyApp-Dev
|
||||
port: 8080
|
||||
debug: true
|
||||
|
||||
database:
|
||||
host: localhost
|
||||
port: 3306
|
||||
username: root
|
||||
password: password
|
||||
dbname: dev_db
|
||||
10
Web开发/04go-viper-demo/config.json
Normal file
10
Web开发/04go-viper-demo/config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "MyApp",
|
||||
"port": 8080
|
||||
},
|
||||
"database": {
|
||||
"host": "localhost",
|
||||
"port": 3306
|
||||
}
|
||||
}
|
||||
11
Web开发/04go-viper-demo/config.prod.yaml
Normal file
11
Web开发/04go-viper-demo/config.prod.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
app:
|
||||
name: MyApp-Prod
|
||||
port: 80
|
||||
debug: false
|
||||
|
||||
database:
|
||||
host: prod.database.com
|
||||
port: 3306
|
||||
username: prod_user
|
||||
password: prod_password
|
||||
dbname: prod_db
|
||||
17
Web开发/04go-viper-demo/config.yaml
Normal file
17
Web开发/04go-viper-demo/config.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
app:
|
||||
name: MangMang
|
||||
version: 1.0.0
|
||||
port: 9999
|
||||
|
||||
database:
|
||||
host: localhost
|
||||
port: 3306
|
||||
username: root
|
||||
password: password
|
||||
dbname: testdb
|
||||
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 0
|
||||
26
Web开发/04go-viper-demo/go.mod
Normal file
26
Web开发/04go-viper-demo/go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
module go-viper-demo
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
52
Web开发/04go-viper-demo/go.sum
Normal file
52
Web开发/04go-viper-demo/go.sum
Normal file
@@ -0,0 +1,52 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
7
Web开发/04go-viper-demo/myconfig.toml
Normal file
7
Web开发/04go-viper-demo/myconfig.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[app]
|
||||
name = "MyApp"
|
||||
port = 8080
|
||||
|
||||
[database]
|
||||
host = "localhost"
|
||||
port = 3306
|
||||
31
Web开发/05go-zap-demo/01zap_basic.go
Normal file
31
Web开发/05go-zap-demo/01zap_basic.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 创建默认 Logger(开发模式)
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer logger.Sync() // 刷新缓冲区
|
||||
|
||||
// 2. 基础日志
|
||||
logger.Info("Hello Zap!")
|
||||
logger.Warn("This is a warning")
|
||||
logger.Error("This is an error")
|
||||
|
||||
// 3. 结构化日志(带字段)
|
||||
logger.Info("User logged in",
|
||||
zap.String("username", "alice"),
|
||||
zap.Int("user_id", 123),
|
||||
zap.Bool("is_admin", true),
|
||||
)
|
||||
|
||||
// 4. 不同日志级别
|
||||
logger.Debug("Debug message")
|
||||
logger.Info("Info message")
|
||||
logger.Warn("Warn message")
|
||||
logger.Error("Error message")
|
||||
// logger.Fatal("Fatal message") // 会退出程序
|
||||
// logger.Panic("Panic message") // 会触发 panic
|
||||
}
|
||||
22
Web开发/05go-zap-demo/02zap_production.go
Normal file
22
Web开发/05go-zap-demo/02zap_production.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建生产模式 Logger(JSON 格式,性能更高)
|
||||
logger, _ := zap.NewProduction()
|
||||
defer logger.Sync()
|
||||
|
||||
logger.Info("Application started",
|
||||
zap.String("version", "1.0.0"),
|
||||
zap.Int("port", 8080),
|
||||
)
|
||||
|
||||
logger.Error("Failed to process request",
|
||||
zap.String("method", "GET"),
|
||||
zap.String("path", "/api/users"),
|
||||
zap.Int("status_code", 500),
|
||||
)
|
||||
}
|
||||
51
Web开发/05go-zap-demo/03zap_custom.go
Normal file
51
Web开发/05go-zap-demo/03zap_custom.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 自定义配置
|
||||
config := zap.Config{
|
||||
Level: zap.NewAtomicLevelAt(zap.InfoLevel), // 日志级别
|
||||
Development: false, // 生产模式
|
||||
Encoding: "json", // 输出格式:json/console
|
||||
OutputPaths: []string{"stdout", "app.log"}, // 输出到控制台和文件
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
EncoderConfig: zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写级别名
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 时间格式
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder, // 短路径
|
||||
},
|
||||
}
|
||||
|
||||
// 2. 创建 Logger
|
||||
logger, _ := config.Build()
|
||||
defer logger.Sync()
|
||||
|
||||
// 3. 使用 Logger
|
||||
logger.Info("Custom logger initialized",
|
||||
zap.String("env", "production"),
|
||||
zap.Int("workers", 10),
|
||||
)
|
||||
|
||||
logger.Warn("Low disk space",
|
||||
zap.Int64("available_mb", 500),
|
||||
zap.Int64("threshold_mb", 1000),
|
||||
)
|
||||
|
||||
logger.Error("Database connection failed",
|
||||
zap.String("host", "localhost"),
|
||||
zap.Int("port", 3306),
|
||||
zap.Error(nil),
|
||||
)
|
||||
}
|
||||
48
Web开发/05go-zap-demo/04zap_rotate.go
Normal file
48
Web开发/05go-zap-demo/04zap_rotate.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 配置日志轮转
|
||||
logRotate := &lumberjack.Logger{
|
||||
Filename: "./logs/app.log", // 日志文件路径
|
||||
MaxSize: 10, // 每个日志文件最大 10MB
|
||||
MaxBackups: 5, // 保留最近 5 个备份
|
||||
MaxAge: 30, // 保留 30 天
|
||||
Compress: true, // 压缩旧日志
|
||||
}
|
||||
|
||||
// 2. 创建 WriteSyncer
|
||||
writeSyncer := zapcore.AddSync(logRotate)
|
||||
|
||||
// 3. 配置 Encoder
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
// 4. 创建 Core
|
||||
core := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
writeSyncer,
|
||||
zap.InfoLevel,
|
||||
)
|
||||
|
||||
// 5. 创建 Logger
|
||||
logger := zap.New(core, zap.AddCaller())
|
||||
defer logger.Sync()
|
||||
|
||||
// 6. 使用 Logger
|
||||
for i := 0; i < 100; i++ {
|
||||
logger.Info("Processing request",
|
||||
zap.Int("request_id", i),
|
||||
zap.String("method", "GET"),
|
||||
zap.String("path", "/api/users"),
|
||||
)
|
||||
}
|
||||
|
||||
logger.Info("Log rotation configured successfully")
|
||||
}
|
||||
39
Web开发/05go-zap-demo/05zap_multi_output.go
Normal file
39
Web开发/05go-zap-demo/05zap_multi_output.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 配置 Encoder
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 带颜色的级别
|
||||
|
||||
// 2. 控制台输出(console 格式)
|
||||
consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
|
||||
consoleOutput := zapcore.AddSync(os.Stdout)
|
||||
|
||||
// 3. 文件输出(json 格式)
|
||||
fileEncoder := zapcore.NewJSONEncoder(encoderConfig)
|
||||
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
fileOutput := zapcore.AddSync(file)
|
||||
|
||||
// 4. 创建多输出 Core
|
||||
core := zapcore.NewTee(
|
||||
zapcore.NewCore(consoleEncoder, consoleOutput, zap.InfoLevel),
|
||||
zapcore.NewCore(fileEncoder, fileOutput, zap.InfoLevel),
|
||||
)
|
||||
|
||||
// 5. 创建 Logger
|
||||
logger := zap.New(core, zap.AddCaller())
|
||||
defer logger.Sync()
|
||||
|
||||
// 6. 使用 Logger
|
||||
logger.Info("Application started")
|
||||
logger.Warn("This is a warning")
|
||||
logger.Error("This is an error")
|
||||
}
|
||||
29
Web开发/05go-zap-demo/06zap_sugar.go
Normal file
29
Web开发/05go-zap-demo/06zap_sugar.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger, _ := zap.NewProduction()
|
||||
defer logger.Sync()
|
||||
|
||||
// 1. 获取 Sugar Logger
|
||||
sugar := logger.Sugar()
|
||||
|
||||
// 2. 使用格式化字符串(类似 fmt.Printf)
|
||||
sugar.Infof("User %s logged in with ID %d", "alice", 123)
|
||||
sugar.Warnf("Low disk space: %d MB available", 500)
|
||||
sugar.Errorf("Failed to connect to %s:%d", "localhost", 3306)
|
||||
|
||||
// 3. 使用键值对
|
||||
sugar.Infow("User logged in",
|
||||
"username", "alice",
|
||||
"user_id", 123,
|
||||
"is_admin", true,
|
||||
)
|
||||
|
||||
// 4. 简洁日志
|
||||
sugar.Info("Simple info message")
|
||||
sugar.Warn("Simple warn message")
|
||||
}
|
||||
45
Web开发/05go-zap-demo/07zap_dynamic_level.go
Normal file
45
Web开发/05go-zap-demo/07zap_dynamic_level.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 创建可调整的日志级别
|
||||
atom := zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||
|
||||
// 2. 配置 Logger
|
||||
config := zap.Config{
|
||||
Level: atom,
|
||||
Development: false,
|
||||
Encoding: "json",
|
||||
OutputPaths: []string{"stdout"},
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
EncoderConfig: zap.NewProductionEncoderConfig(),
|
||||
}
|
||||
|
||||
logger, _ := config.Build()
|
||||
defer logger.Sync()
|
||||
|
||||
// 3. 初始日志级别为 Info
|
||||
logger.Debug("Debug message - 1") // 不会输出
|
||||
logger.Info("Info message - 1") // 会输出
|
||||
|
||||
// 4. 动态调整为 Debug 级别
|
||||
time.Sleep(1 * time.Second)
|
||||
atom.SetLevel(zap.DebugLevel)
|
||||
logger.Info("Log level changed to Debug")
|
||||
|
||||
logger.Debug("Debug message - 2") // 现在会输出
|
||||
logger.Info("Info message - 2") // 会输出
|
||||
|
||||
// 5. 动态调整为 Warn 级别
|
||||
time.Sleep(1 * time.Second)
|
||||
atom.SetLevel(zap.WarnLevel)
|
||||
logger.Info("Log level changed to Warn")
|
||||
|
||||
logger.Info("Info message - 3") // 不会输出
|
||||
logger.Warn("Warn message - 3") // 会输出
|
||||
}
|
||||
32
Web开发/05go-zap-demo/08zap_global.go
Normal file
32
Web开发/05go-zap-demo/08zap_global.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 创建 Logger
|
||||
logger, _ := zap.NewProduction()
|
||||
defer logger.Sync()
|
||||
|
||||
// 2. 设置为全局 Logger
|
||||
zap.ReplaceGlobals(logger)
|
||||
|
||||
// 3. 在任何地方使用全局 Logger
|
||||
doSomething()
|
||||
doAnotherThing()
|
||||
}
|
||||
|
||||
func doSomething() {
|
||||
// 直接使用全局 Logger
|
||||
zap.L().Info("Doing something",
|
||||
zap.String("function", "doSomething"),
|
||||
)
|
||||
}
|
||||
|
||||
func doAnotherThing() {
|
||||
// 使用全局 Sugar Logger
|
||||
zap.S().Infow("Doing another thing",
|
||||
"function", "doAnotherThing",
|
||||
)
|
||||
}
|
||||
34
Web开发/05go-zap-demo/09zap_performance.go
Normal file
34
Web开发/05go-zap-demo/09zap_performance.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. 使用 Sampling 采样(减少高频日志)
|
||||
config := zap.NewProductionConfig()
|
||||
config.Sampling = &zap.SamplingConfig{
|
||||
Initial: 100, // 前 100 条日志全部记录
|
||||
Thereafter: 100, // 之后每 100 条记录 1 条
|
||||
}
|
||||
|
||||
logger, _ := config.Build()
|
||||
defer logger.Sync()
|
||||
|
||||
// 2. 预分配字段(避免重复创建)
|
||||
baseFields := []zapcore.Field{
|
||||
zap.String("service", "user-service"),
|
||||
zap.String("version", "1.0.0"),
|
||||
}
|
||||
|
||||
// 3. 使用 With 创建子 Logger
|
||||
userLogger := logger.With(baseFields...)
|
||||
|
||||
// 4. 高效记录日志
|
||||
for i := 0; i < 1000; i++ {
|
||||
userLogger.Info("Processing request",
|
||||
zap.Int("request_id", i),
|
||||
)
|
||||
}
|
||||
}
|
||||
6
Web开发/05go-zap-demo/app.log
Normal file
6
Web开发/05go-zap-demo/app.log
Normal file
@@ -0,0 +1,6 @@
|
||||
{"level":"info","time":"2025-12-30T10:08:51.909+0800","caller":"05go-zap-demo/03zap_custom.go:36","msg":"Custom logger initialized","env":"production","workers":10}
|
||||
{"level":"warn","time":"2025-12-30T10:08:51.909+0800","caller":"05go-zap-demo/03zap_custom.go:41","msg":"Low disk space","available_mb":500,"threshold_mb":1000}
|
||||
{"level":"error","time":"2025-12-30T10:08:51.909+0800","caller":"05go-zap-demo/03zap_custom.go:46","msg":"Database connection failed","host":"localhost","port":3306,"stacktrace":"main.main\n\t/home/liumangmang/GolandProjects/learn-golang/Web开发/05go-zap-demo/03zap_custom.go:46\nruntime.main\n\t/usr/lib/go-1.22/src/runtime/proc.go:271"}
|
||||
{"level":"\u001b[34mINFO\u001b[0m","ts":"2025-12-30T10:12:45.344+0800","caller":"05go-zap-demo/05zap_multi_output.go:35","msg":"Application started"}
|
||||
{"level":"\u001b[33mWARN\u001b[0m","ts":"2025-12-30T10:12:45.344+0800","caller":"05go-zap-demo/05zap_multi_output.go:36","msg":"This is a warning"}
|
||||
{"level":"\u001b[31mERROR\u001b[0m","ts":"2025-12-30T10:12:45.344+0800","caller":"05go-zap-demo/05zap_multi_output.go:37","msg":"This is an error"}
|
||||
9
Web开发/05go-zap-demo/go.mod
Normal file
9
Web开发/05go-zap-demo/go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module go-zap-demo
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
6
Web开发/05go-zap-demo/go.sum
Normal file
6
Web开发/05go-zap-demo/go.sum
Normal file
@@ -0,0 +1,6 @@
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
101
Web开发/05go-zap-demo/logs/app.log
Normal file
101
Web开发/05go-zap-demo/logs/app.log
Normal file
@@ -0,0 +1,101 @@
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.060+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":0,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":1,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":2,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":3,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":4,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":5,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":6,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":7,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":8,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":9,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":10,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":11,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":12,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":13,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":14,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":15,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":16,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":17,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":18,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":19,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":20,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":21,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":22,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":23,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":24,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":25,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":26,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":27,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":28,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":29,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":30,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":31,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":32,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":33,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":34,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":35,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":36,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":37,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":38,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":39,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":40,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":41,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":42,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":43,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":44,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":45,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":46,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":47,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":48,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":49,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":50,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":51,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":52,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":53,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":54,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":55,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":56,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":57,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":58,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":59,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":60,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":61,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":62,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":63,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":64,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":65,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":66,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":67,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":68,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":69,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":70,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":71,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":72,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":73,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":74,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":75,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":76,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":77,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":78,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":79,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":80,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":81,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":82,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":83,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":84,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":85,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":86,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":87,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":88,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":89,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":90,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":91,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":92,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":93,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":94,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":95,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":96,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":97,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":98,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":99,"method":"GET","path":"/api/users"}
|
||||
{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:47","msg":"Log rotation configured successfully"}
|
||||
8
Web开发/06go-auth-api/.idea/.gitignore
generated
vendored
Normal file
8
Web开发/06go-auth-api/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
9
Web开发/06go-auth-api/.idea/06go-auth-api.iml
generated
Normal file
9
Web开发/06go-auth-api/.idea/06go-auth-api.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
20
Web开发/06go-auth-api/.idea/dataSources.xml
generated
Normal file
20
Web开发/06go-auth-api/.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="auth" uuid="37ffb52c-594a-42a9-a7e1-f151f73e46f9">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/auth.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
<libraries>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
|
||||
</library>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
|
||||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
6
Web开发/06go-auth-api/.idea/data_source_mapping.xml
generated
Normal file
6
Web开发/06go-auth-api/.idea/data_source_mapping.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourcePerFileMappings">
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/37ffb52c-594a-42a9-a7e1-f151f73e46f9/console.sql" value="37ffb52c-594a-42a9-a7e1-f151f73e46f9" />
|
||||
</component>
|
||||
</project>
|
||||
8
Web开发/06go-auth-api/.idea/modules.xml
generated
Normal file
8
Web开发/06go-auth-api/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/06go-auth-api.iml" filepath="$PROJECT_DIR$/.idea/06go-auth-api.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
Web开发/06go-auth-api/.idea/vcs.xml
generated
Normal file
6
Web开发/06go-auth-api/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
826
Web开发/06go-auth-api/21综合实战项目.md
Normal file
826
Web开发/06go-auth-api/21综合实战项目.md
Normal file
@@ -0,0 +1,826 @@
|
||||
---
|
||||
title: 综合实战项目
|
||||
icon: mdi:shield-account
|
||||
date: 2025-12-23
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 工程化
|
||||
- 实战项目
|
||||
tag:
|
||||
- API
|
||||
- 用户认证
|
||||
- 密码加密
|
||||
- JWT
|
||||
- 综合实战
|
||||
---
|
||||
|
||||
整合 Gin、GORM、Viper、Zap 等框架,开发一个完整的用户注册/登录 API。这个项目包含用户认证、密码加密、JWT 令牌和数据库操作,是学习 Go Web 开发的完美案例。
|
||||
|
||||
<!-- more -->
|
||||
|
||||
---
|
||||
|
||||
# Go 综合实战:用户注册/登录 API 完整指南
|
||||
|
||||
这是一个完整的 Web 应用示例,整合了前面学到的所有知识:Gin、GORM、Viper、Zap、中间件等。
|
||||
|
||||
---
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
```
|
||||
go-auth-api/
|
||||
├── config/
|
||||
│ ├── app.yaml
|
||||
│ ├── app.dev.yaml
|
||||
│ └── app.prod.yaml
|
||||
├── logs/
|
||||
│ └── app.log
|
||||
├── main.go
|
||||
├── config.go
|
||||
├── logger.go
|
||||
├── db.go
|
||||
├── models.go
|
||||
├── handlers.go
|
||||
├── middleware.go
|
||||
├── jwt.go
|
||||
├── utils.go
|
||||
└── go.mod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、项目初始化
|
||||
|
||||
### 2.1 创建项目
|
||||
|
||||
```bash
|
||||
cd ~/GolandProjects
|
||||
mkdir go-auth-api && cd go-auth-api
|
||||
go mod init go-auth-api
|
||||
|
||||
# 安装依赖
|
||||
go get -u github.com/gin-gonic/gin
|
||||
go get -u gorm.io/gorm
|
||||
go get -u gorm.io/driver/sqlite
|
||||
go get -u github.com/spf13/viper
|
||||
go get -u go.uber.org/zap
|
||||
go get -u github.com/golang-jwt/jwt/v4
|
||||
go get -u golang.org/x/crypto
|
||||
go get -u gopkg.in/natefinch/lumberjack.v2
|
||||
```
|
||||
|
||||
> **版本说明**:代码兼容 Go 1.18+ 版本(建议 1.22+ 体验最佳性能)
|
||||
|
||||
### 2.2 创建目录
|
||||
|
||||
```bash
|
||||
mkdir -p config logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、配置文件
|
||||
|
||||
### 3.1 config/app.yaml
|
||||
|
||||
```yaml
|
||||
app:
|
||||
name: AuthAPI
|
||||
version: 1.0.0
|
||||
port: 8080
|
||||
env: dev
|
||||
|
||||
database:
|
||||
driver: sqlite
|
||||
path: auth.db
|
||||
|
||||
jwt:
|
||||
secret: your-secret-key-change-in-production
|
||||
expire: 86400 # 24 小时
|
||||
|
||||
password:
|
||||
bcrypt_cost: 10
|
||||
|
||||
logging:
|
||||
level: info
|
||||
format: json
|
||||
```
|
||||
|
||||
### 3.2 config/app.prod.yaml
|
||||
|
||||
```yaml
|
||||
app:
|
||||
port: 80
|
||||
env: prod
|
||||
|
||||
logging:
|
||||
level: warn
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、模型定义(models.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
Email string `gorm:"size:100;unique;not null" json:"email"`
|
||||
Password string `gorm:"size:255;not null" json:"-"` // 不在 JSON 中显示
|
||||
Phone string `gorm:"size:20" json:"phone,omitempty"`
|
||||
Age int `json:"age,omitempty"`
|
||||
Active bool `gorm:"default:true" json:"active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"-"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// 请求体
|
||||
type RegisterRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Phone string `json:"phone" binding:"omitempty,len=11"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库初始化(db.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
func InitDB(cfg *Config) error {
|
||||
var dsn string
|
||||
|
||||
if cfg.Database.Driver == "sqlite" {
|
||||
dsn = cfg.Database.Path
|
||||
}
|
||||
|
||||
var err error
|
||||
DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect database: %w", err)
|
||||
}
|
||||
|
||||
// 自动迁移
|
||||
if err = DB.AutoMigrate(&User{}); err != nil {
|
||||
return fmt.Errorf("failed to migrate database: %w", err)
|
||||
}
|
||||
|
||||
Logger.Info("Database initialized successfully")
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、JWT 和密码工具(jwt.go + utils.go)
|
||||
|
||||
### 6.1 jwt.go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
UserID uint `json:"user_id"`
|
||||
Email string `json:"email"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func GenerateToken(userID uint, email string, secret string, expire int64) (string, error) {
|
||||
claims := Claims{
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func VerifyToken(tokenString string, secret string) (*Claims, error) {
|
||||
claims := &Claims{}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 utils.go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// 密码加密
|
||||
func HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
func VerifyPassword(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// 检查邮箱是否已注册
|
||||
func EmailExists(email string) bool {
|
||||
var count int64
|
||||
DB.Model(&User{}).Where("email = ?", email).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、中间件(middleware.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 请求日志中间件
|
||||
func LoggingMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
Logger.Info("Request",
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("ip", c.ClientIP()),
|
||||
)
|
||||
|
||||
c.Next()
|
||||
|
||||
Logger.Info("Response",
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.Int("status", c.Writer.Status()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// JWT 认证中间件
|
||||
func AuthMiddleware(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
|
||||
if authHeader == "" {
|
||||
c.JSON(401, gin.H{"error": "Missing authorization header"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.JSON(401, gin.H{"error": "Invalid authorization header"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := VerifyToken(parts[1], cfg.JWT.Secret)
|
||||
if err != nil {
|
||||
Logger.Error("Token verification failed", zap.Error(err))
|
||||
c.JSON(401, gin.H{"error": "Invalid token"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息存储在上下文中
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("email", claims.Email)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// CORS 中间件
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、业务逻辑处理器(handlers.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册用户
|
||||
func Register(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
Logger.Error("Invalid registration request", zap.Error(err))
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if EmailExists(req.Email) {
|
||||
c.JSON(400, gin.H{"error": "Email already registered"})
|
||||
return
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
hashedPassword, err := HashPassword(req.Password)
|
||||
if err != nil {
|
||||
Logger.Error("Failed to hash password", zap.Error(err))
|
||||
c.JSON(500, gin.H{"error": "Internal server error"})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := User{
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Password: hashedPassword,
|
||||
Phone: req.Phone,
|
||||
}
|
||||
|
||||
if err := DB.Create(&user).Error; err != nil {
|
||||
Logger.Error("Failed to create user", zap.Error(err))
|
||||
c.JSON(500, gin.H{"error": "Failed to register user"})
|
||||
return
|
||||
}
|
||||
|
||||
Logger.Info("User registered successfully", zap.String("email", user.Email))
|
||||
|
||||
c.JSON(201, gin.H{
|
||||
"message": "User registered successfully",
|
||||
"user_id": user.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
func Login(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
var user User
|
||||
if err := DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||
Logger.Warn("User not found", zap.String("email", req.Email))
|
||||
c.JSON(401, gin.H{"error": "Invalid email or password"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !VerifyPassword(user.Password, req.Password) {
|
||||
Logger.Warn("Invalid password", zap.String("email", req.Email))
|
||||
c.JSON(401, gin.H{"error": "Invalid email or password"})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成 JWT token
|
||||
token, err := GenerateToken(user.ID, user.Email, cfg.JWT.Secret, int64(cfg.JWT.Expire))
|
||||
if err != nil {
|
||||
Logger.Error("Failed to generate token", zap.Error(err))
|
||||
c.JSON(500, gin.H{"error": "Failed to generate token"})
|
||||
return
|
||||
}
|
||||
|
||||
Logger.Info("User logged in successfully", zap.String("email", user.Email))
|
||||
|
||||
c.JSON(200, LoginResponse{
|
||||
Token: token,
|
||||
User: User{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Age: user.Age,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
func GetProfile(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var user User
|
||||
if err := DB.First(&user, userID.(uint)).Error; err != nil {
|
||||
c.JSON(404, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, user)
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
func UpdateProfile(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := DB.Model(&User{}).Where("id = ?", userID.(uint)).Updates(req).Error; err != nil {
|
||||
Logger.Error("Failed to update user", zap.Error(err))
|
||||
c.JSON(500, gin.H{"error": "Failed to update profile"})
|
||||
return
|
||||
}
|
||||
|
||||
Logger.Info("User profile updated", zap.Uint("user_id", userID.(uint)))
|
||||
c.JSON(200, gin.H{"message": "Profile updated successfully"})
|
||||
}
|
||||
|
||||
// 健康检查
|
||||
func HealthCheck(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "ok",
|
||||
"app": "AuthAPI",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、配置加载(config.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Version string `mapstructure:"version"`
|
||||
Port int `mapstructure:"port"`
|
||||
Env string `mapstructure:"env"`
|
||||
} `mapstructure:"app"`
|
||||
|
||||
Database struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Path string `mapstructure:"path"`
|
||||
} `mapstructure:"database"`
|
||||
|
||||
JWT struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
Expire int `mapstructure:"expire"`
|
||||
} `mapstructure:"jwt"`
|
||||
|
||||
Logging struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Format string `mapstructure:"format"`
|
||||
} `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
var GlobalConfig *Config
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
env := os.Getenv("GO_ENV")
|
||||
if env == "" {
|
||||
env = "dev"
|
||||
}
|
||||
|
||||
viper.SetConfigName("app")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("./config")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("failed to read config: %w", err)
|
||||
}
|
||||
|
||||
viper.SetConfigName("app." + env)
|
||||
viper.MergeInConfig()
|
||||
|
||||
var cfg Config
|
||||
if err := viper.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
GlobalConfig = &cfg
|
||||
return &cfg, nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、日志初始化(logger.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var Logger *zap.Logger
|
||||
|
||||
func InitLogger(env string) error {
|
||||
var level zapcore.Level
|
||||
|
||||
switch env {
|
||||
case "prod":
|
||||
level = zapcore.WarnLevel
|
||||
case "test":
|
||||
level = zapcore.DebugLevel
|
||||
default:
|
||||
level = zapcore.InfoLevel
|
||||
}
|
||||
|
||||
logFile := &lumberjack.Logger{
|
||||
Filename: "logs/app.log",
|
||||
MaxSize: 100,
|
||||
MaxBackups: 10,
|
||||
MaxAge: 7,
|
||||
Compress: true,
|
||||
}
|
||||
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "ts",
|
||||
LevelKey: "level",
|
||||
MessageKey: "msg",
|
||||
CallerKey: "caller",
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
core := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.NewMultiWriteSyncer(
|
||||
zapcore.AddSync(os.Stdout),
|
||||
zapcore.AddSync(logFile),
|
||||
),
|
||||
level,
|
||||
)
|
||||
|
||||
Logger = zap.New(core, zap.AddCaller())
|
||||
zap.ReplaceGlobals(Logger)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、主程序(main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载配置
|
||||
cfg, err := LoadConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 初始化日志
|
||||
if err = InitLogger(cfg.App.Env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer Logger.Sync()
|
||||
|
||||
// 初始化数据库
|
||||
if err = InitDB(cfg); err != nil {
|
||||
Logger.Fatal("Failed to initialize database", zap.Error(err))
|
||||
}
|
||||
|
||||
// 创建 Gin 应用
|
||||
r := gin.Default()
|
||||
|
||||
// 应用中间件
|
||||
r.Use(LoggingMiddleware())
|
||||
r.Use(CORSMiddleware())
|
||||
|
||||
// 公开路由
|
||||
public := r.Group("/api")
|
||||
{
|
||||
public.GET("/health", HealthCheck)
|
||||
public.POST("/register", Register(cfg))
|
||||
public.POST("/login", Login(cfg))
|
||||
}
|
||||
|
||||
// 受保护的路由
|
||||
protected := r.Group("/api")
|
||||
protected.Use(AuthMiddleware(cfg))
|
||||
{
|
||||
protected.GET("/profile", GetProfile)
|
||||
protected.PUT("/profile", UpdateProfile)
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
addr := fmt.Sprintf(":%d", cfg.App.Port)
|
||||
Logger.Info("Server starting",
|
||||
zap.String("app", cfg.App.Name),
|
||||
zap.Int("port", cfg.App.Port),
|
||||
zap.String("env", cfg.App.Env),
|
||||
)
|
||||
|
||||
if err = r.Run(addr); err != nil {
|
||||
Logger.Fatal("Server error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十二、API 使用示例
|
||||
|
||||
### 12.1 注册用户
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"password": "password123",
|
||||
"phone": "13800138000"
|
||||
}'
|
||||
|
||||
# 响应
|
||||
{
|
||||
"message": "User registered successfully",
|
||||
"user_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 12.2 用户登录
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "alice@example.com",
|
||||
"password": "password123"
|
||||
}'
|
||||
|
||||
# 响应
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"phone": "13800138000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 12.3 获取用户信息(需要认证)
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8080/api/profile \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
|
||||
|
||||
# 响应
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"phone": "13800138000",
|
||||
"age": 0,
|
||||
"active": true,
|
||||
"created_at": "2025-12-23T14:30:45Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十三、项目验收清单
|
||||
|
||||
- ✅ 用户注册(密码加密、邮箱验证)
|
||||
- ✅ 用户登录(JWT 令牌生成)
|
||||
- ✅ 用户认证(JWT 验证中间件)
|
||||
- ✅ 用户信息管理(获取、更新)
|
||||
- ✅ 配置管理(多环境)
|
||||
- ✅ 日志记录(结构化、日志轮转)
|
||||
- ✅ 错误处理(友好的错误消息)
|
||||
- ✅ 代码组织(清晰的文件结构)
|
||||
|
||||
---
|
||||
|
||||
## 十四、进阶扩展方向
|
||||
|
||||
1. **添加邮箱验证** - 发送验证码验证邮箱
|
||||
2. **实现刷新令牌** - 增加安全性
|
||||
3. **添加速率限制** - 防止暴力破解
|
||||
4. **用户权限管理** - 基于角色的访问控制
|
||||
5. **社交登录** - 集成 OAuth2(GitHub、Google)
|
||||
6. **单元测试** - 为关键业务逻辑编写测试
|
||||
7. **Docker 打包** - 容器化部署
|
||||
8. **CI/CD 流程** - 自动化测试和部署
|
||||
|
||||
---
|
||||
|
||||
祝你编码愉快!🚀 这个项目整合了 Go Web 开发的所有核心知识,是学习和面试的好材料。
|
||||
|
||||
BIN
Web开发/06go-auth-api/auth.db
Normal file
BIN
Web开发/06go-auth-api/auth.db
Normal file
Binary file not shown.
60
Web开发/06go-auth-api/config.go
Normal file
60
Web开发/06go-auth-api/config.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Version string `mapstructure:"version"`
|
||||
Port int `mapstructure:"port"`
|
||||
Env string `mapstructure:"env"`
|
||||
} `mapstructure:"app"`
|
||||
|
||||
Database struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Path string `mapstructure:"path"`
|
||||
} `mapstructure:"database"`
|
||||
|
||||
JWT struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
Expire int `mapstructure:"expire"`
|
||||
} `mapstructure:"jwt"`
|
||||
|
||||
Logging struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Format string `mapstructure:"format"`
|
||||
} `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
var GlobalConfig *Config
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
env := os.Getenv("APP_ENV")
|
||||
if env == "" {
|
||||
env = "dev"
|
||||
}
|
||||
|
||||
viper.SetConfigName("app")
|
||||
viper.AddConfigPath("./config")
|
||||
viper.SetConfigType("yaml")
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
viper.SetConfigName("app." + env)
|
||||
err = viper.MergeInConfig()
|
||||
var cfg Config
|
||||
err = viper.Unmarshal(&cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
||||
}
|
||||
GlobalConfig = &cfg
|
||||
return &cfg, nil
|
||||
}
|
||||
6
Web开发/06go-auth-api/config/app.prod.yaml
Normal file
6
Web开发/06go-auth-api/config/app.prod.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
app:
|
||||
port: 80
|
||||
env: prod
|
||||
|
||||
logging:
|
||||
level: warn
|
||||
20
Web开发/06go-auth-api/config/app.yaml
Normal file
20
Web开发/06go-auth-api/config/app.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
app:
|
||||
name: AuthAPI
|
||||
version: 1.0.0
|
||||
port: 8088
|
||||
env: dev
|
||||
|
||||
database:
|
||||
driver: sqlite
|
||||
path: auth.db
|
||||
|
||||
jwt:
|
||||
secret: your-secret-key-change-in-production
|
||||
expire: 86400 # 24 小时
|
||||
|
||||
password:
|
||||
bcrypt_cost: 10
|
||||
|
||||
logging:
|
||||
level: info
|
||||
format: json
|
||||
32
Web开发/06go-auth-api/db.go
Normal file
32
Web开发/06go-auth-api/db.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
func InitDB(cfg *Config) error {
|
||||
var dsn string
|
||||
if cfg.Database.Driver == "sqlite" {
|
||||
dsn = cfg.Database.Path
|
||||
}
|
||||
|
||||
var err error
|
||||
DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
//自动迁移
|
||||
err = DB.AutoMigrate(&User{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("自动迁移数据库失败: %w", err)
|
||||
}
|
||||
|
||||
Logger.Info("数据库初始化成功")
|
||||
return nil
|
||||
}
|
||||
62
Web开发/06go-auth-api/go.mod
Normal file
62
Web开发/06go-auth-api/go.mod
Normal file
@@ -0,0 +1,62 @@
|
||||
module go-auth-api
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
134
Web开发/06go-auth-api/go.sum
Normal file
134
Web开发/06go-auth-api/go.sum
Normal file
@@ -0,0 +1,134 @@
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
159
Web开发/06go-auth-api/handlers.go
Normal file
159
Web开发/06go-auth-api/handlers.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册用户
|
||||
func Register(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
Logger.Error("参数错误", zap.Error(err))
|
||||
c.JSON(400, gin.H{
|
||||
"message": "参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证邮箱
|
||||
if EmailExists(req.Email) {
|
||||
c.JSON(400, gin.H{
|
||||
"message": "邮箱已存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
//密码加密
|
||||
hashedPassword, err := HashPassword(req.Password)
|
||||
if err != nil {
|
||||
Logger.Error("密码加密失败", zap.Error(err))
|
||||
c.JSON(500, gin.H{
|
||||
"message": "密码加密失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := User{
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Password: hashedPassword,
|
||||
Phone: req.Phone,
|
||||
}
|
||||
|
||||
err = DB.Create(&user).Error
|
||||
if err != nil {
|
||||
Logger.Error("创建用户失败", zap.Error(err))
|
||||
c.JSON(500, gin.H{
|
||||
"message": "创建用户失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
Logger.Info("用户注册成功", zap.String("email", req.Email))
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"message": "用户注册成功",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 登录
|
||||
func Login(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"错误": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
//查找用户
|
||||
var user User
|
||||
err = DB.Where("email = ?", req.Email).First(&user).Error
|
||||
if err != nil {
|
||||
Logger.Warn("用户不存在", zap.String("email", req.Email))
|
||||
c.JSON(401, gin.H{"错误": "电子邮件或密码无效"})
|
||||
return
|
||||
}
|
||||
|
||||
//验证密码
|
||||
if !VerifyPassword(user.Password, req.Password) {
|
||||
Logger.Warn("密码错误", zap.String("email", req.Email))
|
||||
c.JSON(401, gin.H{"错误": "电子邮件或密码无效"})
|
||||
return
|
||||
}
|
||||
|
||||
//生成token
|
||||
token, err := GenerateToken(user.ID, user.Email, cfg.JWT.Secret, int64(cfg.JWT.Expire))
|
||||
|
||||
if err != nil {
|
||||
Logger.Error("生成token失败", zap.Error(err))
|
||||
c.JSON(500, gin.H{"错误": "生成token失败"})
|
||||
return
|
||||
}
|
||||
|
||||
Logger.Info("用户登录成功", zap.String("email", req.Email))
|
||||
|
||||
c.JSON(200, LoginResponse{
|
||||
Token: token,
|
||||
User: User{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Age: user.Age,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
func GetProfile(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var user User
|
||||
if err := DB.First(&user, userID.(uint)).Error; err != nil {
|
||||
c.JSON(404, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, user)
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
func UpdateProfile(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := DB.Model(&User{}).Where("id = ?", userID.(uint)).Updates(req).Error; err != nil {
|
||||
Logger.Error("Failed to update user", zap.Error(err))
|
||||
c.JSON(500, gin.H{"error": "Failed to update profile"})
|
||||
return
|
||||
}
|
||||
|
||||
Logger.Info("User profile updated", zap.Uint("user_id", userID.(uint)))
|
||||
c.JSON(200, gin.H{"message": "Profile updated successfully"})
|
||||
}
|
||||
|
||||
// 健康检查
|
||||
func HealthCheck(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "ok",
|
||||
"app": "AuthAPI",
|
||||
})
|
||||
}
|
||||
66
Web开发/06go-auth-api/jwt.go
Normal file
66
Web开发/06go-auth-api/jwt.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
UserID uint `json:"user_id"`
|
||||
Email string `json:"email"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func GenerateToken(userID uint, email string, secret string, expire int64) (string, error) {
|
||||
claims := Claims{
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// VerifyToken 验证JWT令牌的有效性
|
||||
// 参数:
|
||||
//
|
||||
// tokenString - JWT令牌字符串
|
||||
// secret - 用于验证令牌的密钥
|
||||
//
|
||||
// 返回值:
|
||||
//
|
||||
// *Claims - 解析出的令牌声明信息
|
||||
// error - 验证过程中出现的错误
|
||||
func VerifyToken(tokenString string, secret string) (*Claims, error) {
|
||||
claims := &Claims{}
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
|
||||
// 检查签名方法是否为HMAC
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证令牌是否有效
|
||||
if !token.Valid {
|
||||
return nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
|
||||
}
|
||||
56
Web开发/06go-auth-api/logger.go
Normal file
56
Web开发/06go-auth-api/logger.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var Logger *zap.Logger
|
||||
|
||||
func InitLogger(env string) error {
|
||||
var level zapcore.Level
|
||||
|
||||
switch env {
|
||||
case "prod":
|
||||
level = zapcore.WarnLevel
|
||||
case "test":
|
||||
level = zapcore.DebugLevel
|
||||
default:
|
||||
level = zapcore.InfoLevel
|
||||
}
|
||||
|
||||
logfile := &lumberjack.Logger{
|
||||
Filename: "logs/app.log",
|
||||
MaxSize: 100,
|
||||
MaxBackups: 10,
|
||||
MaxAge: 30,
|
||||
Compress: true,
|
||||
}
|
||||
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "ts",
|
||||
LevelKey: "level",
|
||||
MessageKey: "msg",
|
||||
CallerKey: "caller",
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
core := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.NewMultiWriteSyncer(
|
||||
zapcore.AddSync(os.Stdout),
|
||||
zapcore.AddSync(logfile),
|
||||
),
|
||||
level,
|
||||
)
|
||||
|
||||
Logger = zap.New(core, zap.AddCaller())
|
||||
zap.ReplaceGlobals(Logger)
|
||||
|
||||
return nil
|
||||
}
|
||||
24
Web开发/06go-auth-api/logs/app.log
Normal file
24
Web开发/06go-auth-api/logs/app.log
Normal file
@@ -0,0 +1,24 @@
|
||||
{"level":"info","ts":"2025-12-30T17:50:54.962+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
|
||||
{"level":"info","ts":"2025-12-30T17:50:54.962+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8080,"env":"dev"}
|
||||
{"level":"fatal","ts":"2025-12-30T17:50:54.963+0800","caller":"06go-auth-api/main.go:64","msg":"Server error","error":"listen tcp :8080: bind: address already in use"}
|
||||
{"level":"info","ts":"2025-12-30T17:52:46.052+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
|
||||
{"level":"info","ts":"2025-12-30T17:52:46.052+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8088,"env":"dev"}
|
||||
{"level":"info","ts":"2025-12-30T17:53:51.134+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"POST","路径":"/api/register","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:53:51.210+0800","caller":"06go-auth-api/handlers.go:56","msg":"用户注册成功","email":"alice@example.com"}
|
||||
{"level":"info","ts":"2025-12-30T17:53:51.210+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/register","状态码":200}
|
||||
{"level":"info","ts":"2025-12-30T17:54:17.482+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"POST","路径":"/api/login","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:54:17.532+0800","caller":"06go-auth-api/handlers.go:100","msg":"用户登录成功","email":"alice@example.com"}
|
||||
{"level":"info","ts":"2025-12-30T17:54:17.532+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/login","状态码":200}
|
||||
{"level":"info","ts":"2025-12-30T17:54:53.342+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:55:00.113+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:55:11.577+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:56:24.245+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:56:56.927+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
|
||||
{"level":"info","ts":"2025-12-30T17:56:56.927+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8088,"env":"dev"}
|
||||
{"level":"info","ts":"2025-12-30T17:57:06.731+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:58:16.750+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
|
||||
{"level":"info","ts":"2025-12-30T17:58:16.750+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8088,"env":"dev"}
|
||||
{"level":"info","ts":"2025-12-30T17:58:23.713+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:58:23.714+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/profile","状态码":200}
|
||||
{"level":"info","ts":"2025-12-30T17:58:57.676+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
|
||||
{"level":"info","ts":"2025-12-30T17:58:57.676+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/profile","状态码":401}
|
||||
66
Web开发/06go-auth-api/main.go
Normal file
66
Web开发/06go-auth-api/main.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//加载配置
|
||||
cfg, err := LoadConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//初始化日志
|
||||
err = InitLogger(cfg.App.Env)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer Logger.Sync()
|
||||
|
||||
//初始化数据库
|
||||
err = InitDB(cfg)
|
||||
if err != nil {
|
||||
Logger.Fatal("数据库初始化失败", zap.Error(err))
|
||||
}
|
||||
|
||||
//创建Gin应用
|
||||
r := gin.Default()
|
||||
|
||||
//中间件
|
||||
r.Use(LoggingMiddleware())
|
||||
r.Use(CORSMiddleware())
|
||||
|
||||
// 公开路由
|
||||
public := r.Group("/api")
|
||||
{
|
||||
public.GET("/health", HealthCheck)
|
||||
public.POST("/register", Register(cfg))
|
||||
public.POST("/login", Login(cfg))
|
||||
}
|
||||
|
||||
// 受保护的路由
|
||||
protected := r.Group("/api")
|
||||
protected.Use(AuthMiddleware(cfg))
|
||||
{
|
||||
protected.GET("/profile", GetProfile)
|
||||
protected.PUT("/profile", UpdateProfile)
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
addr := fmt.Sprintf(":%d", cfg.App.Port)
|
||||
Logger.Info("Server starting",
|
||||
zap.String("app", cfg.App.Name),
|
||||
zap.Int("port", cfg.App.Port),
|
||||
zap.String("env", cfg.App.Env),
|
||||
)
|
||||
|
||||
if err = r.Run(addr); err != nil {
|
||||
Logger.Fatal("Server error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
94
Web开发/06go-auth-api/middleware.go
Normal file
94
Web开发/06go-auth-api/middleware.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// LoggingMiddleware 创建一个请求日志中间件
|
||||
// 该中间件会在每个HTTP请求进入时记录请求信息,在响应返回时记录响应信息
|
||||
// 参数: 无
|
||||
// 返回值: gin.HandlerFunc - Gin框架的中间件处理函数
|
||||
func LoggingMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 记录请求日志:方法、路径、客户端IP
|
||||
Logger.Info("请求",
|
||||
zap.String("方法", c.Request.Method),
|
||||
zap.String("路径", c.Request.URL.Path),
|
||||
zap.String("IP", c.ClientIP()),
|
||||
)
|
||||
|
||||
c.Next()
|
||||
|
||||
// 记录响应日志:路径、状态码
|
||||
Logger.Info("响应",
|
||||
zap.String("路径", c.Request.URL.Path),
|
||||
zap.Int("状态码", c.Writer.Status()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// AuthMiddleware JWT 认证中间件
|
||||
// 参数: cfg - 配置对象,包含JWT密钥等配置信息
|
||||
// 返回值: gin.HandlerFunc - Gin框架的中间件处理函数
|
||||
func AuthMiddleware(cfg *Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
|
||||
if authHeader == "" {
|
||||
c.JSON(401, gin.H{"错误": "错误的认证头"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 解析Authorization头,验证格式为 "Bearer <token>"
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.JSON(401, gin.H{"错误": "错误的认证头"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 验证JWT token的有效性
|
||||
claims, err := VerifyToken(parts[1], cfg.JWT.Secret)
|
||||
if err != nil {
|
||||
Logger.Error("验证token失败", zap.Error(err))
|
||||
c.JSON(401, gin.H{"错误": "验证token失败"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息保存到上下文
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("email", claims.Email)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// CORSMiddleware 创建并返回一个CORS(跨域资源共享)中间件处理器
|
||||
// 该中间件用于处理跨域请求,设置相应的响应头信息
|
||||
//
|
||||
// 参数:
|
||||
//
|
||||
// 无
|
||||
//
|
||||
// 返回值:
|
||||
//
|
||||
// gin.HandlerFunc - Gin框架的中间件处理器函数
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 设置CORS相关响应头
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
// 处理预检请求(OPTIONS方法)
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
42
Web开发/06go-auth-api/models.go
Normal file
42
Web开发/06go-auth-api/models.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
Email string `gorm:"size:100;unique;not null" json:"email"`
|
||||
Password string `gorm:"size:255;not null" json:"-"` // 不在 JSON 中显示
|
||||
Phone string `gorm:"size:20" json:"phone,omitempty"`
|
||||
Age int `json:"age,omitempty"`
|
||||
Active bool `gorm:"default:true" json:"active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"-"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// 请求体
|
||||
type RegisterRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Phone string `json:"phone" binding:"omitempty,len=11"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
28
Web开发/06go-auth-api/utils.go
Normal file
28
Web开发/06go-auth-api/utils.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// 密码加密
|
||||
func HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
// 密码验证
|
||||
func VerifyPassword(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// 检查邮箱是否注册
|
||||
func EmailExists(email string) bool {
|
||||
var count int64
|
||||
DB.Model(&User{}).Where("email = ?", email).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
Reference in New Issue
Block a user