重构项目的实现,优化使用方法与使用逻辑

This commit is contained in:
2026-06-25 00:03:59 +08:00
parent a6e8101e09
commit 6072ec57e8
49 changed files with 1663 additions and 12534 deletions

View File

@@ -8,117 +8,35 @@ import (
"log"
"git.toowon.com/jimmy/go-common/config"
"git.toowon.com/jimmy/go-common/email"
"git.toowon.com/jimmy/go-common/factory"
)
func main() {
// 加载配置
cfg, err := config.LoadFromFile("./config/example.json")
if err != nil {
log.Fatal("Failed to load config:", err)
log.Fatal(err)
}
// 创建邮件发送器
emailConfig := cfg.GetEmail()
if emailConfig == nil {
log.Fatal("Email config is nil")
}
mailer, err := email.NewEmail(emailConfig)
app := factory.New(cfg)
mail, err := app.Email()
if err != nil {
log.Fatal("Failed to create email client:", err)
log.Fatal(err)
}
defer mail.Close()
// 示例1发送原始邮件内容推荐最灵活
fmt.Println("=== Example 1: Send Raw Email Content ===")
// 外部构建完整的邮件内容MIME格式
emailBody := []byte(`From: ` + emailConfig.From + `
To: recipient@example.com
Subject: 原始邮件测试
Content-Type: text/html; charset=UTF-8
<html>
<body>
<h1>这是原始邮件内容</h1>
<p>由外部完全控制邮件格式和内容</p>
</body>
</html>
`)
err = mailer.SendRaw(
[]string{"recipient@example.com"},
emailBody,
)
if err != nil {
log.Printf("Failed to send raw email: %v", err)
} else {
fmt.Println("Raw email sent successfully")
}
// 示例2发送简单邮件便捷方法
fmt.Println("\n=== Example 2: Send Simple Email ===")
err = mailer.SendSimple(
// 同步发送(验证码等需等待结果
err = mail.SendEmail(
[]string{"recipient@example.com"},
"测试邮件",
"这是一封测试邮件使用Go标准库发送。",
"这是一封测试邮件。",
)
if err != nil {
log.Printf("Failed to send email: %v", err)
log.Printf("sync send failed: %v", err)
} else {
fmt.Println("Email sent successfully")
fmt.Println("sync email sent")
}
// 示例3发送HTML邮件
fmt.Println("\n=== Example 3: Send HTML Email ===")
htmlBody := `
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>欢迎使用邮件服务</h1>
<p>这是一封HTML格式的邮件。</p>
<p>支持<strong>富文本</strong>格式。</p>
</body>
</html>
`
err = mailer.SendHTML(
[]string{"recipient@example.com"},
"HTML邮件测试",
htmlBody,
)
if err != nil {
log.Printf("Failed to send HTML email: %v", err)
} else {
fmt.Println("HTML email sent successfully")
}
// 示例4发送完整邮件包含抄送、密送
fmt.Println("\n=== Example 4: Send Full Email ===")
msg := &email.Message{
To: []string{"to@example.com"},
Cc: []string{"cc@example.com"},
Bcc: []string{"bcc@example.com"},
Subject: "完整邮件示例",
Body: "这是纯文本正文",
HTMLBody: `
<html>
<body>
<h1>这是HTML正文</h1>
<p>支持同时发送纯文本和HTML版本。</p>
</body>
</html>
`,
}
err = mailer.Send(msg)
if err != nil {
log.Printf("Failed to send full email: %v", err)
} else {
fmt.Println("Full email sent successfully")
}
fmt.Println("\nNote: Make sure your email configuration is correct and SMTP service is enabled.")
// 异步发送HTTP 通知类)
mail.SendEmailAsync(nil, []string{"recipient@example.com"}, "异步通知", "后台发送,不阻塞请求")
fmt.Println("async email enqueued")
}

View File

@@ -6,7 +6,7 @@ package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"git.toowon.com/jimmy/go-common/excel"
@@ -14,7 +14,6 @@ import (
"git.toowon.com/jimmy/go-common/tools"
)
// User 用户结构体示例
type User struct {
ID int `json:"id"`
Name string `json:"name"`
@@ -24,213 +23,47 @@ type User struct {
}
func main() {
// 创建工厂可选Excel导出不需要配置
fac, err := factory.NewFactoryFromFile("./config/example.json")
if err != nil {
// Excel导出不需要配置可以传nil
fac = factory.NewFactory(nil)
}
// 示例1导出结构体切片到文件
fmt.Println("=== Example 1: Export Struct Slice to File ===")
example1(fac)
// 示例2导出到HTTP响应
fmt.Println("\n=== Example 2: Export to HTTP Response ===")
example2(fac)
// 示例3使用格式化函数
fmt.Println("\n=== Example 3: Export with Format Functions ===")
example3(fac)
// 示例4使用ExportData接口
fmt.Println("\n=== Example 4: Export with ExportData Interface ===")
example4(fac)
}
// 示例1导出结构体切片到文件
func example1(fac *factory.Factory) {
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-24 * time.Hour), Status: 1},
{ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-48 * time.Hour), Status: 0},
}
columns := []factory.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{Header: "邮箱", Field: "Email", Width: 30},
{Header: "创建时间", Field: "CreatedAt", Width: 20},
{Header: "状态", Field: "Status", Width: 10},
}
err := fac.ExportToExcelFile("users.xlsx", "用户列表", columns, users)
if err != nil {
log.Printf("Failed to export to file: %v", err)
} else {
fmt.Println("Excel file exported successfully: users.xlsx")
}
}
// 示例2导出到HTTP响应
func example2(fac *factory.Factory) {
// 模拟HTTP响应
w := &mockResponseWriter{}
app := factory.New(nil)
ex := app.Excel()
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now(), Status: 0},
}
columns := []factory.ExportColumn{
{Header: "ID", Field: "ID"},
{Header: "姓名", Field: "Name"},
{Header: "邮箱", Field: "Email"},
}
// 设置HTTP响应头
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=users.xlsx")
err := fac.ExportToExcel(w, "用户列表", columns, users)
if err != nil {
log.Printf("Failed to export to HTTP response: %v", err)
} else {
fmt.Printf("Excel exported to HTTP response successfully, size: %d bytes\n", len(w.data))
}
}
// 示例3使用格式化函数
func example3(fac *factory.Factory) {
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-24 * time.Hour), Status: 0},
}
columns := []factory.ExportColumn{
columns := []excel.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{Header: "邮箱", Field: "Email", Width: 30},
{
Header: "创建时间",
Field: "CreatedAt",
Width: 20,
Format: excel.AdaptTimeFormatter(tools.FormatDateTime), // 使用适配器直接调用tools函数
},
{
Header: "状态",
Field: "Status",
Width: 10,
Format: func(value interface{}) string {
// 自定义格式化函数
if status, ok := value.(int); ok {
if status == 1 {
return "启用"
}
return "禁用"
}
return ""
},
},
}
err := fac.ExportToExcelFile("users_formatted.xlsx", "用户列表", columns, users)
if err != nil {
log.Printf("Failed to export with format: %v", err)
} else {
fmt.Println("Excel file exported with format successfully: users_formatted.xlsx")
}
}
// 示例4使用ExportData接口
func example4(fac *factory.Factory) {
// 创建实现了ExportData接口的数据对象
exportData := &UserExportData{
users: []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now(), Status: 1},
},
}
// 使用接口方法获取列定义和数据
columns := exportData.GetExportColumns()
err := fac.ExportToExcelFile("users_interface.xlsx", "用户列表", columns, exportData)
if err != nil {
log.Printf("Failed to export with interface: %v", err)
} else {
fmt.Println("Excel file exported with interface successfully: users_interface.xlsx")
}
}
// UserExportData 实现了ExportData接口的用户导出数据
type UserExportData struct {
users []User
}
// GetExportColumns 获取导出列定义
func (d *UserExportData) GetExportColumns() []excel.ExportColumn {
return []excel.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{Header: "邮箱", Field: "Email", Width: 30},
{
Header: "创建时间",
Field: "CreatedAt",
Width: 20,
Format: excel.AdaptTimeFormatter(tools.FormatDateTime),
},
{
Header: "状态",
Field: "Status",
Width: 10,
Format: func(value interface{}) string {
if status, ok := value.(int); ok {
if status == 1 {
return "启用"
}
return "禁用"
if status, ok := value.(int); ok && status == 1 {
return "启用"
}
return ""
return "禁用"
},
},
}
}
// GetExportRows 获取导出数据行
func (d *UserExportData) GetExportRows() [][]interface{} {
rows := make([][]interface{}, 0, len(d.users))
for _, user := range d.users {
row := []interface{}{
user.ID,
user.Name,
user.Email,
user.CreatedAt,
user.Status,
}
rows = append(rows, row)
if err := ex.ExportToFile("users.xlsx", "用户列表", columns, users); err != nil {
log.Fatal(err)
}
return rows
}
fmt.Println("exported users.xlsx")
// mockResponseWriter 模拟HTTP响应写入器用于示例
type mockResponseWriter struct {
header http.Header
data []byte
}
func (w *mockResponseWriter) Header() http.Header {
if w.header == nil {
w.header = make(http.Header)
f, err := os.Create("users_http.xlsx")
if err != nil {
log.Fatal(err)
}
return w.header
}
func (w *mockResponseWriter) Write(data []byte) (int, error) {
w.data = append(w.data, data...)
return len(data), nil
}
func (w *mockResponseWriter) WriteHeader(statusCode int) {
// 模拟实现
defer f.Close()
if err := ex.ExportToWriter(f, "用户列表", columns, users); err != nil {
log.Fatal(err)
}
fmt.Println("exported users_http.xlsx")
}

View File

@@ -1,166 +0,0 @@
package main
import (
"context"
"log"
"net/http"
"time"
"git.toowon.com/jimmy/go-common/factory"
)
// 示例Factory黑盒模式 - 最简化的使用方式
//
// 核心理念:
//
// 外部项目只需要传递一个配置文件路径,
// 直接使用 factory 的黑盒方法,无需获取内部对象
func main() {
// ====== 第1步创建工厂只需要配置文件路径======
fac, err := factory.NewFactoryFromFile("config.json")
if err != nil {
log.Fatal(err)
}
// ====== 第2步使用黑盒方法推荐======
// 1. 获取中间件链(自动配置所有基础中间件)
chain := fac.GetMiddlewareChain()
// 2. 添加项目特定的自定义中间件
chain.Append(authMiddleware, metricsMiddleware)
// 3. 注册路由
http.Handle("/api/users", chain.ThenFunc(handleUsers))
http.Handle("/api/upload", chain.ThenFunc(handleUpload))
// 4. 启动服务
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// ====== API处理器 ======
// 用户列表
func handleUsers(w http.ResponseWriter, r *http.Request) {
// 创建工厂(在处理器中也可以复用)
fac, _ := factory.NewFactoryFromFile("config.json")
ctx := context.Background()
// 1. 使用数据库需要获取对象因为GORM很复杂
db, _ := fac.GetDatabase()
var users []map[string]interface{}
db.Table("users").Find(&users)
// 2. 使用Redis黑盒方法推荐
cacheKey := "users:list"
cached, _ := fac.RedisGet(ctx, cacheKey)
if cached != "" {
fac.Success(w, cached)
return
}
// 3. 记录日志(黑盒方法,推荐)
fac.LogInfof(map[string]interface{}{
"action": "list_users",
"count": len(users),
}, "查询用户列表")
// 4. 缓存结果
fac.RedisSet(ctx, cacheKey, users, 5*time.Minute)
fac.Success(w, users)
}
// 文件上传
func handleUpload(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
ctx := context.Background()
// 解析上传的文件
file, header, err := r.FormFile("file")
if err != nil {
fac.LogError("文件上传失败: %v", err)
fac.Error(w, 400, "文件上传失败")
return
}
defer file.Close()
// 上传文件黑盒方法自动选择OSS或MinIO
objectKey := "uploads/" + header.Filename
url, err := fac.UploadFile(ctx, objectKey, file, header.Header.Get("Content-Type"))
if err != nil {
fac.LogError("文件上传到存储失败: %v", err)
fac.Error(w, 500, "文件上传失败")
return
}
// 记录上传日志
fac.LogInfof(map[string]interface{}{
"filename": header.Filename,
"size": header.Size,
"url": url,
}, "文件上传成功")
fac.Success(w, map[string]interface{}{
"url": url,
})
}
// ====== 自定义中间件 ======
// 认证中间件
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
ctx := context.Background()
// 获取token
token := r.Header.Get("Authorization")
if token == "" {
fac.Error(w, 401, "未授权")
return
}
// 从Redis验证token黑盒方法
userID, err := fac.RedisGet(ctx, "token:"+token)
if err != nil || userID == "" {
fac.Error(w, 401, "token无效")
return
}
// 记录日志(黑盒方法)
fac.LogInfof(map[string]interface{}{
"user_id": userID,
"path": r.URL.Path,
}, "用户请求")
// 将用户ID存入context或header
r.Header.Set("X-User-ID", userID)
next.ServeHTTP(w, r)
})
}
// 指标中间件
func metricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
ctx := context.Background()
start := time.Now()
// 继续处理请求
next.ServeHTTP(w, r)
// 记录请求耗时到Redis黑盒方法
latency := time.Since(start).Milliseconds()
key := "metrics:" + r.URL.Path
fac.RedisSet(ctx, key, latency, time.Minute)
// 记录指标日志(黑盒方法)
fac.LogDebugf(map[string]interface{}{
"path": r.URL.Path,
"latency": latency,
}, "请求指标")
})
}

View File

@@ -1,3 +1,6 @@
//go:build example
// +build example
package main
import (
@@ -6,107 +9,44 @@ import (
"git.toowon.com/jimmy/go-common/factory"
commonhttp "git.toowon.com/jimmy/go-common/http"
"git.toowon.com/jimmy/go-common/tools"
)
// 用户结构
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// 获取用户列表使用公共方法和factory
func GetUserList(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
func main() {
if err := factory.Init("config.json"); err != nil {
log.Fatal(err)
}
app := factory.Default()
chain := app.MiddlewareChain()
// 获取分页参数(使用公共方法)
pagination := commonhttp.ParsePaginationRequest(r)
page := pagination.GetPage()
pageSize := pagination.GetPageSize()
http.Handle("/users", chain.ThenFunc(listUsers))
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 获取查询参数(使用公共方法)
_ = r.URL.Query().Get("keyword") // 示例:获取查询参数
func listUsers(w http.ResponseWriter, r *http.Request) {
app := factory.Default()
i18n, _ := app.I18n()
h := commonhttp.NewHandler(w, r, commonhttp.WithI18n(i18n))
// 模拟查询数据
var req struct {
Keyword string `json:"keyword"`
commonhttp.PaginationRequest
}
if err := h.ParseJSON(&req); err != nil {
h.Error("common.invalid_request")
return
}
p := h.Pagination()
users := []User{
{ID: 1, Name: "User1", Email: "user1@example.com"},
{ID: 2, Name: "User2", Email: "user2@example.com"},
}
total := int64(100)
// 返回分页响应使用factory方法
fac.SuccessPage(w, users, total, page, pageSize)
}
// 创建用户使用公共方法和factory
func CreateUser(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
// 解析请求体(使用公共方法)
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := commonhttp.ParseJSON(r, &req); err != nil {
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
return
}
// 参数验证
if req.Name == "" {
fac.Error(w, 1001, "用户名不能为空")
return
}
// 模拟创建用户
user := User{
ID: 1,
Name: req.Name,
Email: req.Email,
}
// 返回成功响应使用factory方法统一Success方法
fac.Success(w, user, "创建成功")
}
// 获取用户详情使用公共方法和factory
func GetUser(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
// 获取查询参数(使用公共方法)
id := tools.ConvertInt64(r.URL.Query().Get("id"), 0)
if id == 0 {
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "用户ID不能为空", nil)
return
}
// 模拟查询用户
if id == 1 {
user := User{ID: 1, Name: "User1", Email: "user1@example.com"}
fac.Success(w, user)
} else {
fac.Error(w, 1002, "用户不存在")
}
}
func main() {
// 使用标准http.HandleFunc
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
GetUserList(w, r)
case http.MethodPost:
CreateUser(w, r)
default:
commonhttp.WriteJSON(w, http.StatusMethodNotAllowed, 405, "方法不支持", nil)
}
})
http.HandleFunc("/user", GetUser)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
h.SuccessPage(users, 100)
_ = p
}

View File

@@ -1,3 +1,6 @@
//go:build example
// +build example
package main
import (
@@ -8,56 +11,47 @@ import (
commonhttp "git.toowon.com/jimmy/go-common/http"
)
// ListUserRequest 用户列表请求(包含分页字段)
type ListUserRequest struct {
Keyword string `json:"keyword"`
commonhttp.PaginationRequest // 嵌入分页请求结构
Keyword string `json:"keyword"`
commonhttp.PaginationRequest
}
// User 用户结构
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// 获取用户列表使用公共方法和factory
func GetUserList(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("config.json")
var req ListUserRequest
func main() {
if err := factory.Init("config.json"); err != nil {
log.Fatal(err)
}
app := factory.Default()
chain := app.MiddlewareChain()
http.Handle("/users", chain.ThenFunc(listUsers))
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 方式1从JSON请求体解析分页字段会自动解析
func listUsers(w http.ResponseWriter, r *http.Request) {
app := factory.Default()
i18n, _ := app.I18n()
h := commonhttp.NewHandler(w, r, commonhttp.WithI18n(i18n))
var req ListUserRequest
if r.Method == http.MethodPost {
if err := commonhttp.ParseJSON(r, &req); err != nil {
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
if err := h.ParseJSON(&req); err != nil {
h.Error("common.invalid_request")
return
}
} else {
// 方式2从查询参数解析分页
pagination := commonhttp.ParsePaginationRequest(r)
req.PaginationRequest = *pagination
p := h.Pagination()
req.PaginationRequest = *p
req.Keyword = r.URL.Query().Get("keyword")
}
// 使用分页方法
page := req.GetPage() // 获取页码默认1
pageSize := req.GetPageSize() // 获取每页数量默认20最大100
_ = req.GetOffset() // 计算偏移量
// 模拟查询数据
users := []User{
{ID: 1, Name: "User1", Email: "user1@example.com"},
{ID: 2, Name: "User2", Email: "user2@example.com"},
}
total := int64(100)
// 返回分页响应使用factory方法
fac.SuccessPage(w, users, total, page, pageSize)
}
func main() {
http.HandleFunc("/users", GetUserList)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
h.SuccessPage(users, 100)
}

View File

@@ -1,3 +1,6 @@
//go:build example
// +build example
package main
import (
@@ -8,94 +11,22 @@ import (
)
func main() {
// 创建工厂实例
fac, err := factory.NewFactoryFromFile("config.json")
if err := factory.Init("config.json"); err != nil {
log.Fatal(err)
}
app := factory.Default()
i18n, err := app.I18n()
if err != nil {
log.Fatal(err)
}
// ====== 方式1从目录加载多个语言文件推荐 ======
// 目录结构:
// locales/
// zh-CN.json
// en-US.json
// ja-JP.json
fac.InitI18n("zh-CN") // 设置默认语言为中文
if err := fac.LoadI18nFromDir("locales"); err != nil {
if err := i18n.LoadFromDir("locales"); err != nil {
log.Fatal(err)
}
// 使用示例
fmt.Println("=== 示例1简单消息 ===")
msg1 := fac.GetMessage("zh-CN", "user.not_found")
fmt.Printf("中文: %s\n", msg1)
msg2 := fac.GetMessage("en-US", "user.not_found")
fmt.Printf("英文: %s\n", msg2)
// ====== 方式2从单个文件加载 ======
fmt.Println("\n=== 示例2从单个文件加载 ===")
fac2, _ := factory.NewFactoryFromFile("config.json")
fac2.InitI18n("zh-CN")
fac2.LoadI18nFromFile("locales/zh-CN.json", "zh-CN")
fac2.LoadI18nFromFile("locales/en-US.json", "en-US")
msg3 := fac2.GetMessage("zh-CN", "user.login_success")
fmt.Printf("中文: %s\n", msg3)
// ====== 示例3带参数的消息 ======
fmt.Println("\n=== 示例3带参数的消息 ===")
// 如果消息内容是 "欢迎,%s",可以使用参数
msg4 := fac.GetMessage("zh-CN", "user.welcome", "Alice")
fmt.Printf("中文: %s\n", msg4)
msg5 := fac.GetMessage("en-US", "user.welcome", "Alice")
fmt.Printf("英文: %s\n", msg5)
// ====== 示例4语言回退机制 ======
fmt.Println("\n=== 示例4语言回退机制 ===")
// 如果请求的语言不存在,会使用默认语言
msg6 := fac.GetMessage("fr-FR", "user.not_found") // fr-FR 不存在,使用默认语言 zh-CN
fmt.Printf("法语(不存在,回退到默认语言): %s\n", msg6)
// ====== 示例5高级功能 ======
fmt.Println("\n=== 示例5高级功能 ===")
i18n, _ := fac.GetI18n()
langs := i18n.GetSupportedLangs()
fmt.Printf("支持的语言: %v\n", langs)
hasLang := i18n.HasLang("en-US")
fmt.Printf("是否支持英文: %v\n", hasLang)
// ====== 示例6在HTTP处理中使用 ======
fmt.Println("\n=== 示例6在HTTP处理中使用 ===")
// 在实际HTTP处理中可以从请求头获取语言
// lang := r.Header.Get("Accept-Language") // 例如: "zh-CN,en-US;q=0.9"
// 简化示例,假设从请求中获取到语言代码
userLang := "zh-CN"
errorMsg := fac.GetMessage(userLang, "user.not_found")
fmt.Printf("错误消息: %s\n", errorMsg)
successMsg := fac.GetMessage(userLang, "user.login_success")
fmt.Printf("成功消息: %s\n", successMsg)
// ====== 示例7通过错误码直接返回国际化消息推荐 ======
fmt.Println("\n=== 示例7通过错误码直接返回国际化消息 ===")
// 在实际HTTP处理中Error 方法会自动识别消息代码并返回国际化消息
// 需要确保使用了 middleware.Language 中间件factory.GetMiddlewareChain() 已包含)
//
// 示例代码在HTTP handler中
// func handleGetUser(w http.ResponseWriter, r *http.Request) {
// fac, _ := factory.NewFactoryFromFile("config.json")
//
// // 如果用户不存在,直接传入消息代码,会自动获取国际化消息
// fac.Error(w, r, 404, "user.not_found")
// // 自动从context获取语言由middleware.Language设置返回对应语言的错误消息
// // 返回: {"code": 404, "message": "用户不存在", "message_code": "user.not_found"}
// }
//
// 如果请求头是 Accept-Language: zh-CN返回: {"code": 404, "message": "用户不存在", "message_code": "user.not_found"}
// 如果请求头是 Accept-Language: en-US返回: {"code": 404, "message": "User not found", "message_code": "user.not_found"}
fmt.Println("Error 方法会自动识别消息代码并返回国际化消息和消息代码")
fmt.Println("zh-CN:", i18n.GetMessage("zh-CN", "user.not_found"))
fmt.Println("en-US:", i18n.GetMessage("en-US", "user.not_found"))
fmt.Println("welcome:", i18n.GetMessage("zh-CN", "user.welcome", "Alice"))
fmt.Println("langs:", i18n.GetSupportedLangs())
}

View File

@@ -1,3 +1,6 @@
//go:build example
// +build example
package main
import (
@@ -8,57 +11,24 @@ import (
)
func main() {
// 加载配置
cfg, err := config.LoadFromFile("./config/example.json")
if err != nil {
log.Fatal("Failed to load config:", err)
log.Fatal(err)
}
// 方式1使用工厂创建日志记录器推荐方式
fac := factory.NewFactory(cfg)
logger, err := fac.GetLogger()
app := factory.New(cfg)
logInst, err := app.Logger()
if err != nil {
log.Fatal("Failed to create logger:", err)
log.Fatal(err)
}
defer logInst.Close()
// 如果使用异步模式程序退出前需要关闭logger
defer logger.Close()
// 方式2直接使用工厂的日志方法黑盒模式更简单
// fac.LogInfo("Application started")
// fac.LogError("An error occurred")
// 示例1基本日志记录
logger.Info("Application started")
logger.Debug("Debug message: %s", "This is a debug message")
logger.Warn("Warning message: %s", "This is a warning")
logger.Error("Error message: %s", "This is an error")
// 示例2带字段的日志记录
logger.Infof(map[string]interface{}{
logInst.Info("Application started", nil)
logInst.Info("User logged in", map[string]any{
"user_id": 123,
"action": "login",
"ip": "192.168.1.1",
}, "User logged in successfully")
logger.Errorf(map[string]interface{}{
"error_code": 1001,
"module": "database",
}, "Failed to connect to database: %v", "connection timeout")
// 示例3不同级别的日志
logger.Debug("This is a debug log")
logger.Info("This is an info log")
logger.Warn("This is a warn log")
logger.Error("This is an error log")
// 示例4异步模式使用
// 如果配置中设置了 "async": true日志会异步写入
// 程序退出前需要调用 Close() 确保所有日志写入完成
// logger.Close()
// 注意Fatal和Panic会终止程序示例中不执行
// logger.Fatal("This would exit the program")
// logger.Panic("This would panic")
})
logInst.Error("Failed to connect to database", map[string]any{
"error": "connection timeout",
})
}

View File

@@ -1,40 +1,30 @@
//go:build example
// +build example
package main
import (
"log"
"net/http"
"git.toowon.com/jimmy/go-common/factory"
commonhttp "git.toowon.com/jimmy/go-common/http"
"git.toowon.com/jimmy/go-common/middleware"
)
// 示例:简单的中间件配置
// 包括Recovery、Logging、CORS、Timezone
func main() {
// 创建简单的中间件链(使用默认配置)
chain := middleware.NewChain(
middleware.Recovery(nil), // Panic恢复使用默认logger
middleware.Logging(nil), // 请求日志使用默认logger
middleware.CORS(nil), // CORS允许所有源
middleware.Timezone, // 时区处理默认AsiaShanghai
)
if err := factory.Init("config.json"); err != nil {
log.Fatal(err)
}
app := factory.Default()
chain := app.MiddlewareChain()
// 定义API处理器
http.Handle("/api/hello", chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
// 获取时区(使用公共方法)
timezone := commonhttp.GetTimezone(r)
// 返回响应(使用公共方法)
commonhttp.Success(w, map[string]interface{}{
h := commonhttp.NewHandler(w, r)
h.Success(map[string]any{
"message": "Hello, World!",
"timezone": timezone,
"timezone": h.GetTimezone(),
})
}))
// 启动服务器
addr := ":8080"
log.Printf("Server starting on %s", addr)
log.Printf("Try: http://localhost%s/api/hello", addr)
log.Fatal(http.ListenAndServe(addr, nil))
log.Fatal(http.ListenAndServe(":8080", nil))
}

View File

@@ -1,139 +0,0 @@
# 数据库迁移示例
这个目录包含了数据库迁移的示例SQL文件。
## 文件说明
```
examples/migrations/
├── migrations/ # 迁移文件目录
│ ├── 20240101000001_create_users_table.sql # 迁移SQL
│ └── 20240101000001_create_users_table.down.sql # 回滚SQL
└── README.md # 本文件
```
## 快速开始
### 在你的项目中使用
#### 1. 创建迁移工具
在项目根目录创建 `migrate.go`
```go
package main
import (
"log"
"os"
"git.toowon.com/jimmy/go-common/migration"
)
func main() {
command := "up"
if len(os.Args) > 1 {
command = os.Args[1]
}
err := migration.RunMigrationsFromConfigWithCommand("config.json", "migrations", command)
if err != nil {
log.Fatal(err)
}
}
```
#### 2. 创建迁移文件
```bash
# 创建目录
mkdir -p migrations
# 创建迁移文件(使用时间戳作为版本号)
vim migrations/20240101000001_create_users.sql
```
#### 3. 编写SQL
```sql
-- migrations/20240101000001_create_users.sql
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL
);
```
#### 4. 执行迁移
```bash
# 执行迁移
go run migrate.go up
# 查看状态
go run migrate.go status
# 回滚
go run migrate.go down
```
### 更简单:在应用启动时自动执行
在你的 `main.go` 中:
```go
import "git.toowon.com/jimmy/go-common/migration"
func main() {
// 一行代码,启动时自动迁移
migration.RunMigrationsFromConfig("config.json", "migrations")
// 继续启动应用
startServer()
}
```
## 配置方式
### 方式1配置文件
`config.json`:
```json
{
"database": {
"type": "mysql",
"host": "localhost",
"port": 3306,
"user": "root",
"password": "password",
"database": "mydb"
}
}
```
### 方式2使用配置文件推荐
```bash
# 使用默认配置文件 config.json
go run migrate.go up
# 或指定配置文件路径
go run migrate.go up -config /path/to/config.json
```
**Docker 中**
```yaml
# docker-compose.yml
services:
app:
volumes:
# 挂载配置文件
- ./config.json:/app/config.json:ro
command: sh -c "go run migrate.go up && ./app"
```
**注意**:配置文件中的数据库主机应使用服务名(`db`),不是 `localhost`
## 更多信息
- [数据库迁移完整指南](../../MIGRATION.md) ⭐
- [详细功能文档](../../docs/migration.md)

View File

@@ -1,3 +1,6 @@
//go:build example
// +build example
package main
import (
@@ -5,98 +8,30 @@ import (
"log"
"git.toowon.com/jimmy/go-common/config"
"git.toowon.com/jimmy/go-common/sms"
"git.toowon.com/jimmy/go-common/factory"
)
func main() {
// 加载配置
cfg, err := config.LoadFromFile("./config/example.json")
if err != nil {
log.Fatal("Failed to load config:", err)
log.Fatal(err)
}
// 创建短信发送器
smsConfig := cfg.GetSMS()
if smsConfig == nil {
log.Fatal("SMS config is nil")
}
smsClient, err := sms.NewSMS(smsConfig)
app := factory.New(cfg)
smsClient, err := app.SMS()
if err != nil {
log.Fatal("Failed to create SMS client:", err)
log.Fatal(err)
}
defer smsClient.Close()
// 示例1发送原始请求推荐最灵活
fmt.Println("=== Example 1: Send Raw SMS Request ===")
// 外部构建完整的请求参数
params := map[string]string{
"PhoneNumbers": "13800138000",
"SignName": smsConfig.SignName,
"TemplateCode": smsConfig.TemplateCode,
"TemplateParam": `{"code":"123456","expire":"5"}`,
}
resp, err := smsClient.SendRaw(params)
params := map[string]string{"code": "123456"}
resp, err := smsClient.SendSMS([]string{"13800138000"}, params)
if err != nil {
log.Printf("Failed to send raw SMS: %v", err)
log.Printf("sync send failed: %v", err)
} else {
fmt.Printf("Raw SMS sent successfully, RequestID: %s\n", resp.RequestID)
fmt.Printf("sync sms sent, RequestID: %s\n", resp.RequestID)
}
// 示例2发送简单短信使用配置中的模板代码
fmt.Println("\n=== Example 2: Send Simple SMS ===")
templateParam := map[string]string{
"code": "123456",
"expire": "5",
}
resp2, err := smsClient.SendSimple(
[]string{"13800138000"},
templateParam,
)
if err != nil {
log.Printf("Failed to send SMS: %v", err)
} else {
fmt.Printf("SMS sent successfully, RequestID: %s\n", resp2.RequestID)
}
// 示例3使用指定模板发送短信
fmt.Println("\n=== Example 3: Send SMS with Template ===")
templateParam3 := map[string]string{
"code": "654321",
"expire": "10",
}
resp3, err := smsClient.SendWithTemplate(
[]string{"13800138000"},
"SMS_123456789", // 使用指定的模板代码
templateParam3,
)
if err != nil {
log.Printf("Failed to send SMS: %v", err)
} else {
fmt.Printf("SMS sent successfully, RequestID: %s\n", resp3.RequestID)
}
// 示例4使用JSON字符串作为模板参数
fmt.Println("\n=== Example 4: Send SMS with JSON String Template Param ===")
req := &sms.SendRequest{
PhoneNumbers: []string{"13800138000"},
TemplateCode: smsConfig.TemplateCode,
TemplateParam: `{"code":"888888","expire":"15"}`, // 直接使用JSON字符串
}
resp4, err := smsClient.Send(req)
if err != nil {
log.Printf("Failed to send SMS: %v", err)
} else {
fmt.Printf("SMS sent successfully, RequestID: %s\n", resp4.RequestID)
}
fmt.Println("\nNote: Make sure your Aliyun SMS service is configured correctly:")
fmt.Println("1. AccessKey ID and Secret are valid")
fmt.Println("2. Sign name is approved")
fmt.Println("3. Template code is approved")
fmt.Println("4. Template parameters match the template definition")
smsClient.SendSMSAsync(nil, []string{"13800138000"}, params)
fmt.Println("async sms enqueued")
}