diff --git a/upload/.gitignore b/upload/.gitignore new file mode 100755 index 0000000..618163d --- /dev/null +++ b/upload/.gitignore @@ -0,0 +1,12 @@ +.idea +.trae +.codebuddy +*.log +*.rar +build +ziqian +upload +package +build +node_modules +config.yaml diff --git a/upload/README.md b/upload/README.md new file mode 100755 index 0000000..5a2d0f6 --- /dev/null +++ b/upload/README.md @@ -0,0 +1,749 @@ +# 子千项目技能文档 + +## 1. 项目概述 + +子千项目是一个基于 Gin 框架开发的 Web 应用,提供了一套完整的工具函数库,用于简化开发流程和统一代码风格。本文档主要介绍项目中的核心工具函数,包括响应处理、身份验证、IP 处理等功能。 + +## 2. 核心工具包 + +### 2.1 utils/zi.go - 响应处理工具 + +`Zi` 是一个核心工具类,用于统一 API 响应格式,提供了多种响应方法,包括成功响应、错误响应、调试响应等。 + +#### 2.1.1 Zi.Echo - 成功响应 + +**功能**:返回标准的成功响应,状态码为 200。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 +- `data interface{}` - 要返回的数据,可以是任意类型 + +**返回格式**: +```json +{ + "code": 200, + "message": "ok", + "data": {...}, // 传入的数据 + "uuid": "..." // 请求 UUID +} +``` + +**使用示例**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func testHandler(c *gin.Context) { + // 简单成功响应,仅返回状态 + u.Zi.Echo(c, gin.H{}) + + // 返回复杂数据结构 + userInfo := map[string]interface{}{ + "id": 1, + "name": "张三", + "age": 25 + } + u.Zi.Echo(c, gin.H{"user": userInfo}) + + // 直接返回数据列表 + users := []string{"user1", "user2", "user3"} + u.Zi.Echo(c, gin.H{"users": users}) + + // 返回处理结果和相关数据 + u.Zi.Echo(c, gin.H{"userId": 1001, "action": "create"}) +} +``` + +**代码规范**: +> 注意1:请不要在 `data` 中使用 `message` 字段,因为 `Zi.Echo` 方法会自动在响应根级别添加 `message` 字段(值为 "ok")。 +> +> 注意2:对于简单的成功响应,后端只需要返回状态即可,不需要在 `data` 中返回成功信息。成功信息应由前端根据返回的 `code` 和 `message` 来处理: +> ```go +> // 推荐:简单成功响应 +> u.Zi.Echo(c, gin.H{}) +> +> // 不推荐:后端返回成功信息 +> u.Zi.Echo(c, gin.H{"result": "测试成功"}) // 成功信息应交给前端处理 +> ``` +> +> 注意3:只有当需要返回具体数据时,才在 `data` 中添加相应字段: +> ```go +> // 推荐:返回具体数据 +> u.Zi.Echo(c, gin.H{"users": users}) +> u.Zi.Echo(c, gin.H{"user": userInfo}) +> ``` + +#### 2.1.2 Zi.Error - 错误响应 + +**功能**:返回标准的错误响应,默认状态码为 100001。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 +- `message string` - 错误信息 +- `code ...int` - 可选参数,自定义错误码 + +**返回格式**: +```json +{ + "code": 100001, // 或自定义错误码 + "message": "错误信息", + "data": {}, + "uuid": "..." +} +``` + +**使用示例**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func testHandler(c *gin.Context) { + // 使用默认错误码 + u.Zi.Error(c, "参数错误") + + // 使用自定义错误码 + u.Zi.Error(c, "权限不足", 403) + + // 在条件判断中使用 + if !hasPermission { + u.Zi.Error(c, "没有操作权限") + return + } +} +``` + +#### 2.1.3 Zi.Exit - 自定义响应 + +**功能**:返回自定义的响应,包括状态码、消息和数据。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 +- `code int` - 状态码 +- `message string` - 响应消息 +- `data interface{}` - 响应数据 + +**返回格式**: +```json +{ + "code": code, + "message": message, + "data": data, + "uuid": "..." +} +``` + +**使用示例**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func testHandler(c *gin.Context) { + // 自定义响应 + u.Zi.Exit(c, 2001, "自定义成功", gin.H{"data": "自定义数据"}) +} +``` + +#### 2.1.4 Zi.Debug - 调试响应 + +**功能**:返回调试响应,用于开发阶段调试。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 +- `data interface{}` - 调试数据 + +**返回格式**: +```json +{ + "code": 100000, + "message": "debug", + "data": {...}, // 调试数据 + "uuid": "..." +} +``` + +**使用示例**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func testHandler(c *gin.Context) { + // 调试响应 + u.Zi.Debug(c, gin.H{"request": c.Request}) +} +``` + +#### 2.1.5 Zi.Html - HTML 响应 + +**功能**:返回 HTML 页面响应。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 +- `name string` - 模板名称 +- `data interface{}` - 模板数据 + +**使用示例**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func testHandler(c *gin.Context) { + // 返回 HTML 页面 + u.Zi.Html(c, "test.html", gin.H{"title": "测试页面"}) +} +``` + +### 2.2 utils/login.go - 身份验证工具 + +`Login` 工具类用于处理管理员身份验证和权限检查,提供了简洁的 API 用于保护需要登录的路由。 + +#### 2.2.1 Login.Admin - 管理员身份验证 + +**功能**:检查管理员是否登录,并验证权限。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 +- `mustAuth []string` - 必须具备的权限列表 +- `oneAuth []string` - 至少具备一个的权限列表 + +**返回值**: +- `bool` - 验证通过返回 true,否则返回 false + +**使用示例**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +// 不需要权限的路由 +func adminStatusHandler(c *gin.Context) { + if !u.Login.Admin(c, []string{}, []string{}) { + return + } + u.Zi.Echo(c, gin.H{"status": "online"}) +} + +// 需要特定权限的路由 +func adminUserHandler(c *gin.Context) { + // 必须具备 user:read 权限 + if !u.Login.Admin(c, []string{"user:read"}, []string{}) { + return + } + u.Zi.Echo(c, gin.H{"users": []string{"user1", "user2"}}) +} + +// 至少具备一个权限的路由 +func adminActionHandler(c *gin.Context) { + // 至少具备 create 或 update 权限 + if !u.Login.Admin(c, []string{}, []string{"action:create", "action:update"}) { + return + } + // 简单成功响应,仅返回状态 + u.Zi.Echo(c, gin.H{}) +} +``` + +### 2.3 IP 处理工具 + +#### 2.3.1 GetClientIP - 获取客户端真实 IP + +**功能**:从请求头或上下文获取客户端的真实 IP 地址,支持代理转发。 + +**参数**: +- `c *gin.Context` - Gin 上下文对象 + +**返回值**: +- `string` - 客户端 IP 地址 + +**使用示例**: +```go +package middleware + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func IpMiddleware(c *gin.Context) { + clientIP := u.GetClientIP(c) + c.Set("client_ip", clientIP) + c.Next() +} +``` + +#### 2.3.2 Zi.Region - IP 地址解析 + +**功能**:根据 IP 地址解析地理位置信息。 + +**参数**: +- `ip string` - IP 地址字符串 + +**返回值**: +- `string` - 地理位置信息,格式如 "省份|城市|运营商" +- `error` - 解析错误 + +**使用示例**: +```go +package utils + +import ( + "log" +) + +func testIpRegion() { + ip := "8.8.8.8" + region, err := Zi.Region(ip) + if err != nil { + log.Printf("IP 解析错误: %v", err) + return + } + log.Printf("IP %s 所属地区: %s", ip, region) +} +``` + +## 3. 路由注册示例 + +### 3.1 基本路由注册 + +**功能**:注册普通路由,无需身份验证。 + +**示例代码**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func SetupRouter() *gin.Engine { + r := gin.Default() + + // 注册普通路由 + r.GET("/", func(c *gin.Context) { + u.Zi.Echo(c, gin.H{"welcome": "欢迎访问子千项目"}) + }) + + return r +} +``` + +### 3.2 API 路由注册 + +**功能**:注册 API 路由,按模块分组管理。 + +**示例代码**: +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func RegisterAdminRoutes(router *gin.RouterGroup) { + group := router.Group("/admin") + { + group.POST("/login", adminLoginHandler) + group.POST("/status", adminStatusHandler) + group.POST("/info", adminInfoHandler) + group.POST("/menu", adminMenuHandler) + } +} + +func RegisterApiRoutes(r *gin.Engine) { + apiGroup := r.Group("/api") + { + RegisterAdminRoutes(apiGroup) + // 注册其他模块路由 + } +} +``` + +### 3.3 子路由注册 + +**功能**:从子包注册路由,实现模块化管理。 + +**示例代码**: + +```go +// router/admin.go +package router + +import ( + "ziqian/router/admin" + "github.com/gin-gonic/gin" +) + +func RegisterAdminRoutes(router *gin.RouterGroup) { + group := router.Group("/admin") + { + // 注册基础路由 + group.POST("/login", adminLoginHandler) + + // 从子包注册路由 + admin.RegisterAdminChangeRoutes(group) + } +} + +// router/admin/change.go +package admin + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" +) + +func RegisterAdminChangeRoutes(router *gin.RouterGroup) { + group := router.Group("/change") + { + group.POST("/info", adminChangeInfoHandler) + group.POST("/password", adminChangePasswordHandler) + } +} +``` + +## 4. 最佳实践 + +### 4.1 统一响应格式 + +在所有 API 路由中使用 `Zi` 工具类返回响应,保持 API 响应格式的一致性。 + +**核心规范**: + +1. **禁止在 `data` 中使用 `message` 字段**:`Zi.Echo` 会自动在响应根级别添加 `message` 字段(值为 "ok"),避免嵌套混乱。 + +2. **简单成功响应只返回状态**:对于简单的成功操作,后端只需要返回状态即可,成功信息由前端根据 `code` 和 `message` 处理: + ```go + // 推荐:简单成功响应 + u.Zi.Echo(c, gin.H{}) + + // 不推荐:后端返回成功信息 + u.Zi.Echo(c, gin.H{"result": "操作成功"}) // 成功信息应交给前端处理 + ``` + +3. **仅在需要时返回数据**:只有当需要返回具体业务数据时,才在 `data` 中添加相应字段: + ```go + // 推荐:返回具体数据 + u.Zi.Echo(c, gin.H{"users": users}) // 返回用户列表 + u.Zi.Echo(c, gin.H{"user": userInfo}) // 返回用户信息 + u.Zi.Echo(c, gin.H{"userId": 1001}) // 返回新创建的用户ID + ``` + +4. **推荐格式示例**: + ```json + // 简单成功响应 + { + "code": 200, + "message": "ok", + "data": {}, + "uuid": "..." + } + + // 返回具体数据 + { + "code": 200, + "message": "ok", + "data": { + "users": ["user1", "user2", "user3"] + }, + "uuid": "..." + } + ``` + +### 4.2 权限验证 + +对于需要登录的路由,使用 `Login.Admin` 方法进行身份验证和权限检查,确保 API 安全。 + +### 4.3 错误处理 + +使用 `Zi.Error` 方法返回错误信息,提供明确的错误码和错误描述,方便前端处理。 + +### 4.4 模块化路由 + +按功能模块组织路由,使用子包注册路由,提高代码的可维护性和扩展性。 + +### 4.5 模型导入别名约定 + +了解 `models` 文件夹结构,需要使用别名导入不同模块的模型,以保持代码的清晰性和一致性。 + +**核心约定**: + +1. **admin 模块模型**:使用 `amod` 作为别名 + ```go + import ( + amod "ziqian/models/admin" + ) + ``` + +2. **user 模块模型**:使用 `umod` 作为别名 + ```go + import ( + umod "ziqian/models/user" + ) + ``` + +**使用示例**: + +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" + "ziqian/database" + amod "ziqian/models/admin" + umod "ziqian/models/user" +) + +// 使用 admin 模块模型 +func getAdminListHandler(c *gin.Context) { + var admins []amod.Admin + // 业务逻辑... +} + +// 使用 user 模块模型 +func getUserListHandler(c *gin.Context) { + var users []umod.User + // 业务逻辑... +} +``` + +**注意事项**: + +- 严格按照模块使用对应的别名,避免混用 +- 在所有导入模型的文件中保持一致的别名约定 +- 当需要同时使用多个模块的模型时,同时导入多个别名 + +```go +// 同时使用 admin 和 user 模块模型 +import ( + amod "ziqian/models/admin" + umod "ziqian/models/user" +) +``` + +## 5. 完整示例 + +### 5.1 实现一个完整的 API 路由 + +```go +package router + +import ( + u "ziqian/utils" + "github.com/gin-gonic/gin" + "ziqian/database" + amod "ziqian/models/admin" + umod "ziqian/models/user" +) + +func RegisterUserRoutes(router *gin.RouterGroup) { + group := router.Group("/user") + { + group.GET("/list", getUserListHandler) + group.GET("/info", getUserInfoHandler) + group.POST("/create", createUserHandler) + group.POST("/update", updateUserHandler) + group.POST("/delete", deleteUserHandler) + } +} + +func getUserListHandler(c *gin.Context) { + if !u.Login.Admin(c, []string{"user:read"}, []string{}) { + return + } + + db := database.GetDB("main") + if db == nil { + u.Zi.Error(c, "数据库连接失败") + return + } + + var users []umod.User + if err := db.Find(&users).Error; err != nil { + u.Zi.Error(c, "获取用户列表失败") + return + } + + u.Zi.Echo(c, gin.H{"users": users}) +} + +func getUserInfoHandler(c *gin.Context) { + if !u.Login.Admin(c, []string{"user:read"}, []string{}) { + return + } + + // 实现获取用户信息逻辑 + userInfo := map[string]interface{}{ + "id": 1, + "name": "张三", + "age": 25 + } + u.Zi.Echo(c, gin.H{"user": userInfo}) +} + +func createUserHandler(c *gin.Context) { + if !u.Login.Admin(c, []string{"user:create"}, []string{}) { + return + } + + // 实现创建用户逻辑 + // 如果需要返回新创建的用户ID,可以这样做 + u.Zi.Echo(c, gin.H{"userId": 1001}) + + // 如果不需要返回数据,直接返回空即可 + // u.Zi.Echo(c, gin.H{}) +} + +func updateUserHandler(c *gin.Context) { + if !u.Login.Admin(c, []string{"user:update"}, []string{}) { + return + } + + // 实现更新用户逻辑 + // 简单更新操作,只返回状态 + u.Zi.Echo(c, gin.H{}) +} + +func deleteUserHandler(c *gin.Context) { + if !u.Login.Admin(c, []string{"user:delete"}, []string{}) { + return + } + + // 实现删除用户逻辑 + // 简单删除操作,只返回状态 + u.Zi.Echo(c, gin.H{}) +} +``` + +## 6. 总结 + +本文档介绍了子千项目中的核心工具函数,包括响应处理、身份验证、IP 处理等功能。通过使用这些工具函数,可以简化开发流程,统一代码风格,提高代码的可维护性和扩展性。 + +在实际开发中,建议按照本文档中的最佳实践使用这些工具函数,确保 API 设计的一致性和安全性。 + +## 7. 后续更新 + +本文档将随着项目的发展不断更新,新增的功能和工具函数将及时添加到文档中。 + +## 8. 注意事项 + +### 8.1 时间格式化规范 + +**功能**:统一 API 响应中的时间格式,确保前端展示一致性。 + +**规范**: +- 所有时间字段(如 `created_at`、`updated_at`)应使用 `Format("2006-01-02 15:04:05")` 进行格式化 +- 格式化后的时间格式为:`2026-01-31 15:28:07` +- 避免使用默认的 RFC3339 格式(如 `2026-01-31T15:28:07+08:00`),因为这种格式在前端展示时不够直观 + +**使用示例**: +```go +// 错误:使用默认时间格式 +resultList = append(resultList, map[string]interface{}{ + "created_at": item.CreatedAt, // 会返回 RFC3339 格式 + "updated_at": item.UpdatedAt +}) + +// 正确:使用标准时间格式 +resultList = append(resultList, map[string]interface{}{ + "created_at": item.CreatedAt.Format("2006-01-02 15:04:05"), + "updated_at": item.UpdatedAt.Format("2006-01-02 15:04:05") +}) +``` + +**适用范围**: +- 所有 API 响应中的时间字段 +- 特别是列表接口(如 `/api/admin/admin/list`)中的时间展示 + +--- + +## 9. 项目构建与运行 + +### 9.1 构建项目 + +使用以下命令构建 Linux 64 位版本的可执行文件: + +```bash +GOOS=linux GOARCH=amd64 go build -o build/ziqian-linux-amd64-$(date +%Y%m%d%H%M) main.go + +GOOS=windows GOARCH=amd64 go build -o build/ziqian-windows-amd64-$(date +%Y%m%d%H%M).exe main.go +``` + +### 9.2 运行项目 + +使用以下命令直接运行项目: + +```bash +go run main.go +``` + +--- + +## 10. Nginx 配置 + +以下是项目的 Nginx 配置示例,用于部署到生产环境: + +```nginx +# mana 管理目录(无缓存,内容频繁变动) +location /mana/ { + root /data/web/ziqian.online/golang/hoho; + autoindex off; # 关闭目录列表(安全) + expires -1; # 完全禁止缓存,确保获取最新内容 + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate"; + add_header Pragma "no-cache"; # 兼容旧浏览器 + add_header Expires "0"; # 立即过期 + try_files $uri $uri/ =404; +} + +# static 静态目录(长期缓存,资源稳定) +location /static/ { + root /data/web/ziqian.online/golang/hoho; + autoindex off; + expires 30d; + add_header Cache-Control "public, max-age=2592000"; + try_files $uri $uri/ =404; +} + +# upload 上传目录(无缓存+请求限制) +location /upload/ { + root /data/web/ziqian.online/golang/hoho; + autoindex off; + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + try_files $uri $uri/ =404; + # 仅允许GET/HEAD请求,防止恶意上传 + limit_except GET HEAD { + deny all; + } +} +``` + +--- + +**文档版本**:1.0.0 +**更新时间**:2026-02-08 +**作者**:子千项目组 \ No newline at end of file diff --git a/upload/config.yaml.example b/upload/config.yaml.example new file mode 100755 index 0000000..ff17e23 --- /dev/null +++ b/upload/config.yaml.example @@ -0,0 +1,7 @@ +# 子千项目配置文件示例 +# 复制此文件为 config.yaml 并根据实际环境修改 + +# Gin 框架配置 +gin: + # 服务器监听端口 + port: "8850" \ No newline at end of file diff --git a/upload/config/config.go b/upload/config/config.go new file mode 100755 index 0000000..311b498 --- /dev/null +++ b/upload/config/config.go @@ -0,0 +1,79 @@ +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// Config 应用配置 +type Config struct { + Gin GinConfig `json:"gin" yaml:"gin"` +} + +// GinConfig Gin配置 +type GinConfig struct { + Port string `json:"port" yaml:"port"` +} + +// getDefaultConfig 获取默认配置 +func getDefaultConfig() *Config { + return &Config{ + Gin: GinConfig{ + Port: "8850", + }, + } +} + +// loadFromFile 从配置文件加载配置 +func loadFromFile() (*Config, error) { + configPaths := []string{ + "./config.yaml", + } + + for _, path := range configPaths { + if _, err := os.Stat(path); err == nil { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read config file error: %w", err) + } + + var config Config + if err := yaml.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("unmarshal config file error: %w", err) + } + + return &config, nil + } + } + + return nil, nil +} + +// loadFromEnv 从环境变量加载配置 +func loadFromEnv(config *Config) { + // 加载 Gin 配置 + if port := os.Getenv("GIN_PORT"); port != "" { + config.Gin.Port = port + } +} + +// GetConfig 获取配置 +func GetConfig() *Config { + // 先加载默认配置 + config := getDefaultConfig() + + // 再从配置文件加载 + if fileConfig, err := loadFromFile(); err == nil && fileConfig != nil { + // 合并配置文件中的配置 + if fileConfig.Gin.Port != "" { + config.Gin.Port = fileConfig.Gin.Port + } + } + + // 最后从环境变量加载 + loadFromEnv(config) + + return config +} \ No newline at end of file diff --git a/upload/go.mod b/upload/go.mod new file mode 100755 index 0000000..e4e53db --- /dev/null +++ b/upload/go.mod @@ -0,0 +1,41 @@ +module ziqian + +go 1.24.0 + +require ( + github.com/gin-gonic/gin v1.11.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.uber.org/mock v0.6.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/upload/go.sum b/upload/go.sum new file mode 100755 index 0000000..8a648ea --- /dev/null +++ b/upload/go.sum @@ -0,0 +1,96 @@ +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/upload/main.go b/upload/main.go new file mode 100755 index 0000000..5e5f81e --- /dev/null +++ b/upload/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "ziqian/config" + "ziqian/router" +) + +func main() { + // 初始化配置 + cfg := config.GetConfig() + + // 设置路由 + r := router.SetupRouter() + + // 启动服务器 + addr := fmt.Sprintf(":%s", cfg.Gin.Port) + fmt.Printf("server is running on %s\n", addr) + if err := r.Run(addr); err != nil { + fmt.Printf("start server error: %v\n", err) + } +} \ No newline at end of file diff --git a/upload/router/router.go b/upload/router/router.go new file mode 100755 index 0000000..d73964f --- /dev/null +++ b/upload/router/router.go @@ -0,0 +1,123 @@ +package router + +import ( + "fmt" + "strings" + "time" + u "ziqian/utils" + + "github.com/gin-gonic/gin" +) + +func SetupRouter() *gin.Engine { + r := gin.Default() + + r.LoadHTMLFiles("templates/index.html") + + // 只在开发环境中配置静态文件服务 + if gin.Mode() == gin.DebugMode { + // 配置静态文件服务 + r.Static("/static", "./static") + fmt.Println("Development mode: Static file services enabled") + } else { + fmt.Println("Production mode: Static file services disabled") + } + + // 添加CORS中间件 + r.Use(func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + }) + + r.GET("/", func(c *gin.Context) { + u.Zi.Html(c, "index.html", gin.H{ + "Title": "Ziqian API", + "Subtitle": "基于 Gin 框架的 API 服务", + "CurrentTime": time.Now().Format("2006-01-02 15:04:05"), + "Version": "1.0.0", + "Year": time.Now().Year(), + }) + }) + + apiGroup := r.Group("/api") + { + apiGroup.Any("/ho", func(c *gin.Context) { + u.Zi.Echo(c, gin.H{ + "status": "ok", + }) + }) + + apiGroup.Any("/map", func(c *gin.Context) { + cParam := c.Query("c") + typeList := strings.Split(cParam, ",") + if cParam == "" { + typeList = nil + } + + processed := make(map[string]bool) + apiRoutes := make(map[string]string) + + protocol := "http" + xForwardedProto := c.GetHeader("X-Forwarded-Proto") + xScheme := c.GetHeader("X-Scheme") + if c.Request.TLS != nil || xForwardedProto == "https" || xScheme == "https" { + protocol = "https" + } + host := c.Request.Host + + for _, route := range r.Routes() { + path := route.Path + if !strings.HasPrefix(path, "/api/") || path == "/api/map" || processed[path] { + continue + } + processed[path] = true + + keyName := "" + for i, part := range strings.Split(strings.TrimPrefix(path, "/api/"), "/") { + if part == "" { + continue + } + upperPart := strings.ToUpper(string(part[0])) + part[1:] + keyName += func() string { + if i > 0 { + return "_" + upperPart + } + return upperPart + }() + } + + if typeList != nil { + typePrefix := strings.ToLower(strings.Split(keyName, "_")[0]) + matched := false + for _, t := range typeList { + if strings.ToLower(t) == typePrefix { + matched = true + break + } + } + if !matched { + continue + } + } + + apiRoutes[keyName] = fmt.Sprintf("%s://%s%s", protocol, host, path) + } + + apiRoutes["Map"] = fmt.Sprintf("%s://%s%s", protocol, host, "/api/map") + apiRoutes["Ho"] = fmt.Sprintf("%s://%s%s", protocol, host, "/api/ho") + + u.Zi.Echo(c, apiRoutes) + }) + } + + return r +} diff --git a/upload/static/clash.yaml b/upload/static/clash.yaml new file mode 100755 index 0000000..7218c38 --- /dev/null +++ b/upload/static/clash.yaml @@ -0,0 +1,19 @@ +port: 7890 +socks-port: 7891 +allow-lan: true +mode: Rule +log-level: info +external-controller: 127.0.0.1:9090 +proxies: + - {name: 美国🇺🇸, server: 142.171.118.127, port: 59701, type: ssr, cipher: dummy, password: ssff6288, protocol: auth_aes128_md5, obfs: plain, protocol-param: "", obfs-param: "", udp: true} +proxy-groups: + - name: 🚀 节点选择 + type: select + proxies: + - 🚀 手动切换 + - DIRECT + - name: 🚀 手动切换 + type: select + proxies: + - 美国🇺🇸 +rules: diff --git a/upload/templates/index.html b/upload/templates/index.html new file mode 100755 index 0000000..a0ae0e6 --- /dev/null +++ b/upload/templates/index.html @@ -0,0 +1,83 @@ + + + + + + Ziqian - 首页 + + + +
+
+

{{.Title}}

+

{{.Subtitle}}

+
+
+ +
+
+

欢迎使用 Ziqian API

+

这是一个基于 Gin 框架开发的 API 服务。

+ +
+

当前系统信息

+

服务器时间:{{.CurrentTime}}

+

项目版本:{{.Version}}

+
+
+ + +
+ + \ No newline at end of file diff --git a/upload/utils/zi.go b/upload/utils/zi.go new file mode 100755 index 0000000..c67b58a --- /dev/null +++ b/upload/utils/zi.go @@ -0,0 +1,60 @@ +package utils + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func GetClientIP(c *gin.Context) string { + realIP := c.GetHeader("X-Forwarded-For") + if realIP != "" { + ips := strings.Split(realIP, ",") + for _, ip := range ips { + ip = strings.TrimSpace(ip) + if ip != "" && ip != "127.0.0.1" { + return ip + } + } + } + + realIP = c.GetHeader("X-Real-IP") + if realIP != "" && realIP != "127.0.0.1" { + return realIP + } + + return c.ClientIP() +} + +type ZiUtil struct{} + +var Zi = &ZiUtil{} + +func (z *ZiUtil) Exit(c *gin.Context, code int, message string, data interface{}) { + c.JSON(http.StatusOK, gin.H{ + "code": code, + "message": message, + "data": data, + }) +} + +func (z *ZiUtil) Echo(c *gin.Context, data interface{}) { + Zi.Exit(c, 200, "ok", data) +} + +func (z *ZiUtil) Error(c *gin.Context, message string, code ...int) { + errCode := 100001 + if len(code) > 0 { + errCode = code[0] + } + Zi.Exit(c, errCode, message, gin.H{}) +} + +func (z *ZiUtil) Debug(c *gin.Context, data interface{}) { + Zi.Exit(c, 100000, "debug", data) +} + +func (z *ZiUtil) Html(c *gin.Context, name string, data interface{}) { + c.HTML(200, name, data) +}