main
鹿和sa0ChunLuyu 4 days ago
parent caf831c320
commit 184f05a7f7

12
upload/.gitignore vendored

@ -0,0 +1,12 @@
.idea
.trae
.codebuddy
*.log
*.rar
build
ziqian
upload
package
build
node_modules
config.yaml

@ -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
**作者**:子千项目组

@ -0,0 +1,7 @@
# 子千项目配置文件示例
# 复制此文件为 config.yaml 并根据实际环境修改
# Gin 框架配置
gin:
# 服务器监听端口
port: "8850"

@ -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
}

@ -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
)

@ -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=

@ -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)
}
}

@ -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
}

@ -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:

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ziqian - 首页</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #333;
color: white;
padding: 20px 0;
text-align: center;
}
h1 {
margin: 0;
}
.content {
background-color: white;
padding: 40px;
margin-top: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.info {
margin: 20px 0;
padding: 20px;
background-color: #f0f0f0;
border-radius: 4px;
}
.api-list {
margin-top: 30px;
}
.api-item {
margin: 10px 0;
padding: 15px;
background-color: #e9ecef;
border-radius: 4px;
}
.footer {
text-align: center;
margin-top: 40px;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<header>
<div class="container">
<h1>{{.Title}}</h1>
<p>{{.Subtitle}}</p>
</div>
</header>
<div class="container">
<div class="content">
<h2>欢迎使用 Ziqian API</h2>
<p>这是一个基于 Gin 框架开发的 API 服务。</p>
<div class="info">
<h3>当前系统信息</h3>
<p><strong>服务器时间:</strong>{{.CurrentTime}}</p>
<p><strong>项目版本:</strong>{{.Version}}</p>
</div>
</div>
<div class="footer">
<p>© {{.Year}} Ziqian. All rights reserved.</p>
</div>
</div>
</body>
</html>

@ -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)
}
Loading…
Cancel
Save