Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 339920a940 |
22
README.md
22
README.md
@@ -295,12 +295,24 @@ http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||
```
|
||||
|
||||
### 日期时间
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/datetime"
|
||||
**推荐方式:通过 factory 使用(黑盒模式)**
|
||||
|
||||
datetime.SetDefaultTimeZone(datetime.AsiaShanghai)
|
||||
now := datetime.Now()
|
||||
str := datetime.FormatDateTime(now)
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/factory"
|
||||
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
str := fac.FormatDateTime(now)
|
||||
```
|
||||
|
||||
**或者直接使用 tools 包:**
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/tools"
|
||||
|
||||
tools.SetDefaultTimeZone(tools.AsiaShanghai)
|
||||
now := tools.Now()
|
||||
str := tools.FormatDateTime(now)
|
||||
```
|
||||
|
||||
更多示例:[examples目录](./examples/)
|
||||
|
||||
@@ -57,12 +57,24 @@ CREATE TABLE users (id BIGINT PRIMARY KEY AUTO_INCREMENT, ...);
|
||||
|
||||
#### 日期转换
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/datetime"
|
||||
**推荐方式:通过 factory 使用(黑盒模式)**
|
||||
|
||||
datetime.SetDefaultTimeZone(datetime.AsiaShanghai)
|
||||
now := datetime.Now()
|
||||
str := datetime.FormatDateTime(now)
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/factory"
|
||||
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
str := fac.FormatDateTime(now)
|
||||
```
|
||||
|
||||
**或者直接使用 tools 包:**
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/tools"
|
||||
|
||||
tools.SetDefaultTimeZone(tools.AsiaShanghai)
|
||||
now := tools.Now()
|
||||
str := tools.FormatDateTime(now)
|
||||
```
|
||||
|
||||
#### HTTP响应(Handler黑盒模式)
|
||||
|
||||
439
docs/datetime.md
439
docs/datetime.md
@@ -4,6 +4,8 @@
|
||||
|
||||
日期转换工具提供了丰富的日期时间处理功能,支持时区设定、格式转换、时间计算等常用操作。
|
||||
|
||||
**重要说明**:日期时间功能位于 `tools` 包中,推荐通过 `factory` 包使用(黑盒模式),也可以直接使用 `tools` 包。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持时区设定和转换
|
||||
@@ -16,13 +18,34 @@
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 设置默认时区
|
||||
### 方式1:通过 factory 使用(推荐,黑盒模式)
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/datetime"
|
||||
import "git.toowon.com/jimmy/go-common/factory"
|
||||
|
||||
// 创建工厂
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取当前时间
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
|
||||
// 格式化时间
|
||||
str := fac.FormatDateTime(now)
|
||||
|
||||
// 解析时间
|
||||
t, _ := fac.ParseDateTime("2024-01-01 12:00:00", "Asia/Shanghai")
|
||||
|
||||
// 时间计算
|
||||
tomorrow := fac.AddDays(now, 1)
|
||||
```
|
||||
|
||||
### 方式2:直接使用 tools 包
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/tools"
|
||||
|
||||
// 设置默认时区为上海时区
|
||||
err := datetime.SetDefaultTimeZone(datetime.AsiaShanghai)
|
||||
err := tools.SetDefaultTimeZone(tools.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -30,166 +53,284 @@ if err != nil {
|
||||
|
||||
### 2. 获取当前时间
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
// 使用默认时区
|
||||
now := datetime.Now()
|
||||
now := fac.Now()
|
||||
|
||||
// 使用指定时区
|
||||
now := datetime.Now(datetime.AsiaShanghai)
|
||||
now := datetime.Now("America/New_York")
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
now := fac.Now("America/New_York")
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
// 使用默认时区
|
||||
now := tools.Now()
|
||||
|
||||
// 使用指定时区
|
||||
now := tools.Now(tools.AsiaShanghai)
|
||||
now := tools.Now("America/New_York")
|
||||
```
|
||||
|
||||
### 3. 解析时间字符串
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
// 使用默认时区解析
|
||||
t, err := datetime.Parse("2006-01-02 15:04:05", "2024-01-01 12:00:00")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t, err := fac.ParseDateTime("2024-01-01 12:00:00")
|
||||
|
||||
// 使用指定时区解析
|
||||
t, err := datetime.Parse("2006-01-02 15:04:05", "2024-01-01 12:00:00", datetime.AsiaShanghai)
|
||||
t, err := fac.ParseDateTime("2024-01-01 12:00:00", "Asia/Shanghai")
|
||||
|
||||
// 解析日期
|
||||
t, err := fac.ParseDate("2024-01-01")
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
// 使用默认时区解析
|
||||
t, err := tools.Parse("2006-01-02 15:04:05", "2024-01-01 12:00:00")
|
||||
|
||||
// 使用指定时区解析
|
||||
t, err := tools.Parse("2006-01-02 15:04:05", "2024-01-01 12:00:00", tools.AsiaShanghai)
|
||||
|
||||
// 使用常用格式解析
|
||||
t, err := datetime.ParseDateTime("2024-01-01 12:00:00")
|
||||
t, err := datetime.ParseDate("2024-01-01")
|
||||
t, err := tools.ParseDateTime("2024-01-01 12:00:00")
|
||||
t, err := tools.ParseDate("2024-01-01")
|
||||
```
|
||||
|
||||
### 4. 格式化时间
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 使用默认时区格式化
|
||||
str := datetime.Format(t, "2006-01-02 15:04:05")
|
||||
str := fac.FormatDateTime(t)
|
||||
str := fac.FormatDate(t)
|
||||
str := fac.FormatTime(t)
|
||||
|
||||
// 使用指定时区格式化
|
||||
str := datetime.Format(t, "2006-01-02 15:04:05", datetime.AsiaShanghai)
|
||||
str := fac.FormatDateTime(t, "Asia/Shanghai")
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 使用默认时区格式化
|
||||
str := tools.Format(t, "2006-01-02 15:04:05")
|
||||
|
||||
// 使用指定时区格式化
|
||||
str := tools.Format(t, "2006-01-02 15:04:05", tools.AsiaShanghai)
|
||||
|
||||
// 使用常用格式
|
||||
str := datetime.FormatDateTime(t) // "2006-01-02 15:04:05"
|
||||
str := datetime.FormatDate(t) // "2006-01-02"
|
||||
str := datetime.FormatTime(t) // "15:04:05"
|
||||
str := tools.FormatDateTime(t) // "2006-01-02 15:04:05"
|
||||
str := tools.FormatDate(t) // "2006-01-02"
|
||||
str := tools.FormatTime(t) // "15:04:05"
|
||||
```
|
||||
|
||||
### 5. 时区转换
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
t := time.Now()
|
||||
t2, err := tools.ToTimezone(t, "Asia/Shanghai")
|
||||
```
|
||||
|
||||
// 转换到指定时区
|
||||
t2, err := datetime.ToTimezone(t, datetime.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
t := time.Now()
|
||||
t2, err := tools.ToTimezone(t, tools.AsiaShanghai)
|
||||
```
|
||||
|
||||
### 6. Unix时间戳转换
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 转换为Unix时间戳(秒)
|
||||
unix := datetime.ToUnix(t)
|
||||
unix := fac.ToUnix(t)
|
||||
|
||||
// 从Unix时间戳创建时间
|
||||
t2 := datetime.FromUnix(unix)
|
||||
t2 := fac.FromUnix(unix)
|
||||
|
||||
// 转换为Unix毫秒时间戳
|
||||
unixMilli := datetime.ToUnixMilli(t)
|
||||
unixMilli := fac.ToUnixMilli(t)
|
||||
|
||||
// 从Unix毫秒时间戳创建时间
|
||||
t3 := datetime.FromUnixMilli(unixMilli)
|
||||
t3 := fac.FromUnixMilli(unixMilli)
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 转换为Unix时间戳(秒)
|
||||
unix := tools.ToUnix(t)
|
||||
|
||||
// 从Unix时间戳创建时间
|
||||
t2 := tools.FromUnix(unix)
|
||||
|
||||
// 转换为Unix毫秒时间戳
|
||||
unixMilli := tools.ToUnixMilli(t)
|
||||
|
||||
// 从Unix毫秒时间戳创建时间
|
||||
t3 := tools.FromUnixMilli(unixMilli)
|
||||
```
|
||||
|
||||
### 7. 时间计算
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 添加天数
|
||||
t1 := datetime.AddDays(t, 7)
|
||||
t1 := fac.AddDays(t, 7)
|
||||
|
||||
// 添加月数
|
||||
t2 := datetime.AddMonths(t, 1)
|
||||
t2 := fac.AddMonths(t, 1)
|
||||
|
||||
// 添加年数
|
||||
t3 := datetime.AddYears(t, 1)
|
||||
t3 := fac.AddYears(t, 1)
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 添加天数
|
||||
t1 := tools.AddDays(t, 7)
|
||||
|
||||
// 添加月数
|
||||
t2 := tools.AddMonths(t, 1)
|
||||
|
||||
// 添加年数
|
||||
t3 := tools.AddYears(t, 1)
|
||||
```
|
||||
|
||||
### 8. 时间范围获取
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 获取一天的开始时间(00:00:00)
|
||||
start := datetime.StartOfDay(t)
|
||||
start := fac.StartOfDay(t)
|
||||
|
||||
// 获取一天的结束时间(23:59:59.999999999)
|
||||
end := datetime.EndOfDay(t)
|
||||
end := fac.EndOfDay(t)
|
||||
|
||||
// 获取月份的开始时间
|
||||
monthStart := datetime.StartOfMonth(t)
|
||||
monthStart := fac.StartOfMonth(t)
|
||||
|
||||
// 获取月份的结束时间
|
||||
monthEnd := datetime.EndOfMonth(t)
|
||||
monthEnd := fac.EndOfMonth(t)
|
||||
|
||||
// 获取年份的开始时间
|
||||
yearStart := datetime.StartOfYear(t)
|
||||
yearStart := fac.StartOfYear(t)
|
||||
|
||||
// 获取年份的结束时间
|
||||
yearEnd := datetime.EndOfYear(t)
|
||||
yearEnd := fac.EndOfYear(t)
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
t := time.Now()
|
||||
|
||||
// 获取一天的开始时间(00:00:00)
|
||||
start := tools.StartOfDay(t)
|
||||
|
||||
// 获取一天的结束时间(23:59:59.999999999)
|
||||
end := tools.EndOfDay(t)
|
||||
|
||||
// 获取月份的开始时间
|
||||
monthStart := tools.StartOfMonth(t)
|
||||
|
||||
// 获取月份的结束时间
|
||||
monthEnd := tools.EndOfMonth(t)
|
||||
|
||||
// 获取年份的开始时间
|
||||
yearStart := tools.StartOfYear(t)
|
||||
|
||||
// 获取年份的结束时间
|
||||
yearEnd := tools.EndOfYear(t)
|
||||
```
|
||||
|
||||
### 9. 时间差计算
|
||||
|
||||
**通过 factory:**
|
||||
```go
|
||||
t1 := time.Now()
|
||||
t2 := time.Now().Add(24 * time.Hour)
|
||||
|
||||
// 计算天数差
|
||||
days := datetime.DiffDays(t1, t2)
|
||||
days := fac.DiffDays(t1, t2)
|
||||
|
||||
// 计算小时差
|
||||
hours := datetime.DiffHours(t1, t2)
|
||||
hours := fac.DiffHours(t1, t2)
|
||||
|
||||
// 计算分钟差
|
||||
minutes := datetime.DiffMinutes(t1, t2)
|
||||
minutes := fac.DiffMinutes(t1, t2)
|
||||
|
||||
// 计算秒数差
|
||||
seconds := datetime.DiffSeconds(t1, t2)
|
||||
seconds := fac.DiffSeconds(t1, t2)
|
||||
```
|
||||
|
||||
**直接使用 tools:**
|
||||
```go
|
||||
t1 := time.Now()
|
||||
t2 := time.Now().Add(24 * time.Hour)
|
||||
|
||||
// 计算天数差
|
||||
days := tools.DiffDays(t1, t2)
|
||||
|
||||
// 计算小时差
|
||||
hours := tools.DiffHours(t1, t2)
|
||||
|
||||
// 计算分钟差
|
||||
minutes := tools.DiffMinutes(t1, t2)
|
||||
|
||||
// 计算秒数差
|
||||
seconds := tools.DiffSeconds(t1, t2)
|
||||
```
|
||||
|
||||
### 10. 转换为UTC时间(用于数据库存储)
|
||||
|
||||
**直接使用 tools(factory 暂未提供):**
|
||||
```go
|
||||
// 将任意时区的时间转换为UTC
|
||||
t := time.Now() // 当前时区的时间
|
||||
utcTime := datetime.ToUTC(t)
|
||||
utcTime := tools.ToUTC(t)
|
||||
|
||||
// 从指定时区转换为UTC
|
||||
t, _ := datetime.ParseDateTime("2024-01-01 12:00:00", datetime.AsiaShanghai)
|
||||
utcTime, err := datetime.ToUTCFromTimezone(t, datetime.AsiaShanghai)
|
||||
t, _ := tools.ParseDateTime("2024-01-01 12:00:00", tools.AsiaShanghai)
|
||||
utcTime, err := tools.ToUTCFromTimezone(t, tools.AsiaShanghai)
|
||||
|
||||
// 解析时间字符串并直接转换为UTC
|
||||
utcTime, err := datetime.ParseDateTimeToUTC("2024-01-01 12:00:00", datetime.AsiaShanghai)
|
||||
utcTime, err := tools.ParseDateTimeToUTC("2024-01-01 12:00:00", tools.AsiaShanghai)
|
||||
|
||||
// 解析日期并转换为UTC(当天的00:00:00 UTC)
|
||||
utcTime, err := datetime.ParseDateToUTC("2024-01-01", datetime.AsiaShanghai)
|
||||
utcTime, err := tools.ParseDateToUTC("2024-01-01", tools.AsiaShanghai)
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### 时区常量
|
||||
|
||||
**通过 tools 包使用:**
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/tools"
|
||||
|
||||
const (
|
||||
UTC = "UTC"
|
||||
AsiaShanghai = "Asia/Shanghai"
|
||||
AmericaNewYork = "America/New_York"
|
||||
EuropeLondon = "Europe/London"
|
||||
AsiaTokyo = "Asia/Tokyo"
|
||||
tools.UTC = "UTC"
|
||||
tools.AsiaShanghai = "Asia/Shanghai"
|
||||
tools.AmericaNewYork = "America/New_York"
|
||||
tools.EuropeLondon = "Europe/London"
|
||||
tools.AsiaTokyo = "Asia/Tokyo"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -208,15 +349,22 @@ CommonLayouts.RFC3339Nano = time.RFC3339Nano
|
||||
|
||||
### 主要函数
|
||||
|
||||
**注意**:以下函数可以通过 `factory` 或 `tools` 包调用。推荐使用 `factory` 的黑盒模式。
|
||||
|
||||
#### SetDefaultTimeZone(timezone string) error
|
||||
|
||||
设置默认时区。
|
||||
设置默认时区(仅 tools 包提供)。
|
||||
|
||||
**参数:**
|
||||
- `timezone`: 时区字符串,如 "Asia/Shanghai"
|
||||
|
||||
**返回:** 错误信息
|
||||
|
||||
**使用方式:**
|
||||
```go
|
||||
tools.SetDefaultTimeZone(tools.AsiaShanghai)
|
||||
```
|
||||
|
||||
#### Now(timezone ...string) time.Time
|
||||
|
||||
获取当前时间。
|
||||
@@ -226,105 +374,54 @@ CommonLayouts.RFC3339Nano = time.RFC3339Nano
|
||||
|
||||
**返回:** 时间对象
|
||||
|
||||
#### Parse(layout, value string, timezone ...string) (time.Time, error)
|
||||
**使用方式:**
|
||||
```go
|
||||
// 通过 factory
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
|
||||
解析时间字符串。
|
||||
// 直接使用 tools
|
||||
now := tools.Now(tools.AsiaShanghai)
|
||||
```
|
||||
|
||||
#### ParseDateTime(value string, timezone ...string) (time.Time, error)
|
||||
|
||||
解析日期时间字符串(2006-01-02 15:04:05)。
|
||||
|
||||
**参数:**
|
||||
- `layout`: 时间格式,如 "2006-01-02 15:04:05"
|
||||
- `value`: 时间字符串
|
||||
- `timezone`: 可选,时区字符串
|
||||
|
||||
**返回:** 时间对象和错误信息
|
||||
|
||||
#### Format(t time.Time, layout string, timezone ...string) string
|
||||
**使用方式:**
|
||||
```go
|
||||
// 通过 factory
|
||||
t, err := fac.ParseDateTime("2024-01-01 12:00:00", "Asia/Shanghai")
|
||||
|
||||
格式化时间。
|
||||
// 直接使用 tools
|
||||
t, err := tools.ParseDateTime("2024-01-01 12:00:00", tools.AsiaShanghai)
|
||||
```
|
||||
|
||||
#### FormatDateTime(t time.Time, timezone ...string) string
|
||||
|
||||
格式化日期时间(2006-01-02 15:04:05)。
|
||||
|
||||
**参数:**
|
||||
- `t`: 时间对象
|
||||
- `layout`: 时间格式
|
||||
- `timezone`: 可选,时区字符串
|
||||
|
||||
**返回:** 格式化后的时间字符串
|
||||
|
||||
#### ToTimezone(t time.Time, timezone string) (time.Time, error)
|
||||
**使用方式:**
|
||||
```go
|
||||
// 通过 factory
|
||||
str := fac.FormatDateTime(t, "Asia/Shanghai")
|
||||
|
||||
转换时区。
|
||||
// 直接使用 tools
|
||||
str := tools.FormatDateTime(t, tools.AsiaShanghai)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `t`: 时间对象
|
||||
- `timezone`: 目标时区
|
||||
|
||||
**返回:** 转换后的时间对象和错误信息
|
||||
|
||||
#### ToUnix(t time.Time) int64
|
||||
|
||||
转换为Unix时间戳(秒)。
|
||||
|
||||
#### FromUnix(sec int64, timezone ...string) time.Time
|
||||
|
||||
从Unix时间戳创建时间。
|
||||
|
||||
#### ToUnixMilli(t time.Time) int64
|
||||
|
||||
转换为Unix毫秒时间戳。
|
||||
|
||||
#### FromUnixMilli(msec int64, timezone ...string) time.Time
|
||||
|
||||
从Unix毫秒时间戳创建时间。
|
||||
|
||||
#### AddDays(t time.Time, days int) time.Time
|
||||
|
||||
添加天数。
|
||||
|
||||
#### AddMonths(t time.Time, months int) time.Time
|
||||
|
||||
添加月数。
|
||||
|
||||
#### AddYears(t time.Time, years int) time.Time
|
||||
|
||||
添加年数。
|
||||
|
||||
#### StartOfDay(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取一天的开始时间。
|
||||
|
||||
#### EndOfDay(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取一天的结束时间。
|
||||
|
||||
#### StartOfMonth(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取月份的开始时间。
|
||||
|
||||
#### EndOfMonth(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取月份的结束时间。
|
||||
|
||||
#### StartOfYear(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取年份的开始时间。
|
||||
|
||||
#### EndOfYear(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取年份的结束时间。
|
||||
|
||||
#### DiffDays(t1, t2 time.Time) int
|
||||
|
||||
计算两个时间之间的天数差。
|
||||
|
||||
#### DiffHours(t1, t2 time.Time) int64
|
||||
|
||||
计算两个时间之间的小时差。
|
||||
|
||||
#### DiffMinutes(t1, t2 time.Time) int64
|
||||
|
||||
计算两个时间之间的分钟差。
|
||||
|
||||
#### DiffSeconds(t1, t2 time.Time) int64
|
||||
|
||||
计算两个时间之间的秒数差。
|
||||
更多函数请参考 `factory` 包或 `tools` 包的 API 文档。
|
||||
|
||||
### UTC转换函数
|
||||
|
||||
@@ -391,7 +488,7 @@ CommonLayouts.RFC3339Nano = time.RFC3339Nano
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例1:基本使用
|
||||
### 示例1:通过 factory 使用(推荐)
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -399,27 +496,67 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建工厂
|
||||
fac, err := factory.NewFactoryFromFile("config.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
fmt.Printf("Current time: %s\n", fac.FormatDateTime(now))
|
||||
|
||||
// 解析时间
|
||||
t, err := fac.ParseDateTime("2024-01-01 12:00:00", "Asia/Shanghai")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
fmt.Printf("Parsed time: %s\n", fac.FormatDateTime(t))
|
||||
|
||||
// 时间计算
|
||||
tomorrow := fac.AddDays(now, 1)
|
||||
fmt.Printf("Tomorrow: %s\n", fac.FormatDate(tomorrow))
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:直接使用 tools 包
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 设置默认时区
|
||||
datetime.SetDefaultTimeZone(datetime.AsiaShanghai)
|
||||
err := tools.SetDefaultTimeZone(tools.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
now := datetime.Now()
|
||||
fmt.Printf("Current time: %s\n", datetime.FormatDateTime(now))
|
||||
now := tools.Now()
|
||||
fmt.Printf("Current time: %s\n", tools.FormatDateTime(now))
|
||||
|
||||
// 时区转换
|
||||
t, _ := datetime.ParseDateTime("2024-01-01 12:00:00")
|
||||
t2, _ := datetime.ToTimezone(t, datetime.AmericaNewYork)
|
||||
fmt.Printf("Time in New York: %s\n", datetime.FormatDateTime(t2))
|
||||
t, _ := tools.ParseDateTime("2024-01-01 12:00:00")
|
||||
t2, _ := tools.ToTimezone(t, tools.AmericaNewYork)
|
||||
fmt.Printf("Time in New York: %s\n", tools.FormatDateTime(t2))
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:UTC转换(数据库存储场景)
|
||||
### 示例3:UTC转换(数据库存储场景)
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -428,34 +565,32 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 从请求中获取时间(假设是上海时区)
|
||||
requestTimeStr := "2024-01-01 12:00:00"
|
||||
requestTimezone := datetime.AsiaShanghai
|
||||
requestTimezone := tools.AsiaShanghai
|
||||
|
||||
// 转换为UTC时间(用于数据库存储)
|
||||
dbTime, err := datetime.ParseDateTimeToUTC(requestTimeStr, requestTimezone)
|
||||
dbTime, err := tools.ParseDateTimeToUTC(requestTimeStr, requestTimezone)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Request time (Shanghai): %s\n", requestTimeStr)
|
||||
fmt.Printf("Database time (UTC): %s\n", datetime.FormatDateTime(dbTime, datetime.UTC))
|
||||
fmt.Printf("Database time (UTC): %s\n", tools.FormatDateTime(dbTime, tools.UTC))
|
||||
|
||||
// 从数据库读取UTC时间,转换为用户时区显示
|
||||
userTimezone := datetime.AsiaShanghai
|
||||
displayTime, err := datetime.ToTimezone(dbTime, userTimezone)
|
||||
userTimezone := tools.AsiaShanghai
|
||||
displayTime, err := tools.ToTimezone(dbTime, userTimezone)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Display time (Shanghai): %s\n", datetime.FormatDateTime(displayTime, userTimezone))
|
||||
fmt.Printf("Display time (Shanghai): %s\n", tools.FormatDateTime(displayTime, userTimezone))
|
||||
}
|
||||
```
|
||||
|
||||
完整示例请参考:
|
||||
- `examples/datetime_example.go` - 基本使用示例
|
||||
- `examples/datetime_utc_example.go` - UTC转换示例
|
||||
完整示例请参考 `factory` 包中的 datetime 相关方法,通过 `factory` 调用 `tools` 包中的 datetime 功能。
|
||||
|
||||
|
||||
299
docs/factory.md
299
docs/factory.md
@@ -26,6 +26,11 @@
|
||||
| **邮件** | `SendEmail()` | `fac.SendEmail(to, subject, body)` |
|
||||
| **短信** | `SendSMS()` | `fac.SendSMS(phones, params)` |
|
||||
| **存储** | `UploadFile()`, `GetFileURL()` | `fac.UploadFile(ctx, key, file)` |
|
||||
| **日期时间** | `Now()`, `ParseDateTime()`, `FormatDateTime()` 等 | `fac.Now("Asia/Shanghai")` |
|
||||
| **金额计算** | `YuanToCents()`, `CentsToYuan()`, `FormatYuan()` | `fac.YuanToCents(100.5)` |
|
||||
| **版本信息** | `GetVersion()` | `fac.GetVersion()` |
|
||||
| **HTTP响应** | `Success()`, `Error()`, `SuccessPage()` | `fac.Success(w, data)` |
|
||||
| **HTTP请求** | `ParseJSON()`, `GetQuery()`, `GetTimezone()` 等 | `fac.ParseJSON(r, &req)` |
|
||||
|
||||
### 🔧 高级功能:Get方法(仅在必要时使用)
|
||||
|
||||
@@ -176,7 +181,83 @@ db.Find(&users)
|
||||
db.Create(&user)
|
||||
```
|
||||
|
||||
### 8. Redis操作(获取客户端对象)
|
||||
### 8. 日期时间操作(黑盒模式)
|
||||
|
||||
```go
|
||||
// 获取当前时间
|
||||
now := fac.Now("Asia/Shanghai")
|
||||
|
||||
// 解析时间
|
||||
t, _ := fac.ParseDateTime("2024-01-01 12:00:00", "Asia/Shanghai")
|
||||
|
||||
// 格式化时间
|
||||
str := fac.FormatDateTime(now)
|
||||
|
||||
// 时间计算
|
||||
tomorrow := fac.AddDays(now, 1)
|
||||
startOfDay := fac.StartOfDay(now, "Asia/Shanghai")
|
||||
endOfDay := fac.EndOfDay(now, "Asia/Shanghai")
|
||||
|
||||
// Unix时间戳
|
||||
unix := fac.ToUnix(now)
|
||||
t2 := fac.FromUnix(unix, "Asia/Shanghai")
|
||||
```
|
||||
|
||||
### 9. 金额计算(黑盒模式)
|
||||
|
||||
```go
|
||||
// 元转分
|
||||
cents := fac.YuanToCents(100.5) // 10050
|
||||
|
||||
// 分转元
|
||||
yuan := fac.CentsToYuan(10050) // 100.5
|
||||
|
||||
// 格式化显示
|
||||
str := fac.FormatYuan(10050) // "100.50"
|
||||
```
|
||||
|
||||
### 10. 版本信息(黑盒模式)
|
||||
|
||||
```go
|
||||
version := fac.GetVersion()
|
||||
fmt.Println("当前版本:", version)
|
||||
```
|
||||
|
||||
### 11. HTTP响应(黑盒模式)
|
||||
|
||||
```go
|
||||
import "net/http"
|
||||
|
||||
// 成功响应
|
||||
fac.Success(w, data)
|
||||
fac.Success(w, data, "操作成功")
|
||||
|
||||
// 分页响应
|
||||
fac.SuccessPage(w, users, total, page, pageSize)
|
||||
|
||||
// 错误响应
|
||||
fac.Error(w, 1001, "用户不存在")
|
||||
fac.SystemError(w, "系统错误")
|
||||
```
|
||||
|
||||
### 12. HTTP请求解析(黑盒模式)
|
||||
|
||||
```go
|
||||
import "net/http"
|
||||
|
||||
// 解析JSON
|
||||
var req UserRequest
|
||||
fac.ParseJSON(r, &req)
|
||||
|
||||
// 获取查询参数
|
||||
id := fac.GetQueryInt64(r, "id", 0)
|
||||
keyword := fac.GetQuery(r, "keyword", "")
|
||||
|
||||
// 获取时区(需要配合middleware.Timezone使用)
|
||||
timezone := fac.GetTimezone(r)
|
||||
```
|
||||
|
||||
### 13. Redis操作(获取客户端对象)
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -481,6 +562,222 @@ func main() {
|
||||
|
||||
**返回:** 配置对象
|
||||
|
||||
### HTTP响应方法(黑盒模式)
|
||||
|
||||
#### Success(w http.ResponseWriter, data interface{}, message ...string)
|
||||
|
||||
发送成功响应。
|
||||
|
||||
**参数:**
|
||||
- `w`: HTTP响应写入器
|
||||
- `data`: 响应数据
|
||||
- `message`: 可选,成功消息(如果不提供,使用默认消息)
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
fac.Success(w, user) // 使用默认消息
|
||||
fac.Success(w, user, "获取成功") // 自定义消息
|
||||
```
|
||||
|
||||
#### Error(w http.ResponseWriter, code int, message string)
|
||||
|
||||
发送错误响应。
|
||||
|
||||
**参数:**
|
||||
- `w`: HTTP响应写入器
|
||||
- `code`: 业务错误码
|
||||
- `message`: 错误消息
|
||||
|
||||
#### SystemError(w http.ResponseWriter, message string)
|
||||
|
||||
发送系统错误响应(HTTP 500)。
|
||||
|
||||
**参数:**
|
||||
- `w`: HTTP响应写入器
|
||||
- `message`: 错误消息
|
||||
|
||||
#### SuccessPage(w http.ResponseWriter, data interface{}, total int64, page, pageSize int, message ...string)
|
||||
|
||||
发送分页成功响应。
|
||||
|
||||
**参数:**
|
||||
- `w`: HTTP响应写入器
|
||||
- `data`: 响应数据列表
|
||||
- `total`: 总记录数
|
||||
- `page`: 当前页码
|
||||
- `pageSize`: 每页大小
|
||||
- `message`: 可选,成功消息
|
||||
|
||||
### HTTP请求方法(黑盒模式)
|
||||
|
||||
#### ParseJSON(r *http.Request, v interface{}) error
|
||||
|
||||
解析JSON请求体。
|
||||
|
||||
**参数:**
|
||||
- `r`: HTTP请求
|
||||
- `v`: 目标结构体指针
|
||||
|
||||
#### GetQuery(r *http.Request, key, defaultValue string) string
|
||||
|
||||
获取查询参数。
|
||||
|
||||
#### GetQueryInt(r *http.Request, key string, defaultValue int) int
|
||||
|
||||
获取查询整数参数。
|
||||
|
||||
#### GetQueryInt64(r *http.Request, key string, defaultValue int64) int64
|
||||
|
||||
获取查询64位整数参数。
|
||||
|
||||
#### GetFormValue(r *http.Request, key, defaultValue string) string
|
||||
|
||||
获取表单值。
|
||||
|
||||
#### GetTimezone(r *http.Request) string
|
||||
|
||||
从请求的context中获取时区(需要配合middleware.Timezone使用)。
|
||||
|
||||
### 日期时间工具方法(黑盒模式)
|
||||
|
||||
#### Now(timezone ...string) time.Time
|
||||
|
||||
获取当前时间。
|
||||
|
||||
**参数:**
|
||||
- `timezone`: 可选,时区字符串(如 "Asia/Shanghai"),不指定则使用默认时区
|
||||
|
||||
#### ParseDateTime(value string, timezone ...string) (time.Time, error)
|
||||
|
||||
解析日期时间字符串(格式:2006-01-02 15:04:05)。
|
||||
|
||||
#### ParseDate(value string, timezone ...string) (time.Time, error)
|
||||
|
||||
解析日期字符串(格式:2006-01-02)。
|
||||
|
||||
#### FormatDateTime(t time.Time, timezone ...string) string
|
||||
|
||||
格式化日期时间(格式:2006-01-02 15:04:05)。
|
||||
|
||||
#### FormatDate(t time.Time, timezone ...string) string
|
||||
|
||||
格式化日期(格式:2006-01-02)。
|
||||
|
||||
#### FormatTime(t time.Time, timezone ...string) string
|
||||
|
||||
格式化时间(格式:15:04:05)。
|
||||
|
||||
#### ToUnix(t time.Time) int64
|
||||
|
||||
转换为Unix时间戳(秒)。
|
||||
|
||||
#### FromUnix(sec int64, timezone ...string) time.Time
|
||||
|
||||
从Unix时间戳创建时间。
|
||||
|
||||
#### ToUnixMilli(t time.Time) int64
|
||||
|
||||
转换为Unix毫秒时间戳。
|
||||
|
||||
#### FromUnixMilli(msec int64, timezone ...string) time.Time
|
||||
|
||||
从Unix毫秒时间戳创建时间。
|
||||
|
||||
#### AddDays(t time.Time, days int) time.Time
|
||||
|
||||
添加天数。
|
||||
|
||||
#### AddMonths(t time.Time, months int) time.Time
|
||||
|
||||
添加月数。
|
||||
|
||||
#### AddYears(t time.Time, years int) time.Time
|
||||
|
||||
添加年数。
|
||||
|
||||
#### StartOfDay(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取一天的开始时间(00:00:00)。
|
||||
|
||||
#### EndOfDay(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取一天的结束时间(23:59:59.999999999)。
|
||||
|
||||
#### StartOfMonth(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取月份的开始时间。
|
||||
|
||||
#### EndOfMonth(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取月份的结束时间。
|
||||
|
||||
#### StartOfYear(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取年份的开始时间。
|
||||
|
||||
#### EndOfYear(t time.Time, timezone ...string) time.Time
|
||||
|
||||
获取年份的结束时间。
|
||||
|
||||
#### DiffDays(t1, t2 time.Time) int
|
||||
|
||||
计算两个时间之间的天数差。
|
||||
|
||||
#### DiffHours(t1, t2 time.Time) int64
|
||||
|
||||
计算两个时间之间的小时差。
|
||||
|
||||
#### DiffMinutes(t1, t2 time.Time) int64
|
||||
|
||||
计算两个时间之间的分钟差。
|
||||
|
||||
#### DiffSeconds(t1, t2 time.Time) int64
|
||||
|
||||
计算两个时间之间的秒数差。
|
||||
|
||||
### 金额工具方法(黑盒模式)
|
||||
|
||||
#### GetMoneyCalculator() *tools.MoneyCalculator
|
||||
|
||||
获取金额计算器实例。
|
||||
|
||||
#### YuanToCents(yuan float64) int64
|
||||
|
||||
元转分。
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
cents := fac.YuanToCents(100.5) // 返回 10050
|
||||
```
|
||||
|
||||
#### CentsToYuan(cents int64) float64
|
||||
|
||||
分转元。
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
yuan := fac.CentsToYuan(10050) // 返回 100.5
|
||||
```
|
||||
|
||||
#### FormatYuan(cents int64) string
|
||||
|
||||
格式化显示金额(分转元,保留2位小数)。
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
str := fac.FormatYuan(10050) // 返回 "100.50"
|
||||
```
|
||||
|
||||
### 版本工具方法(黑盒模式)
|
||||
|
||||
#### GetVersion() string
|
||||
|
||||
获取版本号。
|
||||
|
||||
**说明:**
|
||||
- 优先从环境变量 `DOCKER_TAG` 或 `VERSION` 中读取
|
||||
- 如果没有设置环境变量,则使用默认版本号
|
||||
|
||||
## 设计优势
|
||||
|
||||
### 优势总结
|
||||
|
||||
654
docs/http.md
654
docs/http.md
@@ -2,16 +2,17 @@
|
||||
|
||||
## 概述
|
||||
|
||||
HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,采用Handler黑盒模式,封装了`ResponseWriter`和`Request`,提供简洁的API,无需每次都传递这两个参数。
|
||||
HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,提供公共方法供外部调用,保持低耦合。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **黑盒模式**:封装`ResponseWriter`和`Request`,提供简洁的API
|
||||
- **低耦合设计**:提供公共方法,不封装Handler结构
|
||||
- **标准化的响应结构**:`{code, message, timestamp, data}`
|
||||
- **分离HTTP状态码和业务状态码**
|
||||
- **支持分页响应**
|
||||
- **提供便捷的请求参数解析方法**
|
||||
- **支持JSON请求体解析**
|
||||
- **Factory黑盒模式**:推荐使用 `factory.Success()` 等方法
|
||||
|
||||
## 响应结构
|
||||
|
||||
@@ -26,6 +27,18 @@ HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,采用H
|
||||
}
|
||||
```
|
||||
|
||||
**结构体类型(暴露在 factory 中):**
|
||||
|
||||
```go
|
||||
// 在 factory 包中可以直接使用
|
||||
type Response struct {
|
||||
Code int `json:"code"` // 业务状态码,0表示成功
|
||||
Message string `json:"message"` // 响应消息
|
||||
Timestamp int64 `json:"timestamp"` // 时间戳
|
||||
Data interface{} `json:"data"` // 响应数据
|
||||
}
|
||||
```
|
||||
|
||||
### 分页响应结构
|
||||
|
||||
```json
|
||||
@@ -42,9 +55,116 @@ HTTP Restful工具提供了标准化的HTTP请求和响应处理功能,采用H
|
||||
}
|
||||
```
|
||||
|
||||
**结构体类型(暴露在 factory 中):**
|
||||
|
||||
```go
|
||||
// 在 factory 包中可以直接使用
|
||||
type PageData struct {
|
||||
List interface{} `json:"list"` // 数据列表
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"pageSize"` // 每页大小
|
||||
}
|
||||
|
||||
type PageResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Data *PageData `json:"data"`
|
||||
}
|
||||
```
|
||||
|
||||
### 使用暴露的结构体
|
||||
|
||||
外部项目可以直接使用 `factory.Response`、`factory.PageData` 等类型:
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/factory"
|
||||
|
||||
// 创建标准响应对象
|
||||
response := factory.Response{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: userData,
|
||||
}
|
||||
|
||||
// 创建分页数据对象
|
||||
pageData := &factory.PageData{
|
||||
List: users,
|
||||
Total: 100,
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
}
|
||||
|
||||
// 传递给 Success 方法
|
||||
fac.Success(w, pageData)
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 创建Handler
|
||||
### 方式一:使用Factory黑盒方法(推荐)⭐
|
||||
|
||||
这是最简单的方式,直接使用 `factory.Success()` 等方法:
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取查询参数(使用公共方法)
|
||||
id := commonhttp.GetQueryInt64(r, "id", 0)
|
||||
|
||||
// 返回成功响应(使用factory方法)
|
||||
fac.Success(w, data)
|
||||
}
|
||||
|
||||
func CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 解析JSON(使用公共方法)
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回成功响应(使用factory方法)
|
||||
fac.Success(w, data, "创建成功")
|
||||
}
|
||||
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取分页参数(使用factory方法,推荐)
|
||||
pagination := fac.ParsePaginationRequest(r)
|
||||
page := pagination.GetPage()
|
||||
pageSize := pagination.GetSize()
|
||||
|
||||
// 获取查询参数(使用factory方法,推荐)
|
||||
keyword := fac.GetQuery(r, "keyword", "")
|
||||
|
||||
// 查询数据
|
||||
list, total := getDataList(keyword, page, pageSize)
|
||||
|
||||
// 返回分页响应(使用factory方法)
|
||||
fac.SuccessPage(w, list, total, page, pageSize)
|
||||
}
|
||||
|
||||
// 注册路由
|
||||
http.HandleFunc("/user", GetUser)
|
||||
http.HandleFunc("/users", GetUserList)
|
||||
```
|
||||
|
||||
### 方式二:直接使用公共方法
|
||||
|
||||
如果不想使用Factory,可以直接使用 `http` 包的公共方法:
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -52,154 +172,110 @@ import (
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
// 方式1:使用HandleFunc包装器(推荐,最简洁)
|
||||
func GetUser(h *commonhttp.Handler) {
|
||||
id := h.GetQueryInt64("id", 0)
|
||||
h.Success(data)
|
||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取查询参数
|
||||
id := commonhttp.GetQueryInt64(r, "id", 0)
|
||||
|
||||
// 返回成功响应
|
||||
commonhttp.Success(w, data)
|
||||
}
|
||||
|
||||
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||
|
||||
// 方式2:手动创建Handler(需要更多控制时)
|
||||
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
GetUser(h)
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 成功响应
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 简单成功响应(data为nil)
|
||||
h.Success(nil)
|
||||
|
||||
// 带数据的成功响应
|
||||
data := map[string]interface{}{
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
func CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
// 解析JSON
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
h.Success(data)
|
||||
|
||||
// 带消息的成功响应
|
||||
h.SuccessWithMessage("操作成功", data)
|
||||
// 返回成功响应
|
||||
commonhttp.Success(w, data, "创建成功")
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 错误响应
|
||||
### 成功响应
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 业务错误(HTTP 200,业务code非0)
|
||||
h.Error(1001, "用户不存在")
|
||||
// 使用Factory(推荐)
|
||||
fac.Success(w, data) // 只有数据,使用默认消息 "success"
|
||||
fac.Success(w, data, "操作成功") // 数据+消息
|
||||
|
||||
// 系统错误(HTTP 500)
|
||||
h.SystemError("服务器内部错误")
|
||||
|
||||
// 其他HTTP错误状态码,使用WriteJSON直接指定
|
||||
// 请求错误(HTTP 400)
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数错误", nil)
|
||||
|
||||
// 未授权(HTTP 401)
|
||||
h.WriteJSON(http.StatusUnauthorized, 401, "未登录", nil)
|
||||
|
||||
// 禁止访问(HTTP 403)
|
||||
h.WriteJSON(http.StatusForbidden, 403, "无权限访问", nil)
|
||||
|
||||
// 未找到(HTTP 404)
|
||||
h.WriteJSON(http.StatusNotFound, 404, "资源不存在", nil)
|
||||
}
|
||||
// 或直接使用公共方法
|
||||
commonhttp.Success(w, data) // 只有数据
|
||||
commonhttp.Success(w, data, "操作成功") // 数据+消息
|
||||
```
|
||||
|
||||
### 4. 分页响应
|
||||
### 错误响应
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 获取分页参数
|
||||
pagination := h.ParsePaginationRequest()
|
||||
page := pagination.GetPage()
|
||||
pageSize := pagination.GetSize()
|
||||
// 使用Factory(推荐)
|
||||
fac.Error(w, 1001, "用户不存在") // 业务错误(HTTP 200,业务code非0)
|
||||
fac.SystemError(w, "服务器内部错误") // 系统错误(HTTP 500)
|
||||
|
||||
// 查询数据(示例)
|
||||
list, total := getDataList(page, pageSize)
|
||||
|
||||
// 返回分页响应(使用默认消息)
|
||||
h.SuccessPage(list, total, page, pageSize)
|
||||
|
||||
// 返回分页响应(自定义消息)
|
||||
h.SuccessPage(list, total, page, pageSize, "查询成功")
|
||||
}
|
||||
// 或直接使用公共方法
|
||||
commonhttp.Error(w, 1001, "用户不存在")
|
||||
commonhttp.SystemError(w, "服务器内部错误")
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil) // 自定义HTTP状态码(仅公共方法)
|
||||
```
|
||||
|
||||
### 5. 解析请求
|
||||
### 分页响应
|
||||
|
||||
```go
|
||||
// 使用Factory(推荐)
|
||||
fac.SuccessPage(w, list, total, page, pageSize)
|
||||
fac.SuccessPage(w, list, total, page, pageSize, "查询成功")
|
||||
|
||||
// 或直接使用公共方法
|
||||
commonhttp.SuccessPage(w, list, total, page, pageSize)
|
||||
commonhttp.SuccessPage(w, list, total, page, pageSize, "查询成功")
|
||||
```
|
||||
|
||||
### 解析请求
|
||||
|
||||
#### 解析JSON请求体
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
type CreateUserRequest struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
// 使用公共方法
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var req CreateUserRequest
|
||||
if err := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用req...
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取查询参数
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 获取字符串参数
|
||||
name := h.GetQuery("name", "")
|
||||
email := h.GetQuery("email", "default@example.com")
|
||||
|
||||
// 获取整数参数
|
||||
id := h.GetQueryInt("id", 0)
|
||||
age := h.GetQueryInt("age", 18)
|
||||
|
||||
// 获取int64参数
|
||||
userId := h.GetQueryInt64("userId", 0)
|
||||
|
||||
// 获取布尔参数
|
||||
isActive := h.GetQueryBool("isActive", false)
|
||||
|
||||
// 获取浮点数参数
|
||||
price := h.GetQueryFloat64("price", 0.0)
|
||||
}
|
||||
// 使用公共方法
|
||||
name := commonhttp.GetQuery(r, "name", "")
|
||||
id := commonhttp.GetQueryInt(r, "id", 0)
|
||||
userId := commonhttp.GetQueryInt64(r, "userId", 0)
|
||||
isActive := commonhttp.GetQueryBool(r, "isActive", false)
|
||||
price := commonhttp.GetQueryFloat64(r, "price", 0.0)
|
||||
```
|
||||
|
||||
#### 获取表单参数
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 获取表单字符串
|
||||
name := h.GetFormValue("name", "")
|
||||
|
||||
// 获取表单整数
|
||||
age := h.GetFormInt("age", 0)
|
||||
|
||||
// 获取表单int64
|
||||
userId := h.GetFormInt64("userId", 0)
|
||||
|
||||
// 获取表单布尔值
|
||||
isActive := h.GetFormBool("isActive", false)
|
||||
}
|
||||
// 使用公共方法
|
||||
name := commonhttp.GetFormValue(r, "name", "")
|
||||
age := commonhttp.GetFormInt(r, "age", 0)
|
||||
userId := commonhttp.GetFormInt64(r, "userId", 0)
|
||||
isActive := commonhttp.GetFormBool(r, "isActive", false)
|
||||
```
|
||||
|
||||
#### 获取请求头
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
token := h.GetHeader("Authorization", "")
|
||||
contentType := h.GetHeader("Content-Type", "application/json")
|
||||
}
|
||||
// 使用公共方法
|
||||
token := commonhttp.GetHeader(r, "Authorization", "")
|
||||
contentType := commonhttp.GetHeader(r, "Content-Type", "application/json")
|
||||
```
|
||||
|
||||
#### 获取分页参数
|
||||
@@ -207,71 +283,55 @@ func handler(h *commonhttp.Handler) {
|
||||
**方式1:使用 PaginationRequest 结构(推荐)**
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 定义请求结构(包含分页字段)
|
||||
type ListUserRequest struct {
|
||||
Keyword string `json:"keyword"`
|
||||
commonhttp.PaginationRequest // 嵌入分页请求结构
|
||||
}
|
||||
|
||||
// 从JSON请求体解析(分页字段会自动解析)
|
||||
var req ListUserRequest
|
||||
if err := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用分页方法
|
||||
page := req.GetPage() // 获取页码(默认1)
|
||||
size := req.GetSize() // 获取每页数量(默认20,最大100,优先使用page_size)
|
||||
offset := req.GetOffset() // 计算偏移量
|
||||
// 定义请求结构(包含分页字段)
|
||||
type ListUserRequest struct {
|
||||
Keyword string `json:"keyword"`
|
||||
commonhttp.PaginationRequest // 嵌入分页请求结构
|
||||
}
|
||||
|
||||
// 或者从查询参数/form解析分页
|
||||
func handler(h *commonhttp.Handler) {
|
||||
pagination := h.ParsePaginationRequest()
|
||||
page := pagination.GetPage()
|
||||
size := pagination.GetSize()
|
||||
offset := pagination.GetOffset()
|
||||
// 从JSON请求体解析(分页字段会自动解析)
|
||||
var req ListUserRequest
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用分页方法
|
||||
page := req.GetPage() // 获取页码(默认1)
|
||||
size := req.GetSize() // 获取每页数量(默认20,最大100,优先使用page_size)
|
||||
offset := req.GetOffset() // 计算偏移量
|
||||
```
|
||||
|
||||
**方式2:从查询参数/form解析分页**
|
||||
|
||||
```go
|
||||
// 使用公共方法
|
||||
pagination := commonhttp.ParsePaginationRequest(r)
|
||||
page := pagination.GetPage()
|
||||
size := pagination.GetSize()
|
||||
offset := pagination.GetOffset()
|
||||
```
|
||||
|
||||
#### 获取时区
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 从请求的context中获取时区
|
||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
// 如果未设置,返回默认时区 AsiaShanghai
|
||||
timezone := h.GetTimezone()
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 访问原始对象
|
||||
|
||||
如果需要访问原始的`ResponseWriter`或`Request`:
|
||||
|
||||
```go
|
||||
func handler(h *commonhttp.Handler) {
|
||||
// 获取原始ResponseWriter
|
||||
w := h.ResponseWriter()
|
||||
|
||||
// 获取原始Request
|
||||
r := h.Request()
|
||||
|
||||
// 获取Context
|
||||
ctx := h.Context()
|
||||
}
|
||||
// 使用公共方法
|
||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
// 如果未设置,返回默认时区 AsiaShanghai
|
||||
timezone := commonhttp.GetTimezone(r)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 使用Factory(推荐)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
@@ -283,91 +343,97 @@ type User struct {
|
||||
}
|
||||
|
||||
// 用户列表接口
|
||||
func GetUserList(h *commonhttp.Handler) {
|
||||
// 获取分页参数
|
||||
pagination := h.ParsePaginationRequest()
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取分页参数(使用公共方法)
|
||||
pagination := commonhttp.ParsePaginationRequest(r)
|
||||
page := pagination.GetPage()
|
||||
pageSize := pagination.GetSize()
|
||||
|
||||
// 获取查询参数
|
||||
keyword := h.GetQuery("keyword", "")
|
||||
// 获取查询参数(使用公共方法)
|
||||
keyword := commonhttp.GetQuery(r, "keyword", "")
|
||||
|
||||
// 查询数据
|
||||
users, total := queryUsers(keyword, page, pageSize)
|
||||
|
||||
// 返回分页响应
|
||||
h.SuccessPage(users, total, page, pageSize)
|
||||
// 返回分页响应(使用factory方法)
|
||||
fac.SuccessPage(w, users, total, page, pageSize)
|
||||
}
|
||||
|
||||
// 创建用户接口
|
||||
func CreateUser(h *commonhttp.Handler) {
|
||||
// 解析请求体
|
||||
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 := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
fac.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if req.Name == "" {
|
||||
h.Error(1001, "用户名不能为空")
|
||||
fac.Error(w, 1001, "用户名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user, err := createUser(req.Name, req.Email)
|
||||
if err != nil {
|
||||
h.SystemError("创建用户失败")
|
||||
fac.SystemError(w, "创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
h.SuccessWithMessage("创建成功", user)
|
||||
// 返回成功响应(使用factory方法)
|
||||
fac.Success(w, user, "创建成功")
|
||||
}
|
||||
|
||||
// 获取用户详情接口
|
||||
func GetUser(h *commonhttp.Handler) {
|
||||
// 获取查询参数
|
||||
id := h.GetQueryInt64("id", 0)
|
||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取查询参数(使用factory方法,推荐)
|
||||
id := fac.GetQueryInt64(r, "id", 0)
|
||||
|
||||
if id == 0 {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
user, err := getUserByID(id)
|
||||
if err != nil {
|
||||
h.SystemError("查询用户失败")
|
||||
fac.SystemError(w, "查询用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
h.Error(1002, "用户不存在")
|
||||
fac.Error(w, 1002, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.Success(user)
|
||||
// 返回成功响应(使用factory方法)
|
||||
fac.Success(w, user)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 使用HandleFunc包装器(推荐)
|
||||
http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
switch h.Request().Method {
|
||||
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
GetUserList(h)
|
||||
GetUserList(w, r)
|
||||
case http.MethodPost:
|
||||
CreateUser(h)
|
||||
CreateUser(w, r)
|
||||
default:
|
||||
h.WriteJSON(http.StatusMethodNotAllowed, 405, "方法不支持", nil)
|
||||
commonhttp.WriteJSON(w, http.StatusMethodNotAllowed, 405, "方法不支持", nil)
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||
http.HandleFunc("/user", GetUser)
|
||||
|
||||
log.Println("Server started on :8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
@@ -376,53 +442,93 @@ func main() {
|
||||
|
||||
## API 参考
|
||||
|
||||
### Handler结构
|
||||
### Factory HTTP响应结构体(暴露给外部项目使用)
|
||||
|
||||
Handler封装了`ResponseWriter`和`Request`,提供更简洁的API。
|
||||
#### Response
|
||||
|
||||
```go
|
||||
type Handler struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
}
|
||||
```
|
||||
标准响应结构体,外部项目可以直接使用 `factory.Response`。
|
||||
|
||||
### 创建Handler
|
||||
|
||||
#### NewHandler(w http.ResponseWriter, r *http.Request) *Handler
|
||||
|
||||
创建Handler实例。
|
||||
|
||||
#### HandleFunc(fn func(*Handler)) http.HandlerFunc
|
||||
|
||||
将Handler函数转换为标准的http.HandlerFunc(便捷包装器)。
|
||||
**字段:**
|
||||
- `Code`: 业务状态码,0表示成功
|
||||
- `Message`: 响应消息
|
||||
- `Timestamp`: 时间戳(Unix时间戳,秒)
|
||||
- `Data`: 响应数据
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
h.Success(data)
|
||||
}))
|
||||
response := factory.Response{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: userData,
|
||||
}
|
||||
```
|
||||
|
||||
### Handler响应方法
|
||||
#### PageData
|
||||
|
||||
#### (h *Handler) Success(data interface{})
|
||||
分页数据结构体,外部项目可以直接使用 `factory.PageData`。
|
||||
|
||||
**字段:**
|
||||
- `List`: 数据列表
|
||||
- `Total`: 总记录数
|
||||
- `Page`: 当前页码
|
||||
- `PageSize`: 每页大小
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
pageData := &factory.PageData{
|
||||
List: users,
|
||||
Total: 100,
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
}
|
||||
fac.Success(w, pageData)
|
||||
```
|
||||
|
||||
#### PageResponse
|
||||
|
||||
分页响应结构体,外部项目可以直接使用 `factory.PageResponse`。
|
||||
|
||||
**字段:**
|
||||
- `Code`: 业务状态码
|
||||
- `Message`: 响应消息
|
||||
- `Timestamp`: 时间戳
|
||||
- `Data`: 分页数据(*PageData)
|
||||
|
||||
### Factory HTTP响应方法(推荐使用)
|
||||
|
||||
#### (f *Factory) Success(w http.ResponseWriter, data interface{}, message ...string)
|
||||
|
||||
成功响应,HTTP 200,业务code 0。
|
||||
|
||||
#### (h *Handler) SuccessWithMessage(message string, data interface{})
|
||||
**参数:**
|
||||
- `data`: 响应数据,可以为nil
|
||||
- `message`: 响应消息(可选),如果为空则使用默认消息 "success"
|
||||
|
||||
带消息的成功响应。
|
||||
**示例:**
|
||||
```go
|
||||
fac.Success(w, data) // 只有数据,使用默认消息 "success"
|
||||
fac.Success(w, data, "操作成功") // 数据+消息
|
||||
```
|
||||
|
||||
#### (h *Handler) Error(code int, message string)
|
||||
#### (f *Factory) Error(w http.ResponseWriter, code int, message string)
|
||||
|
||||
业务错误响应,HTTP 200,业务code非0。
|
||||
|
||||
#### (h *Handler) SystemError(message string)
|
||||
**示例:**
|
||||
```go
|
||||
fac.Error(w, 1001, "用户不存在")
|
||||
```
|
||||
|
||||
#### (f *Factory) SystemError(w http.ResponseWriter, message string)
|
||||
|
||||
系统错误响应,HTTP 500,业务code 500。
|
||||
|
||||
#### (h *Handler) WriteJSON(httpCode, code int, message string, data interface{})
|
||||
**示例:**
|
||||
```go
|
||||
fac.SystemError(w, "服务器内部错误")
|
||||
```
|
||||
|
||||
#### (f *Factory) WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})
|
||||
|
||||
写入JSON响应(自定义)。
|
||||
|
||||
@@ -432,7 +538,17 @@ http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
- `message`: 响应消息
|
||||
- `data`: 响应数据
|
||||
|
||||
#### (h *Handler) SuccessPage(list interface{}, total int64, page, pageSize int, message ...string)
|
||||
**说明:**
|
||||
- 此方法不在 Factory 中,直接使用 `commonhttp.WriteJSON()`
|
||||
- 用于需要自定义HTTP状态码的场景(如 400, 401, 403, 404 等)
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil)
|
||||
commonhttp.WriteJSON(w, http.StatusUnauthorized, 401, "未登录", nil)
|
||||
```
|
||||
|
||||
#### (f *Factory) SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string)
|
||||
|
||||
分页成功响应。
|
||||
|
||||
@@ -443,53 +559,81 @@ http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
- `pageSize`: 每页大小
|
||||
- `message`: 响应消息(可选,如果为空则使用默认消息 "success")
|
||||
|
||||
### Handler请求解析方法
|
||||
**示例:**
|
||||
```go
|
||||
fac.SuccessPage(w, users, total, page, pageSize)
|
||||
fac.SuccessPage(w, users, total, page, pageSize, "查询成功")
|
||||
```
|
||||
|
||||
#### (h *Handler) ParseJSON(v interface{}) error
|
||||
### HTTP公共方法(直接使用)
|
||||
|
||||
#### WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})
|
||||
|
||||
写入JSON响应(自定义HTTP状态码和业务状态码)。
|
||||
|
||||
**说明:**
|
||||
- 此方法不在 Factory 中,直接使用 `commonhttp.WriteJSON()`
|
||||
- 用于需要自定义HTTP状态码的场景(如 400, 401, 403, 404 等)
|
||||
|
||||
**示例:**
|
||||
```go
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数错误", nil)
|
||||
commonhttp.WriteJSON(w, http.StatusUnauthorized, 401, "未登录", nil)
|
||||
```
|
||||
|
||||
#### ParseJSON(r *http.Request, v interface{}) error
|
||||
|
||||
解析JSON请求体。
|
||||
|
||||
#### (h *Handler) GetQuery(key, defaultValue string) string
|
||||
**示例:**
|
||||
```go
|
||||
var req CreateUserRequest
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
// 处理错误
|
||||
}
|
||||
```
|
||||
|
||||
#### GetQuery(r *http.Request, key, defaultValue string) string
|
||||
|
||||
获取查询参数(字符串)。
|
||||
|
||||
#### (h *Handler) GetQueryInt(key string, defaultValue int) int
|
||||
#### GetQueryInt(r *http.Request, key string, defaultValue int) int
|
||||
|
||||
获取查询参数(整数)。
|
||||
|
||||
#### (h *Handler) GetQueryInt64(key string, defaultValue int64) int64
|
||||
#### GetQueryInt64(r *http.Request, key string, defaultValue int64) int64
|
||||
|
||||
获取查询参数(int64)。
|
||||
|
||||
#### (h *Handler) GetQueryBool(key string, defaultValue bool) bool
|
||||
#### GetQueryBool(r *http.Request, key string, defaultValue bool) bool
|
||||
|
||||
获取查询参数(布尔值)。
|
||||
|
||||
#### (h *Handler) GetQueryFloat64(key string, defaultValue float64) float64
|
||||
#### GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64
|
||||
|
||||
获取查询参数(浮点数)。
|
||||
|
||||
#### (h *Handler) GetFormValue(key, defaultValue string) string
|
||||
#### GetFormValue(r *http.Request, key, defaultValue string) string
|
||||
|
||||
获取表单值(字符串)。
|
||||
|
||||
#### (h *Handler) GetFormInt(key string, defaultValue int) int
|
||||
#### GetFormInt(r *http.Request, key string, defaultValue int) int
|
||||
|
||||
获取表单值(整数)。
|
||||
|
||||
#### (h *Handler) GetFormInt64(key string, defaultValue int64) int64
|
||||
#### GetFormInt64(r *http.Request, key string, defaultValue int64) int64
|
||||
|
||||
获取表单值(int64)。
|
||||
|
||||
#### (h *Handler) GetFormBool(key string, defaultValue bool) bool
|
||||
#### GetFormBool(r *http.Request, key string, defaultValue bool) bool
|
||||
|
||||
获取表单值(布尔值)。
|
||||
|
||||
#### (h *Handler) GetHeader(key, defaultValue string) string
|
||||
#### GetHeader(r *http.Request, key, defaultValue string) string
|
||||
|
||||
获取请求头。
|
||||
|
||||
#### (h *Handler) ParsePaginationRequest() *PaginationRequest
|
||||
#### ParsePaginationRequest(r *http.Request) *PaginationRequest
|
||||
|
||||
从请求中解析分页参数。
|
||||
|
||||
@@ -498,7 +642,7 @@ http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
- 优先级:查询参数 > form表单
|
||||
- 如果请求体是JSON格式且包含分页字段,建议先使用`ParseJSON`解析完整请求体到包含`PaginationRequest`的结构体中
|
||||
|
||||
#### (h *Handler) GetTimezone() string
|
||||
#### GetTimezone(r *http.Request) string
|
||||
|
||||
从请求的context中获取时区。
|
||||
|
||||
@@ -506,19 +650,39 @@ http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
- 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
- 如果未设置,返回默认时区 AsiaShanghai
|
||||
|
||||
### Handler访问原始对象
|
||||
#### Success(w http.ResponseWriter, data interface{}, message ...string)
|
||||
|
||||
#### (h *Handler) ResponseWriter() http.ResponseWriter
|
||||
成功响应(公共方法)。
|
||||
|
||||
获取原始的ResponseWriter(需要时使用)。
|
||||
**参数:**
|
||||
- `data`: 响应数据,可以为nil
|
||||
- `message`: 响应消息(可选),如果为空则使用默认消息 "success"
|
||||
|
||||
#### (h *Handler) Request() *http.Request
|
||||
**示例:**
|
||||
```go
|
||||
commonhttp.Success(w, data) // 只有数据
|
||||
commonhttp.Success(w, data, "操作成功") // 数据+消息
|
||||
```
|
||||
|
||||
获取原始的Request(需要时使用)。
|
||||
#### Error(w http.ResponseWriter, code int, message string)
|
||||
|
||||
#### (h *Handler) Context() context.Context
|
||||
错误响应(公共方法)。
|
||||
|
||||
获取请求的Context。
|
||||
#### SystemError(w http.ResponseWriter, message string)
|
||||
|
||||
系统错误响应(公共方法)。
|
||||
|
||||
#### WriteJSON(w http.ResponseWriter, httpCode, code int, message string, data interface{})
|
||||
|
||||
写入JSON响应(公共方法,不在Factory中)。
|
||||
|
||||
**说明:**
|
||||
- 用于需要自定义HTTP状态码的场景
|
||||
- 直接使用 `commonhttp.WriteJSON()`,不在 Factory 中
|
||||
|
||||
#### SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string)
|
||||
|
||||
分页成功响应(公共方法)。
|
||||
|
||||
### 分页请求结构
|
||||
|
||||
@@ -575,10 +739,14 @@ http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
- 使用`SystemError`返回系统错误(HTTP 500)
|
||||
- 其他HTTP错误状态码(400, 401, 403, 404等)使用`WriteJSON`方法直接指定
|
||||
|
||||
5. **黑盒模式**:
|
||||
- 所有功能都通过Handler对象调用,无需传递`w`和`r`参数
|
||||
- 代码更简洁,减少调用方工作量
|
||||
5. **推荐使用Factory**:
|
||||
- 使用 `factory.Success()` 等方法,代码更简洁
|
||||
- 直接使用 `http` 包的公共方法,保持低耦合
|
||||
- 不需要Handler结构,减少不必要的封装
|
||||
|
||||
## 示例
|
||||
|
||||
完整示例请参考 `examples/http_handler_example.go`
|
||||
完整示例请参考:
|
||||
- `examples/http_handler_example.go` - 使用Factory和公共方法
|
||||
- `examples/http_pagination_example.go` - 分页示例
|
||||
- `examples/factory_blackbox_example.go` - Factory黑盒模式示例
|
||||
|
||||
@@ -136,7 +136,7 @@ import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -146,12 +146,12 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
timezone := h.GetTimezone()
|
||||
|
||||
// 使用时区
|
||||
now := datetime.Now(timezone)
|
||||
datetime.FormatDateTime(now, timezone)
|
||||
now := tools.Now(timezone)
|
||||
tools.FormatDateTime(now, timezone)
|
||||
|
||||
h.Success(map[string]interface{}{
|
||||
"timezone": timezone,
|
||||
"time": datetime.FormatDateTime(now),
|
||||
"time": tools.FormatDateTime(now),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ func main() {
|
||||
import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -177,7 +177,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func main() {
|
||||
// 使用自定义默认时区
|
||||
handler := middleware.TimezoneWithDefault(datetime.UTC)(http.HandlerFunc(handler))
|
||||
handler := middleware.TimezoneWithDefault(tools.UTC)(http.HandlerFunc(handler))
|
||||
http.Handle("/api", handler)
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
@@ -185,11 +185,43 @@ func main() {
|
||||
|
||||
#### 在业务代码中使用时区
|
||||
|
||||
**推荐方式:通过 factory 使用(黑盒模式)**
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
)
|
||||
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 从请求中获取时区
|
||||
timezone := fac.GetTimezone(r)
|
||||
|
||||
// 使用时区进行时间处理
|
||||
now := fac.Now(timezone)
|
||||
|
||||
// 查询数据时使用时区
|
||||
startTime := fac.StartOfDay(now, timezone)
|
||||
endTime := fac.EndOfDay(now, timezone)
|
||||
|
||||
// 返回数据
|
||||
fac.Success(w, map[string]interface{}{
|
||||
"timezone": timezone,
|
||||
"startTime": fac.FormatDateTime(startTime),
|
||||
"endTime": fac.FormatDateTime(endTime),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**或者直接使用 tools 包:**
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -199,17 +231,17 @@ func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
timezone := h.GetTimezone()
|
||||
|
||||
// 使用时区进行时间处理
|
||||
now := datetime.Now(timezone)
|
||||
now := tools.Now(timezone)
|
||||
|
||||
// 查询数据时使用时区
|
||||
startTime := datetime.StartOfDay(now, timezone)
|
||||
endTime := datetime.EndOfDay(now, timezone)
|
||||
startTime := tools.StartOfDay(now, timezone)
|
||||
endTime := tools.EndOfDay(now, timezone)
|
||||
|
||||
// 返回数据
|
||||
h.Success(map[string]interface{}{
|
||||
"timezone": timezone,
|
||||
"startTime": datetime.FormatDateTime(startTime),
|
||||
"endTime": datetime.FormatDateTime(endTime),
|
||||
"startTime": tools.FormatDateTime(startTime),
|
||||
"endTime": tools.FormatDateTime(endTime),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -658,7 +690,7 @@ import (
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
"git.toowon.com/jimmy/go-common/logger"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -666,12 +698,12 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 从Handler获取时区
|
||||
timezone := h.GetTimezone()
|
||||
now := datetime.Now(timezone)
|
||||
now := tools.Now(timezone)
|
||||
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Hello",
|
||||
"timezone": timezone,
|
||||
"time": datetime.FormatDateTime(now),
|
||||
"time": tools.FormatDateTime(now),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -756,7 +788,7 @@ import (
|
||||
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -764,12 +796,12 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 从Handler获取时区
|
||||
timezone := h.GetTimezone()
|
||||
now := datetime.Now(timezone)
|
||||
now := tools.Now(timezone)
|
||||
|
||||
h.Success(map[string]interface{}{
|
||||
"message": "Hello",
|
||||
"timezone": timezone,
|
||||
"time": datetime.FormatDateTime(now),
|
||||
"time": tools.FormatDateTime(now),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
179
email/email.go
179
email/email.go
@@ -17,37 +17,43 @@ type Email struct {
|
||||
}
|
||||
|
||||
// NewEmail 创建邮件发送器
|
||||
func NewEmail(cfg *config.EmailConfig) (*Email, error) {
|
||||
if cfg == nil {
|
||||
func NewEmail(cfg *config.Config) *Email {
|
||||
if cfg == nil || cfg.Email == nil {
|
||||
return &Email{config: nil}
|
||||
}
|
||||
return &Email{config: cfg.Email}
|
||||
}
|
||||
|
||||
// getEmailConfig 获取邮件配置(内部方法)
|
||||
func (e *Email) getEmailConfig() (*config.EmailConfig, error) {
|
||||
if e.config == nil {
|
||||
return nil, fmt.Errorf("email config is nil")
|
||||
}
|
||||
|
||||
if cfg.Host == "" {
|
||||
if e.config.Host == "" {
|
||||
return nil, fmt.Errorf("email host is required")
|
||||
}
|
||||
|
||||
if cfg.Username == "" {
|
||||
if e.config.Username == "" {
|
||||
return nil, fmt.Errorf("email username is required")
|
||||
}
|
||||
|
||||
if cfg.Password == "" {
|
||||
if e.config.Password == "" {
|
||||
return nil, fmt.Errorf("email password is required")
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if cfg.Port == 0 {
|
||||
cfg.Port = 587
|
||||
if e.config.Port == 0 {
|
||||
e.config.Port = 587
|
||||
}
|
||||
if cfg.From == "" {
|
||||
cfg.From = cfg.Username
|
||||
if e.config.From == "" {
|
||||
e.config.From = e.config.Username
|
||||
}
|
||||
if cfg.Timeout == 0 {
|
||||
cfg.Timeout = 30
|
||||
if e.config.Timeout == 0 {
|
||||
e.config.Timeout = 30
|
||||
}
|
||||
|
||||
return &Email{
|
||||
config: cfg,
|
||||
}, nil
|
||||
return e.config, nil
|
||||
}
|
||||
|
||||
// Message 邮件消息
|
||||
@@ -69,66 +75,91 @@ type Message struct {
|
||||
|
||||
// HTMLBody HTML正文(可选,如果设置了会优先使用)
|
||||
HTMLBody string
|
||||
|
||||
// Attachments 附件列表(可选)
|
||||
Attachments []Attachment
|
||||
}
|
||||
|
||||
// Attachment 附件
|
||||
type Attachment struct {
|
||||
// Filename 文件名
|
||||
Filename string
|
||||
// SendEmail 发送邮件
|
||||
// to: 收件人列表
|
||||
// subject: 邮件主题
|
||||
// body: 邮件正文(纯文本)
|
||||
// htmlBody: HTML正文(可选,如果设置了会优先使用)
|
||||
func (e *Email) SendEmail(to []string, subject, body string, htmlBody ...string) error {
|
||||
cfg, err := e.getEmailConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Content 文件内容
|
||||
Content []byte
|
||||
msg := &Message{
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// ContentType 文件类型(如:application/pdf)
|
||||
ContentType string
|
||||
if len(htmlBody) > 0 && htmlBody[0] != "" {
|
||||
msg.HTMLBody = htmlBody[0]
|
||||
}
|
||||
|
||||
return e.send(msg, cfg)
|
||||
}
|
||||
|
||||
// SendRaw 发送原始邮件内容
|
||||
// recipients: 收件人列表(To、Cc、Bcc的合并列表)
|
||||
// body: 完整的邮件内容(MIME格式),由外部构建
|
||||
func (e *Email) SendRaw(recipients []string, body []byte) error {
|
||||
if len(recipients) == 0 {
|
||||
// send 发送邮件(内部方法)
|
||||
func (e *Email) send(msg *Message, cfg *config.EmailConfig) error {
|
||||
if msg == nil {
|
||||
return fmt.Errorf("message is nil")
|
||||
}
|
||||
|
||||
if len(msg.To) == 0 {
|
||||
return fmt.Errorf("recipients are required")
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("email body is required")
|
||||
if msg.Subject == "" {
|
||||
return fmt.Errorf("subject is required")
|
||||
}
|
||||
|
||||
if msg.Body == "" && msg.HTMLBody == "" {
|
||||
return fmt.Errorf("body or HTMLBody is required")
|
||||
}
|
||||
|
||||
// 构建邮件内容
|
||||
emailBody, err := e.buildEmailBody(msg, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build email body: %w", err)
|
||||
}
|
||||
|
||||
// 合并收件人列表
|
||||
recipients := append(msg.To, msg.Cc...)
|
||||
recipients = append(recipients, msg.Bcc...)
|
||||
|
||||
// 连接SMTP服务器
|
||||
addr := fmt.Sprintf("%s:%d", e.config.Host, e.config.Port)
|
||||
auth := smtp.PlainAuth("", e.config.Username, e.config.Password, e.config.Host)
|
||||
addr := net.JoinHostPort(cfg.Host, fmt.Sprintf("%d", cfg.Port))
|
||||
auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
|
||||
|
||||
// 创建连接
|
||||
conn, err := net.DialTimeout("tcp", addr, time.Duration(e.config.Timeout)*time.Second)
|
||||
conn, err := net.DialTimeout("tcp", addr, time.Duration(cfg.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 创建SMTP客户端
|
||||
client, err := smtp.NewClient(conn, e.config.Host)
|
||||
client, err := smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SMTP client: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// TLS/SSL处理
|
||||
if e.config.UseSSL {
|
||||
if cfg.UseSSL {
|
||||
// SSL模式(端口通常是465)
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: e.config.Host,
|
||||
ServerName: cfg.Host,
|
||||
}
|
||||
if err := client.StartTLS(tlsConfig); err != nil {
|
||||
return fmt.Errorf("failed to start TLS: %w", err)
|
||||
}
|
||||
} else if e.config.UseTLS {
|
||||
} else if cfg.UseTLS {
|
||||
// TLS模式(STARTTLS,端口通常是587)
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: e.config.Host,
|
||||
ServerName: cfg.Host,
|
||||
}
|
||||
if err := client.StartTLS(tlsConfig); err != nil {
|
||||
return fmt.Errorf("failed to start TLS: %w", err)
|
||||
@@ -141,7 +172,7 @@ func (e *Email) SendRaw(recipients []string, body []byte) error {
|
||||
}
|
||||
|
||||
// 设置发件人
|
||||
if err := client.Mail(e.config.From); err != nil {
|
||||
if err := client.Mail(cfg.From); err != nil {
|
||||
return fmt.Errorf("failed to set sender: %w", err)
|
||||
}
|
||||
|
||||
@@ -158,7 +189,7 @@ func (e *Email) SendRaw(recipients []string, body []byte) error {
|
||||
return fmt.Errorf("failed to get data writer: %w", err)
|
||||
}
|
||||
|
||||
_, err = writer.Write(body)
|
||||
_, err = writer.Write(emailBody)
|
||||
if err != nil {
|
||||
writer.Close()
|
||||
return fmt.Errorf("failed to write email body: %w", err)
|
||||
@@ -177,47 +208,14 @@ func (e *Email) SendRaw(recipients []string, body []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send 发送邮件(使用Message结构,内部会构建邮件内容)
|
||||
// 注意:如果需要完全控制邮件内容,请使用SendRaw方法
|
||||
func (e *Email) Send(msg *Message) error {
|
||||
if msg == nil {
|
||||
return fmt.Errorf("message is nil")
|
||||
}
|
||||
|
||||
if len(msg.To) == 0 {
|
||||
return fmt.Errorf("recipients are required")
|
||||
}
|
||||
|
||||
if msg.Subject == "" {
|
||||
return fmt.Errorf("subject is required")
|
||||
}
|
||||
|
||||
if msg.Body == "" && msg.HTMLBody == "" {
|
||||
return fmt.Errorf("body or HTMLBody is required")
|
||||
}
|
||||
|
||||
// 构建邮件内容
|
||||
emailBody, err := e.buildEmailBody(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build email body: %w", err)
|
||||
}
|
||||
|
||||
// 合并收件人列表
|
||||
recipients := append(msg.To, msg.Cc...)
|
||||
recipients = append(recipients, msg.Bcc...)
|
||||
|
||||
// 使用SendRaw发送
|
||||
return e.SendRaw(recipients, emailBody)
|
||||
}
|
||||
|
||||
// buildEmailBody 构建邮件内容
|
||||
func (e *Email) buildEmailBody(msg *Message) ([]byte, error) {
|
||||
func (e *Email) buildEmailBody(msg *Message, cfg *config.EmailConfig) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// 邮件头
|
||||
from := e.config.From
|
||||
if e.config.FromName != "" {
|
||||
from = fmt.Sprintf("%s <%s>", e.config.FromName, e.config.From)
|
||||
from := cfg.From
|
||||
if cfg.FromName != "" {
|
||||
from = fmt.Sprintf("%s <%s>", cfg.FromName, cfg.From)
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("From: %s\r\n", from))
|
||||
|
||||
@@ -281,26 +279,3 @@ func joinEmails(emails []string) string {
|
||||
return result
|
||||
}
|
||||
|
||||
// SendSimple 发送简单邮件(便捷方法)
|
||||
// to: 收件人
|
||||
// subject: 主题
|
||||
// body: 正文
|
||||
func (e *Email) SendSimple(to []string, subject, body string) error {
|
||||
return e.Send(&Message{
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
})
|
||||
}
|
||||
|
||||
// SendHTML 发送HTML邮件(便捷方法)
|
||||
// to: 收件人
|
||||
// subject: 主题
|
||||
// htmlBody: HTML正文
|
||||
func (e *Email) SendHTML(to []string, subject, htmlBody string) error {
|
||||
return e.Send(&Message{
|
||||
To: to,
|
||||
Subject: subject,
|
||||
HTMLBody: htmlBody,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 设置默认时区
|
||||
err := datetime.SetDefaultTimeZone(datetime.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
now := datetime.Now()
|
||||
fmt.Printf("Current time: %s\n", datetime.FormatDateTime(now))
|
||||
|
||||
// 解析时间字符串
|
||||
t, err := datetime.ParseDateTime("2024-01-01 12:00:00")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Parsed time: %s\n", datetime.FormatDateTime(t))
|
||||
|
||||
// 时区转换
|
||||
t2, err := datetime.ToTimezone(t, datetime.AmericaNewYork)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Time in New York: %s\n", datetime.FormatDateTime(t2))
|
||||
|
||||
// Unix时间戳
|
||||
unix := datetime.ToUnix(now)
|
||||
fmt.Printf("Unix timestamp: %d\n", unix)
|
||||
t3 := datetime.FromUnix(unix)
|
||||
fmt.Printf("From Unix: %s\n", datetime.FormatDateTime(t3))
|
||||
|
||||
// 时间计算
|
||||
tomorrow := datetime.AddDays(now, 1)
|
||||
fmt.Printf("Tomorrow: %s\n", datetime.FormatDate(tomorrow))
|
||||
|
||||
// 时间范围
|
||||
startOfDay := datetime.StartOfDay(now)
|
||||
endOfDay := datetime.EndOfDay(now)
|
||||
fmt.Printf("Start of day: %s\n", datetime.FormatDateTime(startOfDay))
|
||||
fmt.Printf("End of day: %s\n", datetime.FormatDateTime(endOfDay))
|
||||
|
||||
// 时间差
|
||||
diff := datetime.DiffDays(now, tomorrow)
|
||||
fmt.Printf("Days difference: %d\n", diff)
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 示例1:将当前时间转换为UTC
|
||||
fmt.Println("=== Example 1: Convert Current Time to UTC ===")
|
||||
now := time.Now()
|
||||
utcTime := datetime.ToUTC(now)
|
||||
fmt.Printf("Local time: %s\n", datetime.FormatDateTime(now))
|
||||
fmt.Printf("UTC time: %s\n", datetime.FormatDateTime(utcTime))
|
||||
|
||||
// 示例2:从指定时区转换为UTC
|
||||
fmt.Println("\n=== Example 2: Convert from Specific Timezone to UTC ===")
|
||||
// 解析上海时区的时间
|
||||
shanghaiTime, err := datetime.ParseDateTime("2024-01-01 12:00:00", datetime.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Shanghai time: %s\n", datetime.FormatDateTime(shanghaiTime, datetime.AsiaShanghai))
|
||||
|
||||
// 转换为UTC
|
||||
utcTime2, err := datetime.ToUTCFromTimezone(shanghaiTime, datetime.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("UTC time: %s\n", datetime.FormatDateTime(utcTime2, datetime.UTC))
|
||||
|
||||
// 示例3:解析时间字符串并直接转换为UTC
|
||||
fmt.Println("\n=== Example 3: Parse and Convert to UTC ===")
|
||||
utcTime3, err := datetime.ParseDateTimeToUTC("2024-01-01 12:00:00", datetime.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Parsed from Shanghai timezone, UTC: %s\n", datetime.FormatDateTime(utcTime3, datetime.UTC))
|
||||
|
||||
// 示例4:解析日期并转换为UTC
|
||||
fmt.Println("\n=== Example 4: Parse Date and Convert to UTC ===")
|
||||
utcTime4, err := datetime.ParseDateToUTC("2024-01-01", datetime.AsiaShanghai)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Date parsed from Shanghai timezone, UTC: %s\n", datetime.FormatDateTime(utcTime4, datetime.UTC))
|
||||
|
||||
// 示例5:数据库存储场景
|
||||
fmt.Println("\n=== Example 5: Database Storage Scenario ===")
|
||||
// 从请求中获取时间(假设是上海时区)
|
||||
requestTimeStr := "2024-01-01 12:00:00"
|
||||
requestTimezone := datetime.AsiaShanghai
|
||||
|
||||
// 转换为UTC时间(用于数据库存储)
|
||||
dbTime, err := datetime.ParseDateTimeToUTC(requestTimeStr, requestTimezone)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Request time (Shanghai): %s\n", requestTimeStr)
|
||||
fmt.Printf("Database time (UTC): %s\n", datetime.FormatDateTime(dbTime, datetime.UTC))
|
||||
|
||||
// 从数据库读取UTC时间,转换为用户时区显示
|
||||
userTimezone := datetime.AsiaShanghai
|
||||
displayTime, err := datetime.ToTimezone(dbTime, userTimezone)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Display time (Shanghai): %s\n", datetime.FormatDateTime(displayTime, userTimezone))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
// 示例:Factory黑盒模式 - 最简化的使用方式
|
||||
@@ -44,8 +43,6 @@ func main() {
|
||||
|
||||
// 用户列表
|
||||
func handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
|
||||
// 创建工厂(在处理器中也可以复用)
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
ctx := context.Background()
|
||||
@@ -59,7 +56,7 @@ func handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
cacheKey := "users:list"
|
||||
cached, _ := fac.RedisGet(ctx, cacheKey)
|
||||
if cached != "" {
|
||||
h.Success(cached)
|
||||
fac.Success(w, cached)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,12 +69,11 @@ func handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
// 4. 缓存结果
|
||||
fac.RedisSet(ctx, cacheKey, users, 5*time.Minute)
|
||||
|
||||
h.Success(users)
|
||||
fac.Success(w, users)
|
||||
}
|
||||
|
||||
// 文件上传
|
||||
func handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -85,7 +81,7 @@ func handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
fac.LogError("文件上传失败: %v", err)
|
||||
h.Error(400, "文件上传失败")
|
||||
fac.Error(w, 400, "文件上传失败")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
@@ -95,7 +91,7 @@ func handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
url, err := fac.UploadFile(ctx, objectKey, file, header.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
fac.LogError("文件上传到存储失败: %v", err)
|
||||
h.Error(500, "文件上传失败")
|
||||
fac.Error(w, 500, "文件上传失败")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -106,7 +102,7 @@ func handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
"url": url,
|
||||
}, "文件上传成功")
|
||||
|
||||
h.Success(map[string]interface{}{
|
||||
fac.Success(w, map[string]interface{}{
|
||||
"url": url,
|
||||
})
|
||||
}
|
||||
@@ -122,16 +118,14 @@ func authMiddleware(next http.Handler) http.Handler {
|
||||
// 获取token
|
||||
token := r.Header.Get("Authorization")
|
||||
if token == "" {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.Error(401, "未授权")
|
||||
fac.Error(w, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
// 从Redis验证token(黑盒方法)
|
||||
userID, err := fac.RedisGet(ctx, "token:"+token)
|
||||
if err != nil || userID == "" {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
h.Error(401, "token无效")
|
||||
fac.Error(w, 401, "token无效")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
)
|
||||
|
||||
// 用户结构
|
||||
@@ -14,15 +15,17 @@ type User struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// 获取用户列表(使用Handler黑盒模式)
|
||||
func GetUserList(h *commonhttp.Handler) {
|
||||
// 获取分页参数(简洁方式)
|
||||
pagination := h.ParsePaginationRequest()
|
||||
// 获取用户列表(使用公共方法和factory)
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取分页参数(使用公共方法)
|
||||
pagination := commonhttp.ParsePaginationRequest(r)
|
||||
page := pagination.GetPage()
|
||||
pageSize := pagination.GetSize()
|
||||
|
||||
// 获取查询参数(简洁方式)
|
||||
_ = h.GetQuery("keyword", "") // 示例:获取查询参数
|
||||
// 获取查询参数(使用公共方法)
|
||||
_ = commonhttp.GetQuery(r, "keyword", "") // 示例:获取查询参数
|
||||
|
||||
// 模拟查询数据
|
||||
users := []User{
|
||||
@@ -31,26 +34,28 @@ func GetUserList(h *commonhttp.Handler) {
|
||||
}
|
||||
total := int64(100)
|
||||
|
||||
// 返回分页响应(简洁方式)
|
||||
h.SuccessPage(users, total, page, pageSize)
|
||||
// 返回分页响应(使用factory方法)
|
||||
fac.SuccessPage(w, users, total, page, pageSize)
|
||||
}
|
||||
|
||||
// 创建用户(使用Handler黑盒模式)
|
||||
func CreateUser(h *commonhttp.Handler) {
|
||||
// 解析请求体(简洁方式)
|
||||
// 创建用户(使用公共方法和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 := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if req.Name == "" {
|
||||
h.Error(1001, "用户名不能为空")
|
||||
fac.Error(w, 1001, "用户名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,49 +66,46 @@ func CreateUser(h *commonhttp.Handler) {
|
||||
Email: req.Email,
|
||||
}
|
||||
|
||||
// 返回成功响应(简洁方式)
|
||||
h.SuccessWithMessage("创建成功", user)
|
||||
// 返回成功响应(使用factory方法,统一Success方法)
|
||||
fac.Success(w, user, "创建成功")
|
||||
}
|
||||
|
||||
// 获取用户详情(使用Handler黑盒模式)
|
||||
func GetUser(h *commonhttp.Handler) {
|
||||
// 获取查询参数(简洁方式)
|
||||
id := h.GetQueryInt64("id", 0)
|
||||
// 获取用户详情(使用公共方法和factory)
|
||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
|
||||
// 获取查询参数(使用公共方法)
|
||||
id := commonhttp.GetQueryInt64(r, "id", 0)
|
||||
|
||||
if id == 0 {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "用户ID不能为空", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟查询用户
|
||||
if id == 1 {
|
||||
user := User{ID: 1, Name: "User1", Email: "user1@example.com"}
|
||||
h.Success(user)
|
||||
fac.Success(w, user)
|
||||
} else {
|
||||
h.Error(1002, "用户不存在")
|
||||
fac.Error(w, 1002, "用户不存在")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 方式1:使用HandleFunc包装器(推荐,最简洁)
|
||||
http.HandleFunc("/users", commonhttp.HandleFunc(func(h *commonhttp.Handler) {
|
||||
switch h.Request().Method {
|
||||
// 使用标准http.HandleFunc
|
||||
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
GetUserList(h)
|
||||
GetUserList(w, r)
|
||||
case http.MethodPost:
|
||||
CreateUser(h)
|
||||
CreateUser(w, r)
|
||||
default:
|
||||
h.WriteJSON(http.StatusMethodNotAllowed, 405, "方法不支持", nil)
|
||||
commonhttp.WriteJSON(w, http.StatusMethodNotAllowed, 405, "方法不支持", nil)
|
||||
}
|
||||
}))
|
||||
|
||||
// 方式2:手动创建Handler(需要更多控制时)
|
||||
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
GetUser(h)
|
||||
})
|
||||
|
||||
http.HandleFunc("/user", GetUser)
|
||||
|
||||
log.Println("Server started on :8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/factory"
|
||||
)
|
||||
|
||||
// ListUserRequest 用户列表请求(包含分页字段)
|
||||
@@ -20,21 +21,22 @@ type User struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// 获取用户列表(使用Handler和PaginationRequest)
|
||||
func GetUserList(h *commonhttp.Handler) {
|
||||
// 获取用户列表(使用公共方法和factory)
|
||||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
var req ListUserRequest
|
||||
|
||||
// 方式1:从JSON请求体解析(分页字段会自动解析)
|
||||
if h.Request().Method == http.MethodPost {
|
||||
if err := h.ParseJSON(&req); err != nil {
|
||||
h.WriteJSON(http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
if r.Method == http.MethodPost {
|
||||
if err := commonhttp.ParseJSON(r, &req); err != nil {
|
||||
commonhttp.WriteJSON(w, http.StatusBadRequest, 400, "请求参数解析失败", nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 方式2:从查询参数解析分页
|
||||
pagination := h.ParsePaginationRequest()
|
||||
pagination := commonhttp.ParsePaginationRequest(r)
|
||||
req.PaginationRequest = *pagination
|
||||
req.Keyword = h.GetQuery("keyword", "")
|
||||
req.Keyword = commonhttp.GetQuery(r, "keyword", "")
|
||||
}
|
||||
|
||||
// 使用分页方法
|
||||
@@ -49,12 +51,12 @@ func GetUserList(h *commonhttp.Handler) {
|
||||
}
|
||||
total := int64(100)
|
||||
|
||||
// 返回分页响应
|
||||
h.SuccessPage(users, total, page, size)
|
||||
// 返回分页响应(使用factory方法)
|
||||
fac.SuccessPage(w, users, total, page, size)
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/users", commonhttp.HandleFunc(GetUserList))
|
||||
http.HandleFunc("/users", GetUserList)
|
||||
|
||||
log.Println("Server started on :8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
|
||||
@@ -21,13 +21,11 @@ func main() {
|
||||
|
||||
// 定义API处理器
|
||||
http.Handle("/api/hello", chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
// 获取时区(使用公共方法)
|
||||
timezone := commonhttp.GetTimezone(r)
|
||||
|
||||
// 获取时区
|
||||
timezone := h.GetTimezone()
|
||||
|
||||
// 返回响应
|
||||
h.Success(map[string]interface{}{
|
||||
// 返回响应(使用公共方法)
|
||||
commonhttp.Success(w, map[string]interface{}{
|
||||
"message": "Hello, World!",
|
||||
"timezone": timezone,
|
||||
})
|
||||
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
|
||||
"git.toowon.com/jimmy/go-common/config"
|
||||
"git.toowon.com/jimmy/go-common/email"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
"git.toowon.com/jimmy/go-common/logger"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
"git.toowon.com/jimmy/go-common/migration"
|
||||
"git.toowon.com/jimmy/go-common/sms"
|
||||
"git.toowon.com/jimmy/go-common/storage"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
@@ -21,6 +23,51 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ========== HTTP响应结构体(暴露给外部项目使用) ==========
|
||||
|
||||
// Response 标准响应结构(暴露给外部项目使用)
|
||||
// 外部项目可以直接使用 factory.Response 创建响应对象
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// response := factory.Response{
|
||||
// Code: 0,
|
||||
// Message: "success",
|
||||
// Data: data,
|
||||
// }
|
||||
type Response = commonhttp.Response
|
||||
|
||||
// PageResponse 分页响应结构(暴露给外部项目使用)
|
||||
// 外部项目可以直接使用 factory.PageResponse 创建分页响应对象
|
||||
type PageResponse = commonhttp.PageResponse
|
||||
|
||||
// PageData 分页数据(暴露给外部项目使用)
|
||||
// 外部项目可以直接使用 factory.PageData 创建分页数据对象
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// pageData := &factory.PageData{
|
||||
// List: users,
|
||||
// Total: 100,
|
||||
// Page: 1,
|
||||
// PageSize: 20,
|
||||
// }
|
||||
// fac.Success(w, pageData)
|
||||
type PageData = commonhttp.PageData
|
||||
|
||||
// ========== HTTP请求结构体(暴露给外部项目使用) ==========
|
||||
|
||||
// PaginationRequest 分页请求结构(暴露给外部项目使用)
|
||||
// 外部项目可以直接使用 factory.PaginationRequest 创建分页请求对象
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// type ListUserRequest struct {
|
||||
// Keyword string `json:"keyword"`
|
||||
// factory.PaginationRequest // 嵌入分页请求结构
|
||||
// }
|
||||
type PaginationRequest = commonhttp.PaginationRequest
|
||||
|
||||
// Factory 工厂类 - 黑盒模式设计
|
||||
//
|
||||
// 核心理念:
|
||||
@@ -90,17 +137,8 @@ func (f *Factory) getEmailClient() (*email.Email, error) {
|
||||
return f.email, nil
|
||||
}
|
||||
|
||||
if f.cfg.Email == nil {
|
||||
return nil, fmt.Errorf("email config is nil")
|
||||
}
|
||||
|
||||
e, err := email.NewEmail(f.cfg.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create email client: %w", err)
|
||||
}
|
||||
|
||||
f.email = e
|
||||
return e, nil
|
||||
f.email = email.NewEmail(f.cfg)
|
||||
return f.email, nil
|
||||
}
|
||||
|
||||
// SendEmail 发送邮件(黑盒模式,推荐使用)
|
||||
@@ -114,18 +152,7 @@ func (f *Factory) SendEmail(to []string, subject, body string, htmlBody ...strin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &email.Message{
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
if len(htmlBody) > 0 && htmlBody[0] != "" {
|
||||
msg.HTMLBody = htmlBody[0]
|
||||
}
|
||||
|
||||
return e.Send(msg)
|
||||
return e.SendEmail(to, subject, body, htmlBody...)
|
||||
}
|
||||
|
||||
// getSMSClient 获取短信客户端(内部方法,延迟初始化)
|
||||
@@ -134,17 +161,8 @@ func (f *Factory) getSMSClient() (*sms.SMS, error) {
|
||||
return f.sms, nil
|
||||
}
|
||||
|
||||
if f.cfg.SMS == nil {
|
||||
return nil, fmt.Errorf("SMS config is nil")
|
||||
}
|
||||
|
||||
s, err := sms.NewSMS(f.cfg.SMS)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SMS client: %w", err)
|
||||
}
|
||||
|
||||
f.sms = s
|
||||
return s, nil
|
||||
f.sms = sms.NewSMS(f.cfg)
|
||||
return f.sms, nil
|
||||
}
|
||||
|
||||
// SendSMS 发送短信(黑盒模式,推荐使用)
|
||||
@@ -157,17 +175,7 @@ func (f *Factory) SendSMS(phoneNumbers []string, templateParam interface{}, temp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &sms.SendRequest{
|
||||
PhoneNumbers: phoneNumbers,
|
||||
TemplateParam: templateParam,
|
||||
}
|
||||
|
||||
if len(templateCode) > 0 && templateCode[0] != "" {
|
||||
req.TemplateCode = templateCode[0]
|
||||
}
|
||||
|
||||
return s.Send(req)
|
||||
return s.SendSMS(phoneNumbers, templateParam, templateCode...)
|
||||
}
|
||||
|
||||
// getLogger 获取日志记录器(内部方法,延迟初始化)
|
||||
@@ -862,3 +870,328 @@ func (f *Factory) GetMigrationStatus(migrationsDir string) ([]migration.Migratio
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// ========== HTTP响应方法(黑盒模式,推荐使用) ==========
|
||||
//
|
||||
// 这些方法直接调用 http 包的公共方法,保持低耦合。
|
||||
// 推荐直接使用 factory.Success() 等方法,而不是通过 handler。
|
||||
|
||||
// Success 成功响应(黑盒模式,推荐使用)
|
||||
// w: ResponseWriter
|
||||
// data: 响应数据,可以为nil
|
||||
// message: 响应消息(可选),如果为空则使用默认消息 "success"
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// fac, _ := factory.NewFactoryFromFile("config.json")
|
||||
// http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
// fac.Success(w, user) // 只有数据
|
||||
// fac.Success(w, user, "查询成功") // 数据+消息
|
||||
// })
|
||||
func (f *Factory) Success(w http.ResponseWriter, data interface{}, message ...string) {
|
||||
commonhttp.Success(w, data, message...)
|
||||
}
|
||||
|
||||
// SuccessPage 分页成功响应(黑盒模式,推荐使用)
|
||||
// w: ResponseWriter
|
||||
// list: 数据列表
|
||||
// total: 总记录数
|
||||
// page: 当前页码
|
||||
// pageSize: 每页大小
|
||||
// message: 响应消息(可选),如果为空则使用默认消息 "success"
|
||||
func (f *Factory) SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string) {
|
||||
commonhttp.SuccessPage(w, list, total, page, pageSize, message...)
|
||||
}
|
||||
|
||||
// Error 错误响应(黑盒模式,推荐使用)
|
||||
// w: ResponseWriter
|
||||
// code: 业务错误码,非0表示业务错误
|
||||
// message: 错误消息
|
||||
func (f *Factory) Error(w http.ResponseWriter, code int, message string) {
|
||||
commonhttp.Error(w, code, message)
|
||||
}
|
||||
|
||||
// SystemError 系统错误响应(返回HTTP 500)(黑盒模式,推荐使用)
|
||||
// w: ResponseWriter
|
||||
// message: 错误消息
|
||||
func (f *Factory) SystemError(w http.ResponseWriter, message string) {
|
||||
commonhttp.SystemError(w, message)
|
||||
}
|
||||
|
||||
// ========== HTTP请求解析方法(黑盒模式,推荐使用) ==========
|
||||
//
|
||||
// 这些方法直接调用 http 包的公共方法,保持低耦合。
|
||||
// 推荐直接使用 factory.ParseJSON()、factory.GetQuery() 等方法。
|
||||
|
||||
// ParseJSON 解析JSON请求体(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// v: 目标结构体指针
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// var req struct {
|
||||
// Name string `json:"name"`
|
||||
// }
|
||||
// if err := fac.ParseJSON(r, &req); err != nil {
|
||||
// fac.Error(w, 400, "请求参数解析失败")
|
||||
// return
|
||||
// }
|
||||
func (f *Factory) ParseJSON(r *http.Request, v interface{}) error {
|
||||
return commonhttp.ParseJSON(r, v)
|
||||
}
|
||||
|
||||
// ParsePaginationRequest 从请求中解析分页参数(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// 支持从查询参数和form表单中解析
|
||||
// 优先级:查询参数 > form表单
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// pagination := fac.ParsePaginationRequest(r)
|
||||
// page := pagination.GetPage()
|
||||
// pageSize := pagination.GetSize()
|
||||
func (f *Factory) ParsePaginationRequest(r *http.Request) *PaginationRequest {
|
||||
return commonhttp.ParsePaginationRequest(r)
|
||||
}
|
||||
|
||||
// GetQuery 获取查询参数(字符串)(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetQuery(r *http.Request, key, defaultValue string) string {
|
||||
return commonhttp.GetQuery(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetQueryInt 获取整数查询参数(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetQueryInt(r *http.Request, key string, defaultValue int) int {
|
||||
return commonhttp.GetQueryInt(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetQueryInt64 获取int64查询参数(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetQueryInt64(r *http.Request, key string, defaultValue int64) int64 {
|
||||
return commonhttp.GetQueryInt64(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetQueryBool 获取布尔查询参数(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetQueryBool(r *http.Request, key string, defaultValue bool) bool {
|
||||
return commonhttp.GetQueryBool(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetQueryFloat64 获取float64查询参数(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64 {
|
||||
return commonhttp.GetQueryFloat64(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetFormValue 获取表单值(字符串)(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetFormValue(r *http.Request, key, defaultValue string) string {
|
||||
return commonhttp.GetFormValue(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetFormInt 获取表单整数(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetFormInt(r *http.Request, key string, defaultValue int) int {
|
||||
return commonhttp.GetFormInt(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetFormInt64 获取表单int64(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetFormInt64(r *http.Request, key string, defaultValue int64) int64 {
|
||||
return commonhttp.GetFormInt64(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetFormBool 获取表单布尔值(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetFormBool(r *http.Request, key string, defaultValue bool) bool {
|
||||
return commonhttp.GetFormBool(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetHeader 获取请求头(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// key: 请求头名称
|
||||
// defaultValue: 默认值
|
||||
func (f *Factory) GetHeader(r *http.Request, key, defaultValue string) string {
|
||||
return commonhttp.GetHeader(r, key, defaultValue)
|
||||
}
|
||||
|
||||
// GetTimezone 从请求的context中获取时区(黑盒模式,推荐使用)
|
||||
// r: HTTP请求
|
||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
// 如果未设置,返回默认时区 AsiaShanghai
|
||||
func (f *Factory) GetTimezone(r *http.Request) string {
|
||||
return commonhttp.GetTimezone(r)
|
||||
}
|
||||
|
||||
// ========== Tools工具方法(黑盒模式,推荐使用) ==========
|
||||
//
|
||||
// 这些方法直接调用 tools 包的公共方法,保持低耦合。
|
||||
// factory 只负责方法暴露,具体业务在 tools 包中实现。
|
||||
|
||||
// ========== Version 版本工具 ==========
|
||||
|
||||
// GetVersion 获取版本号(黑盒模式,推荐使用)
|
||||
// 优先从环境变量 DOCKER_TAG 或 VERSION 中读取
|
||||
// 如果没有设置环境变量,则使用默认版本号
|
||||
func (f *Factory) GetVersion() string {
|
||||
return tools.GetVersion()
|
||||
}
|
||||
|
||||
// ========== Money 金额工具 ==========
|
||||
|
||||
// GetMoneyCalculator 获取金额计算器(黑盒模式,推荐使用)
|
||||
// 返回金额计算器实例,可用于金额计算操作
|
||||
func (f *Factory) GetMoneyCalculator() *tools.MoneyCalculator {
|
||||
return tools.NewMoneyCalculator()
|
||||
}
|
||||
|
||||
// YuanToCents 元转分(黑盒模式,推荐使用)
|
||||
func (f *Factory) YuanToCents(yuan float64) int64 {
|
||||
return tools.YuanToCents(yuan)
|
||||
}
|
||||
|
||||
// CentsToYuan 分转元(黑盒模式,推荐使用)
|
||||
func (f *Factory) CentsToYuan(cents int64) float64 {
|
||||
return tools.CentsToYuan(cents)
|
||||
}
|
||||
|
||||
// FormatYuan 格式化显示金额(分转元,保留2位小数)(黑盒模式,推荐使用)
|
||||
func (f *Factory) FormatYuan(cents int64) string {
|
||||
return tools.FormatYuan(cents)
|
||||
}
|
||||
|
||||
// ========== DateTime 日期时间工具 ==========
|
||||
|
||||
// Now 获取当前时间(使用指定时区)(黑盒模式,推荐使用)
|
||||
func (f *Factory) Now(timezone ...string) time.Time {
|
||||
return tools.Now(timezone...)
|
||||
}
|
||||
|
||||
// ParseDateTime 解析日期时间字符串(2006-01-02 15:04:05)(黑盒模式,推荐使用)
|
||||
func (f *Factory) ParseDateTime(value string, timezone ...string) (time.Time, error) {
|
||||
return tools.ParseDateTime(value, timezone...)
|
||||
}
|
||||
|
||||
// ParseDate 解析日期字符串(2006-01-02)(黑盒模式,推荐使用)
|
||||
func (f *Factory) ParseDate(value string, timezone ...string) (time.Time, error) {
|
||||
return tools.ParseDate(value, timezone...)
|
||||
}
|
||||
|
||||
// FormatDateTime 格式化日期时间(2006-01-02 15:04:05)(黑盒模式,推荐使用)
|
||||
func (f *Factory) FormatDateTime(t time.Time, timezone ...string) string {
|
||||
return tools.FormatDateTime(t, timezone...)
|
||||
}
|
||||
|
||||
// FormatDate 格式化日期(2006-01-02)(黑盒模式,推荐使用)
|
||||
func (f *Factory) FormatDate(t time.Time, timezone ...string) string {
|
||||
return tools.FormatDate(t, timezone...)
|
||||
}
|
||||
|
||||
// FormatTime 格式化时间(15:04:05)(黑盒模式,推荐使用)
|
||||
func (f *Factory) FormatTime(t time.Time, timezone ...string) string {
|
||||
return tools.FormatTime(t, timezone...)
|
||||
}
|
||||
|
||||
// ToUnix 转换为Unix时间戳(黑盒模式,推荐使用)
|
||||
func (f *Factory) ToUnix(t time.Time) int64 {
|
||||
return tools.ToUnix(t)
|
||||
}
|
||||
|
||||
// FromUnix 从Unix时间戳创建时间(黑盒模式,推荐使用)
|
||||
func (f *Factory) FromUnix(sec int64, timezone ...string) time.Time {
|
||||
return tools.FromUnix(sec, timezone...)
|
||||
}
|
||||
|
||||
// ToUnixMilli 转换为Unix毫秒时间戳(黑盒模式,推荐使用)
|
||||
func (f *Factory) ToUnixMilli(t time.Time) int64 {
|
||||
return tools.ToUnixMilli(t)
|
||||
}
|
||||
|
||||
// FromUnixMilli 从Unix毫秒时间戳创建时间(黑盒模式,推荐使用)
|
||||
func (f *Factory) FromUnixMilli(msec int64, timezone ...string) time.Time {
|
||||
return tools.FromUnixMilli(msec, timezone...)
|
||||
}
|
||||
|
||||
// AddDays 添加天数(黑盒模式,推荐使用)
|
||||
func (f *Factory) AddDays(t time.Time, days int) time.Time {
|
||||
return tools.AddDays(t, days)
|
||||
}
|
||||
|
||||
// AddMonths 添加月数(黑盒模式,推荐使用)
|
||||
func (f *Factory) AddMonths(t time.Time, months int) time.Time {
|
||||
return tools.AddMonths(t, months)
|
||||
}
|
||||
|
||||
// AddYears 添加年数(黑盒模式,推荐使用)
|
||||
func (f *Factory) AddYears(t time.Time, years int) time.Time {
|
||||
return tools.AddYears(t, years)
|
||||
}
|
||||
|
||||
// StartOfDay 获取一天的开始时间(00:00:00)(黑盒模式,推荐使用)
|
||||
func (f *Factory) StartOfDay(t time.Time, timezone ...string) time.Time {
|
||||
return tools.StartOfDay(t, timezone...)
|
||||
}
|
||||
|
||||
// EndOfDay 获取一天的结束时间(23:59:59.999999999)(黑盒模式,推荐使用)
|
||||
func (f *Factory) EndOfDay(t time.Time, timezone ...string) time.Time {
|
||||
return tools.EndOfDay(t, timezone...)
|
||||
}
|
||||
|
||||
// StartOfMonth 获取月份的开始时间(黑盒模式,推荐使用)
|
||||
func (f *Factory) StartOfMonth(t time.Time, timezone ...string) time.Time {
|
||||
return tools.StartOfMonth(t, timezone...)
|
||||
}
|
||||
|
||||
// EndOfMonth 获取月份的结束时间(黑盒模式,推荐使用)
|
||||
func (f *Factory) EndOfMonth(t time.Time, timezone ...string) time.Time {
|
||||
return tools.EndOfMonth(t, timezone...)
|
||||
}
|
||||
|
||||
// StartOfYear 获取年份的开始时间(黑盒模式,推荐使用)
|
||||
func (f *Factory) StartOfYear(t time.Time, timezone ...string) time.Time {
|
||||
return tools.StartOfYear(t, timezone...)
|
||||
}
|
||||
|
||||
// EndOfYear 获取年份的结束时间(黑盒模式,推荐使用)
|
||||
func (f *Factory) EndOfYear(t time.Time, timezone ...string) time.Time {
|
||||
return tools.EndOfYear(t, timezone...)
|
||||
}
|
||||
|
||||
// DiffDays 计算两个时间之间的天数差(黑盒模式,推荐使用)
|
||||
func (f *Factory) DiffDays(t1, t2 time.Time) int {
|
||||
return tools.DiffDays(t1, t2)
|
||||
}
|
||||
|
||||
// DiffHours 计算两个时间之间的小时差(黑盒模式,推荐使用)
|
||||
func (f *Factory) DiffHours(t1, t2 time.Time) int64 {
|
||||
return tools.DiffHours(t1, t2)
|
||||
}
|
||||
|
||||
// DiffMinutes 计算两个时间之间的分钟差(黑盒模式,推荐使用)
|
||||
func (f *Factory) DiffMinutes(t1, t2 time.Time) int64 {
|
||||
return tools.DiffMinutes(t1, t2)
|
||||
}
|
||||
|
||||
// DiffSeconds 计算两个时间之间的秒数差(黑盒模式,推荐使用)
|
||||
func (f *Factory) DiffSeconds(t1, t2 time.Time) int64 {
|
||||
return tools.DiffSeconds(t1, t2)
|
||||
}
|
||||
|
||||
279
http/handler.go
279
http/handler.go
@@ -1,279 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
)
|
||||
|
||||
// Handler HTTP处理器包装器,封装ResponseWriter和Request,提供简洁的API
|
||||
type Handler struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
}
|
||||
|
||||
// NewHandler 创建Handler实例
|
||||
func NewHandler(w http.ResponseWriter, r *http.Request) *Handler {
|
||||
return &Handler{
|
||||
w: w,
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseWriter 获取原始的ResponseWriter(需要时使用)
|
||||
func (h *Handler) ResponseWriter() http.ResponseWriter {
|
||||
return h.w
|
||||
}
|
||||
|
||||
// Request 获取原始的Request(需要时使用)
|
||||
func (h *Handler) Request() *http.Request {
|
||||
return h.r
|
||||
}
|
||||
|
||||
// Context 获取请求的Context
|
||||
func (h *Handler) Context() context.Context {
|
||||
return h.r.Context()
|
||||
}
|
||||
|
||||
// ========== 响应方法(黑盒模式) ==========
|
||||
|
||||
// Success 成功响应
|
||||
// data: 响应数据,可以为nil
|
||||
func (h *Handler) Success(data interface{}) {
|
||||
writeJSON(h.w, http.StatusOK, 0, "success", data)
|
||||
}
|
||||
|
||||
// SuccessWithMessage 带消息的成功响应
|
||||
func (h *Handler) SuccessWithMessage(message string, data interface{}) {
|
||||
writeJSON(h.w, http.StatusOK, 0, message, data)
|
||||
}
|
||||
|
||||
// Error 错误响应
|
||||
// code: 业务错误码,非0表示业务错误
|
||||
// message: 错误消息
|
||||
func (h *Handler) Error(code int, message string) {
|
||||
writeJSON(h.w, http.StatusOK, code, message, nil)
|
||||
}
|
||||
|
||||
// SystemError 系统错误响应(返回HTTP 500)
|
||||
// message: 错误消息
|
||||
func (h *Handler) SystemError(message string) {
|
||||
writeJSON(h.w, http.StatusInternalServerError, 500, message, nil)
|
||||
}
|
||||
|
||||
// WriteJSON 写入JSON响应(自定义HTTP状态码和业务状态码)
|
||||
// httpCode: HTTP状态码(200表示正常,500表示系统错误等)
|
||||
// code: 业务状态码(0表示成功,非0表示业务错误)
|
||||
// message: 响应消息
|
||||
// data: 响应数据
|
||||
func (h *Handler) WriteJSON(httpCode, code int, message string, data interface{}) {
|
||||
writeJSON(h.w, httpCode, code, message, data)
|
||||
}
|
||||
|
||||
// SuccessPage 分页成功响应
|
||||
// list: 数据列表
|
||||
// total: 总记录数
|
||||
// page: 当前页码
|
||||
// pageSize: 每页大小
|
||||
// message: 响应消息(可选,如果为空则使用默认消息 "success")
|
||||
func (h *Handler) SuccessPage(list interface{}, total int64, page, pageSize int, message ...string) {
|
||||
msg := "success"
|
||||
if len(message) > 0 && message[0] != "" {
|
||||
msg = message[0]
|
||||
}
|
||||
|
||||
pageData := &PageData{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
|
||||
writeJSON(h.w, http.StatusOK, 0, msg, pageData)
|
||||
}
|
||||
|
||||
// ========== 请求解析方法(黑盒模式) ==========
|
||||
|
||||
// ParseJSON 解析JSON请求体
|
||||
// v: 目标结构体指针
|
||||
func (h *Handler) ParseJSON(v interface{}) error {
|
||||
body, err := io.ReadAll(h.r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.r.Body.Close()
|
||||
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(body, v)
|
||||
}
|
||||
|
||||
// GetQuery 获取查询参数
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (h *Handler) GetQuery(key, defaultValue string) string {
|
||||
value := h.r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetQueryInt 获取整数查询参数
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func (h *Handler) GetQueryInt(key string, defaultValue int) int {
|
||||
value := h.r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetQueryInt64 获取int64查询参数
|
||||
func (h *Handler) GetQueryInt64(key string, defaultValue int64) int64 {
|
||||
value := h.r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetQueryBool 获取布尔查询参数
|
||||
func (h *Handler) GetQueryBool(key string, defaultValue bool) bool {
|
||||
value := h.r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return boolValue
|
||||
}
|
||||
|
||||
// GetQueryFloat64 获取float64查询参数
|
||||
func (h *Handler) GetQueryFloat64(key string, defaultValue float64) float64 {
|
||||
value := h.r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
floatValue, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return floatValue
|
||||
}
|
||||
|
||||
// GetFormValue 获取表单值
|
||||
func (h *Handler) GetFormValue(key, defaultValue string) string {
|
||||
value := h.r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetFormInt 获取表单整数
|
||||
func (h *Handler) GetFormInt(key string, defaultValue int) int {
|
||||
value := h.r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetFormInt64 获取表单int64
|
||||
func (h *Handler) GetFormInt64(key string, defaultValue int64) int64 {
|
||||
value := h.r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetFormBool 获取表单布尔值
|
||||
func (h *Handler) GetFormBool(key string, defaultValue bool) bool {
|
||||
value := h.r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return boolValue
|
||||
}
|
||||
|
||||
// GetHeader 获取请求头
|
||||
func (h *Handler) GetHeader(key, defaultValue string) string {
|
||||
value := h.r.Header.Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// ParsePaginationRequest 从请求中解析分页参数
|
||||
// 支持从查询参数和form表单中解析
|
||||
// 优先级:查询参数 > form表单
|
||||
func (h *Handler) ParsePaginationRequest() *PaginationRequest {
|
||||
return ParsePaginationRequest(h.r)
|
||||
}
|
||||
|
||||
// GetTimezone 从请求的context中获取时区
|
||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
// 如果未设置,返回默认时区 AsiaShanghai
|
||||
func (h *Handler) GetTimezone() string {
|
||||
return middleware.GetTimezoneFromContext(h.r.Context())
|
||||
}
|
||||
|
||||
// HandleFunc 将Handler函数转换为标准的http.HandlerFunc
|
||||
// 这样可以将Handler函数直接用于http.HandleFunc
|
||||
// 示例:
|
||||
//
|
||||
// http.HandleFunc("/users", http.HandleFunc(func(h *http.Handler) {
|
||||
// h.Success(data)
|
||||
// }))
|
||||
func HandleFunc(fn func(*Handler)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h := NewHandler(w, r)
|
||||
fn(h)
|
||||
}
|
||||
}
|
||||
193
http/request.go
193
http/request.go
@@ -1,8 +1,12 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
)
|
||||
|
||||
// getQueryInt 获取整数查询参数(内部方法,供ParsePaginationRequest使用)
|
||||
@@ -114,3 +118,192 @@ func ParsePaginationRequest(r *http.Request) *PaginationRequest {
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// ========== 请求解析公共方法 ==========
|
||||
|
||||
// ParseJSON 解析JSON请求体(公共方法)
|
||||
// r: HTTP请求
|
||||
// v: 目标结构体指针
|
||||
func ParseJSON(r *http.Request, v interface{}) error {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(body, v)
|
||||
}
|
||||
|
||||
// GetQuery 获取查询参数(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetQuery(r *http.Request, key, defaultValue string) string {
|
||||
value := r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetQueryInt 获取整数查询参数(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetQueryInt(r *http.Request, key string, defaultValue int) int {
|
||||
value := r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetQueryInt64 获取int64查询参数(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetQueryInt64(r *http.Request, key string, defaultValue int64) int64 {
|
||||
value := r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetQueryBool 获取布尔查询参数(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetQueryBool(r *http.Request, key string, defaultValue bool) bool {
|
||||
value := r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return boolValue
|
||||
}
|
||||
|
||||
// GetQueryFloat64 获取float64查询参数(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetQueryFloat64(r *http.Request, key string, defaultValue float64) float64 {
|
||||
value := r.URL.Query().Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
floatValue, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return floatValue
|
||||
}
|
||||
|
||||
// GetFormValue 获取表单值(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetFormValue(r *http.Request, key, defaultValue string) string {
|
||||
value := r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetFormInt 获取表单整数(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetFormInt(r *http.Request, key string, defaultValue int) int {
|
||||
value := r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetFormInt64 获取表单int64(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetFormInt64(r *http.Request, key string, defaultValue int64) int64 {
|
||||
value := r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
// GetFormBool 获取表单布尔值(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 参数名
|
||||
// defaultValue: 默认值
|
||||
func GetFormBool(r *http.Request, key string, defaultValue bool) bool {
|
||||
value := r.FormValue(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return boolValue
|
||||
}
|
||||
|
||||
// GetHeader 获取请求头(公共方法)
|
||||
// r: HTTP请求
|
||||
// key: 请求头名称
|
||||
// defaultValue: 默认值
|
||||
func GetHeader(r *http.Request, key, defaultValue string) string {
|
||||
value := r.Header.Get(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetTimezone 从请求的context中获取时区(公共方法)
|
||||
// r: HTTP请求
|
||||
// 如果使用了middleware.Timezone中间件,可以从context中获取时区信息
|
||||
// 如果未设置,返回默认时区 AsiaShanghai
|
||||
func GetTimezone(r *http.Request) string {
|
||||
return middleware.GetTimezoneFromContext(r.Context())
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type PageData struct {
|
||||
PageSize int `json:"pageSize"` // 每页大小
|
||||
}
|
||||
|
||||
// writeJSON 写入JSON响应(内部方法)
|
||||
// writeJSON 写入JSON响应(公共方法)
|
||||
// httpCode: HTTP状态码(200表示正常,500表示系统错误等)
|
||||
// code: 业务状态码(0表示成功,非0表示业务错误)
|
||||
// message: 响应消息
|
||||
@@ -48,3 +48,58 @@ func writeJSON(w http.ResponseWriter, httpCode, code int, message string, data i
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// Success 成功响应(公共方法)
|
||||
// w: ResponseWriter
|
||||
// data: 响应数据,可以为nil
|
||||
// message: 响应消息(可选),如果为空则使用默认消息 "success"
|
||||
//
|
||||
// 使用方式:
|
||||
//
|
||||
// Success(w, data) // 只有数据,使用默认消息 "success"
|
||||
// Success(w, data, "查询成功") // 数据+消息
|
||||
func Success(w http.ResponseWriter, data interface{}, message ...string) {
|
||||
msg := "success"
|
||||
if len(message) > 0 && message[0] != "" {
|
||||
msg = message[0]
|
||||
}
|
||||
writeJSON(w, http.StatusOK, 0, msg, data)
|
||||
}
|
||||
|
||||
// SuccessPage 分页成功响应(公共方法)
|
||||
// w: ResponseWriter
|
||||
// list: 数据列表
|
||||
// total: 总记录数
|
||||
// page: 当前页码
|
||||
// pageSize: 每页大小
|
||||
// message: 响应消息(可选),如果为空则使用默认消息 "success"
|
||||
func SuccessPage(w http.ResponseWriter, list interface{}, total int64, page, pageSize int, message ...string) {
|
||||
msg := "success"
|
||||
if len(message) > 0 && message[0] != "" {
|
||||
msg = message[0]
|
||||
}
|
||||
|
||||
pageData := &PageData{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, 0, msg, pageData)
|
||||
}
|
||||
|
||||
// Error 错误响应(公共方法)
|
||||
// w: ResponseWriter
|
||||
// code: 业务错误码,非0表示业务错误
|
||||
// message: 错误消息
|
||||
func Error(w http.ResponseWriter, code int, message string) {
|
||||
writeJSON(w, http.StatusOK, code, message, nil)
|
||||
}
|
||||
|
||||
// SystemError 系统错误响应(返回HTTP 500)(公共方法)
|
||||
// w: ResponseWriter
|
||||
// message: 错误消息
|
||||
func SystemError(w http.ResponseWriter, message string) {
|
||||
writeJSON(w, http.StatusInternalServerError, 500, message, nil)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"git.toowon.com/jimmy/go-common/datetime"
|
||||
"git.toowon.com/jimmy/go-common/tools"
|
||||
)
|
||||
|
||||
// TimezoneKey context中存储时区的key
|
||||
@@ -14,7 +14,7 @@ type timezoneKey struct{}
|
||||
const TimezoneHeaderName = "X-Timezone"
|
||||
|
||||
// DefaultTimezone 默认时区
|
||||
const DefaultTimezone = datetime.AsiaShanghai
|
||||
const DefaultTimezone = tools.AsiaShanghai
|
||||
|
||||
// GetTimezoneFromContext 从context中获取时区
|
||||
func GetTimezoneFromContext(ctx context.Context) string {
|
||||
@@ -38,7 +38,7 @@ func Timezone(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// 验证时区是否有效
|
||||
if _, err := datetime.GetLocation(timezone); err != nil {
|
||||
if _, err := tools.GetLocation(timezone); err != nil {
|
||||
// 如果时区无效,使用默认时区
|
||||
timezone = DefaultTimezone
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func Timezone(next http.Handler) http.Handler {
|
||||
// defaultTimezone: 默认时区,如果未指定则使用 AsiaShanghai
|
||||
func TimezoneWithDefault(defaultTimezone string) func(http.Handler) http.Handler {
|
||||
// 验证默认时区是否有效
|
||||
if _, err := datetime.GetLocation(defaultTimezone); err != nil {
|
||||
if _, err := tools.GetLocation(defaultTimezone); err != nil {
|
||||
defaultTimezone = DefaultTimezone
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func TimezoneWithDefault(defaultTimezone string) func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
// 验证时区是否有效
|
||||
if _, err := datetime.GetLocation(timezone); err != nil {
|
||||
if _, err := tools.GetLocation(timezone); err != nil {
|
||||
// 如果时区无效,使用默认时区
|
||||
timezone = defaultTimezone
|
||||
}
|
||||
@@ -79,4 +79,3 @@ func TimezoneWithDefault(defaultTimezone string) func(http.Handler) http.Handler
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
269
sms/sms.go
269
sms/sms.go
@@ -16,59 +16,6 @@ import (
|
||||
"git.toowon.com/jimmy/go-common/config"
|
||||
)
|
||||
|
||||
// SMS 短信发送器
|
||||
type SMS struct {
|
||||
config *config.SMSConfig
|
||||
}
|
||||
|
||||
// NewSMS 创建短信发送器
|
||||
func NewSMS(cfg *config.SMSConfig) (*SMS, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("SMS config is nil")
|
||||
}
|
||||
|
||||
if cfg.AccessKeyID == "" {
|
||||
return nil, fmt.Errorf("AccessKeyID is required")
|
||||
}
|
||||
|
||||
if cfg.AccessKeySecret == "" {
|
||||
return nil, fmt.Errorf("AccessKeySecret is required")
|
||||
}
|
||||
|
||||
if cfg.SignName == "" {
|
||||
return nil, fmt.Errorf("SignName is required")
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if cfg.Region == "" {
|
||||
cfg.Region = "cn-hangzhou"
|
||||
}
|
||||
if cfg.Timeout == 0 {
|
||||
cfg.Timeout = 10
|
||||
}
|
||||
|
||||
return &SMS{
|
||||
config: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendRequest 发送短信请求
|
||||
type SendRequest struct {
|
||||
// PhoneNumbers 手机号列表
|
||||
PhoneNumbers []string
|
||||
|
||||
// TemplateCode 模板代码(如果为空,使用配置中的模板代码)
|
||||
TemplateCode string
|
||||
|
||||
// TemplateParam 模板参数(可以是map或JSON字符串)
|
||||
// 如果是map,会自动转换为JSON字符串
|
||||
// 如果是string,直接使用(必须是有效的JSON字符串)
|
||||
TemplateParam interface{}
|
||||
|
||||
// SignName 签名(如果为空,使用配置中的签名)
|
||||
SignName string
|
||||
}
|
||||
|
||||
// SendResponse 发送短信响应
|
||||
type SendResponse struct {
|
||||
// RequestID 请求ID
|
||||
@@ -84,48 +31,123 @@ type SendResponse struct {
|
||||
BizID string `json:"BizId"`
|
||||
}
|
||||
|
||||
// SendRaw 发送原始请求(允许外部完全控制请求参数)
|
||||
// params: 请求参数map,工具只负责添加必要的系统参数(如签名、时间戳等)并发送
|
||||
func (s *SMS) SendRaw(params map[string]string) (*SendResponse, error) {
|
||||
if params == nil {
|
||||
params = make(map[string]string)
|
||||
// SMS 短信发送器
|
||||
type SMS struct {
|
||||
config *config.SMSConfig
|
||||
}
|
||||
|
||||
// NewSMS 创建短信发送器
|
||||
func NewSMS(cfg *config.Config) *SMS {
|
||||
if cfg == nil || cfg.SMS == nil {
|
||||
return &SMS{config: nil}
|
||||
}
|
||||
return &SMS{config: cfg.SMS}
|
||||
}
|
||||
|
||||
// getSMSConfig 获取短信配置(内部方法)
|
||||
func (s *SMS) getSMSConfig() (*config.SMSConfig, error) {
|
||||
if s.config == nil {
|
||||
return nil, fmt.Errorf("SMS config is nil")
|
||||
}
|
||||
|
||||
// 确保必要的系统参数存在
|
||||
if params["Action"] == "" {
|
||||
params["Action"] = "SendSms"
|
||||
if s.config.AccessKeyID == "" {
|
||||
return nil, fmt.Errorf("AccessKeyID is required")
|
||||
}
|
||||
if params["Version"] == "" {
|
||||
params["Version"] = "2017-05-25"
|
||||
|
||||
if s.config.AccessKeySecret == "" {
|
||||
return nil, fmt.Errorf("AccessKeySecret is required")
|
||||
}
|
||||
if params["RegionId"] == "" {
|
||||
params["RegionId"] = s.config.Region
|
||||
|
||||
if s.config.SignName == "" {
|
||||
return nil, fmt.Errorf("SignName is required")
|
||||
}
|
||||
if params["AccessKeyId"] == "" {
|
||||
params["AccessKeyId"] = s.config.AccessKeyID
|
||||
|
||||
// 设置默认值
|
||||
if s.config.Region == "" {
|
||||
s.config.Region = "cn-hangzhou"
|
||||
}
|
||||
if params["Format"] == "" {
|
||||
params["Format"] = "JSON"
|
||||
if s.config.Timeout == 0 {
|
||||
s.config.Timeout = 10
|
||||
}
|
||||
if params["SignatureMethod"] == "" {
|
||||
params["SignatureMethod"] = "HMAC-SHA1"
|
||||
|
||||
return s.config, nil
|
||||
}
|
||||
|
||||
// SendSMS 发送短信
|
||||
// phoneNumbers: 手机号列表
|
||||
// templateParam: 模板参数(map或JSON字符串)
|
||||
// templateCode: 模板代码(可选,如果为空使用配置中的模板代码)
|
||||
func (s *SMS) SendSMS(phoneNumbers []string, templateParam interface{}, templateCode ...string) (*SendResponse, error) {
|
||||
cfg, err := s.getSMSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if params["SignatureVersion"] == "" {
|
||||
params["SignatureVersion"] = "1.0"
|
||||
|
||||
if len(phoneNumbers) == 0 {
|
||||
return nil, fmt.Errorf("phone numbers are required")
|
||||
}
|
||||
if params["SignatureNonce"] == "" {
|
||||
params["SignatureNonce"] = fmt.Sprint(time.Now().UnixNano())
|
||||
|
||||
// 使用配置中的模板代码(如果请求中未指定)
|
||||
templateCodeValue := ""
|
||||
if len(templateCode) > 0 && templateCode[0] != "" {
|
||||
templateCodeValue = templateCode[0]
|
||||
} else {
|
||||
templateCodeValue = cfg.TemplateCode
|
||||
}
|
||||
if params["Timestamp"] == "" {
|
||||
params["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
if templateCodeValue == "" {
|
||||
return nil, fmt.Errorf("template code is required")
|
||||
}
|
||||
|
||||
signName := cfg.SignName
|
||||
|
||||
// 处理模板参数
|
||||
var templateParamJSON string
|
||||
if templateParam != nil {
|
||||
switch v := templateParam.(type) {
|
||||
case string:
|
||||
// 直接使用字符串(必须是有效的JSON)
|
||||
templateParamJSON = v
|
||||
case map[string]string:
|
||||
// 转换为JSON字符串
|
||||
paramBytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal template param: %w", err)
|
||||
}
|
||||
templateParamJSON = string(paramBytes)
|
||||
default:
|
||||
// 尝试JSON序列化
|
||||
paramBytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal template param: %w", err)
|
||||
}
|
||||
templateParamJSON = string(paramBytes)
|
||||
}
|
||||
} else {
|
||||
templateParamJSON = "{}"
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
params := make(map[string]string)
|
||||
params["Action"] = "SendSms"
|
||||
params["Version"] = "2017-05-25"
|
||||
params["RegionId"] = cfg.Region
|
||||
params["AccessKeyId"] = cfg.AccessKeyID
|
||||
params["Format"] = "JSON"
|
||||
params["SignatureMethod"] = "HMAC-SHA1"
|
||||
params["SignatureVersion"] = "1.0"
|
||||
params["SignatureNonce"] = fmt.Sprint(time.Now().UnixNano())
|
||||
params["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
params["PhoneNumbers"] = strings.Join(phoneNumbers, ",")
|
||||
params["SignName"] = signName
|
||||
params["TemplateCode"] = templateCodeValue
|
||||
params["TemplateParam"] = templateParamJSON
|
||||
|
||||
// 计算签名
|
||||
signature := s.calculateSignature(params, "POST")
|
||||
signature := s.calculateSignature(params, "POST", cfg.AccessKeySecret)
|
||||
params["Signature"] = signature
|
||||
|
||||
// 构建请求URL
|
||||
endpoint := s.config.Endpoint
|
||||
endpoint := cfg.Endpoint
|
||||
if endpoint == "" {
|
||||
endpoint = "https://dysmsapi.aliyuncs.com"
|
||||
}
|
||||
@@ -145,7 +167,7 @@ func (s *SMS) SendRaw(params map[string]string) (*SendResponse, error) {
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(s.config.Timeout) * time.Second,
|
||||
Timeout: time.Duration(cfg.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(httpReq)
|
||||
@@ -174,70 +196,8 @@ func (s *SMS) SendRaw(params map[string]string) (*SendResponse, error) {
|
||||
return &sendResp, nil
|
||||
}
|
||||
|
||||
// Send 发送短信(使用SendRequest结构)
|
||||
// 注意:如果需要完全控制请求参数,请使用SendRaw方法
|
||||
func (s *SMS) Send(req *SendRequest) (*SendResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("request is nil")
|
||||
}
|
||||
|
||||
if len(req.PhoneNumbers) == 0 {
|
||||
return nil, fmt.Errorf("phone numbers are required")
|
||||
}
|
||||
|
||||
// 使用配置中的模板代码和签名(如果请求中未指定)
|
||||
templateCode := req.TemplateCode
|
||||
if templateCode == "" {
|
||||
templateCode = s.config.TemplateCode
|
||||
}
|
||||
if templateCode == "" {
|
||||
return nil, fmt.Errorf("template code is required")
|
||||
}
|
||||
|
||||
signName := req.SignName
|
||||
if signName == "" {
|
||||
signName = s.config.SignName
|
||||
}
|
||||
|
||||
// 处理模板参数
|
||||
var templateParamJSON string
|
||||
if req.TemplateParam != nil {
|
||||
switch v := req.TemplateParam.(type) {
|
||||
case string:
|
||||
// 直接使用字符串(必须是有效的JSON)
|
||||
templateParamJSON = v
|
||||
case map[string]string:
|
||||
// 转换为JSON字符串
|
||||
paramBytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal template param: %w", err)
|
||||
}
|
||||
templateParamJSON = string(paramBytes)
|
||||
default:
|
||||
// 尝试JSON序列化
|
||||
paramBytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal template param: %w", err)
|
||||
}
|
||||
templateParamJSON = string(paramBytes)
|
||||
}
|
||||
} else {
|
||||
templateParamJSON = "{}"
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
params := make(map[string]string)
|
||||
params["PhoneNumbers"] = strings.Join(req.PhoneNumbers, ",")
|
||||
params["SignName"] = signName
|
||||
params["TemplateCode"] = templateCode
|
||||
params["TemplateParam"] = templateParamJSON
|
||||
|
||||
// 使用SendRaw发送
|
||||
return s.SendRaw(params)
|
||||
}
|
||||
|
||||
// calculateSignature 计算签名
|
||||
func (s *SMS) calculateSignature(params map[string]string, method string) string {
|
||||
func (s *SMS) calculateSignature(params map[string]string, method, accessKeySecret string) string {
|
||||
// 对参数进行排序
|
||||
keys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
@@ -260,31 +220,10 @@ func (s *SMS) calculateSignature(params map[string]string, method string) string
|
||||
stringToSign := method + "&" + url.QueryEscape("/") + "&" + url.QueryEscape(queryString)
|
||||
|
||||
// 计算HMAC-SHA1签名
|
||||
mac := hmac.New(sha1.New, []byte(s.config.AccessKeySecret+"&"))
|
||||
mac := hmac.New(sha1.New, []byte(accessKeySecret+"&"))
|
||||
mac.Write([]byte(stringToSign))
|
||||
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
// SendSimple 发送简单短信(便捷方法)
|
||||
// phoneNumbers: 手机号列表
|
||||
// templateParam: 模板参数
|
||||
func (s *SMS) SendSimple(phoneNumbers []string, templateParam map[string]string) (*SendResponse, error) {
|
||||
return s.Send(&SendRequest{
|
||||
PhoneNumbers: phoneNumbers,
|
||||
TemplateParam: templateParam,
|
||||
})
|
||||
}
|
||||
|
||||
// SendWithTemplate 使用指定模板发送短信(便捷方法)
|
||||
// phoneNumbers: 手机号列表
|
||||
// templateCode: 模板代码
|
||||
// templateParam: 模板参数
|
||||
func (s *SMS) SendWithTemplate(phoneNumbers []string, templateCode string, templateParam map[string]string) (*SendResponse, error) {
|
||||
return s.Send(&SendRequest{
|
||||
PhoneNumbers: phoneNumbers,
|
||||
TemplateCode: templateCode,
|
||||
TemplateParam: templateParam,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,31 +43,29 @@ func NewUploadHandler(cfg UploadHandlerConfig) *UploadHandler {
|
||||
// 表单字段: file (文件)
|
||||
// 可选字段: prefix (对象键前缀,会覆盖配置中的前缀)
|
||||
func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler := commonhttp.NewHandler(w, r)
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
handler.Error(4001, "Method not allowed")
|
||||
commonhttp.Error(w, 4001, "Method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析multipart表单
|
||||
err := r.ParseMultipartForm(h.maxFileSize)
|
||||
if err != nil {
|
||||
handler.Error(4002, fmt.Sprintf("Failed to parse form: %v", err))
|
||||
commonhttp.Error(w, 4002, fmt.Sprintf("Failed to parse form: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
handler.Error(4003, fmt.Sprintf("Failed to get file: %v", err))
|
||||
commonhttp.Error(w, 4003, fmt.Sprintf("Failed to get file: %v", err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 检查文件大小
|
||||
if h.maxFileSize > 0 && header.Size > h.maxFileSize {
|
||||
handler.Error(1001, fmt.Sprintf("File size exceeds limit: %d bytes", h.maxFileSize))
|
||||
commonhttp.Error(w, 1001, fmt.Sprintf("File size exceeds limit: %d bytes", h.maxFileSize))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,7 +80,7 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
handler.Error(1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
|
||||
commonhttp.Error(w, 1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -110,14 +108,14 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
err = h.storage.Upload(ctx, objectKey, file, contentType)
|
||||
if err != nil {
|
||||
handler.SystemError(fmt.Sprintf("Failed to upload file: %v", err))
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to upload file: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件URL
|
||||
fileURL, err := h.storage.GetURL(objectKey, 0)
|
||||
if err != nil {
|
||||
handler.SystemError(fmt.Sprintf("Failed to get file URL: %v", err))
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to get file URL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -130,7 +128,7 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
UploadTime: time.Now(),
|
||||
}
|
||||
|
||||
handler.SuccessWithMessage("Upload successful", result)
|
||||
commonhttp.Success(w, result, "Upload successful")
|
||||
}
|
||||
|
||||
// generateUniqueFilename 生成唯一文件名
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package datetime
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
173
tools/money.go
Normal file
173
tools/money.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// MoneyCalculator 金额计算工具(以分为单位)
|
||||
type MoneyCalculator struct{}
|
||||
|
||||
// NewMoneyCalculator 创建金额计算器
|
||||
func NewMoneyCalculator() *MoneyCalculator {
|
||||
return &MoneyCalculator{}
|
||||
}
|
||||
|
||||
// YuanToCents 元转分
|
||||
func (m *MoneyCalculator) YuanToCents(yuan float64) int64 {
|
||||
return int64(math.Round(yuan * 100))
|
||||
}
|
||||
|
||||
// CentsToYuan 分转元
|
||||
func (m *MoneyCalculator) CentsToYuan(cents int64) float64 {
|
||||
return float64(cents) / 100
|
||||
}
|
||||
|
||||
// FormatYuan 格式化显示金额(分转元,保留2位小数)
|
||||
func (m *MoneyCalculator) FormatYuan(cents int64) string {
|
||||
return fmt.Sprintf("%.2f", m.CentsToYuan(cents))
|
||||
}
|
||||
|
||||
// Add 金额相加
|
||||
func (m *MoneyCalculator) Add(amount1, amount2 int64) int64 {
|
||||
return amount1 + amount2
|
||||
}
|
||||
|
||||
// Subtract 金额相减
|
||||
func (m *MoneyCalculator) Subtract(amount1, amount2 int64) int64 {
|
||||
return amount1 - amount2
|
||||
}
|
||||
|
||||
// Multiply 金额乘法(金额 * 数量)
|
||||
func (m *MoneyCalculator) Multiply(amount int64, quantity int) int64 {
|
||||
return amount * int64(quantity)
|
||||
}
|
||||
|
||||
// MultiplyFloat 金额乘法(金额 * 浮点数,如折扣)
|
||||
func (m *MoneyCalculator) MultiplyFloat(amount int64, rate float64) int64 {
|
||||
return int64(math.Round(float64(amount) * rate))
|
||||
}
|
||||
|
||||
// Divide 金额除法(平均分配)
|
||||
func (m *MoneyCalculator) Divide(amount int64, count int) int64 {
|
||||
if count <= 0 {
|
||||
return 0
|
||||
}
|
||||
return amount / int64(count)
|
||||
}
|
||||
|
||||
// CalculateDiscount 计算折扣金额
|
||||
func (m *MoneyCalculator) CalculateDiscount(originalAmount int64, discountRate float64) int64 {
|
||||
if discountRate <= 0 || discountRate >= 1 {
|
||||
return 0
|
||||
}
|
||||
return int64(math.Round(float64(originalAmount) * discountRate))
|
||||
}
|
||||
|
||||
// CalculatePercentage 计算百分比金额
|
||||
func (m *MoneyCalculator) CalculatePercentage(amount int64, percentage float64) int64 {
|
||||
return int64(math.Round(float64(amount) * percentage / 100))
|
||||
}
|
||||
|
||||
// Max 取最大金额
|
||||
func (m *MoneyCalculator) Max(amounts ...int64) int64 {
|
||||
if len(amounts) == 0 {
|
||||
return 0
|
||||
}
|
||||
max := amounts[0]
|
||||
for _, amount := range amounts[1:] {
|
||||
if amount > max {
|
||||
max = amount
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// Min 取最小金额
|
||||
func (m *MoneyCalculator) Min(amounts ...int64) int64 {
|
||||
if len(amounts) == 0 {
|
||||
return 0
|
||||
}
|
||||
min := amounts[0]
|
||||
for _, amount := range amounts[1:] {
|
||||
if amount < min {
|
||||
min = amount
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
// IsPositive 判断是否为正数
|
||||
func (m *MoneyCalculator) IsPositive(amount int64) bool {
|
||||
return amount > 0
|
||||
}
|
||||
|
||||
// IsZero 判断是否为零
|
||||
func (m *MoneyCalculator) IsZero(amount int64) bool {
|
||||
return amount == 0
|
||||
}
|
||||
|
||||
// IsNegative 判断是否为负数
|
||||
func (m *MoneyCalculator) IsNegative(amount int64) bool {
|
||||
return amount < 0
|
||||
}
|
||||
|
||||
// Equal 判断金额是否相等
|
||||
func (m *MoneyCalculator) Equal(amount1, amount2 int64) bool {
|
||||
return amount1 == amount2
|
||||
}
|
||||
|
||||
// Greater 判断第一个金额是否大于第二个
|
||||
func (m *MoneyCalculator) Greater(amount1, amount2 int64) bool {
|
||||
return amount1 > amount2
|
||||
}
|
||||
|
||||
// Less 判断第一个金额是否小于第二个
|
||||
func (m *MoneyCalculator) Less(amount1, amount2 int64) bool {
|
||||
return amount1 < amount2
|
||||
}
|
||||
|
||||
// Sum 计算金额总和
|
||||
func (m *MoneyCalculator) Sum(amounts ...int64) int64 {
|
||||
var total int64
|
||||
for _, amount := range amounts {
|
||||
total += amount
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// SplitAmount 金额分摊(处理分摊不均的情况)
|
||||
func (m *MoneyCalculator) SplitAmount(totalAmount int64, count int) []int64 {
|
||||
if count <= 0 {
|
||||
return []int64{}
|
||||
}
|
||||
|
||||
baseAmount := totalAmount / int64(count)
|
||||
remainder := totalAmount % int64(count)
|
||||
|
||||
amounts := make([]int64, count)
|
||||
for i := 0; i < count; i++ {
|
||||
amounts[i] = baseAmount
|
||||
if int64(i) < remainder {
|
||||
amounts[i]++
|
||||
}
|
||||
}
|
||||
|
||||
return amounts
|
||||
}
|
||||
|
||||
// 全局金额计算器实例
|
||||
var Money = NewMoneyCalculator()
|
||||
|
||||
// 便捷函数
|
||||
func YuanToCents(yuan float64) int64 {
|
||||
return Money.YuanToCents(yuan)
|
||||
}
|
||||
|
||||
func CentsToYuan(cents int64) float64 {
|
||||
return Money.CentsToYuan(cents)
|
||||
}
|
||||
|
||||
func FormatYuan(cents int64) string {
|
||||
return Money.FormatYuan(cents)
|
||||
}
|
||||
26
tools/version.go
Normal file
26
tools/version.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Version 版本号 - 在这里修改版本号(默认值)
|
||||
const DefaultVersion = "1.0.1"
|
||||
|
||||
// GetVersion 获取版本号
|
||||
// 优先从环境变量 DOCKER_TAG 或 VERSION 中读取
|
||||
// 如果没有设置环境变量,则使用默认版本号
|
||||
func GetVersion() string {
|
||||
// 优先从 Docker 标签环境变量读取
|
||||
if dockerTag := os.Getenv("DOCKER_TAG"); dockerTag != "" {
|
||||
return dockerTag
|
||||
}
|
||||
|
||||
// 从通用版本环境变量读取
|
||||
if version := os.Getenv("VERSION"); version != "" {
|
||||
return version
|
||||
}
|
||||
|
||||
// 使用默认版本号
|
||||
return DefaultVersion
|
||||
}
|
||||
Reference in New Issue
Block a user