From 719238b1f0885a53f5c81ea2a9b9802fdc643359 Mon Sep 17 00:00:00 2001 From: Jimmy Xue Date: Sun, 30 Nov 2025 13:15:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8C=85=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 +++-- SETUP.md | 174 ++++++++++++++++++++++ config/config.go | 2 +- docs/README.md | 14 +- docs/config.md | 8 +- docs/datetime.md | 6 +- docs/email.md | 10 +- docs/http.md | 4 +- docs/middleware.md | 30 ++-- docs/migration.md | 96 +++++++++++- docs/sms.md | 10 +- docs/storage.md | 22 +-- email/email.go | 2 +- examples/config_example.go | 4 +- examples/datetime_example.go | 2 +- examples/datetime_utc_example.go | 2 +- examples/email_example.go | 4 +- examples/http_example.go | 2 +- examples/middleware_example.go | 6 +- examples/migration_example.go | 2 +- examples/migration_reset_example.go | 105 ++++++++++++++ examples/sms_example.go | 4 +- examples/storage_example.go | 6 +- go.mod | 7 +- go.sum | 10 +- http/request.go | 2 +- middleware/timezone.go | 2 +- migration/migration.go | 217 ++++++++++++++++++++++++++++ sms/sms.go | 2 +- storage/handler.go | 2 +- storage/minio.go | 2 +- storage/oss.go | 2 +- storage/storage.go | 2 +- 33 files changed, 701 insertions(+), 98 deletions(-) create mode 100644 SETUP.md create mode 100644 examples/migration_reset_example.go diff --git a/README.md b/README.md index 83eec61..f40320e 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,24 @@ ## 安装 +### 1. 配置私有仓库(重要) + +由于本项目使用私有 Git 仓库,需要先配置 `GOPRIVATE` 环境变量: + ```bash -go get github.com/go-common +# 使用 go env 命令配置(推荐,永久生效) +go env -w GOPRIVATE=git.toowon.com + +# 验证配置 +go env GOPRIVATE +``` + +**详细配置说明请参考 [SETUP.md](./SETUP.md)** + +### 2. 安装模块 + +```bash +go get git.toowon.com/jimmy/go-commom ``` ## 使用示例 @@ -50,7 +66,7 @@ go get github.com/go-common #### 数据库迁移 ```go -import "github.com/go-common/migration" +import "git.toowon.com/jimmy/go-commom/migration" migrator := migration.NewMigrator(db) migrator.AddMigration(migration.Migration{ @@ -65,7 +81,7 @@ migrator.Up() #### 日期转换 ```go -import "github.com/go-common/datetime" +import "git.toowon.com/jimmy/go-commom/datetime" datetime.SetDefaultTimeZone(datetime.AsiaShanghai) now := datetime.Now() @@ -74,7 +90,7 @@ str := datetime.FormatDateTime(now) #### HTTP响应 ```go -import "github.com/go-common/http" +import "git.toowon.com/jimmy/go-commom/http" http.Success(w, data) http.SuccessPage(w, list, total, page, pageSize) @@ -84,8 +100,8 @@ http.Error(w, 1001, "业务错误") #### 中间件 ```go import ( - "github.com/go-common/middleware" - "github.com/go-common/http" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/http" ) // CORS + 时区中间件 @@ -101,7 +117,7 @@ timezone := http.GetTimezone(r) #### 配置管理 ```go -import "github.com/go-common/config" +import "git.toowon.com/jimmy/go-commom/config" // 从文件加载配置 cfg, err := config.LoadFromFile("./config.json") @@ -114,7 +130,7 @@ corsConfig := cfg.GetCORS() #### 文件上传和查看 ```go -import "github.com/go-common/storage" +import "git.toowon.com/jimmy/go-commom/storage" // 创建存储实例 storage, _ := storage.NewStorage(storage.StorageTypeOSS, cfg) @@ -132,7 +148,7 @@ proxyHandler := storage.NewProxyHandler(storage) #### 邮件发送 ```go -import "github.com/go-common/email" +import "git.toowon.com/jimmy/go-commom/email" // 从配置创建邮件发送器 mailer, _ := email.NewEmail(cfg.GetEmail()) @@ -147,7 +163,7 @@ mailer.SendSimple( #### 短信发送 ```go -import "github.com/go-common/sms" +import "git.toowon.com/jimmy/go-commom/sms" // 从配置创建短信发送器 smsClient, _ := sms.NewSMS(cfg.GetSMS()) diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..8ea75c9 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,174 @@ +# 项目配置说明 + +## GOPRIVATE 环境变量配置 + +由于本项目使用私有 Git 仓库 (`git.toowon.com`),需要配置 `GOPRIVATE` 环境变量,让 Go 工具链知道这些模块是私有的,不要通过公共代理下载。 + +### 配置方法 + +#### 方法一:临时配置(当前终端会话有效) + +```bash +# macOS/Linux +export GOPRIVATE=git.toowon.com + +# Windows (PowerShell) +$env:GOPRIVATE="git.toowon.com" + +# Windows (CMD) +set GOPRIVATE=git.toowon.com +``` + +#### 方法二:永久配置(推荐) + +**macOS/Linux:** + +1. 编辑 shell 配置文件(根据你使用的 shell 选择): + ```bash + # 如果是 zsh(macOS 默认) + nano ~/.zshrc + + # 如果是 bash + nano ~/.bashrc + # 或 + nano ~/.bash_profile + ``` + +2. 添加以下内容: + ```bash + export GOPRIVATE=git.toowon.com + ``` + +3. 保存文件并重新加载配置: + ```bash + # zsh + source ~/.zshrc + + # bash + source ~/.bashrc + ``` + +**Windows:** + +1. 打开"系统属性" -> "高级" -> "环境变量" +2. 在"用户变量"或"系统变量"中点击"新建" +3. 变量名:`GOPRIVATE` +4. 变量值:`git.toowon.com` +5. 点击"确定"保存 + +#### 方法三:使用 go env 命令(推荐,Go 1.13+) + +```bash +# 设置 GOPRIVATE +go env -w GOPRIVATE=git.toowon.com + +# 查看当前配置 +go env GOPRIVATE + +# 如果需要设置多个私有仓库,用逗号分隔 +go env -w GOPRIVATE=git.toowon.com,github.com/your-org +``` + +### 验证配置 + +```bash +# 查看 GOPRIVATE 配置 +go env GOPRIVATE + +# 应该输出: git.toowon.com +``` + +### 其他相关环境变量(可选) + +如果需要更细粒度的控制,还可以配置: + +```bash +# GOPRIVATE: 私有模块,不通过代理下载,不校验 checksum +go env -w GOPRIVATE=git.toowon.com + +# GONOPROXY: 不通过代理下载的模块(默认与 GOPRIVATE 相同) +go env -w GONOPROXY=git.toowon.com + +# GONOSUMDB: 不校验 checksum 的模块(默认与 GOPRIVATE 相同) +go env -w GONOSUMDB=git.toowon.com +``` + +### Git 认证配置 + +由于是私有仓库,还需要配置 Git 认证: + +#### 方法一:使用 SSH(推荐) + +1. 确保已配置 SSH 密钥 +2. 使用 SSH URL: + ```bash + git config --global url."git@git.toowon.com:".insteadOf "https://git.toowon.com/" + ``` + +#### 方法二:使用 HTTPS + 个人访问令牌 + +1. 在 Git 服务器上生成个人访问令牌 +2. 配置 Git 凭据: + ```bash + git config --global credential.helper store + ``` +3. 首次访问时会提示输入用户名和令牌 + +#### 方法三:在 URL 中包含凭据(不推荐,安全性较低) + +```bash +# 在 go.mod 中使用(不推荐) +# 或者通过 .netrc 文件配置 +``` + +### 常见问题 + +#### 问题1:go get 失败,提示找不到模块 + +**解决方案:** +1. 确认 GOPRIVATE 已正确配置 +2. 确认 Git 认证已配置 +3. 尝试手动克隆仓库验证: + ```bash + git clone git@git.toowon.com:jimmy/go-commom.git + ``` + +#### 问题2:go mod download 失败 + +**解决方案:** +```bash +# 清除模块缓存 +go clean -modcache + +# 重新下载 +go mod download +``` + +#### 问题3:IDE 无法识别模块 + +**解决方案:** +1. 重启 IDE +2. 在 IDE 中执行:`go mod tidy` +3. 确认 IDE 的 Go 环境变量配置正确 + +### 完整配置示例 + +```bash +# 1. 配置 GOPRIVATE +go env -w GOPRIVATE=git.toowon.com + +# 2. 配置 Git SSH(如果使用 SSH) +git config --global url."git@git.toowon.com:".insteadOf "https://git.toowon.com/" + +# 3. 验证配置 +go env | grep GOPRIVATE + +# 4. 测试模块下载 +go get git.toowon.com/jimmy/go-commom@latest +``` + +### 参考文档 + +- [Go Modules 官方文档](https://go.dev/ref/mod) +- [Go 私有模块配置](https://go.dev/ref/mod#private-modules) + diff --git a/config/config.go b/config/config.go index 5d940d2..ce04d9f 100644 --- a/config/config.go +++ b/config/config.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/middleware" ) // Config 应用配置 diff --git a/docs/README.md b/docs/README.md index 0decd0c..6c6eaa2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ ### 安装 ```bash -go get github.com/go-common +go get git.toowon.com/jimmy/go-commom ``` ### 使用示例 @@ -24,7 +24,7 @@ go get github.com/go-common #### 数据库迁移 ```go -import "github.com/go-common/migration" +import "git.toowon.com/jimmy/go-commom/migration" migrator := migration.NewMigrator(db) migrator.AddMigration(migration.Migration{ @@ -40,7 +40,7 @@ migrator.Up() #### 日期转换 ```go -import "github.com/go-common/datetime" +import "git.toowon.com/jimmy/go-commom/datetime" datetime.SetDefaultTimeZone(datetime.AsiaShanghai) now := datetime.Now() @@ -50,7 +50,7 @@ str := datetime.FormatDateTime(now) #### HTTP响应 ```go -import "github.com/go-common/http" +import "git.toowon.com/jimmy/go-commom/http" http.Success(w, data) http.SuccessPage(w, list, total, page, pageSize) @@ -61,8 +61,8 @@ http.Error(w, 1001, "业务错误") ```go import ( - "github.com/go-common/middleware" - "github.com/go-common/http" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/http" ) // CORS + 时区中间件 @@ -79,7 +79,7 @@ timezone := http.GetTimezone(r) #### 配置管理 ```go -import "github.com/go-common/config" +import "git.toowon.com/jimmy/go-commom/config" // 从文件加载配置 cfg, err := config.LoadFromFile("./config.json") diff --git a/docs/config.md b/docs/config.md index 1d768c6..9b39d05 100644 --- a/docs/config.md +++ b/docs/config.md @@ -103,7 +103,7 @@ ### 1. 加载配置文件 ```go -import "github.com/go-common/config" +import "git.toowon.com/jimmy/go-commom/config" // 从文件加载配置(支持绝对路径和相对路径) config, err := config.LoadFromFile("/path/to/config.json") @@ -167,7 +167,7 @@ addr := config.GetRedisAddr() corsConfig := config.GetCORS() // 使用CORS中间件 -import "github.com/go-common/middleware" +import "git.toowon.com/jimmy/go-commom/middleware" chain := middleware.NewChain( middleware.CORS(corsConfig), ) @@ -289,8 +289,8 @@ package main import ( "log" - "github.com/go-common/config" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/middleware" "gorm.io/driver/mysql" "gorm.io/gorm" ) diff --git a/docs/datetime.md b/docs/datetime.md index c55be02..90a1c83 100644 --- a/docs/datetime.md +++ b/docs/datetime.md @@ -19,7 +19,7 @@ ### 1. 设置默认时区 ```go -import "github.com/go-common/datetime" +import "git.toowon.com/jimmy/go-commom/datetime" // 设置默认时区为上海时区 err := datetime.SetDefaultTimeZone(datetime.AsiaShanghai) @@ -401,7 +401,7 @@ import ( "log" "time" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/datetime" ) func main() { @@ -428,7 +428,7 @@ import ( "fmt" "log" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/datetime" ) func main() { diff --git a/docs/email.md b/docs/email.md index 3f89600..a6a52cf 100644 --- a/docs/email.md +++ b/docs/email.md @@ -18,8 +18,8 @@ ```go import ( - "github.com/go-common/config" - "github.com/go-common/email" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/email" ) // 从配置加载 @@ -107,7 +107,7 @@ if err != nil { ### 5. 使用Message结构发送(便捷方法) ```go -import "github.com/go-common/email" +import "git.toowon.com/jimmy/go-commom/email" msg := &email.Message{ To: []string{"to@example.com"}, @@ -295,8 +295,8 @@ package main import ( "log" - "github.com/go-common/config" - "github.com/go-common/email" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/email" ) func main() { diff --git a/docs/http.md b/docs/http.md index 4531c1e..3559c51 100644 --- a/docs/http.md +++ b/docs/http.md @@ -49,7 +49,7 @@ HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,包含 ```go import ( "net/http" - "github.com/go-common/http" + "git.toowon.com/jimmy/go-commom/http" ) // 简单成功响应(data为nil) @@ -198,7 +198,7 @@ package main import ( "net/http" - "github.com/go-common/http" + "git.toowon.com/jimmy/go-commom/http" ) // 用户列表接口 diff --git a/docs/middleware.md b/docs/middleware.md index 56cf653..416a1bc 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -29,7 +29,7 @@ CORS中间件用于处理跨域资源共享,支持: ```go import ( "net/http" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/middleware" ) func main() { @@ -50,7 +50,7 @@ func main() { ```go import ( "net/http" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/middleware" ) func main() { @@ -131,9 +131,9 @@ corsHandler := middleware.CORS(corsConfig)(handler) ```go import ( "net/http" - "github.com/go-common/middleware" - "github.com/go-common/http" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/http" + "git.toowon.com/jimmy/go-commom/datetime" ) func handler(w http.ResponseWriter, r *http.Request) { @@ -162,8 +162,8 @@ func main() { ```go import ( "net/http" - "github.com/go-common/middleware" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/datetime" ) func handler(w http.ResponseWriter, r *http.Request) { @@ -183,8 +183,8 @@ func main() { ```go import ( "net/http" - "github.com/go-common/http" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/http" + "git.toowon.com/jimmy/go-commom/datetime" ) func GetUserList(w http.ResponseWriter, r *http.Request) { @@ -240,7 +240,7 @@ X-Timezone: Asia/Shanghai ```go import ( "net/http" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/middleware" ) func handler(w http.ResponseWriter, r *http.Request) { @@ -282,9 +282,9 @@ import ( "log" "net/http" - "github.com/go-common/middleware" - "github.com/go-common/http" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/http" + "git.toowon.com/jimmy/go-commom/datetime" ) func apiHandler(w http.ResponseWriter, r *http.Request) { @@ -331,8 +331,8 @@ package main import ( "net/http" - "github.com/go-common/middleware" - "github.com/go-common/http" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/http" ) func main() { diff --git a/docs/migration.md b/docs/migration.md index da17b4c..6c63a7f 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -21,7 +21,7 @@ import ( "gorm.io/driver/mysql" "gorm.io/gorm" - "github.com/go-common/migration" + "git.toowon.com/jimmy/go-commom/migration" ) // 初始化数据库连接 @@ -135,7 +135,46 @@ for _, s := range status { } ``` -### 6. 生成迁移版本号 +### 6. 重置迁移 + +#### 方式一:仅清空迁移记录(不回滚数据库变更) + +```go +// 直接调用(需要传入确认标志) +err := migrator.Reset(true) +if err != nil { + log.Fatal(err) +} + +// 交互式确认(推荐,会提示警告信息) +err := migrator.ResetWithConfirm() +if err != nil { + log.Fatal(err) +} +``` + +#### 方式二:回滚所有迁移并清空记录 + +```go +// 直接调用(需要传入确认标志) +err := migrator.ResetAll(true) +if err != nil { + log.Fatal(err) +} + +// 交互式确认(推荐,会提示警告信息) +err := migrator.ResetAllWithConfirm() +if err != nil { + log.Fatal(err) +} +``` + +**注意:** +- `Reset()` 仅清空迁移记录表,不会回滚已执行的数据库变更 +- `ResetAll()` 会回滚所有已应用的迁移(执行Down函数),然后清空记录 +- 交互式方法会显示详细的警告信息,需要输入确认文本才能执行 + +### 7. 生成迁移版本号 ```go // 生成基于时间戳的版本号 @@ -194,6 +233,44 @@ type Migration struct { **返回:** 迁移状态列表和错误信息 +#### Reset(confirm bool) error + +重置所有迁移记录(仅清空记录表,不回滚数据库变更)。 + +**参数:** +- `confirm`: 确认标志,必须为true才能执行 + +**返回:** 错误信息 + +**注意:** 此操作只清空迁移记录,不会回滚已执行的迁移操作。如果需要回滚迁移,请先使用Down方法逐个回滚。 + +#### ResetWithConfirm() error + +交互式重置所有迁移记录(带确认提示)。 + +**返回:** 错误信息 + +**说明:** 会显示警告信息,需要输入"RESET"(全大写)才能执行。 + +#### ResetAll(confirm bool) error + +重置所有迁移并回滚所有已应用的迁移。 + +**参数:** +- `confirm`: 确认标志,必须为true才能执行 + +**返回:** 错误信息 + +**注意:** 此操作会回滚所有已应用的迁移(执行Down函数),然后清空迁移记录。操作不可逆,请谨慎使用。 + +#### ResetAllWithConfirm() error + +交互式重置所有迁移并回滚(带确认提示)。 + +**返回:** 错误信息 + +**说明:** 会显示警告信息,需要输入"RESET ALL"(全大写)才能执行。 + ### MigrationStatus 结构体 ```go @@ -230,11 +307,16 @@ type MigrationStatus struct { ## 注意事项 -1. 迁移版本号必须唯一,建议使用时间戳格式 -2. 迁移操作在事务中执行,失败会自动回滚 -3. 迁移记录表会自动创建,无需手动创建 -4. 如果迁移文件没有对应的down文件,回滚操作会失败 -5. 迁移按版本号升序执行,确保顺序正确 +1. **迁移版本号**:必须唯一,建议使用时间戳格式 +2. **事务支持**:迁移操作在事务中执行,失败会自动回滚 +3. **自动创建表**:迁移记录表会自动创建,无需手动创建 +4. **回滚文件**:如果迁移文件没有对应的down文件,回滚操作会失败 +5. **执行顺序**:迁移按版本号升序执行,确保顺序正确 +6. **重置操作**: + - `Reset()` 只清空迁移记录,不会回滚数据库变更 + - `ResetAll()` 会回滚所有迁移,操作不可逆 + - 建议使用交互式方法(`ResetWithConfirm`、`ResetAllWithConfirm`)以确保安全 + - 在生产环境使用重置功能前,请确保已备份数据库 ## 示例 diff --git a/docs/sms.md b/docs/sms.md index 9ee3c17..75ca96e 100644 --- a/docs/sms.md +++ b/docs/sms.md @@ -19,8 +19,8 @@ ```go import ( - "github.com/go-common/config" - "github.com/go-common/sms" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/sms" ) // 从配置加载 @@ -102,7 +102,7 @@ if err != nil { ### 5. 使用SendRequest结构发送(便捷方法) ```go -import "github.com/go-common/sms" +import "git.toowon.com/jimmy/go-commom/sms" req := &sms.SendRequest{ PhoneNumbers: []string{"13800138000", "13900139000"}, @@ -329,8 +329,8 @@ import ( "fmt" "log" - "github.com/go-common/config" - "github.com/go-common/sms" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/sms" ) func main() { diff --git a/docs/storage.md b/docs/storage.md index cca976e..6b03175 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -21,8 +21,8 @@ ```go import ( - "github.com/go-common/config" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/storage" ) // 加载配置 @@ -50,7 +50,7 @@ if err != nil { import ( "context" "os" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/storage" ) // 打开文件 @@ -81,7 +81,7 @@ fmt.Printf("File URL: %s\n", url) ```go import ( "net/http" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/storage" ) // 创建上传处理器 @@ -125,7 +125,7 @@ curl -X POST http://localhost:8080/upload \ ```go import ( "net/http" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/storage" ) // 创建代理查看处理器 @@ -144,7 +144,7 @@ GET /file?key=images/test.jpg ### 5. 生成对象键 ```go -import "github.com/go-common/storage" +import "git.toowon.com/jimmy/go-commom/storage" // 生成简单对象键 objectKey := storage.GenerateObjectKey("images/", "test.jpg") @@ -288,9 +288,9 @@ import ( "log" "net/http" - "github.com/go-common/config" - "github.com/go-common/middleware" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/storage" ) func main() { @@ -344,8 +344,8 @@ import ( "log" "os" - "github.com/go-common/config" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/storage" ) func main() { diff --git a/email/email.go b/email/email.go index 8bd9a1c..8c852f5 100644 --- a/email/email.go +++ b/email/email.go @@ -8,7 +8,7 @@ import ( "net/smtp" "time" - "github.com/go-common/config" + "git.toowon.com/jimmy/go-commom/config" ) // Email 邮件发送器 diff --git a/examples/config_example.go b/examples/config_example.go index cd83542..0054005 100644 --- a/examples/config_example.go +++ b/examples/config_example.go @@ -4,8 +4,8 @@ import ( "fmt" "log" - "github.com/go-common/config" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/middleware" // "gorm.io/driver/mysql" // "gorm.io/gorm" ) diff --git a/examples/datetime_example.go b/examples/datetime_example.go index 7b4765b..4cc93d6 100644 --- a/examples/datetime_example.go +++ b/examples/datetime_example.go @@ -5,7 +5,7 @@ import ( "log" "time" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/datetime" ) func main() { diff --git a/examples/datetime_utc_example.go b/examples/datetime_utc_example.go index 2711db6..36d9804 100644 --- a/examples/datetime_utc_example.go +++ b/examples/datetime_utc_example.go @@ -5,7 +5,7 @@ import ( "log" "time" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/datetime" ) func main() { diff --git a/examples/email_example.go b/examples/email_example.go index b499c15..0b759da 100644 --- a/examples/email_example.go +++ b/examples/email_example.go @@ -4,8 +4,8 @@ import ( "fmt" "log" - "github.com/go-common/config" - "github.com/go-common/email" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/email" ) func main() { diff --git a/examples/http_example.go b/examples/http_example.go index 5e000f6..9043bb3 100644 --- a/examples/http_example.go +++ b/examples/http_example.go @@ -4,7 +4,7 @@ import ( "log" "net/http" - "github.com/go-common/http" + "git.toowon.com/jimmy/go-commom/http" ) // 用户结构 diff --git a/examples/middleware_example.go b/examples/middleware_example.go index e702b82..47dc890 100644 --- a/examples/middleware_example.go +++ b/examples/middleware_example.go @@ -4,9 +4,9 @@ import ( "log" "net/http" - "github.com/go-common/datetime" - "github.com/go-common/http" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/datetime" + "git.toowon.com/jimmy/go-commom/http" + "git.toowon.com/jimmy/go-commom/middleware" ) // 示例:使用CORS和时区中间件 diff --git a/examples/migration_example.go b/examples/migration_example.go index 91f7652..efb44e4 100644 --- a/examples/migration_example.go +++ b/examples/migration_example.go @@ -6,7 +6,7 @@ import ( "gorm.io/driver/mysql" "gorm.io/gorm" - "github.com/go-common/migration" + "git.toowon.com/jimmy/go-commom/migration" ) func main() { diff --git a/examples/migration_reset_example.go b/examples/migration_reset_example.go new file mode 100644 index 0000000..b509931 --- /dev/null +++ b/examples/migration_reset_example.go @@ -0,0 +1,105 @@ +package main + +import ( + "fmt" + "log" + + "git.toowon.com/jimmy/go-commom/migration" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func main() { + // 初始化数据库连接(使用SQLite作为示例) + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + + // 创建迁移器 + migrator := migration.NewMigrator(db) + + // 添加一些迁移 + migrator.AddMigrations( + migration.Migration{ + Version: "20240101000001", + Description: "create_users_table", + Up: func(db *gorm.DB) error { + return db.Exec(` + CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL + ) + `).Error + }, + Down: func(db *gorm.DB) error { + return db.Exec("DROP TABLE IF EXISTS users").Error + }, + }, + migration.Migration{ + Version: "20240101000002", + Description: "add_created_at_to_users", + Up: func(db *gorm.DB) error { + return db.Exec("ALTER TABLE users ADD COLUMN created_at DATETIME").Error + }, + Down: func(db *gorm.DB) error { + return db.Exec("ALTER TABLE users DROP COLUMN created_at").Error + }, + }, + ) + + // 执行迁移 + fmt.Println("=== Executing migrations ===") + err = migrator.Up() + if err != nil { + log.Fatal("Failed to run migrations:", err) + } + + // 查看状态 + fmt.Println("\n=== Migration status ===") + status, err := migrator.Status() + if err != nil { + log.Fatal("Failed to get status:", err) + } + for _, s := range status { + fmt.Printf("Version: %s, Description: %s, Applied: %v\n", + s.Version, s.Description, s.Applied) + } + + // 示例1:仅清空迁移记录(不回滚数据库变更) + fmt.Println("\n=== Example 1: Reset migration records only ===") + fmt.Println("Note: This only clears records, not database changes") + // 直接调用(需要确认标志) + // err = migrator.Reset(true) + // if err != nil { + // log.Fatal("Failed to reset:", err) + // } + + // 交互式确认(推荐) + // 取消注释下面的代码来测试交互式重置 + // err = migrator.ResetWithConfirm() + // if err != nil { + // log.Fatal("Failed to reset with confirm:", err) + // } + + // 示例2:回滚所有迁移并清空记录 + fmt.Println("\n=== Example 2: Reset all migrations (rollback + clear records) ===") + fmt.Println("Note: This will rollback all migrations and clear records") + // 直接调用(需要确认标志) + // err = migrator.ResetAll(true) + // if err != nil { + // log.Fatal("Failed to reset all:", err) + // } + + // 交互式确认(推荐) + // 取消注释下面的代码来测试交互式重置 + // err = migrator.ResetAllWithConfirm() + // if err != nil { + // log.Fatal("Failed to reset all with confirm:", err) + // } + + fmt.Println("\nNote: Reset functions are commented out for safety.") + fmt.Println("Uncomment the code above to test reset functionality.") +} + diff --git a/examples/sms_example.go b/examples/sms_example.go index cbde14c..bc46cd5 100644 --- a/examples/sms_example.go +++ b/examples/sms_example.go @@ -4,8 +4,8 @@ import ( "fmt" "log" - "github.com/go-common/config" - "github.com/go-common/sms" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/sms" ) func main() { diff --git a/examples/storage_example.go b/examples/storage_example.go index 533161b..c495b12 100644 --- a/examples/storage_example.go +++ b/examples/storage_example.go @@ -4,9 +4,9 @@ import ( "log" "net/http" - "github.com/go-common/config" - "github.com/go-common/middleware" - "github.com/go-common/storage" + "git.toowon.com/jimmy/go-commom/config" + "git.toowon.com/jimmy/go-commom/middleware" + "git.toowon.com/jimmy/go-commom/storage" ) func main() { diff --git a/go.mod b/go.mod index 39a4f3c..059301a 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,17 @@ -module github.com/go-common +module git.toowon.com/jimmy/go-commom go 1.21 require ( gorm.io/driver/mysql v1.5.2 - gorm.io/gorm v1.25.5 + gorm.io/driver/sqlite v1.6.0 + gorm.io/gorm v1.30.0 ) require ( github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + golang.org/x/text v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 7a5c143..98c0e5b 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,14 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD 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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= diff --git a/http/request.go b/http/request.go index 5044c3d..59ad956 100644 --- a/http/request.go +++ b/http/request.go @@ -7,7 +7,7 @@ import ( "net/http" "strconv" - "github.com/go-common/middleware" + "git.toowon.com/jimmy/go-commom/middleware" ) // ParseJSON 解析JSON请求体 diff --git a/middleware/timezone.go b/middleware/timezone.go index 24eb16a..ce240ab 100644 --- a/middleware/timezone.go +++ b/middleware/timezone.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/go-common/datetime" + "git.toowon.com/jimmy/go-commom/datetime" ) // TimezoneKey context中存储时区的key diff --git a/migration/migration.go b/migration/migration.go index befec3d..ef01b12 100644 --- a/migration/migration.go +++ b/migration/migration.go @@ -1,6 +1,7 @@ package migration import ( + "bufio" "fmt" "os" "path/filepath" @@ -371,3 +372,219 @@ func LoadMigrationsFromFiles(dir string, pattern string) ([]Migration, error) { func GenerateVersion() string { return strconv.FormatInt(time.Now().Unix(), 10) } + +// Reset 重置所有迁移(清空迁移记录表) +// confirm: 确认标志,必须为true才能执行重置 +// 注意:此操作会清空所有迁移记录,但不会回滚已执行的迁移操作 +// 如果需要回滚迁移,请先使用Down方法逐个回滚 +func (m *Migrator) Reset(confirm bool) error { + if !confirm { + return fmt.Errorf("reset operation requires explicit confirmation (confirm must be true)") + } + + if err := m.initTable(); err != nil { + return err + } + + // 获取已应用的迁移数量 + applied, err := m.getAppliedMigrations() + if err != nil { + return err + } + + count := len(applied) + if count == 0 { + fmt.Println("No migrations to reset") + return nil + } + + // 清空迁移记录表 + err = m.db.Exec(fmt.Sprintf("DELETE FROM %s", m.tableName)).Error + if err != nil { + return fmt.Errorf("failed to reset migrations: %w", err) + } + + fmt.Printf("Reset completed: %d migration record(s) cleared\n", count) + fmt.Println("WARNING: This only clears migration records, not the actual database changes.") + fmt.Println("If you need to rollback database changes, use Down() method before reset.") + + return nil +} + +// ResetWithConfirm 交互式重置所有迁移(带确认提示) +// 会提示用户数据会被清空,需要输入确认才能执行 +func (m *Migrator) ResetWithConfirm() error { + if err := m.initTable(); err != nil { + return err + } + + // 获取已应用的迁移数量 + applied, err := m.getAppliedMigrations() + if err != nil { + return err + } + + count := len(applied) + if count == 0 { + fmt.Println("No migrations to reset") + return nil + } + + // 显示警告信息 + fmt.Println("=" + strings.Repeat("=", 60) + "=") + fmt.Println("WARNING: This operation will clear all migration records!") + fmt.Println("=" + strings.Repeat("=", 60) + "=") + fmt.Printf("This will clear %d migration record(s) from the database.\n", count) + fmt.Println() + fmt.Println("IMPORTANT NOTES:") + fmt.Println(" 1. This operation only clears migration records, NOT the actual database changes.") + fmt.Println(" 2. If you need to rollback database changes, use Down() method before reset.") + fmt.Println(" 3. After reset, you may need to re-run migrations if the database structure changed.") + fmt.Println() + fmt.Print("Type 'RESET' (all caps) to confirm, or anything else to cancel: ") + + // 读取用户输入 + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("failed to read confirmation: %w", err) + } + + // 去除换行符和空格 + input = strings.TrimSpace(input) + + // 检查确认 + if input != "RESET" { + fmt.Println("Reset operation cancelled.") + return nil + } + + // 执行重置 + return m.Reset(true) +} + +// ResetAll 重置所有迁移并回滚所有已应用的迁移 +// confirm: 确认标志,必须为true才能执行 +// 注意:此操作会回滚所有已应用的迁移,然后清空迁移记录 +func (m *Migrator) ResetAll(confirm bool) error { + if !confirm { + return fmt.Errorf("reset all operation requires explicit confirmation (confirm must be true)") + } + + if err := m.initTable(); err != nil { + return err + } + + // 获取已应用的迁移 + applied, err := m.getAppliedMigrations() + if err != nil { + return err + } + + count := len(applied) + if count == 0 { + fmt.Println("No migrations to reset") + return nil + } + + // 回滚所有已应用的迁移 + fmt.Printf("Rolling back %d migration(s)...\n", count) + + // 排序迁移(倒序) + sort.Slice(m.migrations, func(i, j int) bool { + return m.migrations[i].Version > m.migrations[j].Version + }) + + // 回滚所有已应用的迁移 + for _, migration := range m.migrations { + if !applied[migration.Version] { + continue + } + + if migration.Down == nil { + fmt.Printf("Warning: Migration %s has no Down function, skipping rollback\n", migration.Version) + continue + } + + // 开始事务 + tx := m.db.Begin() + if tx.Error != nil { + return fmt.Errorf("failed to begin transaction: %w", tx.Error) + } + + // 执行回滚 + if err := migration.Down(tx); err != nil { + tx.Rollback() + return fmt.Errorf("failed to rollback migration %s: %w", migration.Version, err) + } + + // 删除迁移记录 + if err := m.recordMigrationWithDB(tx, migration.Version, migration.Description, false); err != nil { + tx.Rollback() + return err + } + + // 提交事务 + if err := tx.Commit().Error; err != nil { + return fmt.Errorf("failed to commit rollback %s: %w", migration.Version, err) + } + + fmt.Printf("Rolled back migration: %s - %s\n", migration.Version, migration.Description) + } + + fmt.Printf("Reset all completed: %d migration(s) rolled back and records cleared\n", count) + return nil +} + +// ResetAllWithConfirm 交互式重置所有迁移并回滚(带确认提示) +// 会提示用户数据会被清空,需要输入确认才能执行 +func (m *Migrator) ResetAllWithConfirm() error { + if err := m.initTable(); err != nil { + return err + } + + // 获取已应用的迁移数量 + applied, err := m.getAppliedMigrations() + if err != nil { + return err + } + + count := len(applied) + if count == 0 { + fmt.Println("No migrations to reset") + return nil + } + + // 显示警告信息 + fmt.Println("=" + strings.Repeat("=", 60) + "=") + fmt.Println("WARNING: This operation will rollback ALL migrations and clear records!") + fmt.Println("=" + strings.Repeat("=", 60) + "=") + fmt.Printf("This will rollback %d migration(s) and clear all migration records.\n", count) + fmt.Println() + fmt.Println("IMPORTANT NOTES:") + fmt.Println(" 1. This operation will EXECUTE Down() functions for all applied migrations.") + fmt.Println(" 2. All database changes made by migrations will be REVERTED.") + fmt.Println(" 3. This operation CANNOT be undone!") + fmt.Println(" 4. Make sure you have a database backup before proceeding.") + fmt.Println() + fmt.Print("Type 'RESET ALL' (all caps) to confirm, or anything else to cancel: ") + + // 读取用户输入 + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("failed to read confirmation: %w", err) + } + + // 去除换行符和空格 + input = strings.TrimSpace(input) + + // 检查确认 + if input != "RESET ALL" { + fmt.Println("Reset all operation cancelled.") + return nil + } + + // 执行重置 + return m.ResetAll(true) +} diff --git a/sms/sms.go b/sms/sms.go index c2fac94..5516a30 100644 --- a/sms/sms.go +++ b/sms/sms.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/go-common/config" + "git.toowon.com/jimmy/go-commom/config" ) // SMS 短信发送器 diff --git a/storage/handler.go b/storage/handler.go index ae6adbf..8d81cd2 100644 --- a/storage/handler.go +++ b/storage/handler.go @@ -9,7 +9,7 @@ import ( "strings" "time" - commonhttp "github.com/go-common/http" + commonhttp "git.toowon.com/jimmy/go-commom/http" ) // UploadHandler 文件上传处理器 diff --git a/storage/minio.go b/storage/minio.go index e01c16b..f7299ae 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -6,7 +6,7 @@ import ( "io" "strings" - "github.com/go-common/config" + "git.toowon.com/jimmy/go-commom/config" ) // MinIOStorage MinIO存储实现 diff --git a/storage/oss.go b/storage/oss.go index c2b9861..486bfce 100644 --- a/storage/oss.go +++ b/storage/oss.go @@ -6,7 +6,7 @@ import ( "io" "strings" - "github.com/go-common/config" + "git.toowon.com/jimmy/go-commom/config" ) // OSSStorage OSS存储实现 diff --git a/storage/storage.go b/storage/storage.go index 1f105e5..f056ac0 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -6,7 +6,7 @@ import ( "io" "time" - "github.com/go-common/config" + "git.toowon.com/jimmy/go-commom/config" ) // Storage 存储接口