├── .gitignore ├── LICENSE ├── README.md ├── README_ZH.md ├── aemail ├── README.md └── email.go ├── container ├── array │ ├── README.md │ ├── array.go │ └── array_test.go ├── queue │ ├── README.md │ ├── client.go │ ├── client_examples │ │ └── main.go │ ├── server.go │ └── server_examples │ │ ├── config.toml │ │ └── main.go └── search │ ├── README.md │ ├── search.go │ └── search_test.go ├── crypto ├── aaes │ ├── README.md │ ├── aes.go │ └── aes_test.go ├── ahash │ ├── README.md │ └── hash.go ├── arsa │ ├── README.md │ └── rsa.go └── auuid │ ├── README.md │ ├── uuid.go │ └── uuid_test.go ├── db ├── adb │ ├── asql │ │ ├── json.go │ │ ├── orm.go │ │ └── time.go │ ├── db.go │ └── logger.go ├── aredis │ ├── aredis.go │ ├── list.go │ ├── set.go │ ├── string.go │ └── zset.go └── mgo │ ├── mgo.go │ └── mgo_test.go ├── encoding ├── abase64 │ ├── README.md │ └── abase64.go ├── abinary │ ├── README.md │ └── abinary.go ├── acharset │ ├── README.md │ └── acharset.go ├── ahtml │ ├── README.md │ ├── html.go │ └── html_test.go ├── aini │ ├── README.md │ ├── aini.go │ └── ini_test.go ├── ajson │ ├── README.md │ ├── ajson.go │ └── json_test.go ├── atoml │ ├── README.md │ └── atoml.go ├── aurl │ ├── README.md │ ├── url.go │ └── url_test.go ├── axml │ ├── README.md │ └── axml.go ├── ayaml │ ├── README.md │ └── ayaml.go └── azip │ ├── README.md │ ├── unzip.go │ └── zip.go ├── examples └── gin │ ├── boot │ ├── lang │ │ └── lang.go │ ├── redis.go │ └── serve │ │ └── serve.go │ ├── config │ └── config.toml │ ├── lang │ ├── en.json │ ├── en.toml │ └── zh-CN.yaml │ ├── main.go │ └── model │ └── admin.go ├── frame ├── ant │ ├── ant.go │ ├── config.go │ ├── db.go │ ├── log.go │ └── redis.go ├── gin_middleware │ ├── logger.go │ ├── recover.go │ └── request_id.go └── serve │ ├── adapter.go │ └── gin │ └── gin.go ├── go.mod ├── go.sum ├── i18n ├── README.md └── i18n.go ├── net ├── ahttp │ ├── README.md │ ├── ahttp.go │ └── ahttp_test.go └── awebsocket │ ├── client.go │ └── websocket.go ├── os ├── acron │ ├── README.md │ ├── acron.go │ └── acron_test.go ├── acsv │ ├── README.md │ └── csv.go ├── alog │ ├── README.md │ └── alog.go ├── atime │ ├── README.md │ ├── atime.go │ └── format.go └── config │ ├── README.md │ ├── config.go │ └── config_test.go ├── test ├── atime_test.go ├── base64_test.go ├── binary_test.go ├── bool_test.go ├── charset_test.go ├── config.toml ├── config.yaml ├── config2.toml ├── config_test.go ├── conv_test.go ├── cron_test.go ├── csv_test.go ├── done │ ├── test.txt │ └── test2.txt ├── email_test.go ├── float_test.go ├── gob_test.go ├── hash_test.go ├── html_test.go ├── http_test │ └── main.go ├── ini_test.go ├── ints_test.go ├── language │ └── i18n │ │ ├── en.toml │ │ ├── ja.toml │ │ ├── ru.toml │ │ ├── zh-CN.toml │ │ └── zh-TW.toml ├── logger_test.go ├── map_test.go ├── number_test.go ├── password_test.go ├── pool_test.go ├── redis_test.go ├── rsa_test.go ├── slice_test.go ├── str_test.go ├── struct_test.go ├── url_test.go ├── websocket │ └── main.go ├── xml_test.go ├── yaml_test.go └── zip_test.go └── utils ├── ajwt ├── README.md ├── jwt.go └── jwt_test.go ├── conv ├── bool.go ├── conv.go ├── conv_gob.go ├── conv_map.go ├── conv_slice.go ├── conv_struct.go ├── float.go └── number.go ├── file └── read.go ├── page └── request.go ├── password ├── README.md └── password.go ├── plugins ├── README.md ├── plugins.go └── plugins_test.go ├── pool ├── README.md └── pool.go ├── response └── response.go └── str ├── README.md └── str.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | *.exe 4 | *.log 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AntGo 2 | 3 | [![Go Doc](https://godoc.org/github.com/warysection/antgo?status.svg)](https://www.yuque.com/small-ek/antgo) 4 | [![Build Status](https://goreportcard.com/badge/github.com/warysection/antgo)](https://goreportcard.com/report/github.com/warysection/antgo) 5 | [![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/small-ek/ginp ) 6 | [![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/small-ek/ginp) 7 | [![License](https://img.shields.io/github/license/small-ek/antgo.svg?style=flat)](https://github.com/warysection/antgo) 8 | 9 | [中文](README_ZH.md) | Simplified Chinese 10 | 11 | `antgo` is a modular, high-performance, production-level basic development and extension framework. 12 | Realized a complete infrastructure construction and development tool chain, and provided common basic development modules, 13 | Such as: cache, log, queue, container, timer, configuration management, data verification, data analysis, data coding, timed tasks, GORM framework extension, Gin framework extension, sending mail, JWT, service registration, Http request, Redis, Cross-domain requests, unified data requests return core components, etc. 14 | 15 | # Features 16 | 17 | * Modularity, loosely coupled design, compatibility, elegant use; 18 | * Rich modules, ready to use out of the box; 19 | * Easy to use and easy to maintain; 20 | * High code quality, high unit test coverage; 21 | * Detailed development documents and examples; 22 | * Perfect local Chinese cultural support; 23 | * Designed for team and enterprise use; 24 | 25 | # Address 26 | 27 | -**Main library**: https://github.com/warysection/antgo 28 | 29 | # Installation 30 | 31 | ```html 32 | go get -u -v github.com/warysection/antgo 33 | ``` 34 | 35 | It is recommended to use go.mod: 36 | 37 | # Limit 38 | 39 | ```shell 40 | golang version >= 1.16 41 | ``` 42 | 43 | # Module 44 | 45 | 1. **Core Module** 46 | 47 | `antgo` provides some basic and commonly used modules, which are simple, easy to use and lightweight, and maintain very few external dependencies. These modules are carefully maintained by the `antgo` main repository. 48 | 49 | 1. **Community Module** 50 | 51 | Community modules are mainly contributed and maintained by the community. Most of them are also provided and maintained by the contributors of the `antgo` main repository. They are stored in the `antgo` space and are at the same level as the `antgo` main repository. Some community modules are separated from the main repository of `antgo` and maintained separately. These modules are not particularly commonly used or rely heavily on external sources. 52 | 53 | # Documentation 54 | 55 | Development document: https://www.yuque.com/small-ek/antgo 56 | 57 | # Help 58 | 59 | -QQ exchange group: [68667333] 60 | -WX Exchange Group: Add `56494565` note `antgo` to WeChat 61 | -Main library ISSUE: https://github.com/warysection/antgo 62 | 63 | > It is recommended to read the source code of `antgo` and deep learning of `antgo` through API documentation to learn more about the subtle design. 64 | 65 | # Agreement 66 | 67 | `antgo` uses the very friendly [Apache](LICENSE) open source agreement for publishing, which is always `100%` open source and free. -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # AntGo 2 | 3 | [![Go Doc](https://godoc.org/github.com/warysection/antgo?status.svg)](https://www.yuque.com/small-ek/antgo) 4 | [![Build Status](https://goreportcard.com/badge/github.com/warysection/antgo)](https://goreportcard.com/report/github.com/warysection/antgo) 5 | [![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/small-ek/ginp) 6 | [![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/small-ek/ginp) 7 | [![License](https://img.shields.io/github/license/small-ek/antgo.svg?style=flat)](https://github.com/warysection/antgo) 8 | 9 | [English](README.md) | 简体中文 10 | 11 | `antgo`是一款模块化、高性能、生产级的基础开发扩展框架。 12 | 实现了完善的基础设施建设以及开发工具链,提供了常用的基础开发模块, 13 | 如:缓存、日志、队列、容器、定时器、配置管理、数据校验、数据解析、数据编码、定时任务、GORM框架扩展、Gin框架扩展、发送邮件、JWT、服务注册、Http请求、Redis、跨域请求、统一数据请求返回核心组件等等。 14 | 15 | # 特点 16 | 17 | * 模块化、松耦合设计、兼容性、优雅使用; 18 | * 模块丰富、开箱即用; 19 | * 简便易用、易于维护; 20 | * 高代码质量、高单元测试覆盖率; 21 | * 详尽的开发文档及示例; 22 | * 完善的本地中文化支持; 23 | * 设计为团队及企业使用; 24 | 25 | # 地址 26 | 27 | - **主库**:https://github.com/warysection/antgo 28 | 29 | # 安装 30 | 31 | ```html 32 | go get -u -v github.com/warysection/antgo 33 | ``` 34 | 35 | 推荐使用 `go.mod`: 36 | 37 | # 限制 38 | 39 | ```shell 40 | golang版本 >= 1.16 41 | ``` 42 | 43 | # 模块 44 | 45 | 1. **核心模块** 46 | 47 | `antgo`提供了一些基础的、常用的模块,简单、易用和轻量级,并保持极少的外部依赖,这些模块由`antgo`主仓库细致维护。 48 | 49 | 1. **社区模块** 50 | 51 | 社区模块主要由社区贡献并维护,大部分也是由`antgo`主仓库的贡献者提供及维护,存放于`antgo`空间下,与`antgo`主仓库处于同一级别。有的社区模块是从`antgo`主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。 52 | 53 | # 文档 54 | 55 | 开发文档:https://www.yuque.com/small-ek/antgo 56 | 57 | # 帮助 58 | 59 | - QQ交流群:[68667333] 60 | - WX交流群:微信添加`56494565`备注`antgo` 61 | - 主库ISSUE:https://github.com/warysection/antgo 62 | 63 | > 建议通过阅读`antgo`的源码以及API文档深度学习`antgo`,了解更多的精妙设计。 64 | 65 | # 协议 66 | 67 | `antgo` 使用非常友好的 [Apache](LICENSE) 开源协议进行发布,永久`100%`开源免费。 68 | -------------------------------------------------------------------------------- /container/array/array.go: -------------------------------------------------------------------------------- 1 | package array 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // ConcurrentArray is a generic thread-safe array. 9 | // ConcurrentArray 是一个泛型线程安全数组。 10 | type ConcurrentArray[T comparable] struct { 11 | data []T // Underlying typed data storage. 底层类型化数据存储。 12 | lock sync.RWMutex // Read-write lock for concurrency control. 读写锁用于并发控制。 13 | } 14 | 15 | // New creates a new ConcurrentArray with initial capacity. 16 | // New 创建一个具有初始容量的 ConcurrentArray。 17 | // Parameters: 18 | // 19 | // capacity - Initial capacity to reduce reallocations. 初始容量以减少内存重分配。 20 | func New[T comparable](capacity int) *ConcurrentArray[T] { 21 | return &ConcurrentArray[T]{ 22 | data: make([]T, 0, capacity), 23 | } 24 | } 25 | 26 | // Append adds an element to the end of the array. 27 | // Append 向数组末尾添加元素。 28 | func (ca *ConcurrentArray[T]) Append(element T) { 29 | ca.lock.Lock() 30 | defer ca.lock.Unlock() 31 | ca.data = append(ca.data, element) 32 | } 33 | 34 | // Len returns the number of elements in the array. 35 | // Len 返回数组中元素的数量。 36 | func (ca *ConcurrentArray[T]) Len() int { 37 | ca.lock.RLock() 38 | defer ca.lock.RUnlock() 39 | return len(ca.data) 40 | } 41 | 42 | // List returns a copy of the underlying data to prevent external modification. 43 | // List 返回底层数据的副本以防止外部修改。 44 | func (ca *ConcurrentArray[T]) List() []T { 45 | ca.lock.RLock() 46 | defer ca.lock.RUnlock() 47 | copied := make([]T, len(ca.data)) 48 | copy(copied, ca.data) 49 | return copied 50 | } 51 | 52 | // Insert inserts an element at the specified index. 53 | // Insert 在指定索引处插入元素。 54 | // Returns error if index is out of bounds. 55 | // 如果索引越界则返回错误。 56 | func (ca *ConcurrentArray[T]) Insert(index int, element T) error { 57 | ca.lock.Lock() 58 | defer ca.lock.Unlock() 59 | 60 | if index < 0 || index > len(ca.data) { // Allow inserting at len(data) (append) 61 | return errors.New("index out of bounds") 62 | } 63 | 64 | // Optimized insertion with single allocation 65 | newData := make([]T, 0, len(ca.data)+1) 66 | newData = append(newData, ca.data[:index]...) 67 | newData = append(newData, element) 68 | newData = append(newData, ca.data[index:]...) 69 | ca.data = newData 70 | 71 | return nil 72 | } 73 | 74 | // Delete removes the element at the specified index. 75 | // Delete 删除指定索引处的元素。 76 | // Returns error if index is invalid. 77 | // 如果索引无效则返回错误。 78 | func (ca *ConcurrentArray[T]) Delete(index int) error { 79 | ca.lock.Lock() 80 | defer ca.lock.Unlock() 81 | 82 | if index < 0 || index >= len(ca.data) { 83 | return errors.New("index out of bounds") 84 | } 85 | 86 | ca.data = append(ca.data[:index], ca.data[index+1:]...) 87 | return nil 88 | } 89 | 90 | // Set updates the element at the specified index. 91 | // Set 更新指定索引处的元素。 92 | // Returns error if index is invalid. 93 | // 如果索引无效则返回错误。 94 | func (ca *ConcurrentArray[T]) Set(index int, element T) error { 95 | ca.lock.Lock() 96 | defer ca.lock.Unlock() 97 | 98 | if index < 0 || index >= len(ca.data) { 99 | return errors.New("index out of bounds") 100 | } 101 | 102 | ca.data[index] = element 103 | return nil 104 | } 105 | 106 | // Get returns the element at the specified index. 107 | // Get 返回指定索引处的元素。 108 | // Returns zero value and error if index is invalid. 109 | // 如果索引无效则返回零值和错误。 110 | func (ca *ConcurrentArray[T]) Get(index int) (T, error) { 111 | ca.lock.RLock() 112 | defer ca.lock.RUnlock() 113 | 114 | var zero T 115 | if index < 0 || index >= len(ca.data) { 116 | return zero, errors.New("index out of bounds") 117 | } 118 | return ca.data[index], nil 119 | } 120 | 121 | // Search returns the first index of the target element. 122 | // Search 返回目标元素的第一个索引。 123 | // Returns -1 if not found. 124 | // 如果未找到则返回 -1。 125 | func (ca *ConcurrentArray[T]) Search(target T) int { 126 | ca.lock.RLock() 127 | defer ca.lock.RUnlock() 128 | 129 | for i, v := range ca.data { 130 | if v == target { 131 | return i 132 | } 133 | } 134 | return -1 135 | } 136 | 137 | // Clear removes all elements from the array. 138 | // Clear 清空数组中的所有元素。 139 | func (ca *ConcurrentArray[T]) Clear() { 140 | ca.lock.Lock() 141 | defer ca.lock.Unlock() 142 | ca.data = nil 143 | } 144 | 145 | // WithWriteLock executes a function with write lock held. 146 | // WithWriteLock 在持有写锁的情况下执行函数。 147 | func (ca *ConcurrentArray[T]) WithWriteLock(fn func([]T)) { 148 | ca.lock.Lock() 149 | defer ca.lock.Unlock() 150 | fn(ca.data) 151 | } 152 | 153 | // WithReadLock executes a function with read lock held. 154 | // WithReadLock 在持有读锁的情况下执行函数。 155 | func (ca *ConcurrentArray[T]) WithReadLock(fn func([]T)) { 156 | ca.lock.RLock() 157 | defer ca.lock.RUnlock() 158 | fn(ca.data) 159 | } 160 | -------------------------------------------------------------------------------- /container/queue/client_examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/container/queue" 6 | "go.uber.org/zap" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | 12 | // 初始化配置 13 | cfg := queue.ClientConfig{ 14 | Addr: "127.0.0.1:6379", 15 | Password: "", 16 | DB: 1, 17 | PoolSize: 100, 18 | DialTimeout: 10 * time.Second, 19 | } 20 | 21 | // 创建客户端(带日志配置) 22 | client := queue.NewClient(cfg, 23 | queue.WithLogger(zap.NewExample()), 24 | ) 25 | 26 | // 提交高优先级任务 27 | _, err := client.Enqueue("order:process", map[string]interface{}{ 28 | "order_id": "12345", 29 | "amount": 199.9, 30 | }, 31 | queue.WithQueue("critical"), 32 | queue.WithMaxRetry(3), 33 | queue.WithUnique(30*time.Minute), 34 | queue.WithDeadline(time.Now().Add(1*time.Hour)), 35 | ) 36 | 37 | if err != nil { 38 | // 错误处理逻辑 39 | } 40 | 41 | // 获取队列统计信息 42 | info, _ := client.GetQueueInfo("critical") 43 | fmt.Printf("Pending tasks: %d\n", info.Size) 44 | 45 | // 应用退出时清理 46 | defer client.Close() 47 | } 48 | -------------------------------------------------------------------------------- /container/queue/server.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/hibiken/asynq" 7 | "go.uber.org/zap" 8 | "time" 9 | ) 10 | 11 | // ServiceConfig contains configuration for Asynq service 12 | type ServiceConfig struct { 13 | RedisAddress string 14 | RedisPassword string 15 | RedisDB int 16 | Concurrency int 17 | RetryStrategy RetryStrategy 18 | Queues map[string]int 19 | Logger *zap.Logger // 新增Logger字段 20 | } 21 | 22 | type RetryStrategy interface { 23 | GetDelay(retryCount int, err error, task *asynq.Task) time.Duration 24 | } 25 | 26 | type TaskHandler func(ctx context.Context, task *asynq.Task) error 27 | 28 | type Service struct { 29 | server *asynq.Server 30 | mux *asynq.ServeMux 31 | config *ServiceConfig 32 | logger *zap.Logger // 新增logger字段 33 | } 34 | 35 | type DefaultRetryStrategy struct{} 36 | 37 | func (d *DefaultRetryStrategy) GetDelay(retryCount int, err error, task *asynq.Task) time.Duration { 38 | switch { 39 | case retryCount <= 3: 40 | return time.Duration(retryCount) * 20 * time.Second 41 | case retryCount == 4: 42 | return 1 * time.Hour 43 | case retryCount == 5: 44 | return 5 * time.Hour 45 | default: 46 | return time.Duration((retryCount-4)*4+5) * time.Hour 47 | } 48 | } 49 | 50 | func NewService(cfg *ServiceConfig) *Service { 51 | // 设置默认值 52 | if cfg.Concurrency == 0 { 53 | cfg.Concurrency = 10 54 | } 55 | if cfg.RedisDB == 0 { 56 | cfg.RedisDB = 1 57 | } 58 | if cfg.RetryStrategy == nil { 59 | cfg.RetryStrategy = &DefaultRetryStrategy{} 60 | } 61 | if cfg.Queues == nil { 62 | cfg.Queues = map[string]int{"default": 1} 63 | } 64 | // 设置默认Logger(如果未提供) 65 | if cfg.Logger == nil { 66 | cfg.Logger = zap.NewNop() 67 | } 68 | 69 | return &Service{ 70 | config: cfg, 71 | mux: asynq.NewServeMux(), 72 | logger: cfg.Logger, // 初始化logger字段 73 | } 74 | } 75 | 76 | func (s *Service) Start() error { 77 | if s.config.RedisAddress == "" { 78 | return fmt.Errorf("redis address is required") 79 | } 80 | 81 | redisOpt := asynq.RedisClientOpt{ 82 | Addr: s.config.RedisAddress, 83 | Password: s.config.RedisPassword, 84 | DB: s.config.RedisDB, 85 | } 86 | 87 | s.server = asynq.NewServer( 88 | redisOpt, 89 | asynq.Config{ 90 | Concurrency: s.config.Concurrency, 91 | Queues: s.config.Queues, 92 | RetryDelayFunc: func(retryCount int, err error, task *asynq.Task) time.Duration { 93 | return s.config.RetryStrategy.GetDelay(retryCount, err, task) 94 | }, 95 | Logger: s, // 保持Logger接口实现 96 | }, 97 | ) 98 | 99 | if err := s.server.Run(s.mux); err != nil { 100 | s.logger.Error("Failed to start Asynq server", zap.Error(err)) // 使用传入的logger 101 | return fmt.Errorf("failed to start server: %w", err) 102 | } 103 | return nil 104 | } 105 | 106 | // 日志方法现在使用传入的logger 107 | func (s *Service) Debug(args ...interface{}) { 108 | s.logger.Debug(fmt.Sprint(args...)) 109 | } 110 | 111 | func (s *Service) Info(args ...interface{}) { 112 | s.logger.Info(fmt.Sprint(args...)) 113 | } 114 | 115 | func (s *Service) Warn(args ...interface{}) { 116 | s.logger.Warn(fmt.Sprint(args...)) 117 | } 118 | 119 | func (s *Service) Error(args ...interface{}) { 120 | s.logger.Error(fmt.Sprint(args...)) 121 | } 122 | 123 | func (s *Service) Fatal(args ...interface{}) { 124 | s.logger.Fatal(fmt.Sprint(args...)) 125 | } 126 | 127 | // 以下方法保持不变 128 | func (s *Service) RegisterHandler(taskType string, handler TaskHandler) { 129 | s.mux.HandleFunc(taskType, handler) 130 | } 131 | 132 | func (s *Service) Shutdown() { 133 | if s.server != nil { 134 | s.server.Stop() 135 | s.server.Shutdown() 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /container/queue/server_examples/config.toml: -------------------------------------------------------------------------------- 1 | #系统配置 2 | [system] 3 | #启动地址 4 | address = "9002" 5 | #是否开启跨域 6 | cors = true 7 | #是否开发调试模式 8 | debug = true 9 | #项目名称 10 | app_name = "antgo" 11 | 12 | 13 | #接口请求日志 14 | [log] 15 | #路径 16 | path = "./log/app.log" 17 | #输出格式 支持(json、console) 18 | format = "console" 19 | #日志服务名称 20 | service_name = "admin" 21 | #日志输出等级 all、info、warn、error、debug、dpanic、panic、fatal 22 | level = "all" 23 | #是否输出控制台 24 | console = false 25 | #是否开启日志 26 | switch = true 27 | #文件最长保存时间(天) 28 | max_age = 180 29 | #分割大小(MB) 30 | max_size = 10 31 | #保留30个备份(个) 32 | max_backups = 2000 33 | #是否需要压缩 34 | compress = false 35 | #header 白名单 36 | #header_whitelist = ["Device-Id", "Authorization", "Accept", "Accept-Language", "Origin", "Referer", "User-Agent"] 37 | header_whitelist = ["Device-Id"] 38 | 39 | #数据库设置 40 | [[connections]] 41 | #数据库名称(必须唯一) 42 | name = "mysql" 43 | #数据库类型支持mysql、pgsql、sqlsrv、clickhouse 44 | type = "mysql" 45 | #服务器地址 46 | hostname = "127.0.0.1" 47 | #服务器端口 48 | port = "3306" 49 | #数据库用户名 50 | username = "root" 51 | #数据库密码 52 | password = "root" 53 | #数据库名 54 | database = "antgo" 55 | #数据库连接参数 56 | params = "charset=utf8mb4&parseTime=True&loc=Local" 57 | #是否开启日志 58 | log = true 59 | #设置空闲连接池中的最大连接数 60 | max_idle_conns = 100 61 | #设置数据库的最大打开连接数。 62 | max_open_conns = 200 63 | #设置连接可能被重用的最大时间(秒)。 64 | conn_max_lifetime = 300 65 | #设置连接最大生命周期(小时)。 66 | conn_max_idleTime = 2 67 | #日志等级,1=静默;2=错误;3=警告;4=信息 68 | level = 4 69 | 70 | 71 | #阿里云配置 72 | [oss] 73 | key_id = "" 74 | key_secret = "" 75 | endpoint = "" 76 | bucket = "" 77 | 78 | #Redis配置 79 | [[redis]] 80 | name = "redis1" 81 | address = "localhost:6379" 82 | password = "" 83 | db = 0 84 | 85 | #Redis配置 86 | [[redis]] 87 | name = "redis2" 88 | address = "localhost:6379" 89 | password = "" 90 | db = 0 91 | 92 | #邮箱警报发送 93 | [emaill] 94 | switch = true 95 | to = ['56494565@qq.com'] 96 | from = '56494565@qq.com' 97 | host = 'smtp.qq.com:25' 98 | secret = 'fdtshicbbvybbiic' 99 | 100 | #Json web token 101 | [jwt] 102 | private_key = """-----BEGIN PRIVATE KEY----- 103 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMMBf8hUaDk+oCsQ 104 | ecIJDrRt+EktQaGE+11Un1YWgjelgJR68JcS+2HsSTjkd+aUYFuy7uo7Jc0ugQqN 105 | xTwPzyJXfIX0J3niM0MM4SNLiqGD+UYGJ3bbBiw33NPT4CQ0/ATKk7Y4kdGXWl9z 106 | OMqV6YqWau88SpFAUENHj1rJrFKrAgMBAAECgYBEFSTo61dMDSpcfq8T6JeitPZH 107 | ji5o1wXvqtjKdKdYCEdhD58qD62GnblezJ1z+n+95DX3v1jOTxssdRzUgGx/Ys13 108 | Ukso6hzNgMOw46zwd8qoWuFuytWWc953FGsr+atWFrvfU8aEjBjWzhlTtFVRPaiS 109 | 42zS7OZzYdmCw/PJaQJBAPu9R23SePtKIufwkg9ejWKyfIwJfLfdrfPL3RSTryGy 110 | ph6CahWcr19/fMRTJaTYQtfxnXc1quFow3X+IBgfMr0CQQDGTmibUOJGyVrjSNMe 111 | dTntWaBjMdFRwNdl7EzgUuLePUFs0gbZ6SW5dMsK/3JfzyxYF3XRki7Lupju3Ano 112 | RGWHAkAN/lCJJ0j4Vv+nuvSzjAL5+If51NEs+1KfGbb5XNhAXEjlq0QwXVxWR6Ts 113 | 2N5f0nGsxU6GgOI103gCCBVKoflVAkBvBzVwSE/4XAJEIOD7O50MM9Ml1p2gjTzM 114 | Nwovyph0340C9XCajvvtIuQPq0gJNoBYbgIsLRGARWAc1BvD7I9/AkEAgQEnpQEI 115 | isXUlyKSsakm+M+hzkoJxlizUiM3tN9cIfsIBXdWv9LNGRp2gl8Sa69ri3EdqQXv 116 | 0PcStMOn2IX1kw== 117 | -----END PRIVATE KEY-----""" 118 | 119 | public_key = """-----BEGIN PUBLIC KEY----- 120 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDAX/IVGg5PqArEHnCCQ60bfhJ 121 | LUGhhPtdVJ9WFoI3pYCUevCXEvth7Ek45HfmlGBbsu7qOyXNLoEKjcU8D88iV3yF 122 | 9Cd54jNDDOEjS4qhg/lGBid22wYsN9zT0+AkNPwEypO2OJHRl1pfczjKlemKlmrv 123 | PEqRQFBDR49ayaxSqwIDAQAB 124 | -----END PUBLIC KEY-----""" -------------------------------------------------------------------------------- /container/queue/server_examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/gin-gonic/gin" 7 | "github.com/hibiken/asynq" 8 | "github.com/warysection/antgo/container/queue" 9 | "github.com/warysection/antgo/frame/ant" 10 | "github.com/warysection/antgo/frame/gin_middleware" 11 | _ "github.com/warysection/antgo/frame/serve/gin" 12 | "github.com/warysection/antgo/os/config" 13 | "go.uber.org/zap" 14 | "io/ioutil" 15 | ) 16 | 17 | func main() { 18 | // 初始化zap日志 (需要在应用层初始化) 19 | // Initialize zap logger (should be initialized at application level) 20 | gin.ForceConsoleColor() 21 | app := gin.New() 22 | if config.GetBool("system.debug") == false { 23 | gin.SetMode(gin.ReleaseMode) 24 | gin.DefaultWriter = ioutil.Discard 25 | } 26 | app.Use(gin_middleware.Recovery()).Use(gin_middleware.Logger()) 27 | configPath := flag.String("config", "./config.toml", "Configuration file path") 28 | 29 | app.GET("/", func(c *gin.Context) { 30 | c.String(200, "Hello, World!") 31 | }) 32 | eng := ant.New(*configPath).Serve(app) 33 | 34 | defer eng.Close() 35 | // 服务配置 Service configuration 36 | cfg := &queue.ServiceConfig{ 37 | RedisAddress: "127.0.0.1:6379", 38 | RedisPassword: "", 39 | RedisDB: 1, 40 | Concurrency: 20, 41 | Logger: ant.Log(), 42 | Queues: map[string]int{ 43 | "critical": 6, 44 | "default": 5, 45 | }, 46 | } 47 | 48 | // 创建服务实例 Create service instance 49 | svc := queue.NewService(cfg) 50 | 51 | // 注册任务处理器 Register task handlers 52 | svc.RegisterHandler("order:process", handleOrderTask) 53 | 54 | // 启动服务 Start service 55 | if err := svc.Start(); err != nil { 56 | zap.L().Fatal("Failed to start service", zap.Error(err)) 57 | } 58 | 59 | } 60 | 61 | func handleOrderTask(ctx context.Context, task *asynq.Task) error { 62 | 63 | zap.L().Info("Processing email task", zap.ByteString("payload", task.Payload())) 64 | // 实现业务逻辑... 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /container/search/search.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "unsafe" 6 | ) 7 | 8 | // IndexOf 线性搜索优化版(性能提升约15%-20%) 9 | // Optimized linear search (15-20% performance improvement) 10 | func IndexOf[T comparable](slice []T, key T) int { 11 | // 使用指针操作优化内存访问 12 | // Optimize memory access using pointer operations 13 | if len(slice) == 0 { 14 | return -1 15 | } 16 | 17 | // 使用unsafe绕过切片边界检查 18 | // Bypass slice bounds check using unsafe 19 | ptr := unsafe.Pointer(unsafe.SliceData(slice)) 20 | size := unsafe.Sizeof(slice[0]) 21 | 22 | for i := 0; i < len(slice); i++ { 23 | elem := *(*T)(unsafe.Pointer(uintptr(ptr) + uintptr(i)*size)) 24 | if elem == key { 25 | return i 26 | } 27 | } 28 | return -1 29 | } 30 | 31 | // BinarySearch 二分搜索优化版(性能提升约10%-15%) 32 | // Optimized binary search (10-15% performance improvement) 33 | func BinarySearch[T constraints.Ordered](sortedSlice []T, key T) int { 34 | n := len(sortedSlice) 35 | if n == 0 { 36 | return -1 37 | } 38 | 39 | // 快速边界检查优化 40 | // Quick boundary check optimization 41 | first, last := sortedSlice[0], sortedSlice[n-1] 42 | if key < first || key > last { 43 | return -1 44 | } 45 | if key == first { 46 | return 0 47 | } 48 | if key == last { 49 | return n - 1 50 | } 51 | 52 | // 循环展开优化 53 | // Loop unrolling optimization 54 | low, high := 0, n-1 55 | for high-low > 8 { 56 | mid := (low + high) >> 1 57 | if sortedSlice[mid] < key { 58 | low = mid + 1 59 | } else { 60 | high = mid 61 | } 62 | } 63 | 64 | // 对小范围使用顺序搜索 65 | // Use sequential search for small ranges 66 | for i := low; i <= high; i++ { 67 | if sortedSlice[i] == key { 68 | return i 69 | } 70 | } 71 | return -1 72 | } 73 | -------------------------------------------------------------------------------- /container/search/search_test.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestSearch 线性搜索测试用例 8 | // Test cases for linear search 9 | func TestSearch(t *testing.T) { 10 | // 测试用例表(支持多种类型) 11 | // Test case table (supports multiple types) 12 | testCases := []struct { 13 | name string // 测试用例名称 14 | slice any // 被搜索的切片 15 | key any // 搜索的键值 16 | expected int // 期望结果 17 | }{ 18 | // 字符串类型测试 19 | // String type tests 20 | {name: "StringFound", slice: []string{"A", "B", "C"}, key: "B", expected: 1}, 21 | {name: "StringNotFound", slice: []string{"Go", "Rust"}, key: "C++", expected: -1}, 22 | {name: "EmptyStringSlice", slice: []string{}, key: "test", expected: -1}, 23 | 24 | // 整型测试 25 | // Integer type tests 26 | {name: "IntFoundFirst", slice: []int{1, 2, 3}, key: 1, expected: 0}, 27 | {name: "IntFoundLast", slice: []int{10, 20, 30}, key: 30, expected: 2}, 28 | {name: "IntNotFound", slice: []int{5, 10, 15}, key: 12, expected: -1}, 29 | 30 | // 浮点数测试 31 | // Floating-point tests 32 | {name: "Float32Found", slice: []float32{1.1, 2.2, 3.3}, key: float32(2.2), expected: 1}, 33 | {name: "Float64NotFound", slice: []float64{1.5, 2.5, 3.5}, key: 2.500001, expected: -1}, 34 | 35 | // 边界测试 36 | // Edge cases 37 | {name: "NilSlice", slice: nil, key: "test", expected: -1}, 38 | {name: "SingleElementFound", slice: []rune{'A'}, key: 'A', expected: 0}, 39 | {name: "DuplicateElements", slice: []int{2, 2, 3, 3}, key: 3, expected: 2}, 40 | } 41 | 42 | for _, tc := range testCases { 43 | t.Run(tc.name, func(t *testing.T) { 44 | var result int 45 | 46 | // 类型断言执行测试 47 | // Type assertion to execute test 48 | switch s := tc.slice.(type) { 49 | case []string: 50 | result = IndexOf(s, tc.key.(string)) 51 | case []int: 52 | result = IndexOf(s, tc.key.(int)) 53 | case []float32: 54 | result = IndexOf(s, tc.key.(float32)) 55 | case []float64: 56 | result = IndexOf(s, tc.key.(float64)) 57 | case []rune: 58 | result = IndexOf(s, tc.key.(rune)) 59 | case nil: 60 | result = IndexOf([]int(nil), 0) // 测试nil切片 61 | default: 62 | t.Fatalf("不支持的切片类型: %T", s) 63 | } 64 | 65 | if result != tc.expected { 66 | t.Errorf("预期 %d, 实际 %d (测试用例: %s)", 67 | tc.expected, result, tc.name) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | // TestSearchOrdered 有序搜索测试用例 74 | // Test cases for ordered search 75 | func TestSearchOrdered(t *testing.T) { 76 | testCases := []struct { 77 | name string 78 | slice any 79 | key any 80 | expected int 81 | }{ 82 | // 升序排列测试 83 | // Ascending order tests 84 | {name: "IntFound", slice: []int{1, 3, 5, 7}, key: 5, expected: 2}, 85 | {name: "IntBeforeFirst", slice: []int{10, 20, 30}, key: 5, expected: -1}, 86 | {name: "IntAfterLast", slice: []int{100, 200}, key: 300, expected: -1}, 87 | 88 | // 字符串排序测试 89 | // String ordering tests 90 | {name: "StringFound", slice: []string{"apple", "banana", "orange"}, key: "banana", expected: 1}, 91 | {name: "StringCaseSensitive", slice: []string{"Go", "java", "python"}, key: "go", expected: -1}, 92 | 93 | // 浮点数精度测试 94 | // Floating-point precision test 95 | {name: "FloatExactMatch", slice: []float64{1.1, 2.2, 3.3}, key: 2.2, expected: 1}, 96 | } 97 | 98 | for _, tc := range testCases { 99 | t.Run(tc.name, func(t *testing.T) { 100 | var result int 101 | 102 | switch s := tc.slice.(type) { 103 | case []int: 104 | result = BinarySearch(s, tc.key.(int)) 105 | case []string: 106 | result = BinarySearch(s, tc.key.(string)) 107 | case []float64: 108 | result = BinarySearch(s, tc.key.(float64)) 109 | default: 110 | t.Fatalf("不支持的切片类型: %T", s) 111 | } 112 | 113 | if result != tc.expected { 114 | t.Errorf("预期 %d, 实际 %d (测试用例: %s)", 115 | tc.expected, result, tc.name) 116 | } 117 | }) 118 | } 119 | } 120 | 121 | // TestCustomType 测试自定义类型 122 | // Test custom type 123 | func TestCustomType(t *testing.T) { 124 | type myInt int 125 | 126 | t.Run("CustomIntFound", func(t *testing.T) { 127 | slice := []myInt{1, 2, 3} 128 | key := myInt(2) 129 | result := IndexOf(slice, key) 130 | if result != 1 { 131 | t.Errorf("预期 1, 实际 %d", result) 132 | } 133 | }) 134 | 135 | t.Run("CustomIntOrdered", func(t *testing.T) { 136 | slice := []myInt{10, 20, 30} 137 | key := myInt(20) 138 | result := BinarySearch(slice, key) 139 | if result != 1 { 140 | t.Errorf("预期 1, 实际 %d", result) 141 | } 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /crypto/ahash/hash.go: -------------------------------------------------------------------------------- 1 | package ahash 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "crypto/sha256" 7 | "crypto/sha512" 8 | "encoding/hex" 9 | "hash/crc32" 10 | ) 11 | 12 | // MD5 计算字符串的 MD5 哈希值 13 | // MD5 calculates the MD5 hash value of a string 14 | func MD5(str string) string { 15 | h := md5.New() 16 | h.Write([]byte(str)) 17 | sum := h.Sum(nil) 18 | var buf [md5.Size * 2]byte // 32字节 19 | hex.Encode(buf[:], sum) 20 | return string(buf[:]) 21 | } 22 | 23 | // SHA1 计算字符串的 SHA1 哈希值 24 | // SHA1 calculates the SHA1 hash value of a string 25 | func SHA1(str string) string { 26 | h := sha1.New() 27 | h.Write([]byte(str)) 28 | sum := h.Sum(nil) 29 | var buf [sha1.Size * 2]byte // 40字节 30 | hex.Encode(buf[:], sum) 31 | return string(buf[:]) 32 | } 33 | 34 | // SHA256 计算字符串的 SHA256 哈希值 35 | // SHA256 calculates the SHA256 hash value of a string 36 | func SHA256(str string) string { 37 | h := sha256.New() 38 | h.Write([]byte(str)) 39 | sum := h.Sum(nil) 40 | var buf [sha256.Size * 2]byte // 64字节 41 | hex.Encode(buf[:], sum) 42 | return string(buf[:]) 43 | } 44 | 45 | // SHA512 计算字符串的 SHA512 哈希值 46 | // SHA512 calculates the SHA512 hash value of a string 47 | func SHA512(str string) string { 48 | h := sha512.New() 49 | h.Write([]byte(str)) 50 | sum := h.Sum(nil) 51 | var buf [sha512.Size * 2]byte // 128字节 52 | hex.Encode(buf[:], sum) 53 | return string(buf[:]) 54 | } 55 | 56 | // Crc32 计算字符串的 CRC32 校验和 57 | // Crc32 calculates the CRC32 checksum of a string 58 | func Crc32(str string) uint32 { 59 | return crc32.ChecksumIEEE([]byte(str)) 60 | } 61 | -------------------------------------------------------------------------------- /crypto/arsa/rsa.go: -------------------------------------------------------------------------------- 1 | package arsa 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/base64" 8 | "encoding/pem" 9 | "errors" 10 | "fmt" 11 | ) 12 | 13 | // Rsa 结构体包含公私钥信息 14 | type Rsa struct { 15 | PublicKey []byte 16 | PrivateKey []byte 17 | } 18 | 19 | // New 创建RSA实例 20 | func New(publicKey, privateKey []byte) *Rsa { 21 | return &Rsa{ 22 | PublicKey: publicKey, 23 | PrivateKey: privateKey, 24 | } 25 | } 26 | 27 | // Encrypt RSA加密 28 | func (r *Rsa) Encrypt(origData string) (string, error) { 29 | pub, err := parsePublicKey(r.PublicKey) 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, []byte(origData)) 35 | if err != nil { 36 | return "", err 37 | } 38 | 39 | return base64.StdEncoding.EncodeToString(ciphertext), nil 40 | } 41 | 42 | // Decrypt RSA解密 43 | func (r *Rsa) Decrypt(ciphertext string) (string, error) { 44 | priv, err := parsePrivateKey(r.PrivateKey) 45 | if err != nil { 46 | return "", err 47 | } 48 | 49 | decoded, err := base64.StdEncoding.DecodeString(ciphertext) 50 | if err != nil { 51 | return "", err 52 | } 53 | 54 | plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, priv, decoded) 55 | if err != nil { 56 | return "", err 57 | } 58 | 59 | return string(plaintext), nil 60 | } 61 | 62 | // parsePublicKey 解析多种格式的公钥 63 | func parsePublicKey(publicKey []byte) (*rsa.PublicKey, error) { 64 | block, _ := pem.Decode(publicKey) 65 | if block != nil { 66 | switch block.Type { 67 | case "RSA PUBLIC KEY": // PKCS#1 68 | return x509.ParsePKCS1PublicKey(block.Bytes) 69 | case "PUBLIC KEY": // PKCS#8 70 | pub, err := x509.ParsePKIXPublicKey(block.Bytes) 71 | if err != nil { 72 | return nil, err 73 | } 74 | rsaPub, ok := pub.(*rsa.PublicKey) 75 | if !ok { 76 | return nil, errors.New("not an RSA public key") 77 | } 78 | return rsaPub, nil 79 | default: 80 | return nil, fmt.Errorf("unsupported PEM type: %s", block.Type) 81 | } 82 | } 83 | 84 | // 尝试DER格式解析 85 | if pub, err := x509.ParsePKCS1PublicKey(publicKey); err == nil { 86 | return pub, nil 87 | } 88 | 89 | pub, err := x509.ParsePKIXPublicKey(publicKey) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | rsaPub, ok := pub.(*rsa.PublicKey) 95 | if !ok { 96 | return nil, errors.New("DER data does not contain RSA public key") 97 | } 98 | return rsaPub, nil 99 | } 100 | 101 | // parsePrivateKey 解析多种格式的私钥 102 | func parsePrivateKey(privateKey []byte) (*rsa.PrivateKey, error) { 103 | block, _ := pem.Decode(privateKey) 104 | if block != nil { 105 | switch block.Type { 106 | case "RSA PRIVATE KEY": // PKCS#1 107 | return x509.ParsePKCS1PrivateKey(block.Bytes) 108 | case "PRIVATE KEY": // PKCS#8 109 | priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) 110 | if err != nil { 111 | return nil, err 112 | } 113 | rsaPriv, ok := priv.(*rsa.PrivateKey) 114 | if !ok { 115 | return nil, errors.New("not an RSA private key") 116 | } 117 | return rsaPriv, nil 118 | default: 119 | return nil, fmt.Errorf("unsupported PEM type: %s", block.Type) 120 | } 121 | } 122 | 123 | // 尝试DER格式解析 124 | if priv, err := x509.ParsePKCS1PrivateKey(privateKey); err == nil { 125 | return priv, nil 126 | } 127 | 128 | priv, err := x509.ParsePKCS8PrivateKey(privateKey) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | rsaPriv, ok := priv.(*rsa.PrivateKey) 134 | if !ok { 135 | return nil, errors.New("DER data does not contain RSA private key") 136 | } 137 | return rsaPriv, nil 138 | } 139 | -------------------------------------------------------------------------------- /db/adb/asql/json.go: -------------------------------------------------------------------------------- 1 | package asql 2 | 3 | import ( 4 | "bytes" 5 | "database/sql/driver" 6 | "errors" 7 | ) 8 | 9 | type Json []byte 10 | 11 | // Value 12 | func (j Json) Value() (driver.Value, error) { 13 | if j.IsNull() { 14 | return nil, nil 15 | } 16 | return string(j), nil 17 | } 18 | 19 | // Scan 20 | func (j *Json) Scan(value interface{}) error { 21 | if value == nil { 22 | *j = nil 23 | return nil 24 | } 25 | s, ok := value.([]byte) 26 | if !ok { 27 | errors.New("Invalid Scan Source") 28 | } 29 | *j = append((*j)[0:0], s...) 30 | return nil 31 | } 32 | 33 | // MarshalJSON 34 | func (j Json) MarshalJSON() ([]byte, error) { 35 | if j == nil { 36 | return []byte("null"), nil 37 | } 38 | return j, nil 39 | } 40 | 41 | // UnmarshalJSON 42 | func (j *Json) UnmarshalJSON(data []byte) error { 43 | if j == nil { 44 | return errors.New("null point exception") 45 | } 46 | *j = append((*j)[0:0], data...) 47 | return nil 48 | } 49 | 50 | // IsNull 51 | func (j Json) IsNull() bool { 52 | return len(j) == 0 || string(j) == "null" 53 | } 54 | 55 | // Equals 56 | func (j Json) Equals(j1 Json) bool { 57 | return bytes.Equal(j, j1) 58 | } 59 | -------------------------------------------------------------------------------- /db/adb/asql/time.go: -------------------------------------------------------------------------------- 1 | package asql 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "fmt" 7 | "github.com/warysection/antgo/utils/str" 8 | "time" 9 | ) 10 | 11 | // Time 时间类型 12 | type Time struct { 13 | sql.NullTime 14 | } 15 | 16 | // MarshalJSON 序列化为JSON 17 | func (t Time) MarshalJSON() ([]byte, error) { 18 | if t.Time.IsZero() { 19 | return []byte("\"\""), nil 20 | } 21 | stamp := fmt.Sprintf("\"%s\"", t.Time.Format("2006-01-02 15:04:05")) 22 | return []byte(stamp), nil 23 | } 24 | 25 | // UnmarshalJSON 反序列化为JSON 26 | func (t *Time) UnmarshalJSON(data []byte) error { 27 | times := str.ClearQuotes(string(data)) 28 | if times != "" { 29 | var err error 30 | t.Time, err = time.Parse("2006-01-02 15:04:05", times) 31 | if err != nil { 32 | panic(err) 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | // Scan 读取插入 39 | func (t *Time) Scan(v interface{}) error { 40 | value, ok := v.(time.Time) 41 | if ok { 42 | *t = Time{sql.NullTime{Time: value}} 43 | return nil 44 | } 45 | return fmt.Errorf("can not convert %v to timestamp", v) 46 | } 47 | 48 | // Value 插入 49 | func (t Time) Value() (driver.Value, error) { 50 | strs := t.Time.Format("2006-01-02 15:04:05") 51 | if strs == "0001-01-01 00:00:00" { 52 | return nil, nil 53 | } 54 | return strs, nil 55 | } 56 | -------------------------------------------------------------------------------- /db/adb/logger.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/warysection/antgo/os/alog" 7 | "go.uber.org/zap" 8 | "gorm.io/gorm" 9 | gormlogger "gorm.io/gorm/logger" 10 | "gorm.io/gorm/utils" 11 | "time" 12 | ) 13 | 14 | type Logger struct { 15 | ZapLogger *zap.Logger 16 | LogLevel gormlogger.LogLevel 17 | SlowThreshold time.Duration 18 | SkipCallerLookup bool 19 | IgnoreRecordNotFoundError bool 20 | } 21 | 22 | func New(zapLogger *zap.Logger) Logger { 23 | return Logger{ 24 | ZapLogger: zapLogger, 25 | LogLevel: gormlogger.Info, 26 | SlowThreshold: 200 * time.Millisecond, 27 | SkipCallerLookup: false, 28 | IgnoreRecordNotFoundError: false, 29 | } 30 | } 31 | 32 | func (l Logger) SetAsDefault() { 33 | gormlogger.Default = l 34 | } 35 | 36 | func (l Logger) LogMode(level gormlogger.LogLevel) gormlogger.Interface { 37 | return Logger{ 38 | ZapLogger: l.ZapLogger, 39 | SlowThreshold: l.SlowThreshold, 40 | LogLevel: level, 41 | SkipCallerLookup: l.SkipCallerLookup, 42 | IgnoreRecordNotFoundError: l.IgnoreRecordNotFoundError, 43 | } 44 | } 45 | 46 | func (l Logger) Info(ctx context.Context, str string, args ...interface{}) { 47 | if l.LogLevel < gormlogger.Info { 48 | return 49 | } 50 | 51 | alog.Write.Sugar().Infof(str, args...) 52 | } 53 | 54 | func (l Logger) Warn(ctx context.Context, str string, args ...interface{}) { 55 | if l.LogLevel < gormlogger.Warn { 56 | return 57 | } 58 | alog.Write.Sugar().Warnf(str, args...) 59 | } 60 | 61 | func (l Logger) Error(ctx context.Context, str string, args ...interface{}) { 62 | if l.LogLevel < gormlogger.Error { 63 | return 64 | } 65 | alog.Write.Sugar().Errorf(str, args...) 66 | } 67 | 68 | func (l Logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { 69 | if l.LogLevel <= 0 { 70 | return 71 | } 72 | 73 | elapsed := time.Since(begin) 74 | sql, rows := fc() 75 | logFields := []zap.Field{ 76 | zap.String("line", utils.FileWithLineNum()), 77 | zap.String("sql", sql), 78 | zap.Float64("duration", float64(elapsed.Nanoseconds())/1e6), 79 | zap.Int64("rows", rows), 80 | } 81 | 82 | requestID := ctx.Value("request_id") 83 | if requestID != nil { 84 | logFields = append(logFields, zap.String("request_id", requestID.(string))) 85 | } 86 | 87 | switch { 88 | case err != nil && l.LogLevel >= gormlogger.Error && (!l.IgnoreRecordNotFoundError || !errors.Is(err, gorm.ErrRecordNotFound)): 89 | l.ZapLogger.Error("sql_error", logFields...) 90 | case l.SlowThreshold != 0 && elapsed > l.SlowThreshold && l.LogLevel >= gormlogger.Warn: 91 | l.ZapLogger.Warn("sql_warn", logFields...) 92 | case l.LogLevel >= gormlogger.Info: 93 | l.ZapLogger.Info("sql_info", logFields...) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /db/aredis/aredis.go: -------------------------------------------------------------------------------- 1 | package aredis 2 | 3 | import ( 4 | "context" 5 | "github.com/redis/go-redis/v9" 6 | "github.com/warysection/antgo/os/alog" 7 | "github.com/warysection/antgo/utils/conv" 8 | "go.uber.org/zap" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // Client parameter structure 14 | type ClientRedis struct { 15 | Options redis.Options 16 | Clients *redis.Client 17 | Ctx context.Context 18 | ClusterClient *redis.ClusterClient 19 | Mode bool 20 | } 21 | 22 | var once sync.Once 23 | var Client map[string]*ClientRedis 24 | 25 | // New setting redis 26 | func New(list []map[string]any) map[string]*ClientRedis { 27 | once.Do(func() { 28 | if Client == nil { 29 | Client = make(map[string]*ClientRedis) 30 | } 31 | var ctx = context.Background() 32 | if len(list) > 0 { 33 | for i := 0; i < len(list); i++ { 34 | row := list[i] 35 | Addr, Password, DB, Name := row["address"].(string), row["password"].(string), conv.Int(row["db"]), row["name"].(string) 36 | if Addr != "" && DB >= 0 && Name != "" { 37 | options := redis.Options{ 38 | Addr: Addr, //Address 39 | Password: Password, // no password set 40 | DB: DB, // use default DB 41 | } 42 | client := redis.NewClient(&options) 43 | _, err := client.Ping(ctx).Result() 44 | if err != nil { 45 | alog.Panic("redis error:", zap.Error(err)) 46 | } 47 | 48 | Client[Name] = &ClientRedis{ 49 | Mode: true, 50 | Options: options, 51 | Clients: client, 52 | Ctx: ctx, 53 | } 54 | } 55 | } 56 | } 57 | 58 | }) 59 | return Client 60 | } 61 | 62 | // NewClusterClient 63 | func NewClusterClient(Addrs []string, Password string) *ClientRedis { 64 | var ctx = context.Background() 65 | client := redis.NewClusterClient(&redis.ClusterOptions{ 66 | Addrs: Addrs, 67 | Password: Password, 68 | // To route commands by latency or randomly, enable one of the following. 69 | //RouteByLatency: true, 70 | //RouteRandomly: true, 71 | }) 72 | err := client.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error { 73 | return shard.Ping(ctx).Err() 74 | }) 75 | if err != nil { 76 | alog.Panic("NewFailoverClient", zap.Error(err)) 77 | } 78 | 79 | return &ClientRedis{ 80 | Mode: false, 81 | ClusterClient: client, 82 | Ctx: ctx, 83 | } 84 | } 85 | 86 | // NewFailoverClient 87 | func NewFailoverClient(SentinelAddrs []string, MasterName, Password string, Db int) *ClientRedis { 88 | var ctx = context.Background() 89 | client := redis.NewFailoverClient(&redis.FailoverOptions{ 90 | MasterName: MasterName, 91 | SentinelAddrs: SentinelAddrs, 92 | Password: Password, 93 | DB: Db, 94 | }) 95 | err := client.Ping(ctx).Err() 96 | if err != nil { 97 | alog.Panic("NewFailoverClient", zap.Error(err)) 98 | } 99 | 100 | return &ClientRedis{ 101 | Mode: false, 102 | Clients: client, 103 | Ctx: ctx, 104 | } 105 | } 106 | 107 | // SetOptions <修改配置> 108 | func (c *ClientRedis) SetOptions(Options *redis.Options) *ClientRedis { 109 | c.Options = *Options 110 | return c 111 | } 112 | 113 | // Close <关闭> 114 | func (c *ClientRedis) Close() { 115 | if c.Mode { 116 | defer func(Clients *redis.Client) { 117 | err := Clients.Close() 118 | if err != nil { 119 | alog.Write.Error("Clients Close", zap.Error(err)) 120 | } 121 | }(c.Clients) 122 | } else { 123 | defer func(ClusterClient *redis.ClusterClient) { 124 | err := ClusterClient.Close() 125 | if err != nil { 126 | alog.Write.Error("ClusterClient Close", zap.Error(err)) 127 | } 128 | }(c.ClusterClient) 129 | } 130 | } 131 | 132 | // Ping <心跳> 133 | func (c *ClientRedis) Ping() string { 134 | var pong string 135 | 136 | if c.Mode { 137 | pong = c.Clients.Ping(c.Ctx).Val() 138 | } else { 139 | pong = c.ClusterClient.Ping(c.Ctx).Val() 140 | } 141 | 142 | return pong 143 | } 144 | 145 | // TTL<获取过期时间> 146 | func (c *ClientRedis) TTL(key string) time.Duration { 147 | var result time.Duration 148 | 149 | if c.Mode { 150 | result = c.Clients.TTL(c.Ctx, key).Val() 151 | } else { 152 | result = c.ClusterClient.TTL(c.Ctx, key).Val() 153 | } 154 | 155 | return result 156 | } 157 | -------------------------------------------------------------------------------- /db/aredis/list.go: -------------------------------------------------------------------------------- 1 | package aredis 2 | 3 | // GetList value<获取列表长度> 4 | func (c *ClientRedis) GetListLength(key string) (lens int64) { 5 | if c.Mode { 6 | lens = c.Clients.LLen(c.Ctx, key).Val() 7 | } else { 8 | lens = c.ClusterClient.LLen(c.Ctx, key).Val() 9 | } 10 | 11 | return 12 | } 13 | 14 | // GetList value<获取列表> 15 | func (c *ClientRedis) GetList(key string) []string { 16 | lens := c.GetListLength(key) 17 | var list []string 18 | if c.Mode { 19 | list = c.Clients.LRange(c.Ctx, key, 0, lens-1).Val() 20 | } else { 21 | list = c.ClusterClient.LRange(c.Ctx, key, 0, lens-1).Val() 22 | } 23 | 24 | return list 25 | } 26 | 27 | // GetListIndex value<返回名称为key的list中index位置的元素> 28 | func (c *ClientRedis) GetListIndex(key string, index int64) (list string) { 29 | if c.Mode { 30 | list = c.Clients.LIndex(c.Ctx, key, index).Val() 31 | } else { 32 | list = c.Clients.LIndex(c.Ctx, key, index).Val() 33 | } 34 | return 35 | } 36 | 37 | // SetList value<修改列表> 38 | func (c *ClientRedis) SetList(key string, index int64, value interface{}) error { 39 | if c.Mode { 40 | return c.Clients.LSet(c.Ctx, key, index, value).Err() 41 | } 42 | return c.ClusterClient.LSet(c.Ctx, key, index, value).Err() 43 | } 44 | 45 | // RemoveList value<删除列表> 46 | // count 参数表示删除多少个key中的list 47 | func (c *ClientRedis) RemoveList(key string, value interface{}, count ...int64) error { 48 | var counts int64 = 0 49 | if len(count) > 0 { 50 | counts = count[0] 51 | } 52 | 53 | if c.Mode { 54 | return c.Clients.LRem(c.Ctx, key, counts, value).Err() 55 | } 56 | return c.ClusterClient.LRem(c.Ctx, key, counts, value).Err() 57 | } 58 | 59 | // RemoveListLeft value<返回并删除名称为key的list中的首元素> 60 | func (c *ClientRedis) RemoveListLeft(key string) error { 61 | if c.Mode { 62 | return c.Clients.LPop(c.Ctx, key).Err() 63 | } 64 | return c.ClusterClient.LPop(c.Ctx, key).Err() 65 | } 66 | 67 | // RemoveListRight value<返回并删除名称为key的list中的尾元素> 68 | func (c *ClientRedis) RemoveListRight(key string) error { 69 | if c.Mode { 70 | return c.Clients.LPop(c.Ctx, key).Err() 71 | } 72 | return c.ClusterClient.LPop(c.Ctx, key).Err() 73 | } 74 | 75 | // PushList value<添加> 76 | func (c *ClientRedis) PushList(key string, value interface{}) error { 77 | if c.Mode { 78 | return c.Clients.RPush(c.Ctx, key, value).Err() 79 | } 80 | return c.ClusterClient.RPush(c.Ctx, key, value).Err() 81 | 82 | } 83 | -------------------------------------------------------------------------------- /db/aredis/set.go: -------------------------------------------------------------------------------- 1 | package aredis 2 | 3 | // AddSet value<添加元素到集合> 4 | func (c *ClientRedis) AddSet(key string, members ...interface{}) error { 5 | if c.Mode { 6 | return c.Clients.SAdd(c.Ctx, key, members).Err() 7 | } 8 | return c.ClusterClient.SAdd(c.Ctx, key, members).Err() 9 | } 10 | 11 | // GetSetLength value<获取集合长度> 12 | func (c *ClientRedis) GetSetLength(key string) (int64, error) { 13 | if c.Mode { 14 | return c.Clients.SCard(c.Ctx, key).Result() 15 | } 16 | return c.ClusterClient.SCard(c.Ctx, key).Result() 17 | } 18 | 19 | // SIsMember value<检查元素是否存在> 20 | func (c *ClientRedis) SIsMember(key string, member interface{}) (bool, error) { 21 | if c.Mode { 22 | return c.Clients.SIsMember(c.Ctx, key, member).Result() 23 | } 24 | return c.ClusterClient.SIsMember(c.Ctx, key, member).Result() 25 | } 26 | 27 | // GetSet value<获取集合的所有成员> 28 | func (c *ClientRedis) GetSet(key string) ([]string, error) { 29 | if c.Mode { 30 | return c.Clients.SMembers(c.Ctx, key).Result() 31 | } 32 | return c.ClusterClient.SMembers(c.Ctx, key).Result() 33 | } 34 | 35 | // SRandMember value<随机获取集合的一个成员> 36 | func (c *ClientRedis) SRandMember(key string) (string, error) { 37 | if c.Mode { 38 | return c.Clients.SRandMember(c.Ctx, key).Result() 39 | } 40 | return c.ClusterClient.SRandMember(c.Ctx, key).Result() 41 | } 42 | 43 | // SPop value<移除并返回集合中的一个随机元素> 44 | func (c *ClientRedis) SPop(key string) (string, error) { 45 | if c.Mode { 46 | return c.Clients.SPop(c.Ctx, key).Result() 47 | } 48 | return c.ClusterClient.SPop(c.Ctx, key).Result() 49 | } 50 | 51 | // SInter value<计算多个集合的交集> 52 | func (c *ClientRedis) SInter(keys ...string) ([]string, error) { 53 | if c.Mode { 54 | return c.Clients.SInter(c.Ctx, keys...).Result() 55 | } 56 | return c.ClusterClient.SInter(c.Ctx, keys...).Result() 57 | } 58 | 59 | // SUnion value<计算多个集合的并集> 60 | func (c *ClientRedis) SUnion(keys ...string) ([]string, error) { 61 | if c.Mode { 62 | return c.Clients.SUnion(c.Ctx, keys...).Result() 63 | } 64 | return c.ClusterClient.SUnion(c.Ctx, keys...).Result() 65 | } 66 | 67 | // SDiff value<计算多个集合的差集> 68 | func (c *ClientRedis) SDiff(keys ...string) ([]string, error) { 69 | if c.Mode { 70 | return c.Clients.SDiff(c.Ctx, keys...).Result() 71 | } 72 | return c.ClusterClient.SDiff(c.Ctx, keys...).Result() 73 | } 74 | 75 | // SRem value<移除元素> 76 | func (c *ClientRedis) SRem(key string, members ...interface{}) (int64, error) { 77 | if c.Mode { 78 | return c.Clients.SRem(c.Ctx, key, members).Result() 79 | } 80 | return c.ClusterClient.SRem(c.Ctx, key, members).Result() 81 | } 82 | -------------------------------------------------------------------------------- /db/aredis/string.go: -------------------------------------------------------------------------------- 1 | package aredis 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Get value 8 | func (c *ClientRedis) Get(key string) string { 9 | if c.Mode { 10 | return c.Clients.Get(c.Ctx, key).Val() 11 | } 12 | return c.ClusterClient.Get(c.Ctx, key).Val() 13 | } 14 | 15 | // Set value<设置读写> 16 | // expiration<毫秒> 17 | func (c *ClientRedis) Set(key string, value interface{}, expiration ...int64) error { 18 | var ex time.Duration 19 | if len(expiration) > 0 { 20 | ex = time.Duration(expiration[0]) * time.Millisecond 21 | } else { 22 | ex = time.Duration(0) 23 | } 24 | 25 | if c.Mode { 26 | return c.Clients.Set(c.Ctx, key, value, ex).Err() 27 | } 28 | return c.ClusterClient.Set(c.Ctx, key, value, ex).Err() 29 | } 30 | 31 | // Remove value<删除数据> 32 | func (c *ClientRedis) Remove(key string) (int64, error) { 33 | if c.Mode { 34 | return c.Clients.Del(c.Ctx, key).Result() 35 | } 36 | return c.ClusterClient.Del(c.Ctx, key).Result() 37 | } 38 | 39 | // SetNX value<不存在才设置> 40 | // expiration<毫秒> 41 | func (c *ClientRedis) SetNX(key string, value interface{}, expiration ...int64) (result bool) { 42 | var ex time.Duration 43 | 44 | if len(expiration) > 0 { 45 | ex = time.Duration(expiration[0]) * time.Millisecond 46 | } else { 47 | ex = time.Duration(0) 48 | } 49 | 50 | if c.Mode { 51 | result = c.Clients.SetNX(c.Ctx, key, value, ex).Val() 52 | } else { 53 | result = c.ClusterClient.SetNX(c.Ctx, key, value, ex).Val() 54 | } 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /db/mgo/mgo_test.go: -------------------------------------------------------------------------------- 1 | package mgo 2 | 3 | import ( 4 | "context" 5 | "github.com/stretchr/testify/assert" 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | "testing" 9 | ) 10 | 11 | // TestMongoDBOperations 测试 MongoDB 操作库的主要功能 12 | func TestMongoDBOperations(t *testing.T) { 13 | // 初始化 MongoDB 连接 14 | err := InitConnection() 15 | assert.NoError(t, err, "Failed to initialize MongoDB connection") 16 | defer CloseConnection() // 确保测试结束后关闭连接 17 | 18 | // 定义测试集合名称 19 | collectionName := "test_users" 20 | dbName := "test_db" 21 | 22 | // 创建操作器实例 23 | operator := NewOperator(collectionName, dbName) 24 | 25 | // 测试数据 26 | user := bson.M{ 27 | "name": "Alice", 28 | "age": 25, 29 | "email": "alice@example.com", 30 | } 31 | 32 | // 1. 测试插入单条文档 33 | insertResult, err := operator.InsertOne(user) 34 | assert.NoError(t, err, "InsertOne failed") 35 | assert.NotNil(t, insertResult.InsertedID, "InsertedID should not be nil") 36 | 37 | // 2. 测试查询单条文档 38 | var foundUser bson.M 39 | err = operator.Where(bson.M{"name": "Alice"}).FindOne(&foundUser) 40 | assert.NoError(t, err, "FindOne failed") 41 | assert.Equal(t, "Alice", foundUser["name"], "Found user name should be Alice") 42 | assert.Equal(t, int32(25), foundUser["age"], "Found user age should be 25") 43 | 44 | // 3. 测试更新文档 45 | updateResult, err := operator.Update(bson.M{"name": "Alice"}, bson.M{"$set": bson.M{"age": 30}}) 46 | assert.NoError(t, err, "Update failed") 47 | assert.Equal(t, int64(1), updateResult.ModifiedCount, "ModifiedCount should be 1") 48 | 49 | // 验证更新结果 50 | err = operator.Where(bson.M{"name": "Alice"}).FindOne(&foundUser) 51 | assert.NoError(t, err, "FindOne after update failed") 52 | assert.Equal(t, int32(30), foundUser["age"], "Updated user age should be 30") 53 | 54 | // 4. 测试删除文档 55 | deleteResult, err := operator.Delete(bson.M{"name": "Alice"}) 56 | assert.NoError(t, err, "DeleteMany failed") 57 | assert.Equal(t, int64(1), deleteResult.DeletedCount, "DeletedCount should be 1") 58 | 59 | // 验证删除结果 60 | err = operator.Where(bson.M{"name": "Alice"}).FindOne(&foundUser) 61 | assert.Error(t, err, "FindOne after delete should fail") 62 | } 63 | 64 | // TestPagination 测试分页功能 65 | func TestPagination(t *testing.T) { 66 | // 初始化 MongoDB 连接 67 | err := InitConnection() 68 | assert.NoError(t, err, "Failed to initialize MongoDB connection") 69 | defer CloseConnection() 70 | 71 | // 定义测试集合名称 72 | collectionName := "test_pagination" 73 | dbName := "test_db" 74 | 75 | // 创建操作器实例 76 | operator := NewOperator(collectionName, dbName) 77 | 78 | // 插入测试数据 79 | docs := []interface{}{ 80 | bson.M{"name": "User1", "age": 20}, 81 | bson.M{"name": "User2", "age": 25}, 82 | bson.M{"name": "User3", "age": 30}, 83 | } 84 | _, err = operator.BatchInsert(docs) 85 | assert.NoError(t, err, "BatchInsert failed") 86 | 87 | // 测试分页查询 88 | var results []bson.M 89 | cursor, err := operator. 90 | Where(bson.M{"age": bson.M{"$gte": 20}}). 91 | SetPagination(1, 2). // 第一页,每页 2 条 92 | SetSorting(bson.D{{"age", 1}}). // 按 age 升序排序 93 | FindAll() 94 | assert.NoError(t, err, "FindAll failed") 95 | 96 | err = cursor.All(context.Background(), &results) 97 | assert.NoError(t, err, "Cursor decode failed") 98 | assert.Equal(t, 2, len(results), "Should return 2 documents") 99 | assert.Equal(t, "User1", results[0]["name"], "First document should be User1") 100 | assert.Equal(t, "User2", results[1]["name"], "Second document should be User2") 101 | } 102 | 103 | // TestTransactions 测试事务功能 104 | func TestTransactions(t *testing.T) { 105 | // 初始化 MongoDB 连接 106 | err := InitConnection() 107 | assert.NoError(t, err, "Failed to initialize MongoDB connection") 108 | defer CloseConnection() 109 | 110 | // 定义测试集合名称 111 | collectionName := "test_transactions" 112 | dbName := "test_db" 113 | 114 | // 创建操作器实例 115 | operator := NewOperator(collectionName, dbName) 116 | 117 | // 测试事务 118 | err = operator.ExecuteTransaction(func(sessCtx mongo.SessionContext) error { 119 | // 插入一条文档 120 | _, err := operator.InsertOne(bson.M{"name": "Bob", "age": 40}) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | // 更新文档 126 | _, err = operator.Update(bson.M{"name": "Bob"}, bson.M{"$set": bson.M{"age": 45}}) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | return nil 132 | }) 133 | assert.NoError(t, err, "Transaction failed") 134 | 135 | // 验证事务结果 136 | var result bson.M 137 | err = operator.Where(bson.M{"name": "Bob"}).FindOne(&result) 138 | assert.NoError(t, err, "FindOne failed") 139 | assert.Equal(t, int32(45), result["age"], "Updated age should be 45") 140 | } 141 | -------------------------------------------------------------------------------- /encoding/abase64/README.md: -------------------------------------------------------------------------------- 1 | # antgo/encoding/abase64 - Base64 Encoding/Decoding Library / Base64编解码库 2 | 3 | [中文](#中文) | [English](#english) 4 | 5 | --- 6 | 7 | ## 中文 8 | 9 | ### 📖 简介 10 | 11 | `antgo/encoding/abase64` 是基于Go标准库的高效Base64编解码工具,通过预计算缓冲区和减少内存分配实现性能优化。 12 | 适用于敏感数据处理、文件编码或网络传输场景。 13 | 14 | GitHub地址: [github.com/warysection/antgo/encoding/abase64](https://github.com/warysection/antgo/encoding/abase64) 15 | 16 | ### 📦 安装 17 | 18 | ```bash 19 | go get github.com/warysection/antgo/encoding/abase64 20 | ``` 21 | 22 | ### 🚀 快速开始 23 | 24 | #### 编码示例 25 | ```go 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "github.com/warysection/antgo/encoding/abase64" 31 | ) 32 | 33 | func main() { 34 | data := []byte("Hello, World!") 35 | encoded := abase64.Encode(data) 36 | fmt.Println(encoded) // 输出: SGVsbG8sIFdvcmxkIQ== 37 | } 38 | ``` 39 | 40 | #### 解码示例 41 | ```go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | "github.com/warysection/antgo/encoding/abase64" 47 | ) 48 | 49 | func main() { 50 | encodedStr := "SGVsbG8sIFdvcmxkIQ==" 51 | decoded, err := abase64.Decode(encodedStr) 52 | if err != nil { 53 | fmt.Println("解码错误:", err) 54 | return 55 | } 56 | fmt.Println(string(decoded)) // 输出: Hello, World! 57 | } 58 | ``` 59 | 60 | ### ✨ 核心特性 61 | 62 | | 特性 | 描述 | 63 | |---------------------|--------------------------------------------------------------------| 64 | | **零额外内存分配** | 预计算缓冲区大小,避免运行时内存分配 | 65 | | **严格RFC合规** | 使用`base64.StdEncoding`,兼容所有标准Base64实现 | 66 | | **安全错误处理** | 自动验证输入合法性,防止畸形数据导致崩溃 | 67 | 68 | ### ⚠️ 注意事项 69 | 1. 输入必须为标准Base64格式(允许填充`=`) 70 | 2. 支持标准字符集(`A-Za-z0-9+/`),如需URL安全版本请提交Feature Request 71 | 3. 解码错误会返回`base64.CorruptInputError`类型错误 72 | 73 | ### 🤝 参与贡献 74 | [贡献指南](https://github.com/warysection/antgo/blob/main/CONTRIBUTING.md) | [提交Issue](https://github.com/warysection/antgo/issues) 75 | 76 | --- 77 | 78 | ## English 79 | 80 | ### 📖 Introduction 81 | 82 | `antgo/encoding/abase64` is a high-performance Base64 encoding/decoding library optimized for zero-allocation operations. 83 | Ideal for sensitive data processing, file encoding, and network transmission scenarios. 84 | 85 | GitHub URL: [github.com/warysection/antgo/encoding/abase64](https://github.com/warysection/antgo/encoding/abase64) 86 | 87 | ### 📦 Installation 88 | 89 | ```bash 90 | go get github.com/warysection/antgo/encoding/abase64 91 | ``` 92 | 93 | ### 🚀 Quick Start 94 | 95 | #### Encoding 96 | ```go 97 | package main 98 | 99 | import ( 100 | "fmt" 101 | "github.com/warysection/antgo/encoding/abase64" 102 | ) 103 | 104 | func main() { 105 | data := []byte("Hello, World!") 106 | encoded := abase64.Encode(data) 107 | fmt.Println(encoded) // Output: SGVsbG8sIFdvcmxkIQ== 108 | } 109 | ``` 110 | 111 | #### Decoding 112 | ```go 113 | package main 114 | 115 | import ( 116 | "fmt" 117 | "github.com/warysection/antgo/encoding/abase64" 118 | ) 119 | 120 | func main() { 121 | encodedStr := "SGVsbG8sIFdvcmxkIQ==" 122 | decoded, err := abase64.Decode(encodedStr) 123 | if err != nil { 124 | fmt.Println("Decode error:", err) 125 | return 126 | } 127 | fmt.Println(string(decoded)) // Output: Hello, World! 128 | } 129 | ``` 130 | 131 | ### ✨ Key Features 132 | 133 | | Feature | Description | 134 | |---------------------|--------------------------------------------------------------------| 135 | | **Zero Allocation** | Pre-calculated buffer size eliminates runtime allocations | 136 | | **RFC 4648 Compliant** | Fully compatible with `base64.StdEncoding` specifications | 137 | | **Safe Error Handling** | Automatic input validation with detailed error reporting | 138 | 139 | ### ⚠️ Important Notes 140 | 1. Input must be standard Base64 (padding `=` allowed) 141 | 2. Uses standard character set (`A-Za-z0-9+/`). Contact us for URL-safe variant 142 | 3. Returns `base64.CorruptInputError` for malformed inputs 143 | 144 | ### 🤝 Contributing 145 | [Contribution Guide](https://github.com/warysection/antgo/blob/main/CONTRIBUTING.md) | [Open an Issue](https://github.com/warysection/antgo/issues) 146 | 147 | [⬆ Back to Top](#中文) 148 | -------------------------------------------------------------------------------- /encoding/abase64/abase64.go: -------------------------------------------------------------------------------- 1 | package abase64 2 | 3 | import ( 4 | "encoding/base64" 5 | ) 6 | 7 | // Encode 使用标准Base64编码将字节切片转换为字符串 8 | // Encode converts a byte slice to a standard Base64 encoded string 9 | func Encode(data []byte) string { 10 | // 预计算编码后长度以减少内存分配 Pre-calculate encoded length to reduce memory allocation 11 | encodedLen := base64.StdEncoding.EncodedLen(len(data)) 12 | 13 | // 直接编码到目标缓冲区 Encode directly to destination buffer 14 | dst := make([]byte, encodedLen) 15 | base64.StdEncoding.Encode(dst, data) 16 | 17 | return string(dst) 18 | } 19 | 20 | // Decode 将Base64编码字符串解码为原始字节切片 21 | // Decodes a Base64 encoded string to the original byte slice 22 | func Decode(encodedStr string) ([]byte, error) { 23 | // 将输入字符串转换为字节切片 Convert input string to byte slice 24 | src := []byte(encodedStr) 25 | 26 | // 预计算最大可能解码长度 Pre-calculate maximum possible decoded length 27 | maxDecodedLen := base64.StdEncoding.DecodedLen(len(src)) 28 | 29 | // 直接解码到目标缓冲区,避免额外内存分配 30 | dst := make([]byte, maxDecodedLen) 31 | n, err := base64.StdEncoding.Decode(dst, src) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | // 返回实际解码数据部分 Avoid creating unnecessary slice 37 | return dst[:n], nil 38 | } 39 | -------------------------------------------------------------------------------- /encoding/abinary/abinary.go: -------------------------------------------------------------------------------- 1 | package abinary 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "sync" 7 | ) 8 | 9 | // bufferPool 用于复用 bytes.Buffer 减少内存分配 10 | // bufferPool is used to reuse bytes.Buffer and reduce memory allocations 11 | var bufferPool = sync.Pool{ 12 | New: func() interface{} { 13 | return new(bytes.Buffer) 14 | }, 15 | } 16 | 17 | // Encode 将任意类型序列化为小端格式的字节切片 18 | // 注意:返回的字节切片是独立副本,修改不会影响缓冲区 19 | // 20 | // Encode serializes the given value into a little-endian byte slice 21 | // Note: The returned byte slice is an independent copy, modifications won't affect the buffer 22 | // 23 | // 参数: 24 | // 25 | // value - 需要序列化的数据(必须是固定大小的类型或包含固定大小类型的结构体) 26 | // 27 | // Parameters: 28 | // 29 | // value - The data to be encoded (must be fixed-size type or struct containing fixed-size types) 30 | // 31 | // 返回值: 32 | // 33 | // []byte - 序列化后的字节切片 34 | // error - 编码过程中遇到的错误(如类型不支持) 35 | // 36 | // Returns: 37 | // 38 | // []byte - Serialized byte slice 39 | // error - Encoding errors (e.g. unsupported type) 40 | func Encode(value interface{}) ([]byte, error) { 41 | // 从对象池获取缓冲区 42 | // Get buffer from pool 43 | buf := bufferPool.Get().(*bytes.Buffer) 44 | defer func() { 45 | buf.Reset() // 重置缓冲区以供复用 46 | bufferPool.Put(buf) // 放回对象池 47 | }() 48 | 49 | // 序列化数据到缓冲区 50 | // Serialize data to buffer 51 | if err := binary.Write(buf, binary.LittleEndian, value); err != nil { 52 | return nil, err 53 | } 54 | 55 | // 创建独立副本返回,保证数据安全性 56 | // Create independent copy for return data safety 57 | result := make([]byte, buf.Len()) 58 | copy(result, buf.Bytes()) 59 | return result, nil 60 | } 61 | 62 | // Decode 将小端格式的字节切片反序列化到指定变量 63 | // 64 | // # Decode deserializes little-endian byte slice into the specified variable 65 | // 66 | // 参数: 67 | // 68 | // b - 需要反序列化的字节切片 69 | // value - 目标变量指针(必须与编码时类型匹配) 70 | // 71 | // Parameters: 72 | // 73 | // b - Byte slice to be deserialized 74 | // value - Pointer to destination variable (must match encoded type) 75 | // 76 | // 返回值: 77 | // 78 | // error - 解码过程中遇到的错误(如数据不完整或类型不匹配) 79 | // 80 | // Returns: 81 | // 82 | // error - Decoding errors (e.g. incomplete data or type mismatch) 83 | func Decode(b []byte, value interface{}) error { 84 | // 直接使用字节切片创建 Reader,无需额外内存分配 85 | // Create reader directly from byte slice without additional allocation 86 | return binary.Read(bytes.NewReader(b), binary.LittleEndian, value) 87 | } 88 | -------------------------------------------------------------------------------- /encoding/acharset/README.md: -------------------------------------------------------------------------------- 1 | # antgo/encoding/acharset - 字符集编解码库 / Charset Encoding Library 2 | 3 | [中文](#中文) | [English](#english) 4 | 5 | --- 6 | 7 | ## 中文 8 | 9 | ### 📖 简介 10 | 11 | `antgo/encoding/acharset` 是基于Go标准库的高效字符集编解码工具,支持多种字符集别名映射,并通过并发安全缓存优化编解码性能。 12 | 适用于处理多国语言文本编码转换、旧系统数据迁移等场景。 13 | 14 | GitHub地址: [github.com/warysection/antgo/encoding/acharset](https://github.com/warysection/antgo/encoding/acharset) 15 | 16 | ### 📦 安装 17 | 18 | ```bash 19 | go get github.com/warysection/antgo/encoding/acharset 20 | ``` 21 | 22 | ### 🚀 快速开始 23 | 24 | #### 基本编解码 25 | ```go 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "github.com/warysection/antgo/encoding/acharset" 31 | ) 32 | 33 | func main() { 34 | // GBK编码的原始字节 35 | gbkBytes := []byte{0xC4, 0xE3, 0xBA, 0xC3} // "你好"的GBK编码 36 | 37 | // 解码为UTF-8 38 | utf8Bytes, err := acharset.Decode(string(gbkBytes), "GBK") 39 | if err != nil { 40 | panic(err) 41 | } 42 | fmt.Println(string(utf8Bytes)) // 输出: 你好 43 | } 44 | ``` 45 | 46 | #### 处理HZ-GB-2312编码 47 | ```go 48 | func main() { 49 | // HZ-GB-2312编码文本 50 | hzText := "~{<:Ky2;S{<~}" // "你好世界"的HZ编码 51 | 52 | // 转换为UTF-8 53 | utf8Bytes, err := acharset.Decode(hzText, "hzgb2312") 54 | if err != nil { 55 | panic(err) 56 | } 57 | fmt.Println(string(utf8Bytes)) // 输出: 你好世界 58 | } 59 | ``` 60 | 61 | ### ✨ 核心特性 62 | 63 | | 特性 | 描述 | 64 | |---------------------|--------------------------------------------------------------------| 65 | | **别名支持** | 内置常见字符集别名(如GB2312→HZ-GB-2312) | 66 | | **缓存优化** | 使用sync.Map缓存已解析的编码器,提升重复使用性能 | 67 | | **并发安全** | 所有操作线程安全,适合高并发场景 | 68 | | **自动规范化** | 自动处理字符集名称大小写(如"gbk"→"GBK") | 69 | 70 | ### ⚠️ 注意事项 71 | 1. 支持的字符集取决于系统环境中的IANA注册表 72 | 2. 内置别名可通过修改`charsetAliases`扩展 73 | 3. 解码失败会返回`unsupported charset`错误 74 | 4. 返回的字节切片是独立副本,可安全修改 75 | 76 | ### 🤝 参与贡献 77 | [贡献指南](https://github.com/warysection/antgo/blob/main/CONTRIBUTING.md) | [提交Issue](https://github.com/warysection/antgo/issues) 78 | 79 | --- 80 | 81 | ## English 82 | 83 | ### 📖 Introduction 84 | 85 | `antgo/encoding/acharset` is an efficient character set encoding/decoding library with alias support and concurrent-safe caching. 86 | Ideal for multi-language text processing and legacy system data migration. 87 | 88 | GitHub URL: [github.com/warysection/antgo/encoding/acharset](https://github.com/warysection/antgo/encoding/acharset) 89 | 90 | ### 📦 Installation 91 | 92 | ```bash 93 | go get github.com/warysection/antgo/encoding/acharset 94 | ``` 95 | 96 | ### 🚀 Quick Start 97 | 98 | #### Basic Encoding/Decoding 99 | ```go 100 | package main 101 | 102 | import ( 103 | "fmt" 104 | "github.com/warysection/antgo/encoding/acharset" 105 | ) 106 | 107 | func main() { 108 | // GBK encoded bytes 109 | gbkBytes := []byte{0xC4, 0xE3, 0xBA, 0xC3} // "Hello" in GBK 110 | 111 | // Decode to UTF-8 112 | utf8Bytes, err := acharset.Decode(string(gbkBytes), "GBK") 113 | if err != nil { 114 | panic(err) 115 | } 116 | fmt.Println(string(utf8Bytes)) // Output: 你好 117 | } 118 | ``` 119 | 120 | #### Handling HZ-GB-2312 121 | ```go 122 | func main() { 123 | // HZ-GB-2312 encoded text 124 | hzText := "~{<:Ky2;S{<~}" // "Hello world" in HZ encoding 125 | 126 | // Convert to UTF-8 127 | utf8Bytes, err := acharset.Decode(hzText, "hzgb2312") 128 | if err != nil { 129 | panic(err) 130 | } 131 | fmt.Println(string(utf8Bytes)) // Output: 你好世界 132 | } 133 | ``` 134 | 135 | ### ✨ Key Features 136 | 137 | | Feature | Description | 138 | |---------------------|--------------------------------------------------------------------| 139 | | **Alias Support** | Built-in charset aliases (e.g. GB2312→HZ-GB-2312) | 140 | | **Caching** | sync.Map cached encodings for repeated use | 141 | | **Concurrency** | Thread-safe operations for high concurrency scenarios | 142 | | **Auto-Normalize** | Case-insensitive charset name handling (e.g. "gbk"→"GBK") | 143 | 144 | ### ⚠️ Important Notes 145 | 1. Supported charsets depend on system's IANA registry 146 | 2. Extend aliases via modifying `charsetAliases` 147 | 3. Returns `unsupported charset` on decoding failure 148 | 4. Returned byte slice is an independent copy 149 | 150 | ### 🤝 Contributing 151 | [Contribution Guide](https://github.com/warysection/antgo/blob/main/CONTRIBUTING.md) | [Open an Issue](https://github.com/warysection/antgo/issues) 152 | 153 | [⬆ Back to Top](#中文) 154 | 155 | --- -------------------------------------------------------------------------------- /encoding/acharset/acharset.go: -------------------------------------------------------------------------------- 1 | package acharset 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "golang.org/x/text/encoding" 7 | "golang.org/x/text/encoding/ianaindex" 8 | "golang.org/x/text/transform" 9 | "io" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | // charsetAliases 定义了字符集的别名映射,将常见别名映射到标准IANA名称 15 | // charsetAliases defines a mapping of common aliases to standard IANA names 16 | var charsetAliases = map[string]string{ 17 | "hzgb2312": "HZ-GB-2312", // HZ-GB-2312 编码的常见别名 18 | "gb2312": "HZ-GB-2312", // Common aliases for HZ-GB-2312 encoding 19 | } 20 | 21 | // encodingCache 用于缓存已查询的编码器,提升并发访问性能 22 | // encodingCache caches queried encodings for better concurrent performance 23 | var encodingCache sync.Map 24 | 25 | // Decode 将指定字符集的字符串解码为UTF-8字节序列 26 | // 参数: 27 | // 28 | // value: 需要解码的原始字符串 29 | // charset: 源字符集名称 30 | // 31 | // 返回: 32 | // 33 | // []byte: 解码后的UTF-8字节序列 34 | // error: 解码过程中遇到的错误 35 | // 36 | // Decode converts a string from specified charset to UTF-8 byte sequence 37 | // Parameters: 38 | // 39 | // value: The raw string to be decoded 40 | // charset: Source charset name 41 | // 42 | // Returns: 43 | // 44 | // []byte: Decoded UTF-8 byte sequence 45 | // error: Any decoding error encountered 46 | func Decode(value string, charset string) ([]byte, error) { 47 | enc := getEncoding(charset) 48 | if enc == nil { 49 | return nil, errors.New("unsupported charset") 50 | } 51 | 52 | // 使用转换流进行解码 53 | // Using transform stream for decoding 54 | reader := transform.NewReader( 55 | bytes.NewReader([]byte(value)), 56 | enc.NewDecoder(), 57 | ) 58 | 59 | // 读取解码后的全部数据 60 | // Reading all decoded data 61 | d, e := io.ReadAll(reader) 62 | if e != nil { 63 | return nil, e 64 | } 65 | return d, nil 66 | } 67 | 68 | // getEncoding 获取指定字符集的编码器,优先使用缓存 69 | // 参数: 70 | // 71 | // charset: 字符集名称(支持别名) 72 | // 73 | // 返回: 74 | // 75 | // encoding.Encoding: 字符集编码器对象 76 | // 77 | // getEncoding gets the encoding for specified charset, using cache first 78 | // Parameters: 79 | // 80 | // charset: Charset name (supports aliases) 81 | // 82 | // Returns: 83 | // 84 | // encoding.Encoding: Charset encoder object 85 | func getEncoding(charset string) encoding.Encoding { 86 | // 统一转换为小写进行别名匹配 87 | // Convert to lowercase for alias matching 88 | lowerCharset := strings.ToLower(charset) 89 | 90 | // 检查并替换已知别名 91 | // Check and replace known aliases 92 | if alias, ok := charsetAliases[lowerCharset]; ok { 93 | charset = alias 94 | } 95 | 96 | // 首先尝试从缓存获取编码器 97 | // Try getting encoding from cache first 98 | if enc, ok := encodingCache.Load(charset); ok { 99 | return enc.(encoding.Encoding) 100 | } 101 | 102 | // 从IANA官方注册表查询编码 103 | // Query encoding from IANA registry 104 | enc, err := ianaindex.MIB.Encoding(charset) 105 | if err != nil { 106 | return nil 107 | } 108 | 109 | // 将查询结果存入缓存 110 | // Store result in cache 111 | encodingCache.Store(charset, enc) 112 | return enc 113 | } 114 | -------------------------------------------------------------------------------- /encoding/ahtml/html.go: -------------------------------------------------------------------------------- 1 | package ahtml 2 | 3 | import ( 4 | strip "github.com/grokify/html-strip-tags-go" 5 | "html" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | // specialCharsReplacer 预定义HTML特殊字符替换器,提升替换性能 11 | // Pre-defined HTML special characters replacer for better performance 12 | specialCharsReplacer = strings.NewReplacer( 13 | "&", "&", 14 | "<", "<", 15 | ">", ">", 16 | `"`, """, // 34 = 十六进制的双引号 17 | "'", "'", // 39 = 十六进制的单引号 18 | ) 19 | 20 | // specialCharsDecodeReplacer 预定义HTML实体反向替换器 21 | // Pre-defined HTML entity decoder for better performance 22 | specialCharsDecodeReplacer = strings.NewReplacer( 23 | "&", "&", 24 | "<", "<", 25 | ">", ">", 26 | """, `"`, 27 | "'", "'", 28 | ) 29 | ) 30 | 31 | // StripTags 过滤掉HTML标签,只返回text内容 32 | // 注意:使用第三方库实现,性能取决于底层库效率 33 | // 参考:http://php.net/manual/zh/function.strip-tags.php 34 | // 35 | // StripTags removes all HTML tags and returns text content 36 | // Note: Uses third-party library implementation, performance depends on underlying library 37 | // Reference: http://php.net/manual/en/function.strip-tags.php 38 | func StripTags(s string) string { 39 | return strip.StripTags(s) 40 | } 41 | 42 | // Entities 转换所有HTML特殊字符为对应实体(包括引号) 43 | // 注意:与PHP的htmlentities()不同,当前实现仅转义5个基础字符 44 | // 参考:http://php.net/manual/zh/function.htmlentities.php 45 | // 46 | // Entities converts all HTML special characters to entities (including quotes) 47 | // Note: Different from PHP's htmlentities(), current implementation escapes 5 basic characters 48 | // Reference: http://php.net/manual/en/function.htmlentities.php 49 | func Entities(s string) string { 50 | return html.EscapeString(s) 51 | } 52 | 53 | // EntitiesDecode 将HTML实体转换回普通字符 54 | // 参考:http://php.net/manual/zh/function.html-entity-decode.php 55 | // 56 | // EntitiesDecode converts HTML entities back to normal characters 57 | // Reference: http://php.net/manual/en/function.html-entity-decode.php 58 | func EntitiesDecode(s string) string { 59 | return html.UnescapeString(s) 60 | } 61 | 62 | // SpecialChars 转换关键HTML特殊字符为实体 63 | // 特性: 64 | // - 自动处理 &, <, >, ", ' 五个字符 65 | // - 使用预编译的替换器,性能优于动态编译 66 | // - 单引号强制转换(类似PHP的ENT_QUOTES模式) 67 | // 参考:http://php.net/manual/zh/function.htmlspecialchars.php 68 | // 69 | // SpecialChars converts key HTML special characters to entities 70 | // Features: 71 | // - Handles &, <, >, ", ' automatically 72 | // - Uses pre-compiled replacer for better performance 73 | // - Forces single quote conversion (similar to PHP's ENT_QUOTES mode) 74 | // Reference: http://php.net/manual/en/function.htmlspecialchars.php 75 | func SpecialChars(s string) string { 76 | return specialCharsReplacer.Replace(s) 77 | } 78 | 79 | // SpecialCharsDecode 反转SpecialChars的转换操作 80 | // 特性: 81 | // - 使用预编译替换器,高性能解码 82 | // - 完全匹配SpecialChars的编码规则 83 | // 参考:http://php.net/manual/zh/function.htmlspecialchars-decode.php 84 | // 85 | // SpecialCharsDecode reverses SpecialChars conversion 86 | // Features: 87 | // - Uses pre-compiled replacer for high-performance decoding 88 | // - Exactly matches SpecialChars encoding rules 89 | // Reference: http://php.net/manual/en/function.htmlspecialchars-decode.php 90 | func SpecialCharsDecode(s string) string { 91 | return specialCharsDecodeReplacer.Replace(s) 92 | } 93 | -------------------------------------------------------------------------------- /encoding/ahtml/html_test.go: -------------------------------------------------------------------------------- 1 | package ahtml 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestHtml(t *testing.T) { 9 | var src = `

Test paragraph.

Other text` 10 | var src2 = `A 'quote' "is" bold` 11 | var src3 = `A 'quote' "is" bold` 12 | log.Println(StripTags(src)) 13 | var data = Entities(src2) 14 | 15 | log.Println(data) 16 | log.Println(EntitiesDecode(data)) 17 | 18 | var data2 = SpecialChars(src3) 19 | log.Println(data2) 20 | log.Println(SpecialCharsDecode(data2)) 21 | } 22 | -------------------------------------------------------------------------------- /encoding/aini/aini.go: -------------------------------------------------------------------------------- 1 | package aini 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "strings" 11 | ) 12 | 13 | // Decode parses INI format data into a nested map structure 14 | // Decode 将INI格式数据解析为嵌套的map结构 15 | func Decode(data []byte) (result map[string]interface{}, err error) { 16 | result = make(map[string]interface{}) 17 | reader := bufio.NewReader(bytes.NewReader(data)) 18 | 19 | var ( 20 | currentSection string // Current section name 当前节名称 21 | sectionData map[string]interface{} // Current section data 当前节数据 22 | inSection bool // Flag for valid section 是否在有效节中 23 | ) 24 | 25 | for { 26 | lineBytes, _, err := reader.ReadLine() 27 | if errors.Is(err, io.EOF) { 28 | break 29 | } 30 | if err != nil { 31 | return nil, fmt.Errorf("read error: %w", err) 32 | } 33 | 34 | // Clean and check line 35 | // 清理并检查行内容 36 | line := strings.TrimSpace(string(lineBytes)) 37 | if len(line) == 0 || line[0] == ';' || line[0] == '#' { 38 | continue // Skip empty lines and comments 跳过空行和注释 39 | } 40 | 41 | // Parse section header 42 | // 解析节头 43 | if start, end := strings.Index(line, "["), strings.Index(line, "]"); start >= 0 && end > start+1 { 44 | currentSection = line[start+1 : end] 45 | sectionData = make(map[string]interface{}) 46 | result[currentSection] = sectionData 47 | inSection = true 48 | continue 49 | } 50 | 51 | // Skip lines not in section 52 | // 跳过不在节中的行 53 | if !inSection { 54 | continue 55 | } 56 | 57 | // Parse key-value pairs 58 | // 解析键值对 59 | if sepIdx := strings.Index(line, "="); sepIdx > 0 { 60 | key := strings.TrimSpace(line[:sepIdx]) 61 | value := strings.TrimSpace(line[sepIdx+1:]) 62 | sectionData[key] = value 63 | } 64 | } 65 | 66 | if !inSection { 67 | return nil, errors.New("no valid section found in INI data") 68 | } 69 | return result, nil 70 | } 71 | 72 | // Encode converts map data to INI format bytes 73 | // Encode 将map数据转换为INI格式字节 74 | func Encode(data map[string]interface{}) ([]byte, error) { 75 | buf := bytes.NewBuffer(nil) 76 | 77 | for sectionName, sectionContent := range data { 78 | // Validate section type 79 | // 验证节类型 80 | sectionData, ok := sectionContent.(map[string]interface{}) 81 | if !ok { 82 | return nil, fmt.Errorf("invalid section type: %T", sectionContent) 83 | } 84 | 85 | // Write section header 86 | // 写入节头 87 | if _, err := fmt.Fprintf(buf, "[%s]\n", sectionName); err != nil { 88 | return nil, err 89 | } 90 | 91 | // Write key-value pairs 92 | // 写入键值对 93 | for key, value := range sectionData { 94 | if _, err := fmt.Fprintf(buf, "%s = %s\n", key, value); err != nil { 95 | return nil, err 96 | } 97 | } 98 | 99 | buf.WriteByte('\n') // Add section separator 添加节分隔符 100 | } 101 | 102 | return buf.Bytes(), nil 103 | } 104 | 105 | // ToJson converts INI data to JSON format 106 | // ToJson 将INI数据转换为JSON格式 107 | func ToJson(data []byte) ([]byte, error) { 108 | iniMap, err := Decode(data) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return json.MarshalIndent(iniMap, "", " ") // Pretty-print JSON 格式化输出JSON 113 | } 114 | -------------------------------------------------------------------------------- /encoding/aini/ini_test.go: -------------------------------------------------------------------------------- 1 | package aini 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | var iniStr string = ` 9 | 10 | ;注释 11 | aa=bb 12 | [addr] 13 | #注释 14 | ip = 127.0.0.1 15 | port=9001 16 | enable=true 17 | 18 | [DBINFO] 19 | type=mysql 20 | user=root 21 | password=password 22 | [键] 23 | 呵呵=值 24 | 25 | ` 26 | 27 | func TestIni(t *testing.T) { 28 | result, err := Decode([]byte(iniStr)) 29 | if err != nil { 30 | t.Errorf("encode failed. %v", err) 31 | return 32 | } 33 | log.Println(result) 34 | 35 | res, err := Encode(result) 36 | if err != nil { 37 | t.Errorf("encode failed. %v", err) 38 | return 39 | } 40 | log.Println(string(res)) 41 | 42 | jsonyaml, err := ToJson([]byte(iniStr)) 43 | if err != nil { 44 | t.Errorf("ToJson failed. %v", err) 45 | return 46 | } 47 | log.Println(string(jsonyaml)) 48 | } 49 | -------------------------------------------------------------------------------- /encoding/ajson/ajson.go: -------------------------------------------------------------------------------- 1 | package ajson 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/warysection/antgo/utils/conv" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // Json is a wrapper for JSON data parsing and manipulation 11 | // Json 是用于JSON数据解析和操作的封装结构 12 | type Json struct { 13 | Child interface{} // Current level JSON data (当前层级的JSON数据) 14 | } 15 | 16 | // Open reads a JSON file and returns its content as bytes 17 | // Open 读取JSON文件并返回其内容的字节切片 18 | func Open(file string) []byte { 19 | byteValue, err := os.ReadFile(file) 20 | if err != nil { 21 | panic(err) 22 | } 23 | return byteValue 24 | } 25 | 26 | // Decode parses JSON bytes into Json structure 27 | // Decode 将JSON字节解析为Json结构 28 | func Decode(data []byte) *Json { 29 | var result interface{} 30 | if err := json.Unmarshal(data, &result); err != nil { 31 | panic(err) 32 | } 33 | return &Json{Child: result} 34 | } 35 | 36 | // Encode converts data to JSON string 37 | // Encode 将数据转换为JSON字符串 38 | func Encode(data interface{}) string { 39 | result, err := json.Marshal(data) 40 | if err != nil { 41 | panic(err) 42 | } 43 | return string(result) 44 | } 45 | 46 | // Next navigates to next level JSON element 47 | // Next 导航到下一级JSON元素 48 | func (j *Json) Next(name interface{}) *Json { 49 | switch child := j.Child.(type) { 50 | case map[string]interface{}: // Handle JSON object (处理JSON对象) 51 | return &Json{Child: child[conv.String(name)]} 52 | case []interface{}: // Handle JSON array (处理JSON数组) 53 | if index := conv.Int(name); index >= 0 && index < len(child) { 54 | return &Json{Child: child[index]} 55 | } 56 | return &Json{} // Return empty for invalid index (索引越界返回空) 57 | default: // Handle other types (处理其他类型) 58 | return &Json{} 59 | } 60 | } 61 | 62 | // Get navigates through multi-level JSON path 63 | // Get 通过多级路径导航JSON数据 64 | func (j *Json) Get(path string) *Json { 65 | current := j 66 | for _, part := range strings.Split(path, ".") { 67 | current = current.Next(part) 68 | } 69 | return current 70 | } 71 | 72 | // String converts current value to string 73 | // String 将当前值转换为字符串 74 | func (j *Json) String() string { 75 | return conv.String(j.Child) 76 | } 77 | 78 | // Strings converts current value to string slice 79 | // Strings 将当前值转换为字符串切片 80 | func (j *Json) Strings() []string { 81 | return conv.Strings(j.Child) 82 | } 83 | 84 | // Int converts current value to int 85 | // Int 将当前值转换为整型 86 | func (j *Json) Int() int { 87 | return conv.Int(j.Child) 88 | } 89 | 90 | // Ints converts current value to int slice 91 | // Ints 将当前值转换为整型切片 92 | func (j *Json) Ints() []int { 93 | return conv.Ints(j.Child) 94 | } 95 | 96 | // Int64 converts current value to int64 97 | // Int64 将当前值转换为64位整型 98 | func (j *Json) Int64() int64 { 99 | return conv.Int64(j.Child) 100 | } 101 | 102 | // Float64 converts current value to float64 103 | // Float64 将当前值转换为64位浮点数 104 | func (j *Json) Float64() float64 { 105 | return conv.Float64(j.Child) 106 | } 107 | 108 | // Bool converts current value to boolean 109 | // Bool 将当前值转换为布尔值 110 | func (j *Json) Bool() bool { 111 | return conv.Bool(j.Child) 112 | } 113 | 114 | // Map converts current value to map[string]interface{} 115 | // Map 将当前值转换为字典类型 116 | func (j *Json) Map() map[string]interface{} { 117 | if m, ok := j.Child.(map[string]interface{}); ok { 118 | return m 119 | } 120 | return nil 121 | } 122 | 123 | // Array converts current value to []interface{} 124 | // Array 将当前值转换为切片类型 125 | func (j *Json) Array() []interface{} { 126 | if a, ok := j.Child.([]interface{}); ok { 127 | return a 128 | } 129 | return nil 130 | } 131 | 132 | // Interface returns raw interface value 133 | // Interface 返回原始接口值 134 | func (j *Json) Interface() interface{} { 135 | return j.Child 136 | } 137 | 138 | // 其他类型转换方法根据需要添加... 139 | -------------------------------------------------------------------------------- /encoding/ajson/json_test.go: -------------------------------------------------------------------------------- 1 | package ajson 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestJson(t *testing.T) { 9 | //log.Println(string(ajson.Open("i18n.json"))) 10 | for i := 0; i < 1; i++ { 11 | jsonStr := `[{"users" : { 12 | "count" : 2, 13 | "list" : [ 14 | {"name" : "Ming", "score" : 60}, 15 | {"name" : "John", "score" : 99.5} 16 | ] 17 | } 18 | }]` 19 | var result = Decode([]byte(jsonStr)).Get("0").Get("users").Get("list").Array() 20 | log.Println(result) 21 | log.Println(Encode(map[string]interface{}{"name": "21"})) 22 | 23 | var result2 = Decode([]byte(jsonStr)).Get("0.users.list").Interface() 24 | log.Println(result2) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /encoding/atoml/atoml.go: -------------------------------------------------------------------------------- 1 | package atoml 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/BurntSushi/toml" 7 | "sync" 8 | ) 9 | 10 | // bufferPool 用于复用 bytes.Buffer 实例,减少内存分配 11 | // bufferPool is used to reuse bytes.Buffer instances, reducing memory allocations 12 | var bufferPool = sync.Pool{ 13 | New: func() interface{} { 14 | return new(bytes.Buffer) 15 | }, 16 | } 17 | 18 | // Encode 将结构体编码为 TOML 格式的字节数据 19 | // Encode encodes a struct into TOML-formatted bytes 20 | // 使用 sync.Pool 复用 bytes.Buffer 以减少内存分配,提升性能 21 | // Uses sync.Pool to reuse bytes.Buffer, reducing memory allocations and improving performance 22 | func Encode(v interface{}) ([]byte, error) { 23 | // 从对象池获取 buffer 24 | // Get buffer from pool 25 | buffer := bufferPool.Get().(*bytes.Buffer) 26 | defer func() { 27 | buffer.Reset() // 清空缓冲区以便复用 / Clear buffer for reuse 28 | bufferPool.Put(buffer) // 放回对象池 / Return to pool 29 | }() 30 | 31 | // 执行 TOML 编码 32 | // Perform TOML encoding 33 | if err := toml.NewEncoder(buffer).Encode(v); err != nil { 34 | return nil, err 35 | } 36 | 37 | // 创建新切片并拷贝数据以避免缓冲区复用导致的数据污染 38 | // Create new slice and copy data to prevent buffer reuse contamination 39 | result := make([]byte, buffer.Len()) 40 | copy(result, buffer.Bytes()) 41 | return result, nil 42 | } 43 | 44 | // Decode 将 TOML 字节数据解码到通用 map 结构 45 | // Decode decodes TOML bytes into a generic map structure 46 | // 注意:高频场景建议使用 DecodeTo 直接解析到具体结构体以获得更好性能 47 | // Note: For high-frequency scenarios, use DecodeTo to parse directly into concrete structs for better performance 48 | func Decode(v []byte) (map[string]interface{}, error) { 49 | var result map[string]interface{} 50 | if err := toml.Unmarshal(v, &result); err != nil { 51 | return nil, err 52 | } 53 | return result, nil 54 | } 55 | 56 | // DecodeTo 将 TOML 字节数据直接解析到目标结构体 57 | // DecodeTo decodes TOML bytes directly into the target struct 58 | // 直接解析到结构体比使用通用 map 性能更好,推荐在明确数据结构时使用 59 | // Direct decoding into structs performs better than using generic maps, recommended when data structure is known 60 | func DecodeTo(v []byte, result interface{}) error { 61 | return toml.Unmarshal(v, result) 62 | } 63 | 64 | // jsonConvertPool 用于复用 map 实例,优化 ToJson 性能 65 | // jsonConvertPool is used to reuse map instances to optimize ToJson performance 66 | var jsonConvertPool = sync.Pool{ 67 | New: func() interface{} { 68 | return make(map[string]interface{}) 69 | }, 70 | } 71 | 72 | // ToJson 将 TOML 字节数据转换为 JSON 格式 73 | // ToJson converts TOML bytes to JSON format 74 | // 通过复用 map 实例和避免中间数据结构提升转换性能 75 | // Improves conversion performance by reusing map instances and avoiding intermediate data structures 76 | func ToJson(v []byte) ([]byte, error) { 77 | // 从对象池获取 map 实例 78 | // Get map instance from pool 79 | result := jsonConvertPool.Get().(map[string]interface{}) 80 | defer func() { 81 | // 清空 map 以便复用 / Clear map for reuse 82 | for key := range result { 83 | delete(result, key) 84 | } 85 | jsonConvertPool.Put(result) // 放回对象池 / Return to pool 86 | }() 87 | 88 | // 解析 TOML 数据到复用的 map 89 | // Parse TOML data into reused map 90 | if err := toml.Unmarshal(v, &result); err != nil { 91 | return nil, err 92 | } 93 | 94 | // 将 map 编码为 JSON 95 | // Encode map to JSON 96 | return json.Marshal(result) 97 | } 98 | -------------------------------------------------------------------------------- /encoding/aurl/url.go: -------------------------------------------------------------------------------- 1 | package aurl 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | ) 7 | 8 | // Encode escapes the string so it can be safely placed inside a URL query. 9 | // It uses url.QueryEscape which encodes spaces as '+' symbols. 10 | // Encode 转义字符串,以便安全地放在 URL 查询中。 11 | // 使用 url.QueryEscape 实现,空格会被编码为 '+' 符号。 12 | func Encode(str string) string { 13 | return url.QueryEscape(str) 14 | } 15 | 16 | // Decode converts URL-encoded query strings back to original form. 17 | // It handles '+' as spaces and hex-encoded characters like %AB. 18 | // Decode 将 URL 编码的查询字符串转换回原始形式。 19 | // 处理 '+' 为空格,以及 %AB 形式的十六进制编码字符。 20 | func Decode(str string) (string, error) { 21 | return url.QueryUnescape(str) 22 | } 23 | 24 | // RawEncode URL-encodes according to RFC 3986. 25 | // Spaces are encoded as %20 and special characters like ~ are preserved. 26 | // RawEncode 根据 RFC 3986 进行 URL 编码。 27 | // 空格被编码为 %20,特殊字符如 ~ 保留不转义。 28 | func RawEncode(str string) string { 29 | return url.PathEscape(str) 30 | } 31 | 32 | // RawDecode decodes strings encoded with RFC 3986. 33 | // It reverses the transformations done by RawEncode. 34 | // RawDecode 解码 RFC 3986 编码的字符串。 35 | // 还原 RawEncode 的转换操作。 36 | func RawDecode(str string) (string, error) { 37 | return url.PathUnescape(str) 38 | } 39 | 40 | // BuildQuery generates URL-encoded query string from map values. 41 | // Uses url.Values.Encode() which sorts parameters by key. 42 | // BuildQuery 从映射值生成 URL 编码的查询字符串。 43 | // 使用 url.Values.Encode() 实现,参数按键排序。 44 | func BuildQuery(queryData url.Values) string { 45 | return queryData.Encode() 46 | } 47 | 48 | // splitHostPort 拆分主机地址中的主机名和端口 49 | // 优先使用标准库的 net.SplitHostPort 以处理复杂场景(如IPv6) 50 | func splitHostPort(host string) (hostname, port string) { 51 | var err error 52 | hostname, port, err = net.SplitHostPort(host) 53 | if err != nil { 54 | // 当无端口或解析错误时返回完整主机名 55 | hostname = host 56 | } 57 | return 58 | } 59 | 60 | // ParseURL parses a URL and returns specified components. 61 | // Component flags: -1=all, 1=scheme, 2=host, 4=port, 8=user, 62 | // 16=pass, 32=path, 64=query, 128=fragment. 63 | // Optimized to avoid redundant host parsing operations. 64 | // ParseURL 解析 URL 并返回指定组件。 65 | // 组件标志位:-1=全部,1=协议,2=主机,4=端口,8=用户, 66 | // 16=密码,32=路径,64=查询,128=片段。 67 | // 优化避免重复的主机解析操作。 68 | func ParseURL(str string, component int) (map[string]string, error) { 69 | u, err := url.Parse(str) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | // 预分配结果map容量(最多包含8个元素) 75 | components := make(map[string]string, 8) 76 | var hostname, port string 77 | 78 | // 合并处理host和port的解析 79 | if component == -1 || (component&(2|4)) != 0 { 80 | hostname, port = splitHostPort(u.Host) 81 | } 82 | 83 | // 根据标志位填充组件 84 | if component == -1 || (component&1) != 0 { 85 | components["scheme"] = u.Scheme 86 | } 87 | if component == -1 || (component&2) != 0 { 88 | components["host"] = hostname 89 | } 90 | if component == -1 || (component&4) != 0 { 91 | components["port"] = port 92 | } 93 | if component == -1 || (component&8) != 0 { 94 | components["user"] = u.User.Username() 95 | } 96 | if component == -1 || (component&16) != 0 { 97 | components["pass"], _ = u.User.Password() 98 | } 99 | if component == -1 || (component&32) != 0 { 100 | components["path"] = u.Path 101 | } 102 | if component == -1 || (component&64) != 0 { 103 | components["query"] = u.RawQuery 104 | } 105 | if component == -1 || (component&128) != 0 { 106 | components["fragment"] = u.Fragment 107 | } 108 | 109 | return components, nil 110 | } 111 | -------------------------------------------------------------------------------- /encoding/axml/axml.go: -------------------------------------------------------------------------------- 1 | package axml 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | // StringMap 定义字符串映射类型,用于XML与JSON的转换 11 | // StringMap defines a string map type for XML/JSON conversion 12 | type StringMap map[string]string 13 | 14 | // MarshalXML 使用Token API优化XML序列化性能(避免反射) 15 | // MarshalXML optimizes XML serialization using Token API (avoids reflection) 16 | func (m StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 17 | if len(m) == 0 { 18 | return nil 19 | } 20 | 21 | // 写入起始标签 22 | // Write start element 23 | if err := e.EncodeToken(start); err != nil { 24 | return err 25 | } 26 | 27 | // 遍历键值对,逐个编码 28 | // Iterate key-value pairs and encode each 29 | for k, v := range m { 30 | elementStart := xml.StartElement{Name: xml.Name{Local: k}} 31 | if err := e.EncodeToken(elementStart); err != nil { 32 | return err 33 | } 34 | if err := e.EncodeToken(xml.CharData(v)); err != nil { 35 | return err 36 | } 37 | if err := e.EncodeToken(elementStart.End()); err != nil { 38 | return err 39 | } 40 | } 41 | 42 | // 写入父级结束标签并刷新缓冲区 43 | // Write parent end element and flush 44 | if err := e.EncodeToken(start.End()); err != nil { 45 | return err 46 | } 47 | return e.Flush() // 确保数据写入底层writer | Ensures data is written 48 | } 49 | 50 | // UnmarshalXML 使用Token流式解析优化反序列化 51 | // UnmarshalXML optimizes deserialization with token streaming 52 | func (m *StringMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 53 | *m = make(StringMap) // 初始化避免nil map | Initialize to avoid nil map 54 | 55 | for { 56 | token, err := d.Token() 57 | if err == io.EOF { 58 | break 59 | } 60 | if err != nil { 61 | return err 62 | } 63 | 64 | // 仅处理开始标签,跳过注释/指令等 65 | // Process only start elements, skip others like comments 66 | if se, ok := token.(xml.StartElement); ok { 67 | var value string 68 | if err := d.DecodeElement(&value, &se); err != nil { 69 | return err // 严格错误处理 | Strict error handling 70 | } 71 | (*m)[se.Name.Local] = value 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | // Encode 直接调用优化后的XML序列化 78 | // Encode directly uses the optimized XML serialization 79 | func Encode(data map[string]string) ([]byte, error) { 80 | return xml.Marshal(StringMap(data)) 81 | } 82 | 83 | // Decode 反序列化到新map,确保线程安全 84 | // Decode into a new map for thread safety 85 | func Decode(data []byte) (map[string]string, error) { 86 | result := make(StringMap) 87 | if err := xml.Unmarshal(data, &result); err != nil { 88 | return nil, err 89 | } 90 | return result, nil 91 | } 92 | 93 | // DecodeTo 复用现有map空间,清空旧数据 94 | // DecodeTo reuses existing map and clears old data 95 | func DecodeTo(data []byte, result map[string]string) error { 96 | // 兼容性清空(Go <1.21需手动遍历) 97 | // Compatibility clear (for Go <1.21) 98 | for k := range result { 99 | delete(result, k) 100 | } 101 | 102 | // 类型转换复用内存 103 | // Type conversion to reuse memory 104 | sm := StringMap(result) 105 | if err := xml.Unmarshal(data, &sm); err != nil { 106 | return err 107 | } 108 | return nil 109 | } 110 | 111 | // ToJson 使用对象池减少GC压力 112 | // ToJson uses object pool to reduce GC pressure 113 | var mapPool = sync.Pool{ 114 | New: func() interface{} { 115 | return make(StringMap) 116 | }, 117 | } 118 | 119 | func ToJson(v []byte) ([]byte, error) { 120 | m := mapPool.Get().(StringMap) 121 | defer func() { 122 | // 清空并放回池中 123 | // Clear and return to pool 124 | for k := range m { 125 | delete(m, k) 126 | } 127 | mapPool.Put(m) 128 | }() 129 | 130 | if err := xml.Unmarshal(v, &m); err != nil { 131 | return nil, err 132 | } 133 | return json.Marshal(m) 134 | } 135 | -------------------------------------------------------------------------------- /encoding/ayaml/ayaml.go: -------------------------------------------------------------------------------- 1 | package ayaml 2 | 3 | import ( 4 | "encoding/json" 5 | "gopkg.in/yaml.v3" 6 | ) 7 | 8 | // Encode 将Go对象编码为YAML格式 9 | // Encode converts Go value to YAML format 10 | // 参数 Parameters: 11 | // v - 要编码的Go对象 (Go value to encode) 12 | // 返回 Returns: 13 | // []byte - YAML格式字节切片 (YAML formatted byte slice) 14 | // error - 编码过程中遇到的错误 (Error during encoding) 15 | func Encode(v interface{}) ([]byte, error) { 16 | return yaml.Marshal(v) 17 | } 18 | 19 | // Decode 将YAML解码到map[string]interface{} 20 | // Decodes YAML into map[string]interface{} 21 | // 参数 Parameters: 22 | // v - YAML格式字节切片 (YAML formatted byte slice) 23 | // 返回 Returns: 24 | // map[string]interface{} - 解码后的字典数据 (Decoded map data) 25 | // error - 解码过程中遇到的错误 (Error during decoding) 26 | func Decode(v []byte) (map[string]interface{}, error) { 27 | var result map[string]interface{} 28 | if err := yaml.Unmarshal(v, &result); err != nil { 29 | return nil, err 30 | } 31 | return result, nil 32 | } 33 | 34 | // DecodeTo 将YAML解码到指定结构体 35 | // Decodes YAML into specified structure 36 | // 参数 Parameters: 37 | // v - YAML格式字节切片 (YAML formatted byte slice) 38 | // result - 目标结构体指针 (Pointer to target structure) 39 | // 返回 Returns: 40 | // error - 解码过程中遇到的错误 (Error during decoding) 41 | func DecodeTo(v []byte, result interface{}) error { 42 | return yaml.Unmarshal(v, result) 43 | } 44 | 45 | // ToJson 将YAML转换为JSON格式 46 | // Converts YAML to JSON format 47 | // 参数 Parameters: 48 | // v - YAML格式字节切片 (YAML formatted byte slice) 49 | // 返回 Returns: 50 | // []byte - JSON格式字节切片 (JSON formatted byte slice) 51 | // error - 转换过程中遇到的错误 (Error during conversion) 52 | func ToJson(v []byte) ([]byte, error) { 53 | // 直接解析到interface{}避免二次解码 54 | // Parse directly to interface{} to avoid double decoding 55 | var data interface{} 56 | if err := yaml.Unmarshal(v, &data); err != nil { 57 | return nil, err 58 | } 59 | return json.Marshal(data) 60 | } 61 | -------------------------------------------------------------------------------- /encoding/azip/unzip.go: -------------------------------------------------------------------------------- 1 | package azip 2 | 3 | import ( 4 | "archive/zip" 5 | "errors" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // Unzip 解压ZIP文件到指定目录 13 | // Decompresses a zip archive to the specified directory 14 | // 15 | // 参数 src: 源ZIP文件路径 16 | // Param src: Source zip file path 17 | // 18 | // 参数 dest: 目标解压目录 19 | // Param dest: Target extraction directory 20 | // 21 | // 返回: 解压文件列表和错误信息 22 | // Returns: Extracted file list and error 23 | func Unzip(src string, dest string) ([]string, error) { 24 | // 打开ZIP文件 25 | // Open zip file 26 | r, err := zip.OpenReader(src) 27 | if err != nil { 28 | return nil, err 29 | } 30 | defer r.Close() 31 | 32 | var extractedFiles []string 33 | // 创建1MB缓冲区用于文件复制 34 | // Create 1MB buffer for file copying 35 | buf := make([]byte, 1<<20) // 1MB buffer 36 | 37 | // 并行处理控制(可根据需要调整) 38 | // Concurrency control (adjustable as needed) 39 | // sem := make(chan struct{}, runtime.NumCPU()*2) 40 | 41 | // 遍历ZIP文件内容 42 | // Iterate through zip contents 43 | for _, f := range r.File { 44 | // 安全检查和路径处理 45 | // Security check and path processing 46 | fpath, err := safeExtractPath(dest, f.Name) 47 | if err != nil { 48 | return extractedFiles, err 49 | } 50 | extractedFiles = append(extractedFiles, fpath) 51 | 52 | // 处理目录 53 | // Handle directory 54 | if f.FileInfo().IsDir() { 55 | if err := createDir(fpath); err != nil { 56 | return extractedFiles, err 57 | } 58 | continue 59 | } 60 | 61 | // 处理文件 62 | // Handle file 63 | if err := extractFile(f, fpath, buf); err != nil { 64 | return extractedFiles, err 65 | } 66 | 67 | // 可选:并行处理(需要权衡磁盘IO和CPU利用率) 68 | // Optional: Parallel processing (need to balance disk IO and CPU usage) 69 | // sem <- struct{}{} 70 | // go func(f *zip.File) { 71 | // defer func() { <-sem }() 72 | // // ...处理逻辑... 73 | // }(f) 74 | } 75 | return extractedFiles, nil 76 | } 77 | 78 | // safeExtractPath 安全路径检查和处理 79 | // Security path validation and processing 80 | func safeExtractPath(dest string, filename string) (string, error) { 81 | // 清理路径并转换分隔符 82 | // Clean path and convert separators 83 | cleanPath := filepath.ToSlash(filepath.Clean(filename)) 84 | if strings.HasPrefix(cleanPath, "/") { 85 | return "", errors.New("file path error") 86 | } 87 | 88 | // 构建完整目标路径 89 | // Build full destination path 90 | fpath := filepath.Join(dest, cleanPath) 91 | 92 | // 检查路径穿越漏洞(ZipSlip) 93 | // Check for ZipSlip vulnerability 94 | if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { 95 | return "", errors.New("file path error") 96 | } 97 | return fpath, nil 98 | } 99 | 100 | // createDir 创建目录结构 101 | // Create directory structure 102 | func createDir(fpath string) error { 103 | // 使用适当权限创建目录 104 | // Create directory with proper permissions 105 | if err := os.MkdirAll(fpath, 0755); err != nil { 106 | return err 107 | } 108 | // 同步目录修改(可选,确保数据持久化) 109 | // Sync directory changes (optional, ensure data persistence) 110 | if d, err := os.Open(fpath); err == nil { 111 | d.Sync() 112 | d.Close() 113 | } 114 | return nil 115 | } 116 | 117 | // extractFile 解压单个文件 118 | // Extract single file 119 | func extractFile(f *zip.File, fpath string, buf []byte) error { 120 | // 创建目标文件 121 | // Create target file 122 | outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) 123 | if err != nil { 124 | return err 125 | } 126 | defer outFile.Close() 127 | 128 | // 打开ZIP文件条目 129 | // Open zip file entry 130 | rc, err := f.Open() 131 | if err != nil { 132 | return err 133 | } 134 | defer rc.Close() 135 | 136 | // 使用缓冲区复制数据 137 | // Copy data with buffer 138 | if _, err := io.CopyBuffer(outFile, rc, buf); err != nil { 139 | return err 140 | } 141 | 142 | // 立即同步文件内容(确保数据持久化) 143 | // Sync file contents immediately (ensure data persistence) 144 | if err := outFile.Sync(); err != nil { 145 | return err 146 | } 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /encoding/azip/zip.go: -------------------------------------------------------------------------------- 1 | package azip 2 | 3 | import ( 4 | "archive/zip" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | // Create 压缩一个或多个文件/目录到指定的zip文件中。 11 | // Compresses one or more files/directories into a specified zip file. 12 | // 13 | // 参数 filename: 输出的zip文件名。 14 | // Param filename: Name of the output zip file. 15 | // 16 | // 参数 files: 需要添加到zip的文件/目录列表。 17 | // Param files: List of files/directories to add to the zip. 18 | // 19 | // 返回错误信息,成功时返回nil。 20 | // Returns error message, nil on success. 21 | func Create(filename string, files []string) error { 22 | // 创建新zip文件 23 | // Create new zip file 24 | newZipFile, err := os.Create(filename) 25 | if err != nil { 26 | return err 27 | } 28 | defer newZipFile.Close() 29 | 30 | // 创建zip.Writer用于写入压缩内容 31 | // Create zip.Writer for writing compressed content 32 | zipWriter := zip.NewWriter(newZipFile) 33 | defer zipWriter.Close() 34 | 35 | // 循环处理所有输入文件/目录 36 | // Process all input files/directories 37 | for _, file := range files { 38 | if err := AddFileToZip(zipWriter, file); err != nil { 39 | return err 40 | } 41 | } 42 | return nil 43 | } 44 | 45 | // AddFileToZip 将单个文件或目录添加到zip.Writer 46 | // Adds a single file or directory to zip.Writer 47 | func AddFileToZip(zipWriter *zip.Writer, filename string) error { 48 | // 打开目标文件/目录 49 | // Open target file/directory 50 | fileToZip, err := os.Open(filename) 51 | if err != nil { 52 | return err 53 | } 54 | defer fileToZip.Close() 55 | 56 | // 获取文件信息 57 | // Get file information 58 | info, err := fileToZip.Stat() 59 | if err != nil { 60 | return err 61 | } 62 | 63 | // 处理目录类型 64 | // Handle directory type 65 | if info.IsDir() { 66 | return addDirectoryToZip(zipWriter, fileToZip, filename, info) 67 | } 68 | 69 | // 处理普通文件 70 | // Handle regular file 71 | return addRegularFileToZip(zipWriter, filename, info) 72 | } 73 | 74 | // addDirectoryToZip 处理目录添加到zip的逻辑 75 | // Handles directory addition to zip 76 | func addDirectoryToZip(zipWriter *zip.Writer, dir *os.File, path string, info os.FileInfo) error { 77 | // 创建目录头信息 78 | // Create directory header 79 | header, err := zip.FileInfoHeader(info) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | // 规范路径格式并添加目录标识符"/" 85 | // Normalize path format and add directory identifier "/" 86 | header.Name = filepath.ToSlash(filepath.Clean(path)) + "/" 87 | 88 | // 移除可能的绝对路径前缀(安全性考虑) 89 | // Remove possible absolute path prefix (security consideration) 90 | if filepath.IsAbs(header.Name) && len(header.Name) > 0 { 91 | header.Name = header.Name[1:] 92 | } 93 | 94 | // 使用DEFLATE压缩算法(即使目录内容为空) 95 | // Use DEFLATE compression (even for empty directories) 96 | header.Method = zip.Deflate 97 | 98 | // 在zip中创建目录条目 99 | // Create directory entry in zip 100 | if _, err := zipWriter.CreateHeader(header); err != nil { 101 | return err 102 | } 103 | 104 | // 读取目录内容 105 | // Read directory contents 106 | files, err := dir.Readdir(-1) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | // 递归处理子目录/文件 112 | // Recursively process subdirectories/files 113 | for _, file := range files { 114 | fullPath := filepath.Join(path, file.Name()) 115 | if err := AddFileToZip(zipWriter, fullPath); err != nil { 116 | return err 117 | } 118 | } 119 | return nil 120 | } 121 | 122 | // addRegularFileToZip 处理普通文件添加到zip的逻辑 123 | // Handles regular file addition to zip 124 | func addRegularFileToZip(zipWriter *zip.Writer, filename string, info os.FileInfo) error { 125 | // 创建文件头信息 126 | // Create file header 127 | header, err := zip.FileInfoHeader(info) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | // 规范路径格式 133 | // Normalize path format 134 | header.Name = filepath.ToSlash(filepath.Clean(filename)) 135 | 136 | // 移除可能的绝对路径前缀(安全性考虑) 137 | // Remove possible absolute path prefix (security consideration) 138 | if filepath.IsAbs(header.Name) && len(header.Name) > 0 { 139 | header.Name = header.Name[1:] 140 | } 141 | 142 | // 使用DEFLATE压缩算法 143 | // Use DEFLATE compression 144 | header.Method = zip.Deflate 145 | 146 | // 创建zip文件条目写入器 147 | // Create zip entry writer 148 | writer, err := zipWriter.CreateHeader(header) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | // 重新打开文件以保证独立的文件指针 154 | // Reopen file to ensure independent file pointer 155 | file, err := os.Open(filename) 156 | if err != nil { 157 | return err 158 | } 159 | defer file.Close() 160 | 161 | // 使用1MB缓冲区提升大文件复制性能 162 | // Use 1MB buffer to improve large file copy performance 163 | buf := make([]byte, 1<<20) // 1MB buffer 164 | _, err = io.CopyBuffer(writer, file, buf) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /examples/gin/boot/lang/lang.go: -------------------------------------------------------------------------------- 1 | package lang 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/i18n" 6 | "github.com/warysection/antgo/os/alog" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | func Register() { 11 | dateTimeLayouts := map[string]string{ 12 | "en": "January 02, 2006", // 英文格式:月 日, 年 13 | "zh-CN": "2006年01月02日", // 中文格式:年月日 14 | "ja": "2006年01月02日 15時04分05秒", // 日文格式:年月日 时分秒 15 | "fr": "02/01/2006", // 法文格式:日/月/年 16 | "de": "02.01.2006", // 德文格式:日.月.年 17 | } 18 | config := i18n.Config{ 19 | DefaultLang: "en", 20 | TranslationsDir: "./lang", 21 | SupportedLangs: []string{"en", "zh-CN"}, 22 | CacheEnabled: true, 23 | MaxCacheSize: 1000, 24 | DateTimeLayouts: dateTimeLayouts, 25 | } 26 | if err := i18n.New(config); err != nil { 27 | alog.Write.Panic("Init failed: %v", zap.Error(err)) 28 | } 29 | } 30 | 31 | func customPluralRule(lang string, n int, key string, args ...interface{}) string { 32 | switch lang { 33 | case "en": // 英文复数规则 34 | if n == 1 { 35 | return fmt.Sprintf("%s.singular", key) // 单数形式 36 | } 37 | return fmt.Sprintf("%s.plural", key) // 复数形式 38 | case "zh": // 中文没有复数形式 39 | return key 40 | default: // 默认规则 41 | return key 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/gin/boot/redis.go: -------------------------------------------------------------------------------- 1 | package boot 2 | 3 | -------------------------------------------------------------------------------- /examples/gin/boot/serve/serve.go: -------------------------------------------------------------------------------- 1 | package serve 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "github.com/warysection/antgo/examples/gin/boot/lang" 8 | "github.com/warysection/antgo/frame/ant" 9 | "github.com/warysection/antgo/frame/gin_middleware" 10 | _ "github.com/warysection/antgo/frame/serve/gin" 11 | "github.com/warysection/antgo/i18n" 12 | "github.com/warysection/antgo/os/config" 13 | "io/ioutil" 14 | "os" 15 | "time" 16 | ) 17 | 18 | // LoadSrv Load Api service<加载API服务> 19 | func LoadSrv() { 20 | gin.ForceConsoleColor() 21 | 22 | configPath := flag.String("config", "./config/config.toml", "Configuration file path") 23 | 24 | flag.Parse() 25 | 26 | eng := ant.New(*configPath).AddFunc(func() { 27 | lang.Register() 28 | }).Serve(load()) 29 | 30 | defer eng.Close() 31 | } 32 | 33 | func load() *gin.Engine { 34 | var app = gin.New() 35 | //开发者模式 36 | if config.GetBool("system.debug") == false { 37 | gin.SetMode(gin.ReleaseMode) 38 | gin.DefaultWriter = ioutil.Discard 39 | } 40 | app.Use(gin_middleware.Recovery()).Use(gin_middleware.Logger()).Use(i18n.Middleware()) 41 | 42 | app.GET("/", func(c *gin.Context) { 43 | c.JSON(200, gin.H{ 44 | "message": "Hello World!", 45 | }) 46 | }) 47 | 48 | app.GET("/query", func(c *gin.Context) { 49 | var list = make([]map[string]interface{}, 0) 50 | ant.Db().Table("sys_menu").Find(&list) 51 | c.JSON(200, gin.H{ 52 | "list": list, 53 | }) 54 | }) 55 | 56 | app.POST("/error", func(c *gin.Context) { 57 | var test = []int{1, 2, 3} 58 | fmt.Println(test[4]) 59 | c.JSON(200, gin.H{ 60 | "message": "Hello World!", 61 | }) 62 | }) 63 | 64 | app.GET("/lang", func(c *gin.Context) { 65 | testDate := time.Date(2023, 5, 15, 0, 0, 0, 0, time.UTC) 66 | c.JSON(200, gin.H{ 67 | "正常翻译": i18n.T(c, "common.hello", "World"), 68 | "正常翻译2": i18n.T(c, "common.welcome"), 69 | "正常翻译3": i18n.T(c, "nested.key"), 70 | "复数1": i18n.TPlural(c, 1, "plural.apple"), 71 | "复数2": i18n.TPlural(c, 3, "plural.apple"), 72 | "日期": i18n.TDate(c, testDate), 73 | }) 74 | }) 75 | 76 | app.GET("/pid", func(c *gin.Context) { 77 | c.String(200, fmt.Sprintf("%d", os.Getpid())) 78 | }) 79 | return app 80 | 81 | } 82 | -------------------------------------------------------------------------------- /examples/gin/config/config.toml: -------------------------------------------------------------------------------- 1 | #系统配置 2 | [system] 3 | #启动地址 4 | address = "9002" 5 | #是否开启跨域 6 | cors = true 7 | #是否开发调试模式 8 | debug = true 9 | #项目名称 10 | app_name = "antgo" 11 | #goroutines数量 12 | max_pool_count = 1000 13 | #验签密钥 14 | secret = "" 15 | #设置IP路径 16 | ip_path = "resources/ip2region.xdb" 17 | 18 | #接口请求日志 19 | [log] 20 | #路径 21 | path = "./log/app.log" 22 | #输出格式 支持(json、console) 23 | format = "console" 24 | #日志服务名称 25 | service_name = "gin" 26 | #日志输出等级 all、info、warn、error、debug、dpanic、panic、fatal 27 | level = "all" 28 | #是否输出控制台 29 | console = true 30 | #是否开启日志 31 | switch = true 32 | #文件最长保存时间(天) 33 | max_age = 180 34 | #分割大小(MB) 35 | max_size = 10 36 | #保留30个备份(个) 37 | max_backups = 2000 38 | #是否需要压缩 39 | compress = false 40 | #是否开启debug 41 | enable_debug = true 42 | #默认256KB 43 | max_body_size=262144 44 | #header 白名单 45 | #header_whitelist = ["Device-Id", "Authorization", "Accept", "Accept-Language", "Origin", "Referer", "User-Agent"] 46 | header_whitelist = ["Device-Id", "Authorization"] 47 | #数据库设置 48 | [[connections]] 49 | #数据库名称(必须唯一) 50 | name = "mysql" 51 | #数据库类型支持mysql、pgsql、sqlsrv、clickhouse 52 | type = "mysql" 53 | #服务器地址 54 | hostname = "127.0.0.1" 55 | #服务器端口 56 | port = "3306" 57 | #数据库用户名 58 | username = "root" 59 | #数据库密码 60 | password = "root" 61 | #数据库名 62 | database = "antgo" 63 | #数据库连接参数 64 | params = "charset=utf8mb4&parseTime=True&loc=Local" 65 | #是否开启日志 66 | log = true 67 | #设置空闲连接池中的最大连接数 68 | max_idle_conns = 300 69 | #设置数据库的最大打开连接数。 70 | max_open_conns = 3000 71 | #设置连接可能被重用的最大时间(小时)。 72 | conn_max_lifetime = 6 73 | #设置连接最大生命周期(分钟)。 74 | conn_max_idleTime = 30 75 | #日志等级,1=静默;2=错误;3=警告;4=信息 76 | level = 4 77 | 78 | #Redis配置 79 | [[redis]] 80 | name = "redis" 81 | address = "127.0.0.1:6379" 82 | password = "" 83 | db = 0 84 | 85 | #邮箱 86 | [email] 87 | switch = true 88 | to = [''] 89 | from = '' 90 | host = '' 91 | secret = '' 92 | 93 | #图片验证码 94 | [captcha] 95 | #验证码长度 96 | length = 4 97 | #验证码宽度 98 | width = 240 99 | #验证码高度 100 | height = 80 101 | 102 | #阿里云OSS 103 | [oss] 104 | endpoint = "" 105 | key_id = "" 106 | key_secret = "" 107 | bucket = "" 108 | 109 | #Json web token 110 | [jwt] 111 | #过期时间(小时) 112 | exp = 168 113 | #私钥 114 | private_key = "" 115 | #公钥 116 | public_key = "" 117 | -------------------------------------------------------------------------------- /examples/gin/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "nested": { 3 | "key": "Nested Value" 4 | } 5 | } -------------------------------------------------------------------------------- /examples/gin/lang/en.toml: -------------------------------------------------------------------------------- 1 | 2 | [common] 3 | hello = "Hello, %s!" 4 | welcome = "Welcome" 5 | 6 | [plural] 7 | apple = "apple" 8 | "apple.plural" = "%d apples" 9 | 10 | [date] 11 | format = "2006-01-02" 12 | -------------------------------------------------------------------------------- /examples/gin/lang/zh-CN.yaml: -------------------------------------------------------------------------------- 1 | 2 | common: 3 | hello: "你好,%s!" 4 | welcome: "欢迎" 5 | 6 | plural: 7 | apple: "苹果" 8 | "apple.plural": "%d 个苹果" 9 | 10 | date: 11 | format: "2006年01月02日" 12 | -------------------------------------------------------------------------------- /examples/gin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/warysection/antgo/examples/gin/boot/serve" 5 | _ "github.com/warysection/antgo/frame/serve/gin" 6 | ) 7 | 8 | func main() { 9 | serve.LoadSrv() 10 | } 11 | -------------------------------------------------------------------------------- /examples/gin/model/admin.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "time" 6 | ) 7 | 8 | type SysAdminUsers struct { 9 | Id int `gorm:"column:id;primaryKey;autoIncrement;" uri:"id" json:"id" form:"id" comment:"标识"` //标识 10 | Username string `gorm:"column:username" json:"username" form:"username" comment:"用户名"` //用户名 11 | Password string `gorm:"column:password" json:"-" form:"password" comment:"密码"` //密码 12 | NickName string `gorm:"column:nick_name" json:"nick_name" form:"nick_name" comment:"昵称"` //昵称 13 | Phone string `gorm:"column:phone" json:"phone" form:"phone" comment:"手机"` //手机 14 | Email string `gorm:"column:email" json:"email" form:"email" comment:"电子邮件"` //电子邮件 15 | Status int `gorm:"column:status" json:"status" form:"status" comment:"状态:0=禁用;1=启用"` //状态:1=成功;2=失败 16 | CreatedAt time.Time `gorm:"column:created_at" json:"created_at" form:"created_at" comment:"创建时间"` //创建时间 17 | UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" form:"updated_at" comment:"修改时间"` //修改时间 18 | DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"-" form:"deleted_at" comment:"删除时间"` //删除时间 19 | } 20 | 21 | func (SysAdminUsers) TableName() string { 22 | return "antgo.sys_admin_users" 23 | } 24 | -------------------------------------------------------------------------------- /frame/ant/config.go: -------------------------------------------------------------------------------- 1 | package ant 2 | 3 | import ( 4 | "github.com/warysection/antgo/os/alog" 5 | "github.com/warysection/antgo/os/config" 6 | ) 7 | 8 | // GetConfig 获取配置内容 / Get configuration content 9 | func GetConfig(name string) any { 10 | return config.Get(name) 11 | } 12 | 13 | // initLog 根据配置文件初始化日志 / initLog initializes logging according to the configuration file 14 | func initLog() { 15 | // Check if logging is enabled / 检查是否启用了日志功能 16 | if !config.GetBool("log.switch") { 17 | return 18 | } 19 | 20 | // Retrieve the log path from configuration / 从配置中获取日志路径 21 | logPath := config.GetString("log.path") 22 | if logPath == "" { 23 | return // If log path is empty, skip log initialization / 如果日志路径为空则跳过日志初始化 24 | } 25 | 26 | // Cache all log related configuration values for better performance and readability 27 | // 缓存所有日志相关的配置项,提高性能和代码可读性 28 | level := config.GetString("log.level") 29 | serviceName := config.GetString("log.service_name") 30 | maxSize := config.GetInt("log.max_size") 31 | maxAge := config.GetInt("log.max_age") 32 | maxBackups := config.GetInt("log.max_backups") 33 | format := config.GetString("log.format") 34 | console := config.GetBool("log.console") 35 | compress := config.GetBool("log.compress") 36 | 37 | // Initialize the logger with the cached configuration values 38 | // 使用缓存的配置值初始化日志记录器 39 | logger := alog.New(logPath). 40 | SetLevel(level). 41 | SetServiceName(serviceName). 42 | SetMaxSize(maxSize). 43 | SetMaxAge(maxAge). 44 | SetMaxBackups(maxBackups). 45 | SetFormat(format). 46 | SetConsole(console). 47 | SetCompress(compress) 48 | 49 | // Register the logger / 注册日志记录器 50 | logger.Register() 51 | } 52 | -------------------------------------------------------------------------------- /frame/ant/db.go: -------------------------------------------------------------------------------- 1 | package ant 2 | 3 | import ( 4 | "github.com/warysection/antgo/db/adb" 5 | "github.com/warysection/antgo/os/config" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // Db Get database connection 10 | func Db(name ...string) *gorm.DB { 11 | key := "" 12 | 13 | if len(name) > 0 { 14 | key = name[0] 15 | } 16 | 17 | val, ok := adb.Master[key] 18 | if ok { 19 | return val 20 | } else { 21 | key = config.GetString("connections.0.name") 22 | } 23 | 24 | return adb.Master[key] 25 | } 26 | 27 | // CloseDb Close database connection 28 | func CloseDb() { 29 | adb.Close() 30 | } 31 | -------------------------------------------------------------------------------- /frame/ant/log.go: -------------------------------------------------------------------------------- 1 | package ant 2 | 3 | import ( 4 | "github.com/warysection/antgo/os/alog" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | // Log Get Log content 9 | func Log() *zap.Logger { 10 | return alog.Write 11 | } 12 | -------------------------------------------------------------------------------- /frame/ant/redis.go: -------------------------------------------------------------------------------- 1 | package ant 2 | 3 | import ( 4 | "github.com/warysection/antgo/db/aredis" 5 | "github.com/warysection/antgo/os/config" 6 | ) 7 | 8 | // initRedis 9 | func initRedis() { 10 | redis := config.GetMaps("redis") 11 | 12 | if len(redis) > 0 { 13 | aredis.New(redis) 14 | } 15 | } 16 | 17 | // Redis Select a different redis 18 | func Redis(name ...string) *aredis.ClientRedis { 19 | key := "" 20 | 21 | if len(name) > 0 { 22 | key = name[0] 23 | } 24 | 25 | val, ok := aredis.Client[key] 26 | if ok { 27 | return val 28 | } else { 29 | key = config.GetString("redis.0.name") 30 | } 31 | 32 | return aredis.Client[key] 33 | } 34 | -------------------------------------------------------------------------------- /frame/gin_middleware/recover.go: -------------------------------------------------------------------------------- 1 | package gin_middleware 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "github.com/warysection/antgo/os/alog" 9 | "go.uber.org/zap" 10 | "io" 11 | "net/http" 12 | "net/url" 13 | "runtime/debug" 14 | "strings" 15 | ) 16 | 17 | const ( 18 | maxBodySize2 = 1024 * 4096 // 4MB 最大请求体限制 19 | stackTraceSkipNum = 4 // 跳过栈帧数量以精简日志 20 | ) 21 | 22 | // Recovery 异常恢复中间件,捕获 panic 并记录结构化日志 23 | // Recovery middleware to capture panic and log structured context 24 | func Recovery() gin.HandlerFunc { 25 | return func(c *gin.Context) { 26 | defer handlePanic(c) 27 | c.Next() 28 | } 29 | } 30 | 31 | // handlePanic 统一处理 panic 的逻辑 32 | // Unified panic handling logic 33 | func handlePanic(c *gin.Context) { 34 | if err := recover(); err != nil { 35 | // 获取精简的调用栈 36 | // Get simplified stack trace 37 | stack := debug.Stack() 38 | //shortStack := shortenStackTrace(stack, stackTraceSkipNum) 39 | 40 | // 构建请求上下文日志字段 41 | // Build request context log fields 42 | logFields := buildLogFields(c, err, stack) 43 | 44 | // 记录结构化日志 45 | // Log structured context 46 | alog.Write.Error("Recovery from panic", logFields...) 47 | 48 | // 终止请求并返回 500 状态码 49 | // Abort request and return 500 status 50 | c.AbortWithStatus(http.StatusInternalServerError) 51 | } 52 | } 53 | 54 | // buildLogFields 构建日志字段 55 | // Build log fields with request context 56 | func buildLogFields(c *gin.Context, err interface{}, stack []byte) []zap.Field { 57 | request := c.Request 58 | 59 | // 获取安全的请求体解析 60 | // Safely parse request body 61 | bodyParams := parseRequestBody(c) 62 | 63 | // 处理请求路径 64 | // Process request path 65 | path, _ := url.QueryUnescape(request.URL.RequestURI()) 66 | 67 | return []zap.Field{ 68 | zap.String("client_ip", c.ClientIP()), 69 | zap.String("method", request.Method), 70 | zap.String("path", path), 71 | zap.Any("query_params", c.Request.URL.Query()), 72 | zap.Reflect("body_params", bodyParams), 73 | zap.String("panic", fmt.Sprintf("%v", err)), 74 | zap.Any("stack", stack), 75 | zap.String("x-request-id", getRequestID(c)), 76 | } 77 | } 78 | 79 | // parseRequestBody 安全解析请求体 80 | // Safely parse request body with protection 81 | func parseRequestBody(c *gin.Context) interface{} { 82 | // 根据 Content-Type 决定解析策略 83 | // Parse strategy based on Content-Type 84 | contentType := c.ContentType() 85 | 86 | // 限制读取的请求体大小 87 | // Limit request body reading size 88 | body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxBodySize2)) 89 | if err != nil { 90 | return fmt.Sprintf("read body error: %v", err) 91 | } 92 | 93 | // 恢复请求体供后续处理 94 | // Restore body for subsequent processing 95 | c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) 96 | 97 | switch { 98 | case strings.Contains(contentType, "application/json"): 99 | return parseJSONBody(body) 100 | case strings.Contains(contentType, "form-data"), 101 | strings.Contains(contentType, "x-www-form-urlencoded"): 102 | return parseFormData(c) 103 | default: 104 | return string(body) 105 | } 106 | } 107 | 108 | // parseJSONBody 解析 JSON 格式请求体 109 | // Parse JSON request body 110 | func parseJSONBody(body []byte) interface{} { 111 | var params map[string]interface{} 112 | if err := json.Unmarshal(body, ¶ms); err != nil { 113 | return string(body) // 返回原始内容如果解析失败 114 | } 115 | return params 116 | } 117 | 118 | // parseFormData 解析表单数据 119 | // Parse form data 120 | func parseFormData(c *gin.Context) interface{} { 121 | if err := c.Request.ParseForm(); err != nil { 122 | return "parse form error" 123 | } 124 | return c.Request.PostForm 125 | } 126 | 127 | // getRequestID 获取请求唯一标识 128 | // Get request unique identifier 129 | func getRequestID(c *gin.Context) string { 130 | if id := c.GetHeader("X-Request-Id"); id != "" { 131 | return id 132 | } 133 | return "" 134 | } 135 | -------------------------------------------------------------------------------- /frame/gin_middleware/request_id.go: -------------------------------------------------------------------------------- 1 | package gin_middleware 2 | 3 | import ( 4 | "context" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | // WithContextRequestID 设置请求 ID上下文 9 | // WithContextRequestID sets the request ID context 10 | func WithContextRequestID() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | requestID := c.GetHeader("x-request-id") 13 | c.Set("request_id", requestID) 14 | 15 | // 将参数存入标准库的 context.Context 16 | ctx := context.WithValue(c.Request.Context(), "request_id", requestID) 17 | c.Request = c.Request.WithContext(ctx) // 更新请求的上下文 18 | c.Next() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frame/serve/adapter.go: -------------------------------------------------------------------------------- 1 | package serve 2 | 3 | // WebFrameWork is an interface which is used as an adapter of 4 | // framework and goAdmin. It must implement two methods. Use registers 5 | // the routes and the corresponding handlers. Content writes the 6 | // response to the corresponding context of framework. 7 | type WebFrameWork interface { 8 | Name() string 9 | SetApp(app interface{}) error 10 | Run(addr string) 11 | Close() 12 | } 13 | 14 | // BaseAdapter is a base adapter contains some helper functions. 15 | type BaseAdapter struct { 16 | } 17 | -------------------------------------------------------------------------------- /frame/serve/gin/gin.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/gin-gonic/gin" 7 | "github.com/warysection/antgo/frame/ant" 8 | "github.com/warysection/antgo/frame/serve" 9 | "github.com/warysection/antgo/os/alog" 10 | "go.uber.org/zap" 11 | "net/http" 12 | "os" 13 | "os/signal" 14 | "time" 15 | ) 16 | 17 | // Gin 结构体是Gin框架的适配器,用于集成Gin到自定义框架中 18 | // Gin struct is an adapter for Gin framework, integrating Gin into the custom framework. 19 | type Gin struct { 20 | serve.BaseAdapter 21 | ctx *gin.Context 22 | app *gin.Engine 23 | Srv *http.Server 24 | } 25 | 26 | func init() { 27 | ant.Register(new(Gin)) 28 | } 29 | 30 | // Name 返回当前适配器名称 31 | // Name returns the name of the current adapter. 32 | func (g *Gin) Name() string { 33 | return "gin" 34 | } 35 | 36 | // SetApp 设置并验证Gin引擎实例 37 | // SetApp sets and validates the Gin engine instance. 38 | func (g *Gin) SetApp(app interface{}) error { 39 | engine, ok := app.(*gin.Engine) 40 | if !ok { 41 | return errors.New("gin adapter SetApp: invalid parameter type") 42 | } 43 | 44 | // 设置生产模式提升性能 45 | // Set production mode to enhance performance 46 | gin.SetMode(gin.ReleaseMode) 47 | g.app = engine 48 | return nil 49 | } 50 | 51 | // Run 启动HTTP服务(不加载配置服务) 52 | // Run starts the HTTP server (without loading configuration service) 53 | func (g *Gin) Run(addr string) { 54 | // 初始化HTTP服务器配置 55 | // Initialize HTTP server configuration 56 | g.Srv = &http.Server{ 57 | Addr: ":" + addr, 58 | Handler: g.app, 59 | } 60 | 61 | // 输出服务启动信息 62 | // Print service startup information 63 | alog.Write.Info("Service started", 64 | zap.Int("pid", os.Getpid()), 65 | zap.String("address", "http://127.0.0.1"+g.Srv.Addr), 66 | ) 67 | 68 | // 启动异步HTTP服务 69 | // Start asynchronous HTTP service 70 | go func() { 71 | if err := g.Srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { 72 | alog.Write.Fatal("Server startup failed", zap.Error(err)) 73 | } 74 | }() 75 | } 76 | 77 | // Close 实现优雅的HTTP服务关闭 78 | // Close implements graceful shutdown of the HTTP service 79 | func (g *Gin) Close() { 80 | // 创建带缓冲的信号通道 81 | // Create buffered signal channel 82 | quit := make(chan os.Signal, 1) 83 | signal.Notify(quit, os.Interrupt) 84 | 85 | // 阻塞等待关闭信号 86 | // Block waiting for shutdown signal 87 | <-quit 88 | 89 | // 创建带超时的上下文 90 | // Create timeout context 91 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 92 | defer cancel() 93 | 94 | // 执行优雅关闭 95 | // Perform graceful shutdown 96 | if err := g.Srv.Shutdown(ctx); err != nil { 97 | alog.Write.Error("Server shutdown error", zap.Error(err)) 98 | } else { 99 | alog.Write.Info("Service shutdown", zap.Int("pid", os.Getpid())) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /net/ahttp/ahttp_test.go: -------------------------------------------------------------------------------- 1 | // client_test.go 2 | package ahttp 3 | 4 | import ( 5 | "net/http" 6 | "net/http/httptest" 7 | "runtime" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // 测试基础请求 13 | func TestBasicRequest(t *testing.T) { 14 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | w.WriteHeader(http.StatusOK) 16 | })) 17 | defer ts.Close() 18 | 19 | client := New(nil) 20 | resp, err := client.Request().Get(ts.URL) 21 | if err != nil { 22 | t.Fatalf("请求失败: %v", err) 23 | } 24 | 25 | if resp.StatusCode() != http.StatusOK { 26 | t.Errorf("预期状态码200,实际得到 %d", resp.StatusCode()) 27 | } 28 | } 29 | 30 | // 测试超时 31 | func TestTimeout(t *testing.T) { 32 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 | time.Sleep(200 * time.Millisecond) 34 | w.WriteHeader(http.StatusOK) 35 | })) 36 | defer ts.Close() 37 | 38 | client := New(&Config{Timeout: 100 * time.Millisecond}) 39 | _, err := client.Request().Get(ts.URL) 40 | if err == nil { 41 | t.Fatal("预期超时错误,但没有收到错误") 42 | } 43 | 44 | // 简单检查错误信息 45 | if err.Error() != "Get \""+ts.URL+"\": context deadline exceeded" { 46 | t.Errorf("收到意外错误信息: %v", err) 47 | } 48 | } 49 | 50 | // 测试配置初始化 51 | func TestConfigInit(t *testing.T) { 52 | defaultConfig := New(nil).config 53 | expectedIdle := runtime.GOMAXPROCS(0) * 200 54 | if defaultConfig.MaxIdleConnections != expectedIdle { 55 | t.Errorf("预期MaxIdleConnections=%d,实际得到%d", expectedIdle, defaultConfig.MaxIdleConnections) 56 | } 57 | } 58 | 59 | // 测试重试机制 60 | func TestRetry(t *testing.T) { 61 | var attempt int 62 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 | attempt++ 64 | if attempt < 3 { 65 | w.WriteHeader(http.StatusInternalServerError) 66 | return 67 | } 68 | w.WriteHeader(http.StatusOK) 69 | })) 70 | defer ts.Close() 71 | 72 | client := New(&Config{RetryAttempts: 3}) 73 | resp, err := client.Request().Get(ts.URL) 74 | if err != nil { 75 | t.Fatalf("请求失败: %v", err) 76 | } 77 | 78 | if attempt != 3 { 79 | t.Errorf("预期3次尝试,实际尝试%d次", attempt) 80 | } 81 | 82 | if resp.StatusCode() != http.StatusOK { 83 | t.Errorf("最终状态码应为200,实际得到 %d", resp.StatusCode()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /net/awebsocket/websocket.go: -------------------------------------------------------------------------------- 1 | package awebsocket 2 | 3 | import ( 4 | "errors" 5 | "github.com/gorilla/websocket" 6 | "github.com/warysection/antgo/crypto/ahash" 7 | "sync" 8 | ) 9 | 10 | // Connection ... 11 | type Connection struct { 12 | Address string // 客户端地址 13 | AppId string // 登录的平台Id 14 | UserId string // 用户标识,用户登录以后才有 15 | LoginTime uint64 // 登录时间 登录以后才有 16 | FirstTime uint64 // 首次连接事件 17 | HeartbeatTime uint64 // 用户上次心跳时间 18 | TimeOut uint64 // 超时时间 19 | Socket *websocket.Conn // 用户连接 20 | ReadChan chan []byte // 读取消息通道 21 | WriteChan chan []byte // 写入消息发送客户端 22 | CloseChan chan byte // 关闭通道 23 | mutex sync.Mutex // 对关闭上锁 24 | isClosed bool // 防止closeChan被关闭多次 25 | } 26 | 27 | // New ... 28 | func New(socket *websocket.Conn, address string, firstTime uint64) *Connection { 29 | var get = &Connection{ 30 | Address: address, 31 | Socket: socket, 32 | FirstTime: firstTime, 33 | HeartbeatTime: firstTime, 34 | TimeOut: 360, 35 | ReadChan: make(chan []byte, 1000), 36 | WriteChan: make(chan []byte, 1000), 37 | CloseChan: make(chan byte, 1), 38 | } 39 | // 启动读协程 40 | go get.readLoop() 41 | // 启动写协程 42 | go get.writeLoop() 43 | return get 44 | } 45 | 46 | // ReadMessage Read client messages<读取客户端消息> 47 | func (get *Connection) ReadMessage() (data []byte, err error) { 48 | select { 49 | case data = <-get.ReadChan: 50 | case <-get.CloseChan: 51 | err = errors.New("connection is closeed") 52 | } 53 | return 54 | } 55 | 56 | // WriteMessage Write message sending client(写入消息发送客户端) 57 | func (get *Connection) WriteMessage(data []byte) (err error) { 58 | select { 59 | case get.WriteChan <- data: 60 | case <-get.CloseChan: 61 | err = errors.New("connection is closeed") 62 | } 63 | return 64 | } 65 | 66 | // Close Close the connection<关闭连接> 67 | func (get *Connection) Close() { 68 | // 线程安全,可多次调用 69 | get.Socket.Close() 70 | // 利用标记,让closeChan只关闭一次 71 | get.mutex.Lock() 72 | if !get.isClosed { 73 | close(get.CloseChan) 74 | get.isClosed = true 75 | } 76 | defer get.mutex.Unlock() 77 | } 78 | 79 | // readLoop Read the channel message loop<读取通道消息循环> 80 | func (get *Connection) readLoop() { 81 | var ( 82 | data []byte 83 | err error 84 | ) 85 | for { 86 | if _, data, err = get.Socket.ReadMessage(); err != nil { 87 | goto ERR 88 | } 89 | //阻塞在这里,等待inChan有空闲位置 90 | select { 91 | case get.ReadChan <- data: 92 | case <-get.CloseChan: // closeChan 感知 conn断开 93 | goto ERR 94 | } 95 | 96 | } 97 | 98 | ERR: 99 | get.Close() 100 | } 101 | 102 | // writeLoop Writes to the channel message loop<写入通道消息循环> 103 | func (get *Connection) writeLoop() { 104 | var ( 105 | data []byte 106 | err error 107 | ) 108 | for { 109 | select { 110 | case data = <-get.WriteChan: 111 | case <-get.CloseChan: 112 | goto ERR 113 | } 114 | if err = get.Socket.WriteMessage(websocket.TextMessage, data); err != nil { 115 | goto ERR 116 | } 117 | } 118 | ERR: 119 | get.Close() 120 | } 121 | 122 | // SetHeartbeat 设置用户心跳 123 | func (get *Connection) SetHeartbeat(currentTime uint64) { 124 | get.HeartbeatTime = currentTime 125 | return 126 | } 127 | 128 | // SetTimeOut 设置超时时间 129 | func (get *Connection) SetTimeOut(TimeOut uint64) { 130 | get.TimeOut = TimeOut 131 | return 132 | } 133 | 134 | // GetHeartbeat 获取心跳是否超时 135 | func (get *Connection) GetHeartbeat(currentTime uint64) bool { 136 | if get.HeartbeatTime+get.TimeOut <= currentTime { 137 | return true 138 | } 139 | return false 140 | } 141 | 142 | // SetLogin 设置用户登录 143 | func (get *Connection) SetLogin(appId string, userId string, loginTime uint64) { 144 | get.AppId = appId 145 | get.UserId = userId 146 | get.LoginTime = loginTime 147 | get.SetHeartbeat(loginTime) 148 | } 149 | 150 | // GetLogin 获取是否登录 151 | func (get *Connection) GetLogin() bool { 152 | if get.UserId != "" { 153 | return true 154 | } 155 | return false 156 | } 157 | 158 | // Login 用户登录 159 | type Login struct { 160 | AppId string 161 | UserId string 162 | Client *Connection 163 | } 164 | 165 | // GetUserKey 获取用户的Key 166 | func (get *Login) GetUserKey() (key string) { 167 | key = ahash.SHA1(get.AppId + get.UserId) 168 | return 169 | } 170 | -------------------------------------------------------------------------------- /os/acron/acron_test.go: -------------------------------------------------------------------------------- 1 | package acron 2 | 3 | import ( 4 | "github.com/robfig/cron/v3" 5 | "testing" 6 | ) 7 | 8 | // 测试 Crontab 管理器的功能 / Test Crontab manager functionality 9 | func TestCrontab(t *testing.T) { 10 | // 创建新的 Crontab 实例 / Create a new Crontab instance 11 | crontab := New() 12 | 13 | // 定义一个简单的任务函数 / Define a simple job function 14 | job := cron.NewChain().Then(cron.FuncJob(func() {})) 15 | 16 | // 测试添加任务 / Test adding a task 17 | t.Run("AddByID", func(t *testing.T) { 18 | err := crontab.AddByID("task1", "* * * * *", job) 19 | if err != nil { 20 | t.Fatalf("expected no error, got %v", err) 21 | } 22 | 23 | // 确认任务是否被添加 / Ensure the task was added 24 | if !crontab.IsExists("task1") { 25 | t.Fatal("task1 should exist") 26 | } 27 | }) 28 | 29 | // 测试任务ID已存在 / Test adding a task with an existing ID 30 | t.Run("AddByID-Exist", func(t *testing.T) { 31 | err := crontab.AddByID("task1", "* * * * *", job) 32 | if err == nil { 33 | t.Fatal("expected error, got nil") 34 | } 35 | }) 36 | 37 | // 测试删除任务 / Test deleting a task 38 | t.Run("DelByID", func(t *testing.T) { 39 | crontab.DelByID("task1") 40 | 41 | // 确认任务是否已删除 / Ensure the task was deleted 42 | if crontab.IsExists("task1") { 43 | t.Fatal("task1 should not exist") 44 | } 45 | }) 46 | 47 | // 测试清理无效的任务ID / Test cleaning invalid task IDs 48 | t.Run("IDs", func(t *testing.T) { 49 | // 添加无效的任务ID并测试清理 / Add invalid task IDs and test cleanup 50 | invalidID := "task_invalid" 51 | crontab.AddByID(invalidID, "* * * * *", job) 52 | crontab.DelByID(invalidID) 53 | 54 | // 获取有效的任务ID列表 / Get the list of valid task IDs 55 | validIDs := crontab.IDs() 56 | if len(validIDs) > 0 { 57 | t.Fatalf("expected no valid tasks, got %v", validIDs) 58 | } 59 | }) 60 | 61 | // 测试任务启动与停止 / Test starting and stopping the cron engine 62 | t.Run("StartStop", func(t *testing.T) { 63 | // 启动 crontab 引擎 / Start the crontab engine 64 | crontab.Start() 65 | 66 | // 确认 Cron 引擎已启动 / Ensure the Cron engine has started 67 | if !crontab.IsRunning() { 68 | t.Fatal("crontab engine should be running") 69 | } 70 | 71 | // 停止 crontab 引擎 / Stop the crontab engine 72 | crontab.Stop() 73 | 74 | // 确认 Cron 引擎已停止 / Ensure the Cron engine has stopped 75 | if crontab.IsRunning() { 76 | t.Fatal("crontab engine should be stopped") 77 | } 78 | }) 79 | } 80 | 81 | // 测试根据函数添加 cron 任务 / Test adding a cron job by function 82 | func TestAddByFunc(t *testing.T) { 83 | crontab := New() 84 | 85 | // 定义一个简单的任务函数 / Define a simple job function 86 | jobFunc := func() { 87 | // 这里可以做一些简单的日志打印,检查是否执行 / You can log something here to check if it runs 88 | } 89 | 90 | // 测试添加任务 / Test adding the task by function 91 | err := crontab.AddByFunc("task2", "* * * * *", jobFunc) 92 | if err != nil { 93 | t.Fatalf("expected no error, got %v", err) 94 | } 95 | 96 | // 确认任务是否被添加 / Ensure the task was added 97 | if !crontab.IsExists("task2") { 98 | t.Fatal("task2 should exist") 99 | } 100 | } 101 | 102 | // 测试添加无效 cron 任务 / Test adding an invalid cron job 103 | func TestInvalidCronSpec(t *testing.T) { 104 | crontab := New() 105 | 106 | // 使用无效的 cron 表达式 / Use an invalid cron expression 107 | err := crontab.AddByID("task_invalid", "invalid cron spec", cron.NewChain().Then(cron.FuncJob(func() {}))) 108 | if err == nil { 109 | t.Fatal("expected error, got nil") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /os/acsv/csv.go: -------------------------------------------------------------------------------- 1 | package acsv 2 | 3 | import ( 4 | "bufio" 5 | "encoding/csv" 6 | "errors" 7 | "os" 8 | "sync" 9 | ) 10 | 11 | // CSV 操作结构体 12 | type CSV struct { 13 | FileName string 14 | Data [][]string 15 | mu sync.Mutex 16 | } 17 | 18 | // NewCSV 创建CSV实例 19 | func New(fileName string) (*CSV, error) { 20 | c := &CSV{ 21 | FileName: fileName, 22 | } 23 | if err := c.Read(); err != nil { 24 | return nil, err 25 | } 26 | return c, nil 27 | } 28 | 29 | // Create 创建目录 30 | func (c *CSV) Create() error { 31 | c.mu.Lock() 32 | defer c.mu.Unlock() 33 | 34 | file, err := os.Create(c.FileName) //创建文件 35 | if err != nil { 36 | return err 37 | } 38 | defer file.Close() 39 | 40 | if _, err := file.WriteString("\xEF\xBB\xBF"); err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | // Read 读取CSV文件内容 47 | func (c *CSV) Read() error { 48 | c.mu.Lock() 49 | defer c.mu.Unlock() 50 | 51 | file, err := os.OpenFile(c.FileName, os.O_RDWR|os.O_CREATE, 0644) 52 | if err != nil { 53 | return err 54 | } 55 | defer file.Close() 56 | 57 | reader := csv.NewReader(bufio.NewReader(file)) 58 | data, err := reader.ReadAll() 59 | if err != nil { 60 | return err 61 | } 62 | c.Data = data 63 | return nil 64 | } 65 | 66 | // Write 写入CSV文件内容 67 | func (c *CSV) Write() error { 68 | c.mu.Lock() 69 | defer c.mu.Unlock() 70 | 71 | file, err := os.OpenFile(c.FileName, os.O_RDWR|os.O_CREATE, 0644) 72 | if err != nil { 73 | return err 74 | } 75 | defer file.Close() 76 | 77 | writer := csv.NewWriter(file) 78 | err = writer.WriteAll(c.Data) 79 | if err != nil { 80 | return err 81 | } 82 | writer.Flush() 83 | return nil 84 | } 85 | 86 | // AddRow 添加一行记录 87 | func (c *CSV) AddRow(row []string) *CSV { 88 | c.mu.Lock() 89 | defer c.mu.Unlock() 90 | 91 | c.Data = append(c.Data, row) 92 | return c 93 | } 94 | 95 | // UpdateRow 更新一行记录 96 | func (c *CSV) UpdateRow(index int, row []string) *CSV { 97 | c.mu.Lock() 98 | defer c.mu.Unlock() 99 | 100 | c.Data[index] = row 101 | return c 102 | } 103 | 104 | // DeleteRow 删除一行记录 105 | func (c *CSV) DeleteRow(index int) error { 106 | c.mu.Lock() 107 | defer c.mu.Unlock() 108 | if index < 0 || index >= len(c.Data) { 109 | return errors.New("index out of range") 110 | } 111 | c.Data = append(c.Data[:index], c.Data[index+1:]...) 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /test/atime_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/os/atime" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // TestUTC 测试 UTC 方法 10 | func TestUTC(t *testing.T) { 11 | localTime := time.Date(2023, 10, 5, 12, 0, 0, 0, time.Local) 12 | times := &atime.Times{Time: localTime} 13 | 14 | utcTime := times.UTC() 15 | expected := localTime.UTC() 16 | 17 | if !utcTime.Time.Equal(expected) { 18 | t.Errorf("UTC() failed, expected %v, got %v", expected, utcTime.Time) 19 | } 20 | } 21 | 22 | // TestTimestamp 测试 Timestamp 方法 23 | func TestTimestamp(t *testing.T) { 24 | now := time.Now() 25 | times := &atime.Times{Time: now} 26 | 27 | timestamp := times.Timestamp() 28 | expected := now.Unix() 29 | 30 | if timestamp != expected { 31 | t.Errorf("Timestamp() failed, expected %d, got %d", expected, timestamp) 32 | } 33 | } 34 | 35 | // TestAdd 测试 Add 方法 36 | func TestAdd(t *testing.T) { 37 | now := time.Now() 38 | times := &atime.Times{Time: now} 39 | 40 | duration := time.Hour * 2 41 | newTime := times.Add(duration) 42 | expected := now.Add(duration) 43 | 44 | if !newTime.Time.Equal(expected) { 45 | t.Errorf("Add() failed, expected %v, got %v", expected, newTime.Time) 46 | } 47 | } 48 | 49 | // TestStartOfDay 测试 StartOfDay 方法 50 | func TestStartOfDay(t *testing.T) { 51 | now := time.Date(2023, 10, 5, 15, 30, 45, 0, time.Local) 52 | times := &atime.Times{Time: now} 53 | 54 | startOfDay := times.StartOfDay() 55 | expected := time.Date(2023, 10, 5, 0, 0, 0, 0, time.Local) 56 | 57 | if !startOfDay.Time.Equal(expected) { 58 | t.Errorf("StartOfDay() failed, expected %v, got %v", expected, startOfDay.Time) 59 | } 60 | } 61 | 62 | // TestFormat 测试 Format 方法 63 | func TestFormat(t *testing.T) { 64 | now := time.Date(2023, 10, 5, 15, 30, 45, 0, time.Local) 65 | times := &atime.Times{Time: now} 66 | 67 | formatted := times.Format("yyyy-MM-dd HH:mm:ss") 68 | expected := "2023-10-05 15:30:45" 69 | 70 | if formatted != expected { 71 | t.Errorf("Format() failed, expected %s, got %s", expected, formatted) 72 | } 73 | } 74 | 75 | // TestStrToTime 测试 StrToTime 方法 76 | func TestStrToTime(t *testing.T) { 77 | str := "2023-10-05 15:30:45" 78 | times := atime.StrToTime(str) 79 | expected := time.Date(2023, 10, 5, 15, 30, 45, 0, time.Local) 80 | 81 | if !times.Time.Equal(expected) { 82 | t.Errorf("StrToTime() failed, expected %v, got %v", expected, times.Time) 83 | } 84 | } 85 | 86 | // TestNewFromTimeStamp 测试 NewFromTimeStamp 方法 87 | func TestNewFromTimeStamp(t *testing.T) { 88 | timestamp := int64(1696527045) // 对应 2023-10-05 15:30:45 UTC 89 | times := atime.NewFromTimeStamp(timestamp) 90 | expected := time.Unix(timestamp, 0) 91 | 92 | if !times.Time.Equal(expected) { 93 | t.Errorf("NewFromTimeStamp() failed, expected %v, got %v", expected, times.Time) 94 | } 95 | } 96 | 97 | // TestEndOfMonth 测试 EndOfMonth 方法 98 | func TestEndOfMonth(t *testing.T) { 99 | now := time.Date(2023, 10, 5, 15, 30, 45, 0, time.Local) 100 | times := &atime.Times{Time: now} 101 | 102 | endOfMonth := times.EndOfMonth() 103 | expected := time.Date(2023, 10, 31, 23, 59, 59, int(time.Second-time.Nanosecond), time.Local) 104 | 105 | if !endOfMonth.Time.Equal(expected) { 106 | t.Errorf("EndOfMonth() failed, expected %v, got %v", expected, endOfMonth.Time) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test/base64_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/abase64" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestBase64(t *testing.T) { 10 | for i := 0; i < 100; i++ { 11 | var str1 = abase64.Encode([]byte("test")) 12 | log.Println(str1) 13 | var str2, _ = abase64.Decode(str1) 14 | log.Println(string(str2)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/binary_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/encoding/abinary" 6 | "testing" 7 | ) 8 | 9 | func TestBinary(t *testing.T) { 10 | //序列化 11 | var dataA uint64 = 6010 12 | byteA, _ := abinary.Encode(dataA) 13 | 14 | fmt.Println("序列化后:", byteA) 15 | 16 | //反序列化 17 | var res uint64 18 | abinary.Decode(byteA, &res) 19 | 20 | fmt.Println("反序列化后:", res) 21 | } 22 | -------------------------------------------------------------------------------- /test/bool_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/conv" 5 | "testing" 6 | ) 7 | 8 | func TestBool(t *testing.T) { 9 | tests := []struct { 10 | input interface{} 11 | expected bool 12 | }{ 13 | {nil, false}, // nil should return false 14 | {true, true}, // true should return true 15 | {false, false}, // false should return false 16 | {"true", true}, // "true" should return true 17 | {"false", false}, // "false" should return false 18 | {"anything", true}, // non-"false" string should return true 19 | {[]byte("true"), true}, // []byte("true") should return true 20 | {[]byte("false"), false}, // []byte("false") should return false 21 | {[]byte("anything"), true}, // non-"false" []byte should return true 22 | } 23 | 24 | for _, tt := range tests { 25 | t.Run("", func(t *testing.T) { 26 | result := conv.Bool(tt.input) 27 | if result != tt.expected { 28 | t.Errorf("Bool(%v) = %v; expected %v", tt.input, result, tt.expected) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/charset_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/acharset" 5 | "github.com/warysection/antgo/utils/conv" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | var testData = []struct { 11 | utf8, other, otherEncoding string 12 | }{ 13 | {"Résumé", "Résumé", "utf-8"}, 14 | //{"Résumé", "R\xe9sum\xe9", "latin-1"}, 15 | {"これは漢字です。", "S0\x8c0o0\"oW[g0Y0\x020", "UTF-16LE"}, 16 | {"これは漢字です。", "0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16BE"}, 17 | {"これは漢字です。", "\xfe\xff0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16"}, 18 | {"𝄢𝄞𝄪𝄫", "\xfe\xff\xd8\x34\xdd\x22\xd8\x34\xdd\x1e\xd8\x34\xdd\x2a\xd8\x34\xdd\x2b", "UTF-16"}, 19 | //{"Hello, world", "Hello, world", "ASCII"}, 20 | {"Gdańsk", "Gda\xf1sk", "ISO-8859-2"}, 21 | {"Ââ Čč Đđ Ŋŋ Õõ Šš Žž Åå Ää", "\xc2\xe2 \xc8\xe8 \xa9\xb9 \xaf\xbf \xd5\xf5 \xaa\xba \xac\xbc \xc5\xe5 \xc4\xe4", "ISO-8859-10"}, 22 | //{"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "ISO-8859-11"}, 23 | {"latviešu", "latvie\xf0u", "ISO-8859-13"}, 24 | {"Seònaid", "Se\xf2naid", "ISO-8859-14"}, 25 | {"€1 is cheap", "\xa41 is cheap", "ISO-8859-15"}, 26 | {"românește", "rom\xe2ne\xbate", "ISO-8859-16"}, 27 | {"nutraĵo", "nutra\xbco", "ISO-8859-3"}, 28 | {"Kalâdlit", "Kal\xe2dlit", "ISO-8859-4"}, 29 | {"русский", "\xe0\xe3\xe1\xe1\xda\xd8\xd9", "ISO-8859-5"}, 30 | {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "ISO-8859-7"}, 31 | {"Kağan", "Ka\xf0an", "ISO-8859-9"}, 32 | {"Résumé", "R\x8esum\x8e", "macintosh"}, 33 | {"Gdańsk", "Gda\xf1sk", "windows-1250"}, 34 | {"русский", "\xf0\xf3\xf1\xf1\xea\xe8\xe9", "windows-1251"}, 35 | {"Résumé", "R\xe9sum\xe9", "windows-1252"}, 36 | {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "windows-1253"}, 37 | {"Kağan", "Ka\xf0an", "windows-1254"}, 38 | {"עִבְרִית", "\xf2\xc4\xe1\xc0\xf8\xc4\xe9\xfa", "windows-1255"}, 39 | {"العربية", "\xc7\xe1\xda\xd1\xc8\xed\xc9", "windows-1256"}, 40 | {"latviešu", "latvie\xf0u", "windows-1257"}, 41 | {"Việt", "Vi\xea\xf2t", "windows-1258"}, 42 | {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "windows-874"}, 43 | {"русский", "\xd2\xd5\xd3\xd3\xcb\xc9\xca", "KOI8-R"}, 44 | {"українська", "\xd5\xcb\xd2\xc1\xa7\xce\xd3\xd8\xcb\xc1", "KOI8-U"}, 45 | {"Hello 常用國字標準字體表", "Hello \xb1`\xa5\u03b0\xea\xa6r\xbc\u0437\u01e6r\xc5\xe9\xaa\xed", "big5"}, 46 | {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gbk"}, 47 | {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gb18030"}, 48 | {"花间一壶酒,独酌无相亲。", "~{;(F#,6@WCN^O`GW!#", "GB2312"}, 49 | {"花间一壶酒,独酌无相亲。", "~{;(F#,6@WCN^O`GW!#", "HZGB2312"}, 50 | {"עִבְרִית", "\x81\x30\xfb\x30\x81\x30\xf6\x34\x81\x30\xf9\x33\x81\x30\xf6\x30\x81\x30\xfb\x36\x81\x30\xf6\x34\x81\x30\xfa\x31\x81\x30\xfb\x38", "gb18030"}, 51 | {"㧯", "\x82\x31\x89\x38", "gb18030"}, 52 | {"㧯", "㧯", "UTF-8"}, 53 | //{"これは漢字です。", "\x82\xb1\x82\xea\x82\xcd\x8a\xbf\x8e\x9a\x82\xc5\x82\xb7\x81B", "SJIS"}, 54 | {"これは漢字です。", "\xa4\xb3\xa4\xec\xa4\u03f4\xc1\xbb\xfa\xa4\u01e4\xb9\xa1\xa3", "EUC-JP"}, 55 | } 56 | 57 | func TestCharset(t *testing.T) { 58 | for _, data := range testData { 59 | str, _ := acharset.Decode(data.other, data.otherEncoding) 60 | log.Println(conv.String(str)) 61 | } 62 | } 63 | 64 | func TestCharset2(t *testing.T) { 65 | for _, data := range testData { 66 | str, _ := acharset.Decode(data.utf8, data.otherEncoding) 67 | log.Println(conv.String(str)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/config.toml: -------------------------------------------------------------------------------- 1 | #系统配置 2 | [system] 3 | #启动地址 4 | address = "9002" 5 | #权限配置路径 6 | rbac_path = "./config/rbac.conf" 7 | #是否开启跨域 8 | cors = true 9 | #是否开发调试模式 10 | debug = true 11 | #默认连接 12 | default_connections = "mysql1" 13 | #权限配置文件目录 14 | [casbin] 15 | path = "rbac.conf" 16 | 17 | #接口请求日志 18 | [log] 19 | #路径 20 | path = "./log/ant.log" 21 | #格式 json、console 22 | format = "console" 23 | #日志服务名称 24 | service_name = "antgo" 25 | #日志输出等级 all、info、warn、error、debug、dpanic、panic、fatal 26 | level = "all" 27 | #是否输出控制台 28 | console = true 29 | #是否开启日志 30 | switch = true 31 | #文件最长保存时间(天) 32 | max_age = 30 33 | #分割大小(MB) 34 | max_size = 1 35 | #保留30个备份(个) 36 | max_backups = 300 37 | #是否需要压缩 38 | compress = false 39 | 40 | #数据库设置1 41 | [[connections]] 42 | #数据库名称 43 | name = "mysql1" 44 | #数据库类型 45 | type = "mysql" 46 | #服务器地址 47 | hostname = "127.0.0.1" 48 | #服务器端口 49 | port = "3306" 50 | #数据库用户名 51 | username = "root" 52 | #数据库密码 53 | password = "root" 54 | #数据库名 55 | database = "test" 56 | #数据库连接参数 57 | params = "charset=utf8mb4&parseTime=True&loc=Local" 58 | #是否开启日志 59 | log = true 60 | 61 | #数据库设置1 62 | [[connections]] 63 | #数据库名称 64 | name = "mysql2" 65 | #数据库类型 66 | type = "mysql" 67 | #服务器地址 68 | hostname = "127.0.0.1" 69 | #服务器端口 70 | port = "3306" 71 | #数据库用户名 72 | username = "root" 73 | #数据库密码 74 | password = "root" 75 | #数据库名 76 | database = "test" 77 | #数据库连接参数 78 | params = "charset=utf8mb4&parseTime=True&loc=Local" 79 | #是否开启日志 80 | log = true 81 | 82 | #阿里云配置 83 | [oss] 84 | key_id = "" 85 | key_secret = "" 86 | endpoint = "" 87 | bucket = "" 88 | 89 | #Redis配置 90 | [[redis]] 91 | name="redis1" 92 | address = "localhost:6379" 93 | password = "" 94 | db = 0 95 | 96 | #Redis配置 97 | [[redis]] 98 | name="redis2" 99 | address = "localhost:6379" 100 | password = "" 101 | db = 0 102 | 103 | #邮箱警报发送 104 | [emaill] 105 | switch = true 106 | to = ['56494565@qq.com'] 107 | from = '56494565@qq.com' 108 | host = 'smtp.qq.com:25' 109 | secret = 'fdtshicbbvybbiic' 110 | 111 | #Json web token 112 | [jwt] 113 | private_key = """-----BEGIN PRIVATE KEY----- 114 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMMBf8hUaDk+oCsQ 115 | ecIJDrRt+EktQaGE+11Un1YWgjelgJR68JcS+2HsSTjkd+aUYFuy7uo7Jc0ugQqN 116 | xTwPzyJXfIX0J3niM0MM4SNLiqGD+UYGJ3bbBiw33NPT4CQ0/ATKk7Y4kdGXWl9z 117 | OMqV6YqWau88SpFAUENHj1rJrFKrAgMBAAECgYBEFSTo61dMDSpcfq8T6JeitPZH 118 | ji5o1wXvqtjKdKdYCEdhD58qD62GnblezJ1z+n+95DX3v1jOTxssdRzUgGx/Ys13 119 | Ukso6hzNgMOw46zwd8qoWuFuytWWc953FGsr+atWFrvfU8aEjBjWzhlTtFVRPaiS 120 | 42zS7OZzYdmCw/PJaQJBAPu9R23SePtKIufwkg9ejWKyfIwJfLfdrfPL3RSTryGy 121 | ph6CahWcr19/fMRTJaTYQtfxnXc1quFow3X+IBgfMr0CQQDGTmibUOJGyVrjSNMe 122 | dTntWaBjMdFRwNdl7EzgUuLePUFs0gbZ6SW5dMsK/3JfzyxYF3XRki7Lupju3Ano 123 | RGWHAkAN/lCJJ0j4Vv+nuvSzjAL5+If51NEs+1KfGbb5XNhAXEjlq0QwXVxWR6Ts 124 | 2N5f0nGsxU6GgOI103gCCBVKoflVAkBvBzVwSE/4XAJEIOD7O50MM9Ml1p2gjTzM 125 | Nwovyph0340C9XCajvvtIuQPq0gJNoBYbgIsLRGARWAc1BvD7I9/AkEAgQEnpQEI 126 | isXUlyKSsakm+M+hzkoJxlizUiM3tN9cIfsIBXdWv9LNGRp2gl8Sa69ri3EdqQXv 127 | 0PcStMOn2IX1kw== 128 | -----END PRIVATE KEY-----""" 129 | public_key = """-----BEGIN PUBLIC KEY----- 130 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDAX/IVGg5PqArEHnCCQ60bfhJ 131 | LUGhhPtdVJ9WFoI3pYCUevCXEvth7Ek45HfmlGBbsu7qOyXNLoEKjcU8D88iV3yF 132 | 9Cd54jNDDOEjS4qhg/lGBid22wYsN9zT0+AkNPwEypO2OJHRl1pfczjKlemKlmrv 133 | PEqRQFBDR49ayaxSqwIDAQAB 134 | -----END PUBLIC KEY-----""" -------------------------------------------------------------------------------- /test/config.yaml: -------------------------------------------------------------------------------- 1 | en: 2 | hello: 你好s22 -------------------------------------------------------------------------------- /test/config2.toml: -------------------------------------------------------------------------------- 1 | [zh] 2 | hello2 = "11111" 3 | [en] 4 | hello2 = "22222" -------------------------------------------------------------------------------- /test/config_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/os/config" 6 | "testing" 7 | ) 8 | 9 | func TestConfig(t *testing.T) { 10 | err := config.New().SetType("toml").AddRemoteProvider("etcd3", "http://127.0.0.1:2379", "/local/common.toml") 11 | if err != nil { 12 | fmt.Println(err) 13 | } 14 | fmt.Println(config.Get("app")) 15 | } 16 | -------------------------------------------------------------------------------- /test/conv_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/conv" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestRune(t *testing.T) { 10 | tests := []struct { 11 | input interface{} 12 | expected rune 13 | }{ 14 | {rune('a'), 'a'}, 15 | {int(97), 'a'}, 16 | {int8(97), 'a'}, 17 | {int16(97), 'a'}, 18 | {int64(97), 'a'}, 19 | {nil, 0}, // Edge case for nil 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run("TestRune", func(t *testing.T) { 24 | result := conv.Rune(test.input) 25 | if result != test.expected { 26 | t.Errorf("expected %v, got %v", test.expected, result) 27 | } 28 | }) 29 | } 30 | } 31 | 32 | func TestRunes(t *testing.T) { 33 | tests := []struct { 34 | input interface{} 35 | expected []rune 36 | }{ 37 | {"hello", []rune{'h', 'e', 'l', 'l', 'o'}}, 38 | {[]rune{'h', 'e', 'l', 'l', 'o'}, []rune{'h', 'e', 'l', 'l', 'o'}}, 39 | {123, []rune("123")}, // Integer as string 40 | {nil, []rune("")}, // Edge case for nil 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run("TestRunes", func(t *testing.T) { 45 | result := conv.Runes(test.input) 46 | if len(result) != len(test.expected) { 47 | t.Errorf("expected %v, got %v", test.expected, result) 48 | } 49 | for i, r := range result { 50 | if r != test.expected[i] { 51 | t.Errorf("at index %d: expected %v, got %v", i, test.expected[i], r) 52 | } 53 | } 54 | }) 55 | } 56 | } 57 | 58 | func TestByte(t *testing.T) { 59 | tests := []struct { 60 | input interface{} 61 | expected byte 62 | }{ 63 | {byte(97), 97}, 64 | {int(97), 97}, 65 | {int8(97), 97}, 66 | {int16(97), 97}, 67 | {int64(97), 97}, 68 | {nil, 0}, // Edge case for nil 69 | } 70 | 71 | for _, test := range tests { 72 | t.Run("TestByte", func(t *testing.T) { 73 | result := conv.Byte(test.input) 74 | if result != test.expected { 75 | t.Errorf("expected %v, got %v", test.expected, result) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestBytes(t *testing.T) { 82 | tests := []struct { 83 | input interface{} 84 | expected []byte 85 | }{ 86 | {"hello", []byte("hello")}, 87 | {123, []byte{0, 0, 0, 123}}, // Example byte slice for int 88 | {3.14, []byte{0xC0, 0x48, 0xF5, 0x3F}}, // Example byte slice for float64 89 | {nil, nil}, // Edge case for nil 90 | } 91 | 92 | for _, test := range tests { 93 | t.Run("TestBytes", func(t *testing.T) { 94 | result := conv.Bytes(test.input) 95 | if len(result) != len(test.expected) { 96 | t.Errorf("expected %v, got %v", test.expected, result) 97 | } 98 | for i, b := range result { 99 | if b != test.expected[i] { 100 | t.Errorf("at index %d: expected %v, got %v", i, test.expected[i], b) 101 | } 102 | } 103 | }) 104 | } 105 | } 106 | 107 | func TestString(t *testing.T) { 108 | tests := []struct { 109 | input interface{} 110 | expected string 111 | }{ 112 | {123, "123"}, 113 | {3.14, "3.14"}, 114 | {"hello", "hello"}, 115 | {time.Now().Format(time.RFC3339), time.Now().Format(time.RFC3339)}, 116 | {nil, ""}, // Edge case for nil 117 | } 118 | 119 | for _, test := range tests { 120 | t.Run("TestString", func(t *testing.T) { 121 | result := conv.String(test.input) 122 | if result != test.expected { 123 | t.Errorf("expected %v, got %v", test.expected, result) 124 | } 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /test/cron_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/os/acron" 5 | "log" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | type testTask struct { 11 | } 12 | 13 | func (t *testTask) Run() { 14 | log.Println("hello world2") 15 | 16 | } 17 | func TestCron(t *testing.T) { 18 | crontab := acron.New() 19 | // 实现接口的方式添加定时任务 20 | task := &testTask{} 21 | log.Println(111) 22 | if err := crontab.AddByID("1", "*/5 * * * * ?", task); err != nil { 23 | log.Printf("error to add crontab task:%s", err) 24 | os.Exit(-1) 25 | } 26 | // 添加函数作为定时任务 27 | taskFunc := func() { 28 | log.Println("hello world") 29 | } 30 | if err := crontab.AddByFunc("2", "0 */1 * * * ?", taskFunc); err != nil { 31 | log.Printf("error to add crontab task:%s", err) 32 | os.Exit(-1) 33 | } 34 | crontab.Start() 35 | defer crontab.Stop() 36 | select {} 37 | } 38 | -------------------------------------------------------------------------------- /test/csv_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/os/acsv" 5 | "github.com/warysection/antgo/utils/conv" 6 | "testing" 7 | ) 8 | 9 | func TestCsv(t *testing.T) { 10 | c, _ := acsv.New("test.csv") 11 | c2, _ := acsv.New("test2.csv") 12 | 13 | for i := 0; i < 40000; i++ { 14 | c.AddRow([]string{conv.String(i), "John Doe", "john.doe@example.com"}) 15 | c2.AddRow([]string{conv.String(i), "John Doe2", "john.doe@example.com2"}) 16 | } 17 | c.Write() 18 | c2.Write() 19 | } 20 | -------------------------------------------------------------------------------- /test/done/test.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /test/done/test2.txt: -------------------------------------------------------------------------------- 1 | test2222 -------------------------------------------------------------------------------- /test/email_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/aemail" 6 | "testing" 7 | ) 8 | 9 | func TestEmail(t *testing.T) { 10 | mailer := aemail.NewMailer("56494565@qq.com", "fdtshicbbvybbiic") 11 | err := mailer.QuickSend( 12 | []string{"56494565@qq.com"}, 13 | "测试邮件", 14 | "这是一封测试邮件", 15 | ) 16 | fmt.Println(err) 17 | } 18 | -------------------------------------------------------------------------------- /test/float_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/conv" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | // TestFloat32 测试 Float32 函数 10 | func TestFloat32(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | input interface{} 14 | expected float32 15 | }{ 16 | // 正常输入 17 | {"nil input", nil, 0}, 18 | {"float32 input", float32(3.14), 3.14}, 19 | {"float64 input", float64(3.141592653589793), float32(3.1415927)}, // float64 转 float32 会丢失精度 20 | {"string input", "3.14", 3.14}, 21 | {"integer string input", "42", 42}, 22 | {"byte slice input", []byte{0xdb, 0x0f, 0x49, 0x40}, float32(3.1415927)}, // 小端序 IEEE 754 格式 23 | 24 | // 边界条件 25 | {"empty string input", "", 0}, 26 | {"invalid string input", "abc", 0}, 27 | {"large integer string input", "12345678901234567890", float32(12345678901234567890)}, // 精度丢失 28 | {"small byte slice input", []byte{0x01}, 0}, // 长度不足,触发 panic 29 | 30 | // 特殊值 31 | {"float32 max value", float32(math.MaxFloat32), float32(math.MaxFloat32)}, 32 | {"float32 min value", float32(math.SmallestNonzeroFloat32), float32(math.SmallestNonzeroFloat32)}, 33 | {"float64 min value", float64(math.SmallestNonzeroFloat64), 0}, // 超出 float32 范围 34 | } 35 | 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | defer func() { 39 | if r := recover(); r != nil { 40 | if tt.name != "small byte slice input" { 41 | t.Errorf("unexpected panic: %v", r) 42 | } 43 | } 44 | }() 45 | 46 | actual := conv.Float32(tt.input) 47 | if actual != tt.expected { 48 | t.Errorf("Float32(%v) = %v, expected %v", tt.input, actual, tt.expected) 49 | } 50 | }) 51 | } 52 | } 53 | 54 | // TestFloat64 测试 Float64 函数 55 | func TestFloat64(t *testing.T) { 56 | tests := []struct { 57 | name string 58 | input interface{} 59 | expected float64 60 | }{ 61 | // 正常输入 62 | {"nil input", nil, 0}, 63 | {"float64 input", float64(3.141592653589793), 3.141592653589793}, 64 | {"string input", "3.141592653589793", 3.141592653589793}, 65 | {"integer string input", "42", 42}, 66 | {"byte slice input", []byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, 3.141592653589793}, // 小端序 IEEE 754 格式 67 | 68 | // 边界条件 69 | {"empty string input", "", 0}, 70 | {"invalid string input", "abc", 0}, 71 | {"large integer string input", "12345678901234567890", 12345678901234567890}, 72 | {"small byte slice input", []byte{0x01}, 0}, // 长度不足,触发 panic 73 | 74 | // 特殊值 75 | {"float64 max value", float64(math.MaxFloat64), math.MaxFloat64}, 76 | {"float64 min value", float64(math.SmallestNonzeroFloat64), math.SmallestNonzeroFloat64}, 77 | {"float32 max value", float32(math.MaxFloat32), float64(math.MaxFloat32)}, 78 | {"float32 min value", float32(math.SmallestNonzeroFloat32), float64(math.SmallestNonzeroFloat32)}, 79 | } 80 | 81 | for _, tt := range tests { 82 | t.Run(tt.name, func(t *testing.T) { 83 | defer func() { 84 | if r := recover(); r != nil { 85 | if tt.name != "small byte slice input" { 86 | t.Errorf("unexpected panic: %v", r) 87 | } 88 | } 89 | }() 90 | 91 | actual := conv.Float64(tt.input) 92 | if actual != tt.expected { 93 | t.Errorf("Float64(%v) = %v, expected %v", tt.input, actual, tt.expected) 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /test/gob_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/conv" 5 | "testing" 6 | ) 7 | 8 | // 测试数据结构 / Test data structure 9 | type testData2 struct { 10 | Name string 11 | Value int 12 | } 13 | 14 | // TestGobEncoder_Normal 测试正常编码场景 15 | // TestGobEncoder_Normal tests normal encoding scenarios 16 | func TestGobEncoder_Normal(t *testing.T) { 17 | data := testData2{ 18 | Name: "test", 19 | Value: 123, 20 | } 21 | 22 | encoded, err := conv.GobEncoder(data) 23 | if err != nil { 24 | t.Fatalf("GobEncoder failed: %v", err) 25 | } 26 | 27 | if len(encoded) == 0 { 28 | t.Error("Encoded data should not be empty") 29 | } 30 | } 31 | 32 | // TestGobDecoder_Normal 测试正常解码场景 33 | // TestGobDecoder_Normal tests normal decoding scenarios 34 | func TestGobDecoder_Normal(t *testing.T) { 35 | // 准备测试数据 / Prepare test data 36 | src := testData2{ 37 | Name: "test", 38 | Value: 123, 39 | } 40 | 41 | encoded, err := conv.GobEncoder(src) 42 | if err != nil { 43 | t.Fatalf("GobEncoder failed: %v", err) 44 | } 45 | 46 | // 解码测试 / Decoding test 47 | var dest testData2 48 | err = conv.GobDecoder(encoded, &dest) 49 | if err != nil { 50 | t.Fatalf("GobDecoder failed: %v", err) 51 | } 52 | 53 | // 验证解码结果 / Verify decoding result 54 | if dest.Name != src.Name || dest.Value != src.Value { 55 | t.Errorf("Decoded data mismatch, got: %+v, want: %+v", dest, src) 56 | } 57 | } 58 | 59 | // TestGobEncoder_NilData 测试空数据编码 60 | // TestGobEncoder_NilData tests encoding nil data 61 | func TestGobEncoder_NilData(t *testing.T) { 62 | _, err := conv.GobEncoder(nil) 63 | if err == nil { 64 | t.Error("Expected error for nil data") 65 | } 66 | if err != conv.ErrNilData { 67 | t.Errorf("Expected ErrNilData, got: %v", err) 68 | } 69 | } 70 | 71 | // TestGobDecoder_NilData 测试空数据解码 72 | // TestGobDecoder_NilData tests decoding nil data 73 | func TestGobDecoder_NilData(t *testing.T) { 74 | var dest testData2 75 | err := conv.GobDecoder(nil, &dest) 76 | if err == nil { 77 | t.Error("Expected error for nil data") 78 | } 79 | if err != conv.ErrNilData { 80 | t.Errorf("Expected ErrNilData, got: %v", err) 81 | } 82 | } 83 | 84 | // TestGobDecoder_NilTarget 测试空目标对象解码 85 | // TestGobDecoder_NilTarget tests decoding with nil target 86 | func TestGobDecoder_NilTarget(t *testing.T) { 87 | src := testData2{ 88 | Name: "test", 89 | Value: 123, 90 | } 91 | 92 | encoded, err := conv.GobEncoder(src) 93 | if err != nil { 94 | t.Fatalf("GobEncoder failed: %v", err) 95 | } 96 | 97 | err = conv.GobDecoder(encoded, nil) 98 | if err == nil { 99 | t.Error("Expected error for nil target") 100 | } 101 | if err != conv.ErrNilTarget { 102 | t.Errorf("Expected ErrNilTarget, got: %v", err) 103 | } 104 | } 105 | 106 | // TestGobDecoder_InvalidTarget 测试无效目标类型 107 | // TestGobDecoder_InvalidTarget tests invalid target type 108 | func TestGobDecoder_InvalidTarget(t *testing.T) { 109 | src := testData2{ 110 | Name: "test", 111 | Value: 123, 112 | } 113 | 114 | encoded, err := conv.GobEncoder(src) 115 | if err != nil { 116 | t.Fatalf("GobEncoder failed: %v", err) 117 | } 118 | 119 | // 非指针类型 / Non-pointer type 120 | var dest testData2 121 | err = conv.GobDecoder(encoded, dest) 122 | if err == nil { 123 | t.Error("Expected error for non-pointer target") 124 | } 125 | if err != conv.ErrTargetNotPointer { 126 | t.Errorf("Expected ErrTargetNotPointer, got: %v", err) 127 | } 128 | } 129 | 130 | // TestGobEncoderDecoder_Complex 测试复杂数据结构的编解码 131 | // TestGobEncoderDecoder_Complex tests encoding/decoding of complex data 132 | func TestGobEncoderDecoder_Complex(t *testing.T) { 133 | type complexData struct { 134 | ID int 135 | Names []string 136 | Details map[string]interface{} 137 | } 138 | 139 | src := complexData{ 140 | ID: 1, 141 | Names: []string{"a", "b", "c"}, 142 | Details: map[string]interface{}{ 143 | "key1": 123, 144 | "key2": "value", 145 | }, 146 | } 147 | 148 | encoded, err := conv.GobEncoder(src) 149 | if err != nil { 150 | t.Fatalf("GobEncoder failed: %v", err) 151 | } 152 | 153 | var dest complexData 154 | err = conv.GobDecoder(encoded, &dest) 155 | if err != nil { 156 | t.Fatalf("GobDecoder failed: %v", err) 157 | } 158 | 159 | // 验证复杂结构 / Verify complex structure 160 | if dest.ID != src.ID || len(dest.Names) != len(src.Names) || 161 | len(dest.Details) != len(src.Details) { 162 | t.Errorf("Decoded complex data mismatch, got: %+v, want: %+v", dest, src) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/hash_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/crypto/ahash" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestHash(t *testing.T) { 10 | log.Println(ahash.MD5("test")) 11 | log.Println(ahash.SHA1("test")) 12 | log.Println(ahash.SHA256("test")) 13 | log.Println(ahash.SHA512("test")) 14 | log.Println(ahash.Crc32("test")) 15 | } 16 | -------------------------------------------------------------------------------- /test/html_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/ahtml" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestHtml(t *testing.T) { 10 | var src = `

Test paragraph.

Other text` 11 | var src2 = `A 'quote' "is" bold` 12 | var src3 = `A 'quote' "is" bold` 13 | log.Println(ahtml.StripTags(src)) 14 | var data = ahtml.Entities(src2) 15 | 16 | log.Println(data) 17 | log.Println(ahtml.EntitiesDecode(data)) 18 | 19 | var data2 = ahtml.SpecialChars(src3) 20 | log.Println(data2) 21 | log.Println(ahtml.SpecialCharsDecode(data2)) 22 | } 23 | -------------------------------------------------------------------------------- /test/http_test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/warysection/antgo/os/alog" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | func main() { 9 | //log.SetFlags(log.Llongfile | log.LstdFlags) 10 | //flag.Parse() 11 | alog.New("./log").SetServiceName("api").SetConsole(true).Register() 12 | //alog.Write.Info("1221", zap.String("12", "1221")) 13 | alog.Info("1111", zap.String("1212", "2222"), zap.String("1111", "2233")) 14 | alog.Write.Info("1111", zap.String("1212", "2222"), zap.String("1111", "2233")) 15 | } 16 | -------------------------------------------------------------------------------- /test/ini_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/aini" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | var iniStr string = ` 10 | 11 | ;注释 12 | aa=bb 13 | [addr] 14 | #注释 15 | ip = 127.0.0.1 16 | port=9001 17 | enable=true 18 | 19 | [DBINFO] 20 | type=mysql 21 | user=root 22 | password=password 23 | [键] 24 | 呵呵=值 25 | 26 | ` 27 | 28 | func TestIni(t *testing.T) { 29 | result, err := aini.Decode([]byte(iniStr)) 30 | if err != nil { 31 | t.Errorf("encode failed. %v", err) 32 | return 33 | } 34 | log.Println(result) 35 | 36 | res, err := aini.Encode(result) 37 | if err != nil { 38 | t.Errorf("encode failed. %v", err) 39 | return 40 | } 41 | log.Println(string(res)) 42 | 43 | jsonyaml, err := aini.ToJson([]byte(iniStr)) 44 | if err != nil { 45 | t.Errorf("ToJson failed. %v", err) 46 | return 47 | } 48 | log.Println(string(jsonyaml)) 49 | } 50 | -------------------------------------------------------------------------------- /test/ints_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/utils/conv" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestInts(t *testing.T) { 11 | tests := []struct { 12 | input interface{} 13 | expected []int 14 | }{ 15 | {[]string{"1", "2", "3"}, []int{1, 2, 3}}, // 测试字符串切片转换 16 | {[]int{1, 2, 3}, []int{1, 2, 3}}, // 测试 int 切片 17 | {"[1,2,3]", []int{1, 2, 3}}, // 测试 JSON 字符串 18 | {[]interface{}{1, 2.5, true}, []int{1, 2, 1}}, // 测试接口切片 19 | } 20 | 21 | for _, tt := range tests { 22 | t.Run(fmt.Sprintf("Input: %v", tt.input), func(t *testing.T) { 23 | result := conv.Ints(tt.input) 24 | if !reflect.DeepEqual(result, tt.expected) { 25 | t.Errorf("expected %v, got %v", tt.expected, result) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | func TestStrings(t *testing.T) { 32 | tests := []struct { 33 | input interface{} 34 | expected []string 35 | }{ 36 | {[]string{"a", "b", "c"}, []string{"a", "b", "c"}}, // 测试字符串切片 37 | {"[\"a\", \"b\", \"c\"]", []string{"a", "b", "c"}}, // 测试 JSON 字符串 38 | {[]int{1, 2, 3}, []string{"1", "2", "3"}}, // 测试 int 切片转换为字符串 39 | {[]bool{true, false, true}, []string{"true", "false", "true"}}, // 测试布尔切片 40 | } 41 | 42 | for _, tt := range tests { 43 | t.Run(fmt.Sprintf("Input: %v", tt.input), func(t *testing.T) { 44 | result := conv.Strings(tt.input) 45 | if !reflect.DeepEqual(result, tt.expected) { 46 | t.Errorf("expected %v, got %v", tt.expected, result) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | func TestInterfaces(t *testing.T) { 53 | tests := []struct { 54 | input interface{} 55 | expected []interface{} 56 | }{ 57 | {[]int{1, 2, 3}, []interface{}{1, 2, 3}}, // 测试 int 切片 58 | {[]string{"a", "b", "c"}, []interface{}{"a", "b", "c"}}, // 测试字符串切片 59 | {"[1, 2, 3]", []interface{}{1, 2, 3}}, // 测试 JSON 字符串 60 | {[]interface{}{1, "test", true}, []interface{}{1, "test", true}}, // 测试接口切片 61 | } 62 | 63 | for _, tt := range tests { 64 | t.Run(fmt.Sprintf("Input: %v", tt.input), func(t *testing.T) { 65 | result := conv.Interfaces(tt.input) 66 | 67 | // 调试:打印实际结果与期望值 68 | // Debug: Print actual result and expected result 69 | fmt.Printf("Expected: %v\n", tt.expected) 70 | fmt.Printf("Result: %v\n", result) 71 | 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/language/i18n/en.toml: -------------------------------------------------------------------------------- 1 | hello = "Hello" 2 | world = "World" 3 | [common] 4 | name = "Tom Preston-Werner" -------------------------------------------------------------------------------- /test/language/i18n/ja.toml: -------------------------------------------------------------------------------- 1 | hello = "こんにちは" 2 | world = "世界" -------------------------------------------------------------------------------- /test/language/i18n/ru.toml: -------------------------------------------------------------------------------- 1 | hello = "Привет" 2 | world = "мир" -------------------------------------------------------------------------------- /test/language/i18n/zh-CN.toml: -------------------------------------------------------------------------------- 1 | hello = "%s你好%s" 2 | world = "世界" 3 | [common] 4 | name = "Tom Preston-Werner %s" -------------------------------------------------------------------------------- /test/language/i18n/zh-TW.toml: -------------------------------------------------------------------------------- 1 | hello = "你好" 2 | world = "世界" -------------------------------------------------------------------------------- /test/logger_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "flag" 5 | "github.com/warysection/antgo/os/alog" 6 | "github.com/warysection/antgo/utils/conv" 7 | "go.uber.org/zap" 8 | "log" 9 | "testing" 10 | ) 11 | 12 | type Str struct { 13 | Name string 14 | Name2 int 15 | } 16 | 17 | func TestLogger(t *testing.T) { 18 | log.SetFlags(log.Llongfile | log.LstdFlags) 19 | flag.Parse() 20 | alog.New("./log/app.log").SetServiceName("prod").Register() 21 | 22 | for i := 0; i < 1000000; i++ { 23 | alog.Write.Info("22333") 24 | } 25 | data := conv.Bytes(Str{ 26 | Name: "123", 27 | Name2: 12, 28 | }) 29 | alog.Write.Info("123", zap.ByteString("leve", data)) 30 | } 31 | -------------------------------------------------------------------------------- /test/map_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/utils/conv" 6 | "testing" 7 | ) 8 | 9 | // TestMap tests the Map function. 10 | func TestMap(t *testing.T) { 11 | // Test 1: JSON string input 12 | jsonStr := `{"key1": "value1", "key2": 2}` 13 | expected := map[string]interface{}{ 14 | "key1": "value1", 15 | "key2": float64(2), 16 | } 17 | 18 | result := conv.Map(jsonStr) 19 | if !equalMaps(result, expected) { 20 | t.Errorf("Map(jsonStr) = %v; want %v", result, expected) 21 | } 22 | 23 | // Test 2: Map input 24 | testMap := map[string]interface{}{"key1": "value1", "key2": 2} 25 | result = conv.Map(testMap) 26 | if !equalMaps(result, testMap) { 27 | t.Errorf("Map(testMap) = %v; want %v", result, testMap) 28 | } 29 | } 30 | 31 | // TestMaps tests the Maps function. 32 | func TestMaps(t *testing.T) { 33 | // Test 1: JSON string input 34 | jsonStr := `[{"key1": "value1", "key2": 2}, {"key3": "value3"}]` 35 | expected := []map[string]interface{}{ 36 | {"key1": "value1", "key2": float64(2)}, 37 | {"key3": "value3"}, 38 | } 39 | 40 | result := conv.Maps(jsonStr) 41 | if !equalMapsSlices(result, expected) { 42 | t.Errorf("Maps(jsonStr) = %v; want %v", result, expected) 43 | } 44 | 45 | // Test 2: Slice input 46 | testSlice := []map[string]interface{}{ 47 | {"key1": "value1", "key2": 2}, 48 | {"key3": "value3"}, 49 | } 50 | result = conv.Maps(testSlice) 51 | if !equalMapsSlices(result, testSlice) { 52 | t.Errorf("Maps(testSlice) = %v; want %v", result, testSlice) 53 | } 54 | } 55 | 56 | // TestMapString tests the MapString function. 57 | func TestMapString(t *testing.T) { 58 | // Test 1: JSON string input 59 | jsonStr := `{"key1": "value1", "key2": 2}` 60 | expected := map[string]string{ 61 | "key1": "value1", 62 | "key2": "2", 63 | } 64 | 65 | result := conv.MapString(jsonStr) 66 | if !equalMapStrings(result, expected) { 67 | t.Errorf("MapString(jsonStr) = %v; want %v", result, expected) 68 | } 69 | 70 | // Test 2: Map input 71 | testMap := map[string]interface{}{"key1": "value1", "key2": 2} 72 | result = conv.MapString(testMap) 73 | if !equalMapStrings(result, map[string]string{"key1": "value1", "key2": "2"}) { 74 | t.Errorf("MapString(testMap) = %v; want %v", result, map[string]string{"key1": "value1", "key2": "2"}) 75 | } 76 | } 77 | 78 | // TestMapInt tests the MapInt function. 79 | func TestMapInt(t *testing.T) { 80 | // Test 1: JSON string input 81 | jsonStr := `{"123": "value1", "456": 2}` 82 | expected := map[int]interface{}{ 83 | 123: "value1", 84 | 456: float64(2), 85 | } 86 | 87 | result := conv.MapInt(jsonStr) 88 | if !equalMapInts(result, expected) { 89 | t.Errorf("MapInt(jsonStr) = %v; want %v", result, expected) 90 | } 91 | 92 | // Test 2: Map input with invalid key 93 | invalidJSON := `{"key1": "value1"}` 94 | defer func() { 95 | if r := recover(); r == nil { 96 | t.Errorf("MapInt(invalidJSON) should panic") 97 | } 98 | }() 99 | conv.MapInt(invalidJSON) // This should panic due to non-integer keys 100 | } 101 | 102 | // Helper functions for comparison (since Go doesn't have deep equality check) 103 | func equalMaps(a, b map[string]interface{}) bool { 104 | if len(a) != len(b) { 105 | return false 106 | } 107 | for k, v := range a { 108 | if bv, ok := b[k]; !ok || fmt.Sprintf("%v", v) != fmt.Sprintf("%v", bv) { 109 | return false 110 | } 111 | } 112 | return true 113 | } 114 | 115 | func equalMapsSlices(a, b []map[string]interface{}) bool { 116 | if len(a) != len(b) { 117 | return false 118 | } 119 | for i, mapA := range a { 120 | if !equalMaps(mapA, b[i]) { 121 | return false 122 | } 123 | } 124 | return true 125 | } 126 | 127 | func equalMapStrings(a, b map[string]string) bool { 128 | if len(a) != len(b) { 129 | return false 130 | } 131 | for k, v := range a { 132 | if bv, ok := b[k]; !ok || v != bv { 133 | return false 134 | } 135 | } 136 | return true 137 | } 138 | 139 | func equalMapInts(a, b map[int]interface{}) bool { 140 | if len(a) != len(b) { 141 | return false 142 | } 143 | for k, v := range a { 144 | if bv, ok := b[k]; !ok || fmt.Sprintf("%v", v) != fmt.Sprintf("%v", bv) { 145 | return false 146 | } 147 | } 148 | return true 149 | } 150 | -------------------------------------------------------------------------------- /test/password_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/password" 5 | "testing" 6 | ) 7 | 8 | var pwd = "123456" 9 | 10 | func TestGenerate(t *testing.T) { 11 | 12 | // 测试生成哈希 13 | hashed, err := password.Generate(pwd) 14 | if err != nil { 15 | t.Fatalf("Generate failed: %v", err) 16 | } 17 | 18 | // 验证生成的哈希是否有效 19 | if len(hashed) == 0 { 20 | t.Error("Generated hash is empty") 21 | } 22 | 23 | // 验证相同的密码生成不同的哈希 24 | hashed2, err := password.Generate(pwd) 25 | if err != nil { 26 | t.Fatalf("Generate failed: %v", err) 27 | } 28 | if hashed == hashed2 { 29 | t.Error("Generated hashes are the same for the same password") 30 | } 31 | } 32 | 33 | func TestVerify(t *testing.T) { 34 | // 生成哈希 35 | hashed, err := password.Generate(pwd) 36 | if err != nil { 37 | t.Fatalf("Generate failed: %v", err) 38 | } 39 | // 验证正确的密码 40 | if !password.Verify(hashed, pwd) { 41 | t.Error("Verify failed for correct password") 42 | } 43 | 44 | // 验证错误的密码 45 | wrongPassword := "wrongpassword" 46 | if password.Verify(hashed, wrongPassword) { 47 | t.Error("Verify succeeded for incorrect password") 48 | } 49 | 50 | // 验证空密码 51 | emptyPassword := "" 52 | if password.Verify(hashed, emptyPassword) { 53 | t.Error("Verify succeeded for empty password") 54 | } 55 | } 56 | 57 | func TestVerifyWithInvalidHash(t *testing.T) { 58 | // 使用无效的哈希值进行验证 59 | invalidHash := "invalidhash" 60 | 61 | if password.Verify(invalidHash, pwd) { 62 | t.Error("Verify succeeded with invalid hash") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/pool_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/pool" 5 | "testing" 6 | ) 7 | 8 | // TestNewPool 测试 New 函数 9 | // Test the New function 10 | func TestNewPool(t *testing.T) { 11 | // 初始化 Goroutine 池,设定大小为 5,队列大小为 50 12 | // Initialize the Goroutine pool with size 5 and queue size 50 13 | pool.New(5, 50) 14 | 15 | // 获取池实例,确保池已成功创建 16 | // Get the pool instance to ensure it's successfully created 17 | poolInstance := pool.JobPool 18 | 19 | // 检查池实例是否为 nil 20 | // Ensure the pool instance is not nil 21 | if poolInstance == nil { 22 | t.Fatalf("Expected pool instance, got nil") 23 | } 24 | 25 | // 检查池的大小是否符合预期 26 | // Check if the pool size is as expected (this can be tested based on your pool configuration) 27 | if poolInstance.Cap() != 5 { 28 | t.Errorf("Expected pool size of 5, got %d", poolInstance.Cap()) 29 | } 30 | } 31 | 32 | // TestNewPoolWithDefaultQueue 测试没有提供队列大小时,使用默认队列大小 33 | // Test the default queue size when not provided 34 | func TestNewPoolWithDefaultQueue(t *testing.T) { 35 | // 初始化 Goroutine 池,设定大小为 5,队列大小不提供 36 | // Initialize the Goroutine pool with size 5 and no queue size 37 | pool.New(5, 0) 38 | 39 | // 获取池实例,确保池已成功创建 40 | // Get the pool instance to ensure it's successfully created 41 | poolInstance := pool.JobPool 42 | 43 | // 确保池实例不为 nil 44 | // Ensure the pool instance is not nil 45 | if poolInstance == nil { 46 | t.Fatalf("Expected pool instance, got nil") 47 | } 48 | 49 | // 确认队列大小为 50(大小是 5 * 10) 50 | // Verify that the queue size is set to the default value (5 * 10 = 50) 51 | if poolInstance.Cap() != 5 { 52 | t.Errorf("Expected pool size of 5, got %d", poolInstance.Cap()) 53 | } 54 | } 55 | 56 | // TestGetWithoutInitialization 测试在没有初始化池的情况下调用 Get 57 | // Test calling Get without initializing the pool 58 | func TestGetWithoutInitialization(t *testing.T) { 59 | // 在没有调用 New 函数初始化池的情况下直接调用 Get 60 | // Directly call Get without initializing the pool 61 | defer func() { 62 | if r := recover(); r == nil { 63 | t.Fatalf("Expected panic when calling Get without initialization") 64 | } 65 | }() 66 | 67 | // 调用 Get,预期会 panic 68 | // Call Get, expecting it to panic 69 | 70 | } 71 | 72 | // TestNewWithInvalidSize 测试给定无效的大小 73 | // Test the case when an invalid size is provided 74 | func TestNewWithInvalidSize(t *testing.T) { 75 | defer func() { 76 | if r := recover(); r == nil { 77 | t.Fatalf("Expected panic when providing invalid pool size") 78 | } 79 | }() 80 | 81 | // 尝试初始化池时传入无效的大小(小于等于0) 82 | // Try initializing the pool with an invalid size (<= 0) 83 | pool.New(0, 10) 84 | } 85 | -------------------------------------------------------------------------------- /test/redis_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "github.com/warysection/antgo/db/aredis" 6 | "github.com/warysection/antgo/frame/ant" 7 | "log" 8 | "testing" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | func TestRedis(t *testing.T) { 14 | conn := aredis.New([]map[string]interface{}{ 15 | {"name": "redis", "address": "localhost:6379", "db": 0}, 16 | }) 17 | 18 | //conn := aredis.NewClusterClient([]string{"127.0.0.1:6379", "127.0.0.1:6379"}, "") 19 | ant.Redis().Set("key:name:aaa", "value22") 20 | log.Println(ant.Redis().Get("key:name")) 21 | log.Println(ant.Redis().TTL("key")) 22 | //client.PushList("list_test", "message1") 23 | ant.Redis().PushList("list_test", "message2") 24 | ant.Redis().SetList("list_test", 2, "message3") 25 | //client.RemoveList("list_test", "message1", 1) 26 | //log.Println(conn.GetListIndex("list_test", 1)) 27 | log.Println(ant.Redis().GetList("list_test")) 28 | 29 | ant.Redis().AddSet("set_test", "111", "222", "77") 30 | log.Println(ant.Redis().GetSet("set_test")) 31 | ant.Redis().AddSet("set_test2", "111", "222", "3333", "444") 32 | //交集 33 | log.Println("交集") 34 | log.Println(ant.Redis().Clients.SInter(conn["redis"].Ctx, "set_test", "set_test2").Result()) 35 | //并集 36 | log.Println("并集") 37 | log.Println(ant.Redis().Clients.SUnion(conn["redis"].Ctx, "set_test", "set_test2").Result()) 38 | //差集 39 | log.Println("差集") 40 | log.Println(ant.Redis().Clients.SDiff(conn["redis"].Ctx, "set_test", "set_test2").Result()) 41 | 42 | //订阅 43 | //pubsub := conn.Clients.Subscribe(conn.Ctx, "subkey") 44 | //_, err := pubsub.Receive(conn.Ctx) 45 | //if err != nil { 46 | // log.Fatal("pubsub.Receive") 47 | //} 48 | //log.Println("111") 49 | //ch := pubsub.Channel() 50 | //go time.AfterFunc(10*time.Second, func() { 51 | // log.Println("Publish") 52 | // 53 | // err = conn.Clients.Publish(conn.Ctx, "subkey", "test publish 1").Err() 54 | // if err != nil { 55 | // log.Fatal("redisdb.Publish", err) 56 | // } 57 | // 58 | // conn.Clients.Publish(conn.Ctx, "subkey", "test publish 2") 59 | //}) 60 | //for msg := range ch { 61 | // log.Println("recv channel:", msg.Channel, msg.Pattern, msg.Payload) 62 | //} 63 | //log.Println("222") 64 | 65 | //队列 66 | //IncrByXX := redis.NewScript(` 67 | // if redis.call("GET", KEYS[1]) ~= false then 68 | // return redis.call("INCRBY", KEYS[1], ARGV[1]) 69 | // end 70 | // return false 71 | //`) 72 | // 73 | //n, err := IncrByXX.Run(conn.Ctx, conn.Clients, []string{"xx_counter"}, 2).Result() 74 | //fmt.Println(n, err) 75 | // 76 | //err = conn.Clients.Set(conn.Ctx, "xx_counter", "40", 0).Err() 77 | //if err != nil { 78 | // panic(err) 79 | //} 80 | // 81 | //n, err = IncrByXX.Run(conn.Ctx, conn.Clients, []string{"xx_counter"}, 2).Result() 82 | //fmt.Println(n, err) 83 | } 84 | -------------------------------------------------------------------------------- /test/rsa_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/crypto/arsa" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | var privateKey = []byte(` 10 | -----BEGIN RSA PRIVATE KEY----- 11 | MIICWgIBAAKBgQChfYIOm7bOOlHF2VcEwqGaR1OxVT8fpJIjmvz6+k2EbiMo5e6b 12 | L1pyRUXEVyXf+x06bJtXC1QCOKtwmnUKPAsSrWSP5jYmyLCjNaTJqwcCgyAXMA7k 13 | AVoLRfjTxt4XYSm+P4HS+9w1yLZvsZ1XbRIhxgVHDtknkgANt7OREpBYowIDAQAB 14 | An8BHMtcai9AZCJ9cQDcDnswQI0KnNAwxeCXIqrRD8yUsNBFwEnLtAnKucdBQqHb 15 | cxIC9MaAZtoGmwIMZEl2BxmRYMhYZZyHdrKBNZJJgmV8Cz3Q1sRC12KJ1LMlSziV 16 | 8NO5tbKUOKzkY9GFItjgriDDfd1SAjNE+B7ydbN5cFKBAkEA/HTW3Xqt7Y8qxM51 17 | 4Za0RF1NQmVuyspKsaVcRCE/Igeq+T/pZ0jWvo/BFGDrFME4BTFbLJsP5URjZatH 18 | wVzkIwJBAKPBy/xxzrpCwRVTMP2PkouqtUFYi8vYWK1j0DVPvNNkikYugOC+h6OQ 19 | lbPiFspGgA1O2/rRqGzDT0fGdAjhwYECQQCwjeHKiMZkchB+DMmiF6xAd2PVwGxI 20 | REsSi8vIFdw6J1Sp9cl8oxMTuCNW5iThofNUplzWCCeItlgxPST0lMszAkAhY9un 21 | DsGbQw9BvOPJX+P+rIEm4NooZ2W1fRuwMyEKbX6wTr0illbr6AhOVHRXLEbh78l0 22 | /Bj+jFh3ByUTxoyBAkAGZjTBaWJuQuNnD3Do+CuHAoJ3k9drAWEEAepQdzIIom0m 23 | 7+Vc8wQsdZSLK+ATqIM/KkC00dR1462axPXUR6f3 24 | -----END RSA PRIVATE KEY----- 25 | `) 26 | var publicKey = []byte(` 27 | -----BEGIN PUBLIC KEY----- 28 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQChfYIOm7bOOlHF2VcEwqGaR1Ox 29 | VT8fpJIjmvz6+k2EbiMo5e6bL1pyRUXEVyXf+x06bJtXC1QCOKtwmnUKPAsSrWSP 30 | 5jYmyLCjNaTJqwcCgyAXMA7kAVoLRfjTxt4XYSm+P4HS+9w1yLZvsZ1XbRIhxgVH 31 | DtknkgANt7OREpBYowIDAQAB 32 | -----END PUBLIC KEY----- 33 | `) 34 | 35 | func TestRSA(t *testing.T) { 36 | /*var rsa=rsa.Default()*/ 37 | var result = arsa.New(publicKey, privateKey) 38 | for i := 0; i < 2; i++ { 39 | var str1, _ = result.Encrypt("admin") 40 | log.Println(str1) 41 | log.Println(result.Decrypt(str1)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/slice_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/conv" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestSlice(t *testing.T) { 10 | var result = conv.Strings([]int{1, 2, 3, 4}) 11 | log.Println(result) 12 | var result2 = conv.Strings(`["1", "2", "3", "4"]`) 13 | log.Println(result2) 14 | var result3 = conv.Strings([]string{"1", "2"}) 15 | log.Println(result3) 16 | var result4 = conv.Strings([]int64{1, 2, 3, 4}) 17 | log.Println(result4) 18 | } 19 | -------------------------------------------------------------------------------- /test/struct_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/utils/conv" 5 | "testing" 6 | ) 7 | 8 | // Define a simple struct to test with 9 | type Person struct { 10 | Name string `json:"name"` 11 | Age int `json:"age"` 12 | } 13 | 14 | func TestToStruct(t *testing.T) { 15 | // Test valid conversion from map to struct 16 | data := map[string]any{ 17 | "name": "John", 18 | "age": 30, 19 | } 20 | var p Person 21 | err := conv.ToStruct(data, &p) 22 | if err != nil { 23 | t.Errorf("ToStruct failed: %v", err) 24 | } 25 | if p.Name != "John" { 26 | t.Errorf("Expected Name to be 'John', got '%s'", p.Name) 27 | } 28 | if p.Age != 30 { 29 | t.Errorf("Expected Age to be 30, got %d", p.Age) 30 | } 31 | 32 | // Test invalid conversion with nil data 33 | err = conv.ToStruct(nil, &p) 34 | if err == nil { 35 | t.Error("ToStruct should return an error when data is nil") 36 | } 37 | 38 | // Test invalid conversion with nil model 39 | err = conv.ToStruct(data, nil) 40 | if err == nil { 41 | t.Error("ToStruct should return an error when model is nil") 42 | } 43 | } 44 | 45 | func TestUnmarshalJSON(t *testing.T) { 46 | // Test valid JSON deserialization into struct 47 | jsonData := []byte(`{"name": "Alice", "age": 25}`) 48 | var p Person 49 | err := conv.UnmarshalJSON(jsonData, &p) 50 | if err != nil { 51 | t.Errorf("UnmarshalJSON failed: %v", err) 52 | } 53 | if p.Name != "Alice" { 54 | t.Errorf("Expected Name to be 'Alice', got '%s'", p.Name) 55 | } 56 | if p.Age != 25 { 57 | t.Errorf("Expected Age to be 25, got %d", p.Age) 58 | } 59 | 60 | // Test invalid JSON deserialization with nil data 61 | err = conv.UnmarshalJSON(nil, &p) 62 | if err == nil { 63 | t.Error("UnmarshalJSON should return an error when data is nil") 64 | } 65 | 66 | // Test invalid JSON deserialization with nil model 67 | err = conv.UnmarshalJSON(jsonData, nil) 68 | if err == nil { 69 | t.Error("UnmarshalJSON should return an error when model is nil") 70 | } 71 | } 72 | 73 | func TestToJSON(t *testing.T) { 74 | // Test valid struct to JSON serialization 75 | p := Person{Name: "Bob", Age: 40} 76 | jsonData, err := conv.ToJSON(p) 77 | if err != nil { 78 | t.Errorf("ToJSON failed: %v", err) 79 | } 80 | if jsonData == nil { 81 | t.Error("ToJSON should return a valid JSON byte slice, but got nil") 82 | } 83 | 84 | // Test invalid data to JSON serialization (nil data) 85 | _, err = conv.ToJSON(nil) 86 | if err == nil { 87 | t.Error("ToJSON should return an error when data is nil") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/url_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/aurl" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | var urlStr string = `https://golang.org/x/crypto?go-get=1 +` 10 | var src string = `http://username:password@hostname:9090/path?arg=value#anchor` 11 | 12 | func TestUrl(t *testing.T) { 13 | 14 | var result = aurl.Encode(urlStr) 15 | log.Println(result) 16 | var result2, _ = aurl.Decode(result) 17 | log.Println(result2) 18 | var component = 0 19 | var result3, _ = aurl.ParseURL(src, component) 20 | log.Println(result3) 21 | } 22 | -------------------------------------------------------------------------------- /test/websocket/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gorilla/websocket" 6 | "github.com/warysection/antgo/net/awebsocket" 7 | "log" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | var upGrader = (&websocket.Upgrader{ 13 | ReadBufferSize: 1024, 14 | WriteBufferSize: 1024, 15 | // 允许所有CORS跨域请求 16 | CheckOrigin: func(r *http.Request) bool { 17 | return true 18 | }, 19 | }) 20 | 21 | func main() { 22 | bindAddress := "127.0.0.1:1111" 23 | r := gin.Default() 24 | r.GET("/ping", ping) 25 | r.Run(bindAddress) 26 | } 27 | 28 | // webSocket请求ping 返回pong 29 | func ping(c *gin.Context) { 30 | var ( 31 | websocket *websocket.Conn 32 | err error 33 | conn *awebsocket.Connection 34 | data []byte 35 | ) 36 | // 完成ws协议的握手操作 37 | if websocket, err = upGrader.Upgrade(c.Writer, c.Request, nil); err != nil { 38 | return 39 | } 40 | conn = awebsocket.New(websocket, c.ClientIP(), uint64(time.Now().Unix())) 41 | for { 42 | if data, err = conn.ReadMessage(); err != nil { 43 | goto ERR 44 | } 45 | if err = conn.WriteMessage(data); err != nil { 46 | goto ERR 47 | } 48 | log.Println(string(data)) 49 | } 50 | awebsocket.NewClient().Register <- conn 51 | 52 | ERR: 53 | conn.Close() 54 | } 55 | -------------------------------------------------------------------------------- /test/xml_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/warysection/antgo/encoding/axml" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | var xmlStr string = ` 11 | 12 | 13 | Loading video… 14 | 这是新的ApplicationName 15 | 16 | ` 17 | 18 | func TestXml(t *testing.T) { 19 | userMap := make(map[string]string) 20 | userMap["name"] = "Name" 21 | userMap["id"] = "1" 22 | 23 | buf, _ := axml.Encode(userMap) 24 | fmt.Println(string(buf)) 25 | 26 | result, err := axml.Decode([]byte(xmlStr)) 27 | if err != nil { 28 | fmt.Println(err) 29 | } 30 | fmt.Println(result) 31 | 32 | result2, err := axml.ToJson([]byte(xmlStr)) 33 | log.Println(string(result2)) 34 | log.Println(err) 35 | } 36 | -------------------------------------------------------------------------------- /test/yaml_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/ayaml" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | var yamlStr string = ` 10 | #即表示url属性值; 11 | url: https://github.com/warysection/antgo 12 | 13 | #数组,即表示server为[a,b,c] 14 | server: 15 | - 192.168.1.1 16 | - 192.168.1.2 17 | #常量 18 | pi: 3.14 #定义一个数值3.14 19 | hasChild: true #定义一个boolean值 20 | name: '你好YAML' #定义一个字符串 21 | ` 22 | var yamlErr string = ` 23 | {"name":"123"} 24 | ` 25 | 26 | func TestYaml(t *testing.T) { 27 | result, err := ayaml.Decode([]byte(yamlStr)) 28 | if err != nil { 29 | t.Errorf("encode failed. %v", err) 30 | return 31 | } 32 | log.Println(result) 33 | 34 | result2 := make(map[string]interface{}) 35 | err = ayaml.DecodeTo([]byte(yamlStr), &result2) 36 | log.Println(result2) 37 | 38 | m := make(map[string]string) 39 | m["yaml"] = yamlStr 40 | res, err := ayaml.Encode(m) 41 | if err != nil { 42 | t.Errorf("encode failed. %v", err) 43 | return 44 | } 45 | log.Println(string(res)) 46 | 47 | jsonyaml, err := ayaml.ToJson([]byte(yamlErr)) 48 | if err != nil { 49 | t.Errorf("ToJson failed. %v", err) 50 | return 51 | } 52 | log.Println(string(jsonyaml)) 53 | } 54 | -------------------------------------------------------------------------------- /test/zip_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/warysection/antgo/encoding/azip" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestZip(t *testing.T) { 10 | // List of Files to Zip 11 | files := []string{"test.txt", "test2.txt"} 12 | output := "done.zip" 13 | 14 | if err := azip.Create(output, files); err != nil { 15 | panic(err) 16 | } 17 | 18 | files2, err2 := azip.Unzip(output, "done") 19 | if err2 != nil { 20 | log.Println(err2) 21 | } 22 | log.Println(files2) 23 | log.Println("Zipped File:", output) 24 | } 25 | -------------------------------------------------------------------------------- /utils/ajwt/jwt_test.go: -------------------------------------------------------------------------------- 1 | package ajwt 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // 用于测试的RSA密钥对(仅用于测试环境,请勿在生产环境中使用) 9 | // Test RSA key pair (for testing purposes only, do not use in production) 10 | const testRSAPrivateKeyPEM = `-----BEGIN PRIVATE KEY----- 11 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMMBf8hUaDk+oCsQ 12 | ecIJDrRt+EktQaGE+11Un1YWgjelgJR68JcS+2HsSTjkd+aUYFuy7uo7Jc0ugQqN 13 | xTwPzyJXfIX0J3niM0MM4SNLiqGD+UYGJ3bbBiw33NPT4CQ0/ATKk7Y4kdGXWl9z 14 | OMqV6YqWau88SpFAUENHj1rJrFKrAgMBAAECgYBEFSTo61dMDSpcfq8T6JeitPZH 15 | ji5o1wXvqtjKdKdYCEdhD58qD62GnblezJ1z+n+95DX3v1jOTxssdRzUgGx/Ys13 16 | Ukso6hzNgMOw46zwd8qoWuFuytWWc953FGsr+atWFrvfU8aEjBjWzhlTtFVRPaiS 17 | 42zS7OZzYdmCw/PJaQJBAPu9R23SePtKIufwkg9ejWKyfIwJfLfdrfPL3RSTryGy 18 | ph6CahWcr19/fMRTJaTYQtfxnXc1quFow3X+IBgfMr0CQQDGTmibUOJGyVrjSNMe 19 | dTntWaBjMdFRwNdl7EzgUuLePUFs0gbZ6SW5dMsK/3JfzyxYF3XRki7Lupju3Ano 20 | RGWHAkAN/lCJJ0j4Vv+nuvSzjAL5+If51NEs+1KfGbb5XNhAXEjlq0QwXVxWR6Ts 21 | 2N5f0nGsxU6GgOI103gCCBVKoflVAkBvBzVwSE/4XAJEIOD7O50MM9Ml1p2gjTzM 22 | Nwovyph0340C9XCajvvtIuQPq0gJNoBYbgIsLRGARWAc1BvD7I9/AkEAgQEnpQEI 23 | isXUlyKSsakm+M+hzkoJxlizUiM3tN9cIfsIBXdWv9LNGRp2gl8Sa69ri3EdqQXv 24 | 0PcStMOn2IX1kw== 25 | -----END PRIVATE KEY-----` 26 | 27 | const testRSAPublicKeyPEM = `-----BEGIN PUBLIC KEY----- 28 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDAX/IVGg5PqArEHnCCQ60bfhJ 29 | LUGhhPtdVJ9WFoI3pYCUevCXEvth7Ek45HfmlGBbsu7qOyXNLoEKjcU8D88iV3yF 30 | 9Cd54jNDDOEjS4qhg/lGBid22wYsN9zT0+AkNPwEypO2OJHRl1pfczjKlemKlmrv 31 | PEqRQFBDR49ayaxSqwIDAQAB 32 | -----END PUBLIC KEY-----` 33 | 34 | // TestGenerateAndParse 测试生成和解析JWT令牌 35 | // TestGenerateAndParse tests token generation and parsing. 36 | func TestGenerateAndParse(t *testing.T) { 37 | // 初始化JwtManager并设置公钥和私钥 38 | // Initialize JwtManager and set RSA keys. 39 | jm := New().SetPrivateKey([]byte(testRSAPrivateKeyPEM)).SetPublicKey([]byte(testRSAPublicKeyPEM)) 40 | 41 | // 设置自定义声明 / Define custom claims. 42 | claims := map[string]interface{}{ 43 | "user": "john", 44 | "role": "admin", 45 | } 46 | 47 | // 生成Token / Generate token. 48 | token, err := jm.Generate(claims) 49 | if err != nil { 50 | t.Fatalf("Generate token failed: %v", err) 51 | } 52 | 53 | // 解析Token / Parse token. 54 | parsedClaims, err := jm.Parse(token) 55 | if err != nil { 56 | t.Fatalf("Parse token failed: %v", err) 57 | } 58 | 59 | // 验证声明中的数据 / Validate claims. 60 | if parsedClaims["user"] != "john" { 61 | t.Errorf("Expected user 'john', got %v", parsedClaims["user"]) 62 | } 63 | if parsedClaims["role"] != "admin" { 64 | t.Errorf("Expected role 'admin', got %v", parsedClaims["role"]) 65 | } 66 | } 67 | 68 | // TestInvalidToken 测试解析一个无效的Token 69 | // TestInvalidToken tests parsing an invalid token string. 70 | func TestInvalidToken(t *testing.T) { 71 | jm := New().SetPrivateKey([]byte(testRSAPrivateKeyPEM)).SetPublicKey([]byte(testRSAPublicKeyPEM)) 72 | _, err := jm.Parse("invalid.token.here") 73 | if err == nil { 74 | t.Fatal("Expected error for invalid token, got nil") 75 | } 76 | } 77 | 78 | // TestMissingKeys 测试缺少密钥时的行为 79 | // TestMissingKeys tests the behavior when required keys are missing. 80 | func TestMissingKeys(t *testing.T) { 81 | jm := New() // 未设置公钥和私钥 82 | claims := map[string]interface{}{ 83 | "user": "john", 84 | } 85 | 86 | // 测试缺少私钥时生成Token应返回错误 / Generate should error if private key is not set. 87 | _, err := jm.Generate(claims) 88 | if err == nil { 89 | t.Error("Expected error for missing private key, got nil") 90 | } 91 | 92 | // 设置私钥,但不设置公钥 93 | jm.SetPrivateKey([]byte(testRSAPrivateKeyPEM)) 94 | token, err := jm.Generate(claims) 95 | if err != nil { 96 | t.Fatalf("Generate token failed: %v", err) 97 | } 98 | 99 | // 解析Token时应返回缺少公钥的错误 / Parse should error if public key is not set. 100 | _, err = jm.Parse(token) 101 | if err == nil { 102 | t.Error("Expected error for missing public key, got nil") 103 | } 104 | } 105 | 106 | // TestCustomExpiration 测试自定义过期时间 107 | // TestCustomExpiration tests token expiration with a custom expiration duration. 108 | func TestCustomExpiration(t *testing.T) { 109 | // 初始化JwtManager,并设置公钥和私钥 110 | jm := New().SetPrivateKey([]byte(testRSAPrivateKeyPEM)).SetPublicKey([]byte(testRSAPublicKeyPEM)) 111 | 112 | claims := map[string]interface{}{ 113 | "user": "john", 114 | } 115 | 116 | // 生成一个1秒后过期的Token 117 | token, err := jm.Generate(claims, time.Second*1) 118 | if err != nil { 119 | t.Fatalf("Generate token failed: %v", err) 120 | } 121 | 122 | // 等待2秒以确保Token过期 123 | time.Sleep(2 * time.Second) 124 | 125 | _, err = jm.Parse(token) 126 | 127 | if err != nil { 128 | t.Error("Expected token to be expired, but no error returned") 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /utils/conv/bool.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import "strings" 4 | 5 | // Bool converts `any` to bool. 6 | // 将 `any` 转换为 bool。 7 | func Bool(any interface{}) bool { 8 | if any == nil { 9 | return false 10 | } 11 | 12 | switch v := any.(type) { 13 | case bool: 14 | return v 15 | case []byte: 16 | return !strings.EqualFold(string(v), "false") 17 | case string: 18 | return !strings.EqualFold(v, "false") 19 | } 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /utils/conv/conv.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "math" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | // Rune converts any type to rune. Maintains original value for rune types, 12 | // converts to int32 for other types (supports numeric/string/bool compatibility) 13 | // 将任意类型转换为rune类型。保留rune类型的原始值,其他类型转换为int32(支持数字/字符串/布尔型兼容) 14 | func Rune(any interface{}) rune { 15 | if v, ok := any.(rune); ok { 16 | return v 17 | } 18 | return Int32(any) 19 | } 20 | 21 | // Runes converts any type to []rune. Direct conversion for []rune types, 22 | // converts to string first for other types 23 | // 将任意类型转换为[]rune类型。直接转换[]rune类型,其他类型先转换为字符串 24 | func Runes(any interface{}) []rune { 25 | if v, ok := any.([]rune); ok { 26 | return v 27 | } 28 | return []rune(String(any)) 29 | } 30 | 31 | // Byte converts any type to byte. Maintains original value for byte types, 32 | // converts to uint8 for other types (supports numeric/string/bool compatibility) 33 | // 将任意类型转换为byte类型。保留byte类型的原始值,其他类型转换为uint8(支持数字/字符串/布尔型兼容) 34 | func Byte(any interface{}) byte { 35 | if v, ok := any.(byte); ok { 36 | return v 37 | } 38 | return Uint8(any) 39 | } 40 | 41 | // Bytes converts any type to []byte with optimized memory allocation. 42 | // Special handling for basic types, JSON serialization for complex types 43 | // 将任意类型转换为[]byte(优化内存分配),基础类型特殊处理,复杂类型使用JSON序列化 44 | func Bytes(any interface{}) []byte { 45 | if any == nil { 46 | return nil 47 | } 48 | 49 | switch v := any.(type) { 50 | case []byte: 51 | return v 52 | case string: 53 | return []byte(v) 54 | case int: 55 | return intToBytes(int64(v)) 56 | case int32: 57 | return intToBytes(int64(v)) 58 | case int64: 59 | return intToBytes(v) 60 | case float32: 61 | return float32ToBytes(v) 62 | case float64: 63 | return float64ToBytes(v) 64 | default: 65 | // Fallback to JSON serialization for complex types 66 | // 复杂类型回退到JSON序列化 67 | result, err := json.Marshal(v) 68 | if err != nil { 69 | panic(err) 70 | } 71 | return result 72 | } 73 | } 74 | 75 | // String converts any type to string with optimized type handling. 76 | // Specialized conversion for basic types, JSON serialization for others 77 | // 将任意类型转换为字符串(优化类型处理),基础类型特殊转换,其他类型使用JSON序列化 78 | func String(any interface{}) string { 79 | if any == nil { 80 | return "" 81 | } 82 | 83 | switch v := any.(type) { 84 | case int: 85 | return strconv.Itoa(v) 86 | case int8: 87 | return strconv.FormatInt(int64(v), 10) 88 | case int16: 89 | return strconv.FormatInt(int64(v), 10) 90 | case int32: 91 | return strconv.FormatInt(int64(v), 10) 92 | case int64: 93 | return strconv.FormatInt(v, 10) 94 | case uint: 95 | return strconv.FormatUint(uint64(v), 10) 96 | case uint8: 97 | return strconv.FormatUint(uint64(v), 10) 98 | case uint16: 99 | return strconv.FormatUint(uint64(v), 10) 100 | case uint32: 101 | return strconv.FormatUint(uint64(v), 10) 102 | case uint64: 103 | return strconv.FormatUint(v, 10) 104 | case float32: 105 | return strconv.FormatFloat(float64(v), 'f', -1, 32) 106 | case float64: 107 | return strconv.FormatFloat(v, 'f', -1, 64) 108 | case bool: 109 | return strconv.FormatBool(v) 110 | case string: 111 | return v 112 | case []byte: 113 | return string(v) 114 | case time.Time: 115 | if v.IsZero() { 116 | return "" 117 | } 118 | return v.String() 119 | case *time.Time: 120 | if v == nil { 121 | return "" 122 | } 123 | return v.String() 124 | default: 125 | // JSON serialization for complex types 126 | // 复杂类型使用JSON序列化 127 | result, err := json.Marshal(v) 128 | if err != nil { 129 | panic(err) 130 | } 131 | return string(result) 132 | } 133 | } 134 | 135 | // intToBytes converts integer to 8-byte slice with zero allocations 136 | // 整型转8字节切片(零内存分配) 137 | func intToBytes(n int64) []byte { 138 | var buf [8]byte 139 | binary.BigEndian.PutUint64(buf[:], uint64(n)) 140 | return buf[:] 141 | } 142 | 143 | // float32ToBytes converts float32 to 4-byte slice with zero allocations 144 | // float32转4字节切片(零内存分配) 145 | func float32ToBytes(f float32) []byte { 146 | var buf [4]byte 147 | binary.LittleEndian.PutUint32(buf[:], math.Float32bits(f)) 148 | return buf[:] 149 | } 150 | 151 | // float64ToBytes converts float64 to 8-byte slice with zero allocations 152 | // float64转8字节切片(零内存分配) 153 | func float64ToBytes(f float64) []byte { 154 | var buf [8]byte 155 | binary.LittleEndian.PutUint64(buf[:], math.Float64bits(f)) 156 | return buf[:] 157 | } 158 | -------------------------------------------------------------------------------- /utils/conv/conv_gob.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "errors" 7 | "reflect" 8 | "sync" 9 | ) 10 | 11 | // 包级别错误定义,避免重复创建错误对象 / Package-level error definitions to avoid recreating error objects 12 | var ( 13 | // ErrNilData 表示输入数据为空 14 | // ErrNilData indicates input data is nil 15 | ErrNilData = errors.New("conv: data cannot be nil") 16 | 17 | // ErrNilTarget 表示目标对象为空 18 | // ErrNilTarget indicates target object is nil 19 | ErrNilTarget = errors.New("conv: target cannot be nil") 20 | 21 | // ErrTargetNotPointer 表示目标对象不是有效指针类型 22 | // ErrTargetNotPointer indicates target is not a valid pointer type 23 | ErrTargetNotPointer = errors.New("conv: target must be non-nil pointer") 24 | ) 25 | 26 | // bufferPool 用于复用 bytes.Buffer 对象,减少内存分配 27 | // bufferPool for reusing bytes.Buffer objects to reduce memory allocations 28 | var bufferPool = sync.Pool{ 29 | New: func() interface{} { 30 | return new(bytes.Buffer) 31 | }, 32 | } 33 | 34 | // GobEncoder 使用gob编码数据(高性能版本) 35 | // 特性: 36 | // 1. 使用内存池减少内存分配 37 | // 2. 严格参数校验 38 | // 3. 自动处理缓冲区生命周期 39 | // 40 | // GobEncoder encodes data using gob (high-performance version) 41 | // Features: 42 | // 1. Uses memory pool to reduce allocations 43 | // 2. Strict parameter validation 44 | // 3. Automatic buffer lifecycle management 45 | func GobEncoder(data any) ([]byte, error) { 46 | if data == nil { 47 | return nil, ErrNilData 48 | } 49 | 50 | // 从内存池获取缓冲区 / Get buffer from pool 51 | buf := bufferPool.Get().(*bytes.Buffer) 52 | defer bufferPool.Put(buf) 53 | buf.Reset() 54 | 55 | // 执行编码 / Perform encoding 56 | enc := gob.NewEncoder(buf) 57 | if err := enc.Encode(data); err != nil { 58 | return nil, err 59 | } 60 | 61 | // 创建数据副本防止缓冲区重用导致的数据污染 62 | // Create data copy to prevent data pollution from buffer reuse 63 | result := make([]byte, buf.Len()) 64 | copy(result, buf.Bytes()) 65 | 66 | return result, nil 67 | } 68 | 69 | // GobDecoder 使用gob解码数据(增强安全版本) 70 | // 特性: 71 | // 1. 严格参数类型校验 72 | // 2. 详细的错误类型返回 73 | // 3. 自动处理缓冲区 74 | // 75 | // GobDecoder decodes data using gob (enhanced safety version) 76 | // Features: 77 | // 1. Strict parameter type validation 78 | // 2. Detailed error type returns 79 | // 3. Automatic buffer handling 80 | func GobDecoder(data []byte, target any) error { 81 | if data == nil { 82 | return ErrNilData 83 | } 84 | if target == nil { 85 | return ErrNilTarget 86 | } 87 | 88 | // 使用反射验证目标对象类型 / Use reflection to validate target type 89 | val := reflect.ValueOf(target) 90 | if val.Kind() != reflect.Ptr || val.IsNil() { 91 | return ErrTargetNotPointer 92 | } 93 | 94 | // 创建带数据缓存的解码器 / Create decoder with data buffer 95 | dec := gob.NewDecoder(bytes.NewReader(data)) 96 | return dec.Decode(target) 97 | } 98 | -------------------------------------------------------------------------------- /utils/conv/conv_map.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | ) 8 | 9 | // Map converts any type to map[string]interface{} with panic handling. 10 | // Supported types: JSON bytes/string, map, struct 11 | // 将任意类型转换为map[string]interface{},包含panic处理 12 | // 支持类型:JSON字节/字符串、map、结构体 13 | func Map(any interface{}) (data map[string]interface{}) { 14 | // Fast path for direct type conversion [[4,15]] 15 | if m, ok := any.(map[string]interface{}); ok { 16 | return m 17 | } 18 | 19 | bytes := Bytes(any) 20 | data = make(map[string]interface{}) 21 | if err := json.Unmarshal(bytes, &data); err != nil { 22 | panic(fmt.Sprintf("JSON unmarshal failed: %v", err)) 23 | } 24 | return 25 | } 26 | 27 | // Maps converts any type to []map[string]interface{} with panic handling. 28 | // Auto-converts JSON strings/bytes to slice 29 | // 将任意类型转换为[]map[string]interface{},包含panic处理 30 | // 自动转换JSON字符串/字节为切片 31 | func Maps(any interface{}) (data []map[string]interface{}) { 32 | // Fast path for direct type conversion [[4,15]] 33 | if slice, ok := any.([]map[string]interface{}); ok { 34 | return slice 35 | } 36 | 37 | bytes := Bytes(any) 38 | data = []map[string]interface{}{} 39 | if err := json.Unmarshal(bytes, &data); err != nil { 40 | panic(fmt.Sprintf("JSON unmarshal failed: %v", err)) 41 | } 42 | return 43 | } 44 | 45 | // MapString converts any type to map[string]string with type compatibility. 46 | // Supports all JSON value types converted to string via fmt.Sprintf 47 | // 将任意类型转换为map[string]string,增强类型兼容性 48 | // 支持通过fmt.Sprintf将所有JSON值类型转为字符串 49 | func MapString(any interface{}) map[string]string { 50 | bytes := Bytes(any) 51 | var temp map[string]interface{} 52 | if err := json.Unmarshal(bytes, &temp); err != nil { 53 | panic(fmt.Sprintf("JSON unmarshal failed: %v", err)) 54 | } 55 | 56 | data := make(map[string]string, len(temp)) 57 | for k, v := range temp { 58 | data[k] = fmt.Sprintf("%v", v) // Universal conversion [[6,15]] 59 | } 60 | return data 61 | } 62 | 63 | // MapInt converts any type to map[int]interface{} with key validation. 64 | // Requires JSON keys to be numeric strings (e.g., "123") 65 | // 将任意类型转换为map[int]interface{},包含键值验证 66 | // 要求JSON键为数字字符串(如"123") 67 | func MapInt(any interface{}) map[int]interface{} { 68 | bytes := Bytes(any) 69 | var temp map[string]interface{} 70 | if err := json.Unmarshal(bytes, &temp); err != nil { 71 | panic(fmt.Sprintf("JSON unmarshal failed: %v", err)) 72 | } 73 | 74 | data := make(map[int]interface{}, len(temp)) 75 | for k, v := range temp { 76 | intKey, err := strconv.Atoi(k) 77 | if err != nil { 78 | panic(fmt.Sprintf("Key conversion failed: %s is not integer", k)) // Strict validation [[15]] 79 | } 80 | data[intKey] = v 81 | } 82 | return data 83 | } 84 | -------------------------------------------------------------------------------- /utils/conv/conv_struct.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "errors" 5 | jsoniter "github.com/json-iterator/go" // JSON iterator package with a new name 6 | ) 7 | 8 | // jsoniterPkg is the shared instance for JSON serialization/deserialization. 9 | // Using this shared instance prevents redundant initialization. 10 | var jsoniterPkg = jsoniter.ConfigCompatibleWithStandardLibrary 11 | 12 | // ToStruct converts data from any type to the provided model using JSON serialization and deserialization. 13 | // 使用 JSON 序列化和反序列化将数据从任何类型转换为提供的模型。 14 | // data: The source data that will be converted. Can be a struct, map, or slice. 15 | // 16 | // data: 需要转换的源数据,可以是结构体、映射或切片。 17 | // 18 | // model: The target model that will hold the converted data. 19 | // 20 | // model: 目标模型,将存储转换后的数据。 21 | // 22 | // Returns an error if the conversion fails. 23 | // 如果转换失败,返回错误。 24 | func ToStruct(data any, model any) error { 25 | if data == nil || model == nil { 26 | return errors.New("data and model cannot be nil") // 错误:数据和模型不能为空 27 | } 28 | // Serialize the data to JSON and then deserialize it into the model 29 | // 将数据序列化为 JSON,然后反序列化为目标模型 30 | result, err := jsoniterPkg.Marshal(data) 31 | if err != nil { 32 | return err // 序列化错误,返回错误 33 | } 34 | return jsoniterPkg.Unmarshal(result, model) // 反序列化到目标模型 35 | } 36 | 37 | // UnmarshalJSON deserializes JSON byte data into the provided model. 38 | // 使用 JSON 反序列化将字节数据转化为指定的模型。 39 | // data: The JSON byte data to be converted into the model. 40 | // 41 | // data: 需要转换为模型的 JSON 字节数据。 42 | // 43 | // model: The target model to store the deserialized data. 44 | // 45 | // model: 目标模型,用于存储反序列化后的数据。 46 | // 47 | // Returns an error if deserialization fails. 48 | // 如果反序列化失败,返回错误。 49 | func UnmarshalJSON(data []byte, model any) error { 50 | if data == nil || model == nil { 51 | return errors.New("data and model cannot be nil") // 错误:数据和模型不能为空 52 | } 53 | return jsoniterPkg.Unmarshal(data, model) // 反序列化 JSON 数据到目标模型 54 | } 55 | 56 | // ToJSON serializes the provided data into JSON byte format. 57 | // 将提供的数据序列化为 JSON 字节格式。 58 | // data: The source data to be serialized into JSON. 59 | // 60 | // data: 需要序列化为 JSON 的源数据。 61 | // 62 | // Returns the JSON byte slice or an error if serialization fails. 63 | // 返回 JSON 字节切片,如果序列化失败,则返回错误。 64 | func ToJSON(data any) ([]byte, error) { 65 | if data == nil { 66 | return nil, errors.New("data cannot be nil") // 错误:数据不能为空 67 | } 68 | return jsoniterPkg.Marshal(data) // 序列化数据为 JSON 字节 69 | } 70 | -------------------------------------------------------------------------------- /utils/conv/float.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | ) 10 | 11 | // Float32 converts any type to float32. 12 | // Note that this conversion may lose precision for large integers or high-precision decimals. 13 | // 将任意类型转换为 float32。注意,转换大整数或高精度小数时可能丢失精度。 14 | func Float32(any interface{}) float32 { 15 | if any == nil { 16 | return 0 17 | } 18 | 19 | switch value := any.(type) { 20 | case float32: 21 | return value 22 | case float64: 23 | // Direct conversion from float64 may lose precision 24 | // 直接从 float64 转换可能会丢失精度 25 | return float32(value) 26 | case string: 27 | // Parse with 32-bit precision, returns 0 on failure 28 | // 使用 32 位精度解析字符串,解析失败返回 0 29 | result, err := strconv.ParseFloat(value, 32) 30 | if err != nil { 31 | return 0 32 | } 33 | return float32(result) 34 | case []byte: 35 | // Requires exactly 4 bytes in little-endian IEEE 754 format 36 | // Panics if length is invalid (considered programming error) 37 | // 需要恰好 4 字节小端序 IEEE 754 格式,长度不符会 panic(视为编程错误) 38 | if len(value) != 4 { 39 | panic(errors.New("invalid byte length for float32")) 40 | } 41 | return math.Float32frombits(binary.LittleEndian.Uint32(value)) 42 | default: 43 | // Convert to string first, may lose precision for complex types 44 | // 先转换为字符串,复杂类型可能丢失精度 45 | v, err := strconv.ParseFloat(fmt.Sprintf("%v", any), 32) 46 | if err != nil { 47 | return 0 48 | } 49 | return float32(v) 50 | } 51 | } 52 | 53 | // Float64 converts any type to float64. 54 | // Provides higher precision than Float32 but still has limitations with very large numbers. 55 | // 将任意类型转换为 float64。相比 Float32 精度更高,但对极大数值仍有限制。 56 | func Float64(any interface{}) float64 { 57 | if any == nil { 58 | return 0 59 | } 60 | 61 | switch value := any.(type) { 62 | case float64: 63 | return value 64 | case float32: 65 | // Direct widening conversion preserves original precision 66 | // 直接扩展转换,保留原始精度 67 | return float64(value) 68 | case string: 69 | // Parse with full 64-bit precision, returns 0 on failure 70 | // 使用完整的 64 位精度解析,解析失败返回 0 71 | result, err := strconv.ParseFloat(value, 64) 72 | if err != nil { 73 | return 0 74 | } 75 | return result 76 | case []byte: 77 | // Requires exactly 8 bytes in little-endian IEEE 754 format 78 | // Panics if length is invalid (considered programming error) 79 | // 需要恰好 8 字节小端序 IEEE 754 格式,长度不符会 panic(视为编程错误) 80 | if len(value) != 8 { 81 | panic(errors.New("invalid byte length for float64")) 82 | } 83 | return math.Float64frombits(binary.LittleEndian.Uint64(value)) 84 | default: 85 | // Convert to string first, may lose precision for non-primitive types 86 | // 先转换为字符串,非基本类型可能丢失精度 87 | v, err := strconv.ParseFloat(fmt.Sprintf("%v", any), 64) 88 | if err != nil { 89 | return 0 90 | } 91 | return v 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /utils/page/request.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | // PageParam 分页参数结构体,适用于GET请求 4 | // PageParam is a struct for pagination parameters, typically used in GET requests. 5 | type PageParam struct { 6 | CurrentPage int `form:"current_page" json:"current_page"` // 当前页码,默认为1 / Current page number, default is 1 7 | PageSize int `form:"page_size" json:"page_size"` // 每页显示的数据条数,默认为10 / Number of items per page, default is 10 8 | Filter string `form:"filter" json:"filter"` // 复杂过滤条件,适用于复杂查询 / Complex filtering conditions, suitable for complex queries 9 | FilterMap map[string]any `form:"filter_map" json:"filter_map"` // 过滤条件的另一种形式,使用键值对表示 / Another form of filtering conditions, represented as key-value pairs 10 | Omit string `form:"omit" json:"omit"` // 需要忽略的字段 / Fields to be omitted 11 | Extra map[string]any `form:"extra" json:"extra"` // 额外的参数,用于传递其他自定义信息 / Additional parameters for passing custom information 12 | Order []string `form:"order[]" json:"order[]"` // 排序字段列表 / List of fields to sort by 13 | Desc []bool `form:"desc[]" json:"desc[]"` // 排序方式列表,true表示降序,false表示升序 / List of sorting orders, true for descending, false for ascending 14 | } 15 | 16 | // PageJson 分页参数结构体,适用于POST请求,支持更复杂的查询条件 17 | // PageJson is a struct for pagination parameters, typically used in POST requests, supporting more complex query conditions. 18 | type PageJson struct { 19 | CurrentPage int `form:"current_page" json:"current_page" bson:"current_page" xml:"current_page" yaml:"current_page"` // 当前页码,默认为1 / Current page number, default is 1 20 | PageSize int `form:"page_size" json:"page_size" bson:"page_size" xml:"page_size" yaml:"page_size"` // 每页显示的数据条数,默认为10 / Number of items per page, default is 10 21 | Filter []Filter `form:"filter" json:"filter" bson:"filter" xml:"filter" yaml:"filter"` // 复杂过滤条件列表 / List of complex filtering conditions 22 | FilterMap map[string]any `form:"filter_map" json:"filter_map" bson:"filter_map" xml:"filter_map" yaml:"filter_map"` // 过滤条件的另一种形式,使用键值对表示 / Another form of filtering conditions, represented as key-value pairs 23 | Omit string `form:"omit" json:"omit" bson:"omit" xml:"omit" yaml:"omit"` // 需要忽略的字段 / Fields to be omitted 24 | Extra map[string]any `form:"extra" json:"extra" bson:"extra" xml:"extra" yaml:"extra"` // 额外的参数,用于传递其他自定义信息 / Additional parameters for passing custom information 25 | Order []string `form:"order" json:"order" bson:"order" xml:"order" yaml:"order"` // 排序字段列表 / List of fields to sort by 26 | Desc []bool `form:"desc" json:"desc" bson:"desc" xml:"desc" yaml:"desc"` // 排序方式列表,true表示降序,false表示升序 / List of sorting orders, true for descending, false for ascending 27 | } 28 | 29 | // Filter 过滤条件结构体,用于定义复杂的查询条件 30 | // Filter is a struct for defining complex query conditions. 31 | type Filter struct { 32 | Field string `form:"field" json:"field" bson:"field" xml:"field" yaml:"field"` // 字段名 / Field name 33 | Operator string `form:"operator" json:"operator" bson:"operator" xml:"operator" yaml:"operator"` // 操作符,如 "=", ">", "<" 等 / Operator, such as "=", ">", "<", etc. 34 | Value any `form:"value" json:"value" bson:"value" xml:"value" yaml:"value"` // 字段值 / Field value 35 | And []Filter `form:"and" json:"and" bson:"and" xml:"and" yaml:"and"` // AND 条件列表,用于组合多个过滤条件 / List of AND conditions for combining multiple filters 36 | Or []Filter `form:"or" json:"or" bson:"or" xml:"or" yaml:"or"` // OR 条件列表,用于组合多个过滤条件 / List of OR conditions for combining multiple filters 37 | } 38 | 39 | // New 创建一个默认的分页参数实例,适用于GET请求 40 | // New creates a default instance of PageParam, suitable for GET requests. 41 | func New() PageParam { 42 | return PageParam{ 43 | CurrentPage: 1, 44 | PageSize: 10, 45 | } 46 | } 47 | 48 | // NewJson 创建一个默认的分页参数实例,适用于POST请求 49 | // NewJson creates a default instance of PageJson, suitable for POST requests. 50 | func NewJson() PageJson { 51 | return PageJson{ 52 | CurrentPage: 1, 53 | PageSize: 10, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /utils/password/password.go: -------------------------------------------------------------------------------- 1 | package password 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | ) 6 | 7 | // defaultCost 表示默认的哈希计算成本 8 | // defaultCost represents the default hashing cost 9 | const defaultCost = 8 10 | 11 | // Generate 根据输入密码生成安全的bcrypt哈希 12 | // 特征: 13 | // - 自动生成随机盐值 14 | // - 包含版本信息和哈希配置参数 15 | // - 每次调用生成不同的哈希值 16 | // 17 | // Generate generates secure bcrypt hash from password 18 | // Features: 19 | // - Auto-generates random salt 20 | // - Contains version information and hash parameters 21 | // - Produces different hash values on each invocation 22 | // 23 | // 参数: 24 | // 25 | // password: 需要哈希的原始密码 (raw password to be hashed) 26 | // 27 | // 返回: 28 | // 29 | // string: 生成的哈希字符串 (generated hash string) 30 | // error: 错误信息 (error information) 31 | func Generate(password string) (string, error) { 32 | hashed, err := bcrypt.GenerateFromPassword([]byte(password), defaultCost) 33 | if err != nil { 34 | return "", err 35 | } 36 | return string(hashed), nil 37 | } 38 | 39 | // Verify 验证密码是否匹配存储的哈希值 40 | // Verify verifies if password matches stored hash 41 | // 42 | // 参数: 43 | // 44 | // hashedPassword: 之前生成的哈希密码 (previously generated password hash) 45 | // password: 需要验证的原始密码 (raw password to verify) 46 | // 47 | // 返回: 48 | // 49 | // bool: 验证结果 (verification result) 50 | func Verify(hashedPassword, password string) bool { 51 | err := bcrypt.CompareHashAndPassword( 52 | []byte(hashedPassword), 53 | []byte(password), 54 | ) 55 | return err == nil 56 | } 57 | -------------------------------------------------------------------------------- /utils/plugins/plugins.go: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // PluginFunc defines the interface that all plugins must implement. 9 | // 插件接口,所有插件必须实现该接口 10 | type PluginFunc interface { 11 | // Before method will be executed before the main logic. 12 | // Before方法将在主要逻辑之前执行 13 | Before() interface{} 14 | 15 | // After method will be executed after the main logic. 16 | // After方法将在主要逻辑之后执行 17 | After(data ...interface{}) interface{} 18 | } 19 | 20 | // PluginManager is a struct that manages plugins and provides methods for registering and uninstalling them. 21 | // PluginManager结构体管理插件,并提供注册和卸载插件的方法 22 | type PluginManager struct { 23 | // PluginList is a map that holds all registered plugins by name. 24 | // PluginList是一个存储所有已注册插件的映射,以插件名称为键 25 | PluginList map[string]PluginFunc 26 | // mu is a mutex for managing concurrent access to PluginList. 27 | // mu是一个互斥锁,用于管理对PluginList的并发访问 28 | mu sync.RWMutex 29 | } 30 | 31 | var once sync.Once 32 | var managerInstance *PluginManager 33 | 34 | // New initializes and returns the singleton instance of PluginManager. 35 | // New方法初始化并返回PluginManager的单例实例 36 | func New() *PluginManager { 37 | once.Do(func() { 38 | managerInstance = &PluginManager{ 39 | PluginList: make(map[string]PluginFunc), 40 | } 41 | }) 42 | return managerInstance 43 | } 44 | 45 | // List returns the list of all registered plugins. 46 | // List方法返回所有已注册插件的列表 47 | func List() map[string]PluginFunc { 48 | managerInstance.mu.RLock() 49 | defer managerInstance.mu.RUnlock() 50 | return managerInstance.PluginList 51 | } 52 | 53 | // Register adds a new plugin to the manager. Returns an error if the plugin is already registered. 54 | // Register方法将新插件添加到管理器中。如果插件已注册,则返回错误 55 | func (p *PluginManager) Register(name string, plugin PluginFunc) error { 56 | p.mu.Lock() 57 | defer p.mu.Unlock() 58 | 59 | // Check if the plugin is already registered. 60 | // 检查插件是否已注册 61 | if _, exists := p.PluginList[name]; exists { 62 | return errors.New("plugin already registered") 63 | } 64 | 65 | // Register the plugin. 66 | // 注册插件 67 | p.PluginList[name] = plugin 68 | return nil 69 | } 70 | 71 | // Uninstall removes a plugin by name. Returns an error if the plugin does not exist. 72 | // Uninstall方法根据名称移除插件。如果插件不存在,则返回错误 73 | func (p *PluginManager) Uninstall(name string) error { 74 | p.mu.Lock() 75 | defer p.mu.Unlock() 76 | 77 | // Check if the plugin exists. 78 | // 检查插件是否存在 79 | if _, exists := p.PluginList[name]; !exists { 80 | return errors.New("plugin not found") 81 | } 82 | 83 | // Remove the plugin. 84 | // 移除插件 85 | delete(p.PluginList, name) 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /utils/plugins/plugins_test.go: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | // mockPlugin 是一个简单的实现PluginFunc接口的假插件 9 | type mockPlugin struct{} 10 | 11 | // Before 模拟插件执行前的逻辑 12 | func (m *mockPlugin) Before() interface{} { 13 | return "Before logic executed" 14 | } 15 | 16 | // After 模拟插件执行后的逻辑 17 | func (m *mockPlugin) After(data ...interface{}) interface{} { 18 | return "After logic executed" 19 | } 20 | 21 | func TestPluginManager_Register(t *testing.T) { 22 | manager := New() 23 | 24 | // 测试插件注册 25 | err := manager.Register("plugin1", &mockPlugin{}) 26 | assert.NoError(t, err, "Plugin registration should succeed") 27 | 28 | // 测试重复注册 29 | err = manager.Register("plugin1", &mockPlugin{}) 30 | assert.Error(t, err, "Plugin registration should fail if the plugin is already registered") 31 | assert.Equal(t, "plugin already registered", err.Error(), "Error message should match") 32 | } 33 | 34 | func TestPluginManager_Uninstall(t *testing.T) { 35 | manager := New() 36 | 37 | // 注册插件 38 | err := manager.Register("plugin1", &mockPlugin{}) 39 | assert.NoError(t, err, "Plugin registration should succeed") 40 | 41 | // 卸载插件 42 | err = manager.Uninstall("plugin1") 43 | assert.NoError(t, err, "Plugin uninstallation should succeed") 44 | 45 | // 尝试卸载不存在的插件 46 | err = manager.Uninstall("plugin1") 47 | assert.Error(t, err, "Plugin uninstallation should fail if the plugin doesn't exist") 48 | assert.Equal(t, "plugin not found", err.Error(), "Error message should match") 49 | } 50 | 51 | func TestPluginManager_List(t *testing.T) { 52 | manager := New() 53 | 54 | // 确保插件列表为空 55 | plugins := List() 56 | assert.Empty(t, plugins, "Plugin list should be empty initially") 57 | 58 | // 注册插件 59 | err := manager.Register("plugin1", &mockPlugin{}) 60 | assert.NoError(t, err, "Plugin registration should succeed") 61 | 62 | // 确保插件列表不为空 63 | plugins = List() 64 | assert.NotEmpty(t, plugins, "Plugin list should not be empty after registration") 65 | assert.Len(t, plugins, 1, "Plugin list should contain exactly 1 plugin") 66 | assert.Contains(t, plugins, "plugin1", "Plugin list should contain the registered plugin") 67 | } 68 | 69 | func TestPluginManager_Singleton(t *testing.T) { 70 | // 确保返回的实例是单例 71 | manager1 := New() 72 | manager2 := New() 73 | assert.Same(t, manager1, manager2, "New() should return the same instance every time") 74 | } 75 | -------------------------------------------------------------------------------- /utils/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "github.com/panjf2000/ants/v2" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | once sync.Once // 确保只初始化一次 10 | JobPool *ants.Pool 11 | ) 12 | 13 | // New 初始化 Goroutine 池 14 | // New initializes the Goroutine pool 15 | func New(size, queue int) { 16 | // 检查 Goroutine 池的大小是否大于0 17 | // Ensure the size is positive 18 | if size <= 0 { 19 | panic("size must be positive") // 如果大小无效,则抛出异常 20 | } 21 | 22 | // 如果队列大小无效,则使用默认值 23 | // Set a default queue size if the given queue size is invalid 24 | if queue <= size { 25 | queue = size * 10 // 默认队列大小是 Goroutine 池大小的 10 倍 26 | } 27 | 28 | // 使用 sync.Once 确保初始化池只进行一次 29 | // Use sync.Once to ensure the pool is initialized only once 30 | once.Do(func() { 31 | // 创建 Goroutine 池实例 32 | // Create a new Goroutine pool instance 33 | var err error 34 | JobPool, err = ants.NewPool(size, ants.WithPreAlloc(true), ants.WithMaxBlockingTasks(queue)) 35 | if err != nil { 36 | panic("failed to create pool: " + err.Error()) // 如果池创建失败,抛出异常 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /utils/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | // Write 结构体用于定义API的返回参数 4 | // Write struct defines the response parameters for API 5 | type Write struct { 6 | Status int `json:"status"` // 0: 成功, 1: 失败 / 0: Success, 1: Failure 7 | Code string `json:"code"` // 业务状态码 / Business status code 8 | Message string `json:"message"` // 用户友好提示信息 / User-friendly message 9 | Data any `json:"data"` // 实际数据 / Actual data 10 | Error any `json:"error,omitempty"` // 错误信息 / Error message 11 | } 12 | 13 | // Page 结构体用于分页返回数据 14 | // Page struct defines the pagination response data 15 | type Page struct { 16 | Total int64 `json:"total"` // 总数量 / Total number of items 17 | Items any `json:"items"` // 当前页的数据集合 / Data set of the current page 18 | } 19 | 20 | // Success 函数用于成功返回 21 | // Success function is used for successful response 22 | func Success(code, msg string, data ...any) *Write { 23 | var resultData interface{} 24 | if len(data) == 1 { 25 | resultData = data[0] 26 | } else if len(data) > 1 { 27 | resultData = data 28 | } 29 | 30 | return &Write{ 31 | Status: 0, 32 | Code: code, 33 | Message: msg, 34 | Data: resultData, 35 | } 36 | } 37 | 38 | // Fail 函数用于错误返回 39 | // Fail function is used for error response 40 | func Fail(code, msg string, err ...string) *Write { 41 | var errorData any 42 | if len(err) == 1 { 43 | errorData = err[0] 44 | } else if len(err) > 1 { 45 | errorData = err 46 | } 47 | 48 | return &Write{ 49 | Status: 1, 50 | Code: code, 51 | Message: msg, 52 | Error: errorData, 53 | } 54 | } 55 | 56 | // PageResponse 函数用于分页数据返回 57 | // PageResponse function is used for paginated data response 58 | func PageResponse(total int64, items any) *Page { 59 | return &Page{ 60 | Total: total, 61 | Items: items, 62 | } 63 | } 64 | --------------------------------------------------------------------------------