Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/.idea/
/go.work
/go.work.sum
/go.sum
/close/test/clsoe.exe
/net/http/session/carrierDb/test/test.exe
/.idea/
/go.work
/go.work.sum
/go.sum
/close/test/clsoe.exe
/net/http/session/carrierDb/test/test.exe
/.claude/
/runtime/
46 changes: 46 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
你是一个中文AI助手。你的思考和表达都以中文进行——所有解释、推理、建议、提问、总结都必须使用中文。
技术内容处理规则:
- 代码、命令、变量名、函数名、API参数、路径、公式、专有名词 → 保留英文原文
- 注释 → 可以使用中文
- 代码展示 → 前有中文用途说明,后有中文逻辑解读
请确保回答清晰、自然,技术术语首次出现时可用中文括号注明英文原词,例如:应用程序编程接口(API)。

## Go 编译环境配置

当执行任何 Go 编译相关命令(`go build`、`go test`、`go mod download`、`go install` 等)时,**必须**先设置以下环境变量,确保所有写入操作都在项目 `runtime/.go/` 目录下集中管理:

### Bash / Zsh

```bash
mkdir -p runtime/.go/{cache,tmp,mod} && \
GOCACHE=$(pwd)/runtime/.go/cache \
GOTMPDIR=$(pwd)/runtime/.go/tmp \
GOMODCACHE=$(pwd)/runtime/.go/mod \
go test -v -cover ./...
```

### PowerShell

```powershell
mkdir -p runtime\.go\cache, runtime\.go\tmp, runtime\.go\mod -Force; `
$env:GOCACHE = "$(Get-Location)\runtime\.go\cache"; `
$env:GOTMPDIR = "$(Get-Location)\runtime\.go\tmp"; `
$env:GOMODCACHE = "$(Get-Location)\runtime\.go\mod"; `
$env:GOTOOLCHAIN = "local"; `
go build -tags "timetzdata dev" -o ./runtime/build/vis-radar-dev.exe ./cmd/vis-radar.go
```

> **必须包含 `$env:GOTOOLCHAIN = "local"`**:项目 go.mod 要求的 Go 版本若高于本机安装版本,
> Go 会默认自动下载对应工具链,其校验缓存写入用户目录(`C:\Users\<user>\go\pkg\sumdb`),
> 在沙箱/受限环境下会以 Access is denied 失败。固定 `local` 后改用本机工具链,
> 版本差异报错时再显式升级本机 Go,而不是让编译过程悄悄联网下载。

### Cmd

```cmd
mkdir runtime\.go\cache runtime\.go\tmp runtime\.go\mod 2>nul && set GOCACHE=%cd%\runtime\.go\cache && set GOTMPDIR=%cd%\runtime\.go\tmp && set GOMODCACHE=%cd%\runtime\.go\mod && set GOTOOLCHAIN=local && go test -v ./...
```

### 注意事项
- 清理缓存:`rm -rf runtime/.go`(Bash)或 `Remove-Item -Recurse -Force runtime\.go`(PowerShell)
- `GOTOOLCHAIN=local` 三种 shell 均需设置;换新机器或升级 Go 后首次编译如报“go.mod requires go >= x.x”,请升级本机 Go 而非移除此变量
36 changes: 32 additions & 4 deletions dataType/bool.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package dataType

import (
"bytes"
"database/sql/driver"
"encoding/json"
"strconv"

"gorm.io/gorm"
"gorm.io/gorm/schema"
Expand Down Expand Up @@ -105,11 +107,37 @@ func (b Bool) MarshalJSON() ([]byte, error) {
}

func (b *Bool) UnmarshalJSON(data []byte) error {
var val bool
if err := json.Unmarshal(data, &val); err != nil {
return err
ds := bytes.TrimSpace(data)
if len(ds) == 0 || bytes.Equal(ds, []byte("null")) {
b.bool = false
return nil
}
switch ds[0] {
case '"': // JSON 字符串
s, err := strconv.Unquote(string(ds))
if err != nil {
return err
}
ok, err := tools.Any2bool(s)
if err != nil {
return err
}
b.bool = ok
case 't': // true
b.bool = true
case 'f': // false
b.bool = false
default: // JSON 数字
f, err := strconv.ParseFloat(string(ds), 64)
if err != nil {
return err
}
ok, err := tools.Any2bool(f)
if err != nil {
return err
}
b.bool = ok
}
b.bool = val
return nil
}

Expand Down
93 changes: 93 additions & 0 deletions dataType/bool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package dataType

import (
"encoding/json"
"testing"
)

func TestBool_UnmarshalJSON_VariousTypes(t *testing.T) {
cases := []struct {
name string
in string
want bool
}{
{"true", `true`, true},
{"false", `false`, false},
{"digit one", `1`, true},
{"digit zero", `0`, false},
{"digit non-zero", `2`, true},
{"string true", `"true"`, true},
{"string false", `"false"`, false},
{"string one", `"1"`, true},
{"string zero", `"0"`, false},
{"string empty", `""`, false},
{"null", `null`, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var b Bool
if err := json.Unmarshal([]byte(c.in), &b); err != nil {
t.Fatalf("json.Unmarshal(%q) error: %v", c.in, err)
}
if b.Bool() != c.want {
t.Fatalf("Bool() = %v, want %v (input %q)", b.Bool(), c.want, c.in)
}
})
}
}

func TestBool_UnmarshalJSON_DefaultField(t *testing.T) {
// 嵌入到结构体,验证常规字段解码路径
type holder struct {
Flag Bool `json:"flag"`
}
var h holder
raw := `{"flag":"1"}`
if err := json.Unmarshal([]byte(raw), &h); err != nil {
t.Fatalf("json.Unmarshal struct error: %v", err)
}
if !h.Flag.Bool() {
t.Fatalf("Flag should be true, got %v", h.Flag.Bool())
}
}

func TestBool_MarshalJSON_RoundTrip(t *testing.T) {
for _, want := range []bool{true, false} {
b := NewBool(want)
raw, err := json.Marshal(b)
if err != nil {
t.Fatalf("Marshal error: %v", err)
}
var back Bool
if err := json.Unmarshal(raw, &back); err != nil {
t.Fatalf("Unmarshal(%s) error: %v", raw, err)
}
if back.Bool() != want {
t.Fatalf("round trip: got %v, want %v (raw %s)", back.Bool(), want, raw)
}
}
}

func TestBool_UnmarshalJSON_Invalid(t *testing.T) {
cases := []string{
`[]`, // 数组
`{}`, // 对象
`"yes"`, // ParseBool 无法识别
`"abc"`, // ParseBool 无法识别
`[`, // 非法 JSON
`"unterminated`, // 非法 JSON
}
for _, in := range cases {
var b Bool
if err := json.Unmarshal([]byte(in), &b); err == nil {
t.Errorf("json.Unmarshal(%q) expected error, got nil", in)
}
}
}

func TestBool_UnmarshalJSON_Empty(t *testing.T) {
var b Bool
if err := json.Unmarshal(nil, &b); err == nil && b.Bool() {
t.Fatalf("empty input should give false without error, got true")
}
}
105 changes: 102 additions & 3 deletions safe/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,31 @@ import (
"sync/atomic"
)

// Resource 基于 atomic.Pointer 的原子、无锁(lock-free)资源。
//
// # 线程安全说明
//
// 只要通过本类型的方法(Read/Write/Get/Set/Reset/Update/Swap 等)访问资源,
// 操作就是原子且线程安全的,且完全无锁(多个 goroutine 读可完美并行)。
//
// 需要区分两种"不安全":
// - 如果调用方拿到 Read() 返回的 *T 指针后,在方法外部并发修改指针所指向的内容,
// 这一段改写不是原子的,会引发数据竞争。此时局部指针只应被单 goroutine 独占使用。
// - "读出来 → 函数外比较/修改 → 再写回"这类 read-modify-write 拆分到多个方法调用,
// 不是原子的,多 goroutine 并发会丢更新。需要原子 RMW 时请用 Update。
//
// 因此,纯读推荐 Get(值拷贝)或 Read(指针,需自行保证不使用),写路径推荐 Update。
//
// 注意:本类型是通用原子资源,不做编译期数值约束,也未内置数值累加方法。
// 若需要原子的数值累加(计数器等),请在业务端用标准库 sync/atomic 的整型专用
// 方法(如 atomic.Int64.Add),或用本类型的 Update 实现 read-modify-write。
type Resource[T any] struct {
_ noCopy
resource atomic.Pointer[T]
}

// NewResource 创建一个原子操作的资源
// 注意这个资源不是线程安全的
// 虽然原子操作不会出现数据竞争,但是数据读取出来后的比较不是线程安全的。可能真实值已经被其他线程修改了
// NewResource 创建一个原子操作的资源。
// resource 允许传入 nil,此时内部会存储 T 的零值。
func NewResource[T any](resource *T) *Resource[T] {
s := &Resource[T]{}
if resource == nil {
Expand All @@ -23,10 +40,92 @@ func NewResource[T any](resource *T) *Resource[T] {
return s
}

// NewResourceOf 按值创建一个原子操作的资源。
// 与 NewResource 的区别在于直接传入值,风格同 NewResourceMutex/NewResourceRWMutex。
func NewResourceOf[T any](resource T) *Resource[T] {
return NewResource(&resource)
}

// Read 返回当前资源的指针。
// 返回值是原始指针,多 goroutine 并发读取指针本身是安全的;
// 但在外部对该指针指向的内容做并发修改并不安全,请谨慎使用或改用 Get。
func (s *Resource[T]) Read() *T {
return s.resource.Load()
}

// Ptr 是 Read 的别名,返回当前资源的原始指针。
func (s *Resource[T]) Ptr() *T {
return s.Read()
}

// Get 返回当前资源的拷贝值。
// 比 Read 更安全:即使内部为空指针,也返回 T 的零值而不会 panic。
func (s *Resource[T]) Get() T {
p := s.resource.Load()
if p == nil {
var zero T
return zero
}
return *p
}

// Write 原子地写入新资源(指针)。
// 允许写入 nil,此时后续 Get 会返回 T 的零值。
func (s *Resource[T]) Write(newResource *T) {
s.resource.Store(newResource)
}

// Set 原子地按值写入新资源。
// 等价于调用 Write(&v),但调用方无需手动取地址。
func (s *Resource[T]) Set(v T) {
s.Write(&v)
}

// Reset 原子地将资源重置为 T 的零值。
func (s *Resource[T]) Reset() {
var zero T
s.Set(zero)
}

// Update 原子地执行修改(read-modify-write)。
// 通过对当前指针快照调用 fn 得到新值,再用 CompareAndSwap 写回;
// 若期间被其他 goroutine 修改则重试,直至成功。
//
// 注意:每次重试都会重新读取最新值并重新调用 fn,因此 fn 应尽量是
// 纯函数(只依赖入参、不修改外部状态),否则可能导致 CAS 反复失败甚至死循环。
// 适用于低竞争热路径,不承诺 wait-free。
func (s *Resource[T]) Update(fn func(t T) T) {
for {
old := s.Read()
var oldVal T
if old != nil {
oldVal = *old
}
newVal := fn(oldVal)
if s.resource.CompareAndSwap(old, &newVal) {
return
}
}
}

// Swap 原子地写入新指针并返回旧指针。
func (s *Resource[T]) Swap(newValue *T) *T {
return s.resource.Swap(newValue)
}

// SwapVal 原子地按值写入并返回旧值。
// 若旧指针为空,返回 T 的零值。
func (s *Resource[T]) SwapVal(v T) T {
old := s.resource.Swap(&v)
if old == nil {
var zero T
return zero
}
return *old
}

// CompareAndSwap 原子地比较并交换指针。
// 仅当当前指针与 old 相等时,才替换为 new 并返回 true。
func (s *Resource[T]) CompareAndSwap(old, new *T) bool {
return s.resource.CompareAndSwap(old, new)
}
Loading
Loading