feat: add precise diagnostics and optional client copy logging controls
This commit is contained in:
18
examples/developerTools/README.md
Normal file
18
examples/developerTools/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# 配置诊断、严格 HTTP 状态、类型预检和日志时区
|
||||
|
||||
在框架根目录运行,不需要数据库、账号或外部服务。HTTP 示例只访问临时 localhost 服务:
|
||||
|
||||
```sh
|
||||
go run ./examples/developerTools
|
||||
DEMO_LOG_TIME_ZONE=UTC go run ./examples/developerTools
|
||||
# 以下两条应非零退出,输出具体字段和环境变量名,不输出原值:
|
||||
DEMO_DB_PORT=70000 go run ./examples/developerTools
|
||||
DEMO_LOG_LEVEL=invalid go run ./examples/developerTools
|
||||
```
|
||||
|
||||
- 配置只做校验,没有连接数据库。失败指向 `database.port` / `server.log.level`,并保留文件行列与变量名。
|
||||
- Logger 使用选定时区;只改变记录的时间戳,不修改全局 time.Local 和业务时间字段。
|
||||
- `copyx.Check[Input, Result]` 发现切片元素内的不兼容字段;加上显式 `strconv.Atoi` converter 后通过。预检不执行 converter,真实非法数字仍在 Copy 时返回错误。
|
||||
- JSON 调用显式 `WithSuccessStatuses(200)`,拒绝 201,框架仍负责关闭响应体。
|
||||
|
||||
详见 [配置](../../CONFIG.md)、[日志](../../logx/README.md)、[HTTP 客户端](../../httpc/README.md)、[copyx](../../COPY.md)。
|
||||
7
examples/developerTools/config.yaml
Normal file
7
examples/developerTools/config.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Validates database settings without connecting in this demonstration.
|
||||
database:
|
||||
port: "${DEMO_DB_PORT:-3306}"
|
||||
server:
|
||||
log:
|
||||
level: "${DEMO_LOG_LEVEL:-info}"
|
||||
timeZone: "${DEMO_LOG_TIME_ZONE:-Asia/Shanghai}"
|
||||
104
examples/developerTools/main.go
Normal file
104
examples/developerTools/main.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Run from the framework root with: go run ./examples/developerTools
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
_ "time/tzdata" // This example embeds zones; production builds can use -tags timetzdata.
|
||||
|
||||
"git.yuntuo.hk/k2safe/go-omni/config"
|
||||
"git.yuntuo.hk/k2safe/go-omni/copyx"
|
||||
mysql "git.yuntuo.hk/k2safe/go-omni/database/mysql"
|
||||
"git.yuntuo.hk/k2safe/go-omni/httpc"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Database mysql.Config `json:"database"`
|
||||
Server config.Config `json:"server"`
|
||||
}
|
||||
|
||||
func Default() Config {
|
||||
c := config.Default()
|
||||
c.Log.Output = "stdout"
|
||||
return Config{Database: mysql.DefaultConfig(), Server: c}
|
||||
}
|
||||
func (c Config) Validate() error {
|
||||
if err := c.Database.Validate(); err != nil {
|
||||
return config.AtField("database", err)
|
||||
}
|
||||
return config.AtField("server", c.Server.Validate())
|
||||
}
|
||||
|
||||
type Input struct{ Values []TextValue }
|
||||
type TextValue struct{ Number string }
|
||||
type Result struct{ Values []NumberValue }
|
||||
type NumberValue struct{ Number int }
|
||||
|
||||
func run(path string) error {
|
||||
cfg, err := config.LoadProject(path, Default())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Validation only: this example never opens a database.
|
||||
logger, err := logx.New(cfg.Server.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer logger.Close()
|
||||
logger.InfoContext(context.Background(), "instance time zone", "configuredZone", cfg.Server.Log.TimeZone)
|
||||
if err := copyx.Check[Input, Result](); !errors.Is(err, copyx.ErrType) {
|
||||
return fmt.Errorf("expected a type mismatch, got %v", err)
|
||||
}
|
||||
// Explicit Go converter owns the number conversion; no JSON mapping file.
|
||||
options := []copyx.Option{copyx.WithConverter(strconv.Atoi)}
|
||||
if err := copyx.Check[Input, Result](options...); err != nil {
|
||||
return err
|
||||
}
|
||||
var output Result
|
||||
if err := copyx.Copy(&output, Input{Values: []TextValue{{Number: "42"}}}, options...); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("type check and copy succeeded: %d\n", output.Values[0].Number)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/created" {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
io.WriteString(w, `{"value":"example"}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
client, err := httpc.New(httpc.Config{DisableLogging: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
var reply struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
if err := client.JSON(context.Background(), http.MethodGet, server.URL, nil, &reply, httpc.WithSuccessStatuses(http.StatusOK)); err != nil {
|
||||
return err
|
||||
}
|
||||
err = client.JSON(context.Background(), http.MethodGet, server.URL+"/created", nil, &reply, httpc.WithSuccessStatuses(http.StatusOK))
|
||||
var status *httpc.StatusError
|
||||
if !errors.As(err, &status) || status.StatusCode != http.StatusCreated {
|
||||
return fmt.Errorf("expected status rejection, got %v", err)
|
||||
}
|
||||
fmt.Println("strict JSON: 200 accepted, 201 rejected")
|
||||
return nil
|
||||
}
|
||||
func main() {
|
||||
path := flag.String("config", "examples/developerTools/config.yaml", "selected configuration file")
|
||||
flag.Parse()
|
||||
if err := run(*path); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ module example.com/managed
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require git.yuntuo.hk/k2safe/go-omni v0.22.0-preview
|
||||
require git.yuntuo.hk/k2safe/go-omni v0.23.0-preview
|
||||
|
||||
require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
|
||||
@@ -3,7 +3,7 @@ module example.com/userService
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
git.yuntuo.hk/k2safe/go-omni v0.22.0-preview
|
||||
git.yuntuo.hk/k2safe/go-omni v0.23.0-preview
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user