Browse Source

refactor: restructure project — server/ → internal/, utils → pkg/, preserve package structure

lq 2 months ago
parent
commit
2661ab0dc6
100 changed files with 24845 additions and 120 deletions
  1. 184 0
      doc/代码规划.md
  2. 56 51
      go.mod
  3. 135 69
      go.sum
  4. 24 0
      internal/Dockerfile
  5. 54 0
      internal/README.md
  6. 15 0
      internal/api/v1/enter.go
  7. 11 0
      internal/api/v1/example/enter.go
  8. 150 0
      internal/api/v1/example/exa_breakpoint_continue.go
  9. 110 0
      internal/api/v1/example/exa_file_upload_download.go
  10. 11 0
      internal/api/v1/owner/enter.go
  11. 120 0
      internal/api/v1/owner/owner.go
  12. 121 0
      internal/api/v1/parking/booth.go
  13. 122 0
      internal/api/v1/parking/channel.go
  14. 7 0
      internal/api/v1/parking/enter.go
  15. 122 0
      internal/api/v1/parking/parking_lot.go
  16. 30 0
      internal/api/v1/system/enter.go
  17. 231 0
      internal/api/v1/system/sys_api.go
  18. 177 0
      internal/api/v1/system/sys_authority.go
  19. 80 0
      internal/api/v1/system/sys_authority_btn.go
  20. 70 0
      internal/api/v1/system/sys_captcha.go
  21. 68 0
      internal/api/v1/system/sys_casbin.go
  22. 129 0
      internal/api/v1/system/sys_dictionary.go
  23. 148 0
      internal/api/v1/system/sys_dictionary_detail.go
  24. 33 0
      internal/api/v1/system/sys_jwt_blacklist.go
  25. 278 0
      internal/api/v1/system/sys_menu.go
  26. 149 0
      internal/api/v1/system/sys_operation_record.go
  27. 473 0
      internal/api/v1/system/sys_user.go
  28. 192 0
      internal/api/v1/uhf/reader.go
  29. 35 0
      internal/api/v1/vehicle/enter.go
  30. 101 0
      internal/api/v1/vehicle/fee_config.go
  31. 86 0
      internal/api/v1/vehicle/shortlist.go
  32. 160 0
      internal/api/v1/vehicle/vehicle.go
  33. 78 0
      internal/api/v1/vehicle/vehicle_record.go
  34. 113 0
      internal/api/v1/vehicle/vehicle_type.go
  35. 220 0
      internal/config.docker.yaml
  36. 222 0
      internal/config.yaml
  37. 9 0
      internal/config/captcha.go
  38. 30 0
      internal/config/config.go
  39. 14 0
      internal/config/cors.go
  40. 32 0
      internal/config/db_list.go
  41. 11 0
      internal/config/email.go
  42. 5 0
      internal/config/excel.go
  43. 14 0
      internal/config/gorm_mssql.go
  44. 13 0
      internal/config/gorm_mysql.go
  45. 14 0
      internal/config/gorm_oracle.go
  46. 21 0
      internal/config/gorm_pgsql.go
  47. 17 0
      internal/config/gorm_sqlite.go
  48. 8 0
      internal/config/jwt.go
  49. 41 0
      internal/config/mongo.go
  50. 10 0
      internal/config/oss_aliyun.go
  51. 13 0
      internal/config/oss_aws.go
  52. 9 0
      internal/config/oss_huawei.go
  53. 6 0
      internal/config/oss_local.go
  54. 11 0
      internal/config/oss_qiniu.go
  55. 10 0
      internal/config/oss_tencent.go
  56. 9 0
      internal/config/redis.go
  57. 13 0
      internal/config/system.go
  58. 60 0
      internal/config/zap.go
  59. 9 0
      internal/core/internal/constant.go
  60. 97 0
      internal/core/internal/cutter.go
  61. 21 0
      internal/core/internal/file_rotatelogs.go
  62. 101 0
      internal/core/internal/zap.go
  63. 40 0
      internal/core/server.go
  64. 19 0
      internal/core/server_other.go
  65. 21 0
      internal/core/server_win.go
  66. 84 0
      internal/core/viper.go
  67. 28 0
      internal/core/zap.go
  68. 19 0
      internal/dao/booth.go
  69. 22 0
      internal/dao/camera.go
  70. 23 0
      internal/dao/channel.go
  71. 62 0
      internal/dao/exa_file_upload_download.go
  72. 21 0
      internal/dao/fee_config.go
  73. 20 0
      internal/dao/owner.go
  74. 20 0
      internal/dao/parking_lot.go
  75. 75 0
      internal/dao/shortlist.go
  76. 113 0
      internal/dao/sys_api.go
  77. 60 0
      internal/dao/sys_authority.go
  78. 62 0
      internal/dao/sys_authority_btn.go
  79. 39 0
      internal/dao/sys_authority_menu.go
  80. 139 0
      internal/dao/sys_base_menu.go
  81. 70 0
      internal/dao/sys_dictionary.go
  82. 101 0
      internal/dao/sys_dictionary_detail.go
  83. 21 0
      internal/dao/sys_jwt_blacklist.go
  84. 10 0
      internal/dao/sys_menu_btn.go
  85. 76 0
      internal/dao/sys_operation_record.go
  86. 155 0
      internal/dao/sys_user.go
  87. 11 0
      internal/dao/sys_user_authority.go
  88. 39 0
      internal/dao/uhf_reader.go
  89. 26 0
      internal/dao/vehicle.go
  90. 25 0
      internal/dao/vehicle_record.go
  91. 13 0
      internal/dao/vehicle_type.go
  92. 7018 0
      internal/docs/docs.go
  93. 6992 0
      internal/docs/swagger.json
  94. 4279 0
      internal/docs/swagger.yaml
  95. 53 0
      internal/global/global.go
  96. 14 0
      internal/global/model.go
  97. 36 0
      internal/initialize/db_list.go
  98. 67 0
      internal/initialize/gorm.go
  99. 59 0
      internal/initialize/gorm_mssql.go
  100. 0 0
      internal/initialize/gorm_mysql.go

+ 184 - 0
doc/代码规划.md

@@ -0,0 +1,184 @@
+针对 **Wails v2 (Go) + Vue3** 的技术栈,建议采用**“按业务功能切片”**的架构设计。
+相较于传统的“把所有 handler 放一个目录,所有 service 放一个目录”,按功能切片(每个模块内部自带 handler/service/repo)能更好地隔离业务逻辑,非常适合这种模块边界清晰的停车系统。
+以下是完整的项目代码模块结构规划:
+### 顶层目录结构
+```text
+parking-system/
+├── build/                      # Wails 构建配置 (图标、安装包脚本)
+├── frontend/                   # Vue3 前端源码 (Vite)
+├── internal/                   # 核心业务代码 (不可被外部引用)
+├── pkg/                        # 通用工具包
+├── app.go                      # Wails 应用主结构体 (统一绑定前端方法)
+├── main.go                     # Wails 程序入口
+├── go.mod
+└── wails.json
+```
+### `internal/` 详细拆解 (核心)
+这是整个后端的灵魂。我们将基础架构、硬件代理、边缘端通信和业务模块分开管理。
+```text
+internal/
+├── core/                       # 基础设施与共享内核
+│   ├── config/                 # 全局配置加载 (解析 config.yaml)
+│   ├── database/               # SQLite/GORM 初始化、连接池、自动迁移
+│   ├── logger/                 # Zap 日志封装
+│   └── response/               # 统一的 API 返回结构封装
+│
+├── hardware/                   # [P0] 本地硬件交互层 (Go直接控制物理设备)
+│   ├── pos/
+│   │   ├── pos_driver.go       # 串口底层管理 (打开/关闭/重连)
+│   │   └── ingenico.go         # POS 机协议解析与指令收发
+│   └── printer/
+│       ├── escpos.go           # ESC/POS 指令生成
+│       └── ticket_template.go  # 小票排版模板渲染
+│
+├── edge/                       # [P0] 边缘端(RK3568)通信层
+│   ├── tcp_server.go           # 监听 RK3568 的 TCP 连接与心跳
+│   ├── protocol.go             # 定义与 RK3568 的上下行报文协议
+│   └── event_bus.go            # 将底层硬件事件分发到业务模块
+│
+├── modules/                    # ★ 业务功能模块 (按功能切片划分) ★
+│   ├── system/                 # [P1] 系统与权限管理模块
+│   ├── parking/                # [P0] 车场与设备管理模块
+│   ├── vehicle/                # [P0] 车辆与车流管理模块
+│   ├── billing/                # [P0] 计费规则引擎模块
+│   ├── order/                  # [P0] 收费与订单管理模块
+│   ├── monitor/                # [P2] 监控与大屏展示模块
+│   └── report/                 # [P2] 数据统计与报表模块
+│
+└── wails_bindings/             # Wails 前端绑定注册中心
+    └── register.go             # 集中实例化所有模块的 Service,并返回给 app.go 用于 Bind
+```
+### `internal/modules/` 业务模块内部结构
+每一个业务模块内部保持一致的“微型三层架构”。以最核心的 `order` (订单收费) 和 `billing` (计费) 模块为例:
+```text
+modules/
+├── billing/                    # [P0] 计费规则引擎
+│   ├── model/
+│   │   └── billing_rule.go     # 计费规则表结构体 (GORM Model)
+│   ├── repository/
+│   │   └── rule_repo.go        # 规则的 CRUD 操作
+│   ├── service/
+│   │   ├── calculator.go       # ★ 核心计费算法 (按时长、阶梯、封顶计算)
+│   │   └── rule_service.go     # 规则的增删改查逻辑
+│   └── handler.go              # (可选) 如果有 REST API 路由则放这里
+│
+├── order/                      # [P0] 收费与订单管理 (前端交互最频繁)
+│   ├── model/
+│   │   ├── order.go            # 订单主表 (入场时间、金额、状态)
+│   │   └── shift_record.go     # 交接班记录表
+│   ├── repository/
+│   │   ├── order_repo.go       # 订单流水查询
+│   │   └── shift_repo.go       # 交接班统计查询
+│   ├── service/
+│   │   ├── checkout_service.go # ★ 出场结算逻辑 (调用 billing 计算费用)
+│   │   ├── payment_service.go  # ★ 支付执行 (联动 hardware/pos 和 printer)
+│   │   └── shift_service.go    # 当班对账逻辑
+│   └── api.go                  # ★ 暴露给 Wails 前端的方法 (如 ProcessCheckout, PayByCash)
+│
+├── vehicle/                    # [P0] 车辆管理
+│   ├── model/
+│   │   ├── whitelist.go        # 白名单/内部车
+│   │   └── monthly_card.go     # 月卡车及续费记录
+│   ├── repository/
+│   │   └── vehicle_repo.go
+│   ├── service/
+│   │   ├── whitelist_service.go# 白名单校验逻辑
+│   │   └── monthly_service.go  # 月卡办理与有效期校验
+│   └── api.go                  # 暴露给前端的车辆管理方法
+│
+├── parking/                    # [P0] 车场与设备配置
+│   ├── model/
+│   │   ├── lane.go             # 车道配置
+│   │   └── device.go           # RK3568节点与本地外设参数
+│   ├── repository/
+│   │   └── config_repo.go
+│   ├── service/
+│   │   └── device_service.go   # 设备状态监控、参数下发
+│   └── api.go
+│
+├── system/                     # [P1] 基础权限
+│   ├── model/
+│   │   └── user.go
+│   ├── service/
+│   │   └── auth_service.go     # 登录验证、密码修改
+│   └── api.go
+│
+├── monitor/                    # [P2] 监控大屏
+│   ├── service/
+│   │   └── realtime_service.go # 聚合设备状态与实时过车数据
+│   └── api.go
+│
+└── report/                     # [P2] 报表
+    ├── service/
+    │   ├── finance_service.go  # 财务收入统计
+    │   └── traffic_service.go  # 车流量分析
+    └── api.go
+```
+### 如何在 Wails 中将这些模块暴露给前端?
+在 `app.go` 中,我们不需要把所有的逻辑都塞进去,而是通过 `internal/wails_bindings/register.go` 统一管理依赖注入。
+**1. `internal/wails_bindings/register.go`**
+```go
+package wails_bindings
+import (
+	"parking-system/internal/core/database"
+	"parking-system/internal/hardware/pos"
+	"parking-system/internal/modules/order"
+	"parking-system/internal/modules/billing"
+	// ... 其他模块
+)
+// Register 初始化所有依赖并返回需要绑定给前端的结构体实例
+func Register(db *gorm.DB) []interface{} {
+	// 1. 初始化底层硬件
+	posDriver := pos.NewDriver("COM3", 9600)
+	
+	// 2. 初始化各模块的 Repository
+	orderRepo := order.NewOrderRepo(db)
+	billingRepo := billing.NewRuleRepo(db)
+	
+	// 3. 初始化各模块的 Service (注入 Repo 和硬件依赖)
+	billingSvc := billing.NewService(billingRepo)
+	orderSvc := order.NewService(orderRepo, billingSvc, posDriver)
+	
+	// 4. 初始化各模块的 API (注入 Service)
+	orderAPI := order.NewAPI(orderSvc)
+	// billingAPI := billing.NewAPI(billingSvc)
+	
+	// 返回所有需要暴露给前端的 API 实例
+	return []interface{}{
+		orderAPI,
+		// billingAPI,
+	}
+}
+```
+**2. `main.go` (Wails 入口)**
+```go
+package main
+import (
+	"context"
+	"parking-system/internal/core/database"
+	"parking-system/internal/wails_bindings"
+	"github.com/wailsapp/wails/v2/pkg/options"
+)
+func main() {
+	// 1. 初始化数据库
+	db := database.Init("%APPDATA%/smart-parking/lc_garage.db")
+	
+	// 2. 获取所有前端绑定实例
+	binds := wails_bindings.Register(db)
+	// 3. 启动 Wails
+	err := wails.Run(&options.App{
+		Title:  "智慧停车收费系统",
+		Width:  1280,
+		Height: 800,
+		Bind:   binds, // ★ 将所有模块的 API 绑定给前端
+	})
+	if err != nil {
+		panic(err)
+	}
+}
+```
+### 这种架构的优势
+1. **按需开发与裁剪**:如果要先开发 P0,你只需要关注 `billing`、`order`、`parking` 和 `hardware` 目录,其他的 `monitor` 和 `report` 目录甚至可以先不建,互不干扰。
+2. **清晰的调用链路**:
+   - 前端按钮点击 -> `order/api.go` -> `order/service/checkout_service.go` (计算费) -> `billing/service/calculator.go` -> 返回金额 -> `order/service/payment_service.go` (调硬件) -> `hardware/pos/pos_driver.go` -> 返回结果给前端。
+3. **Wails 绑定解耦**:通过 `wails_bindings` 统一做依赖注入,避免了业务代码与 Wails 框架强耦合,方便未来做单元测试。

+ 56 - 51
go.mod

@@ -3,35 +3,70 @@ module wails-app
 go 1.25.0
 
 require (
+	github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
+	github.com/aws/aws-sdk-go v1.55.8
+	github.com/casbin/casbin/v2 v2.103.0
+	github.com/casbin/gorm-adapter/v3 v3.41.0
+	github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260630093409-4445d0e583ec
+	github.com/flipped-aurora/ws v1.0.2
+	github.com/fsnotify/fsnotify v1.9.0
+	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
+	github.com/gin-gonic/gin v1.10.0
+	github.com/glebarez/sqlite v1.11.0
+	github.com/go-sql-driver/mysql v1.9.3
+	github.com/gofrs/uuid/v5 v5.0.0
+	github.com/golang-jwt/jwt/v4 v4.5.0
+	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
+	github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
+	github.com/mojocn/base64Captcha v1.3.8
+	github.com/pkg/errors v0.9.1
+	github.com/qiniu/api.v7/v7 v7.4.1
+	github.com/qiniu/qmgo v1.1.9
+	github.com/redis/go-redis/v9 v9.7.0
+	github.com/robfig/cron/v3 v3.0.1
+	github.com/shirou/gopsutil/v3 v3.24.5
+	github.com/songzhibin97/gkit v1.2.13
+	github.com/spf13/viper v1.19.0
+	github.com/stretchr/testify v1.11.1
+	github.com/swaggo/files v1.0.1
+	github.com/swaggo/gin-swagger v1.6.0
+	github.com/swaggo/swag v1.16.4
+	github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
+	github.com/tencentyun/cos-go-sdk-v5 v0.7.60
+	github.com/unrolled/secure v1.17.0
 	github.com/wailsapp/wails/v2 v2.13.0
-	server v0.0.0
+	go.mongodb.org/mongo-driver v1.17.2
+	go.uber.org/automaxprocs v1.6.0
+	go.uber.org/zap v1.27.0
+	golang.org/x/crypto v0.51.0
+	golang.org/x/sync v0.20.0
+	golang.org/x/text v0.37.0
+	gorm.io/driver/mysql v1.6.0
+	gorm.io/driver/postgres v1.6.0
+	gorm.io/driver/sqlserver v1.6.3
+	gorm.io/gorm v1.31.1
+	nhooyr.io/websocket v1.8.17
 )
 
 require (
 	filippo.io/edwards25519 v1.1.0 // indirect
 	git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
 	github.com/KyleBanks/depth v1.2.1 // indirect
-	github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
-	github.com/aws/aws-sdk-go v1.55.8 // indirect
 	github.com/bep/debounce v1.2.1 // indirect
-	github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
+	github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
 	github.com/bytedance/sonic v1.12.7 // indirect
 	github.com/bytedance/sonic/loader v0.2.3 // indirect
-	github.com/casbin/casbin/v2 v2.103.0 // indirect
-	github.com/casbin/gorm-adapter/v3 v3.32.0 // indirect
-	github.com/casbin/govaluate v1.3.0 // indirect
+	github.com/casbin/casbin/v3 v3.10.0 // indirect
+	github.com/casbin/govaluate v1.10.0 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/clbanning/mxj v1.8.4 // indirect
 	github.com/cloudwego/base64x v0.1.5 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
-	github.com/fsnotify/fsnotify v1.9.0 // indirect
-	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/gin-contrib/sse v1.0.0 // indirect
-	github.com/gin-gonic/gin v1.10.0 // indirect
 	github.com/glebarez/go-sqlite v1.22.0 // indirect
-	github.com/glebarez/sqlite v1.11.0 // indirect
 	github.com/go-ole/go-ole v1.3.0 // indirect
 	github.com/go-openapi/jsonpointer v0.21.0 // indirect
 	github.com/go-openapi/jsonreference v0.21.0 // indirect
@@ -40,11 +75,8 @@ require (
 	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.24.0 // indirect
-	github.com/go-sql-driver/mysql v1.8.1 // indirect
 	github.com/goccy/go-json v0.10.4 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
-	github.com/gofrs/uuid/v5 v5.0.0 // indirect
-	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 	github.com/golang-sql/sqlexp v0.1.0 // indirect
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@@ -53,16 +85,14 @@ require (
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
-	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
-	github.com/jackc/pgx/v5 v5.7.2 // indirect
+	github.com/jackc/pgx/v5 v5.8.0 // indirect
 	github.com/jackc/puddle/v2 v2.2.2 // indirect
 	github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.18.0 // indirect
@@ -79,47 +109,34 @@ require (
 	github.com/mailru/easyjson v0.9.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
-	github.com/microsoft/go-mssqldb v1.8.0 // indirect
+	github.com/microsoft/go-mssqldb v1.9.5 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/mojocn/base64Captcha v1.3.8 // indirect
 	github.com/montanaflynn/stats v0.7.1 // indirect
 	github.com/mozillazg/go-httpheader v0.4.0 // indirect
-	github.com/ncruces/go-strftime v0.1.9 // indirect
+	github.com/ncruces/go-strftime v1.0.0 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
-	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
-	github.com/qiniu/api.v7/v7 v7.4.1 // indirect
-	github.com/qiniu/qmgo v1.1.9 // indirect
-	github.com/redis/go-redis/v9 v9.7.0 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
-	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/sagikazarmark/locafero v0.7.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 	github.com/samber/lo v1.49.1 // indirect
-	github.com/shirou/gopsutil/v3 v3.24.5 // indirect
 	github.com/shoenig/go-m1cpu v0.1.6 // indirect
-	github.com/songzhibin97/gkit v1.2.13 // indirect
+	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
 	github.com/spf13/afero v1.12.0 // indirect
 	github.com/spf13/cast v1.7.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.19.0 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
-	github.com/swaggo/files v1.0.1 // indirect
-	github.com/swaggo/gin-swagger v1.6.0 // indirect
-	github.com/swaggo/swag v1.16.4 // indirect
-	github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 // indirect
-	github.com/tencentyun/cos-go-sdk-v5 v0.7.60 // indirect
 	github.com/tklauser/go-sysconf v0.3.14 // indirect
 	github.com/tklauser/numcpus v0.9.0 // indirect
 	github.com/tkrajina/go-reflector v0.5.8 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
-	github.com/unrolled/secure v1.17.0 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/valyala/fasttemplate v1.2.2 // indirect
 	github.com/wailsapp/go-webview2 v1.0.22 // indirect
@@ -129,32 +146,20 @@ require (
 	github.com/xdg-go/stringprep v1.0.4 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
-	go.mongodb.org/mongo-driver v1.17.2 // indirect
-	go.uber.org/automaxprocs v1.6.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/arch v0.13.0 // indirect
-	golang.org/x/crypto v0.51.0 // indirect
-	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
+	golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
 	golang.org/x/image v0.40.0 // indirect
 	golang.org/x/net v0.54.0 // indirect
-	golang.org/x/sync v0.20.0 // indirect
 	golang.org/x/sys v0.44.0 // indirect
-	golang.org/x/text v0.37.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
 	golang.org/x/tools v0.44.0 // indirect
 	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	gorm.io/driver/mysql v1.5.7 // indirect
-	gorm.io/driver/postgres v1.5.11 // indirect
-	gorm.io/driver/sqlserver v1.5.4 // indirect
-	gorm.io/gorm v1.25.12 // indirect
-	gorm.io/plugin/dbresolver v1.5.3 // indirect
-	modernc.org/libc v1.61.9 // indirect
+	gorm.io/plugin/dbresolver v1.6.2 // indirect
+	modernc.org/libc v1.67.4 // indirect
 	modernc.org/mathutil v1.7.1 // indirect
-	modernc.org/memory v1.8.2 // indirect
-	modernc.org/sqlite v1.34.5 // indirect
+	modernc.org/memory v1.11.0 // indirect
+	modernc.org/sqlite v1.42.2 // indirect
 )
-
-replace server => ./server

+ 135 - 69
go.sum

@@ -4,25 +4,29 @@ git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf
 git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
 github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
 github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
@@ -33,8 +37,8 @@ github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPP
 github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
 github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
 github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
-github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
+github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
 github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
 github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -46,10 +50,13 @@ github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wio
 github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=
 github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
-github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus=
-github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI=
-github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
+github.com/casbin/casbin/v3 v3.10.0 h1:039ORla55vCeIZWd0LfzWFt1yiEA5X4W41xBW2bQuHs=
+github.com/casbin/casbin/v3 v3.10.0/go.mod h1:5rJbQr2e6AuuDDNxnPc5lQlC9nIgg6nS1zYwKXhpHC8=
+github.com/casbin/gorm-adapter/v3 v3.41.0 h1:Xhpi0tfRP9aKPDWDf6dgBxHZ9UM6IophxxPIEGWqCNM=
+github.com/casbin/gorm-adapter/v3 v3.41.0/go.mod h1:BQZRJhwUnwMpI+pT2m7/cUJwXxrHfzpBpPcNTyMGeGA=
 github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
+github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
+github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
@@ -57,6 +64,7 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P
 github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
 github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -67,8 +75,10 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/
 github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260327044607-61a3f8297cd3 h1:5Fr7irk6uzNPcMNhWE1kdewxuUB/x49tmP9r7Q1ToSg=
-github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260327044607-61a3f8297cd3/go.mod h1:4vCRqzN/5jwlghElibN0Sa+6V6DvAVoIiBwn3igr+MU=
+github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260630093409-4445d0e583ec h1:+ivMuz5jF4GlFjCK6H3vWMJhsy9G9KhV56Rpblv91bE=
+github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260630093409-4445d0e583ec/go.mod h1:QyF7IUIhF/mVJ1mXo3Yb22M21MF8BoBDWBZ21oRyyE8=
+github.com/flipped-aurora/ws v1.0.2 h1:oEUz7sgrbPENvgli7Q4QpC0NIEbJucgR4yjcDMg/AjY=
+github.com/flipped-aurora/ws v1.0.2/go.mod h1:RdyM2Fnvxx7f7A6WSmU1aAhDrQIAVW7LS/0LsAUE5mE=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -79,8 +89,10 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
 github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
 github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
 github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
 github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
@@ -107,12 +119,15 @@ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/Nu
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 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.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
 github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
-github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
-github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
-github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
+github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
 github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -122,9 +137,9 @@ github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
-github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
-github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -133,8 +148,11 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -142,19 +160,23 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
-github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ4fSNB6mMRuYNvFWou7BZs6SZB925hPrnk=
@@ -163,8 +185,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
-github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
+github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
+github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
 github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
 github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
 github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
@@ -187,8 +209,11 @@ github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
 github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
@@ -196,8 +221,12 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
 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/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@@ -232,18 +261,20 @@ github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 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/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
-github.com/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw=
-github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo=
+github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
+github.com/microsoft/go-mssqldb v1.9.5 h1:orwya0X/5bsL1o+KasupTkk2eNTNFkTQG0BEe/HxCn0=
+github.com/microsoft/go-mssqldb v1.9.5/go.mod h1:VCP2a0KEZZtGLRHd1PsLavLFYy/3xX2yJUPycv3Sr2Q=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
@@ -255,13 +286,15 @@ github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt
 github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
 github.com/mozillazg/go-httpheader v0.4.0 h1:aBn6aRXtFzyDLZ4VIRLsZbbJloagQfMnCiYgOq6hK4w=
 github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA=
-github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
-github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
+github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
 github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
 github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -284,6 +317,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
@@ -298,6 +333,8 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
 github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
 github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
 github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
 github.com/songzhibin97/gkit v1.2.13 h1:paY0XJkdRuy9/8k9nTnbdrzo8pC22jIIFldUkOQv5nU=
 github.com/songzhibin97/gkit v1.2.13/go.mod h1:38CreNR27eTGaG1UMGihrXqI4xc3nGfYxLVKKVx6Ngg=
 github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -323,6 +360,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 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=
@@ -348,6 +386,8 @@ github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo
 github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
 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 v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
 github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=
@@ -376,40 +416,47 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
 go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
 go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
 go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
 golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
 golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
 golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
-golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
-golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
+golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
+golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
 golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
 golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
 golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
@@ -429,11 +476,12 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
 golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
 golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
 golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -443,12 +491,14 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
 golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -465,12 +515,12 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
 golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
@@ -483,12 +533,12 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
 golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
-golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
 golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -504,14 +554,21 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
 golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
 golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@@ -524,8 +581,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -536,40 +595,47 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 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=
-gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
-gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
-gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
-gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
-gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
-gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
-gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
-gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
-gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
-gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
-gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=
-gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=
-modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
-modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00=
-modernc.org/ccgo/v4 v4.23.13/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0=
-modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
-modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
-modernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8=
-modernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM=
-modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk=
+gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
+gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
+gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
+gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
+gorm.io/driver/sqlserver v1.6.3 h1:UR+nWCuphPnq7UxnL57PSrlYjuvs+sf1N59GgFX7uAI=
+gorm.io/driver/sqlserver v1.6.3/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U=
+gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
+gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
+gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
+gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc=
+gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
+modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
+modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
+modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
+modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
+modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
+modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
+modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
+modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
+modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
+modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
+modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg=
+modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
 modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
 modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
-modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
-modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
+modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
+modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
 modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
 modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
 modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
 modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
-modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
+modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74=
+modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8=
 modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
 modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
 modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
 modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
+nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

+ 24 - 0
internal/Dockerfile

@@ -0,0 +1,24 @@
+FROM golang:alpine as builder
+
+WORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/server
+COPY . .
+
+RUN go env -w GO111MODULE=on \
+    && go env -w GOPROXY=https://goproxy.cn,direct \
+    && go env -w CGO_ENABLED=0 \
+    && go env \
+    && go mod tidy \
+    && go build -o server .
+
+FROM alpine:latest
+
+LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com"
+
+WORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/server
+
+COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/server ./
+COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/resource ./resource/
+COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/config.docker.yaml ./
+
+EXPOSE 8888
+ENTRYPOINT ./server -c config.docker.yaml

+ 54 - 0
internal/README.md

@@ -0,0 +1,54 @@
+## server项目结构
+
+```shell
+├── api
+│   └── v1
+├── config
+├── core
+├── docs
+├── global
+├── initialize
+│   └── internal
+├── middleware
+├── model
+│   ├── request
+│   └── response
+├── packfile
+├── resource
+│   ├── excel
+│   ├── page
+│   └── template
+├── router
+├── service
+├── source
+└── utils
+    ├── timer
+    └── upload
+```
+
+| 文件夹       | 说明                    | 描述                        |
+| ------------ | ----------------------- | --------------------------- |
+| `api`        | api层                   | api层 |
+| `--v1`       | v1版本接口              | v1版本接口                  |
+| `config`     | 配置包                  | config.yaml对应的配置结构体 |
+| `core`       | 核心文件                | 核心组件(zap, viper, server)的初始化 |
+| `docs`       | swagger文档目录         | swagger文档目录 |
+| `global`     | 全局对象                | 全局对象 |
+| `initialize` | 初始化 | router,redis,gorm,validator, timer的初始化 |
+| `--internal` | 初始化内部函数 | gorm 的 longger 自定义,在此文件夹的函数只能由 `initialize` 层进行调用 |
+| `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |
+| `model`      | 模型层                  | 模型对应数据表              |
+| `--request`  | 入参结构体              | 接收前端发送到后端的数据。  |
+| `--response` | 出参结构体              | 返回给前端的数据结构体      |
+| `packfile`   | 静态文件打包            | 静态文件打包 |
+| `resource`   | 静态资源文件夹          | 负责存放静态文件                |
+| `--excel` | excel导入导出默认路径 | excel导入导出默认路径 |
+| `--page` | 表单生成器 | 表单生成器 打包后的dist |
+| `--template` | 模板 | 模板文件夹,存放的是代码生成器的模板 |
+| `router`     | 路由层                  | 路由层 |
+| `service`    | service层               | 存放业务逻辑问题 |
+| `source` | source层 | 存放初始化数据的函数 |
+| `utils`      | 工具包                  | 工具函数封装            |
+| `--timer` | timer | 定时器接口封装 |
+| `--upload`      | oss                  | oss接口封装        |
+

+ 15 - 0
internal/api/v1/enter.go

@@ -0,0 +1,15 @@
+package v1
+
+import (
+	"wails-app/internal/api/v1/example"
+	"wails-app/internal/api/v1/system"
+	"wails-app/internal/api/v1/vehicle"
+)
+
+type ApiGroup struct {
+	SystemApiGroup  system.ApiGroup
+	ExampleApiGroup example.ApiGroup
+	VehicleApiGroup vehicle.ApiGroup
+}
+
+var ApiGroupApp = new(ApiGroup)

+ 11 - 0
internal/api/v1/example/enter.go

@@ -0,0 +1,11 @@
+package example
+
+import "wails-app/internal/service"
+
+type ApiGroup struct {
+	FileUploadAndDownloadApi
+}
+
+var (
+	fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
+)

+ 150 - 0
internal/api/v1/example/exa_breakpoint_continue.go

@@ -0,0 +1,150 @@
+package example
+
+import (
+	"fmt"
+	"io"
+	"mime/multipart"
+	"strconv"
+
+	"wails-app/internal/model/example"
+
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+	exampleRes "wails-app/internal/model/example/response"
+	"wails-app/internal/utils"
+)
+
+// BreakpointContinue
+// @Tags      ExaFileUploadAndDownload
+// @Summary   断点续传到服务器
+// @Security  ApiKeyAuth
+// @accept    multipart/form-data
+// @Produce   application/json
+// @Param     file  formData  file                           true  "an example for breakpoint resume, 断点续传示例"
+// @Success   200   {object}  response.Response{msg=string}  "断点续传到服务器"
+// @Router    /fileUploadAndDownload/breakpointContinue [post]
+func (b *FileUploadAndDownloadApi) BreakpointContinue(c *gin.Context) {
+	fileMd5 := c.Request.FormValue("fileMd5")
+	fileName := c.Request.FormValue("fileName")
+	chunkMd5 := c.Request.FormValue("chunkMd5")
+	chunkNumber, _ := strconv.Atoi(c.Request.FormValue("chunkNumber"))
+	chunkTotal, _ := strconv.Atoi(c.Request.FormValue("chunkTotal"))
+	_, FileHeader, err := c.Request.FormFile("file")
+	if err != nil {
+		global.GVA_LOG.Error("接收文件失败!", zap.Error(err))
+		response.FailWithMessage("接收文件失败", c)
+		return
+	}
+	f, err := FileHeader.Open()
+	if err != nil {
+		global.GVA_LOG.Error("文件读取失败!", zap.Error(err))
+		response.FailWithMessage("文件读取失败", c)
+		return
+	}
+	defer func(f multipart.File) {
+		err := f.Close()
+		if err != nil {
+			fmt.Println(err)
+		}
+	}(f)
+	cen, _ := io.ReadAll(f)
+	if !utils.CheckMd5(cen, chunkMd5) {
+		global.GVA_LOG.Error("检查md5失败!", zap.Error(err))
+		response.FailWithMessage("检查md5失败", c)
+		return
+	}
+	file, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal)
+	if err != nil {
+		global.GVA_LOG.Error("查找或创建记录失败!", zap.Error(err))
+		response.FailWithMessage("查找或创建记录失败", c)
+		return
+	}
+	pathC, err := utils.BreakPointContinue(cen, fileName, chunkNumber, chunkTotal, fileMd5)
+	if err != nil {
+		global.GVA_LOG.Error("断点续传失败!", zap.Error(err))
+		response.FailWithMessage("断点续传失败", c)
+		return
+	}
+
+	if err = fileUploadAndDownloadService.CreateFileChunk(file.ID, pathC, chunkNumber); err != nil {
+		global.GVA_LOG.Error("创建文件记录失败!", zap.Error(err))
+		response.FailWithMessage("创建文件记录失败", c)
+		return
+	}
+	response.OkWithMessage("切片创建成功", c)
+}
+
+// FindFile
+// @Tags      ExaFileUploadAndDownload
+// @Summary   查找文件
+// @Security  ApiKeyAuth
+// @accept    multipart/form-data
+// @Produce   application/json
+// @Param     file  formData  file                                                        true  "Find the file, 查找文件"
+// @Success   200   {object}  response.Response{data=exampleRes.FileResponse,msg=string}  "查找文件,返回包括文件详情"
+// @Router    /fileUploadAndDownload/findFile [post]
+func (b *FileUploadAndDownloadApi) FindFile(c *gin.Context) {
+	fileMd5 := c.Query("fileMd5")
+	fileName := c.Query("fileName")
+	chunkTotal, _ := strconv.Atoi(c.Query("chunkTotal"))
+	file, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal)
+	if err != nil {
+		global.GVA_LOG.Error("查找失败!", zap.Error(err))
+		response.FailWithMessage("查找失败", c)
+	} else {
+		response.OkWithDetailed(exampleRes.FileResponse{File: file}, "查找成功", c)
+	}
+}
+
+// BreakpointContinueFinish
+// @Tags      ExaFileUploadAndDownload
+// @Summary   创建文件
+// @Security  ApiKeyAuth
+// @accept    multipart/form-data
+// @Produce   application/json
+// @Param     file  formData  file                                                            true  "上传文件完成"
+// @Success   200   {object}  response.Response{data=exampleRes.FilePathResponse,msg=string}  "创建文件,返回包括文件路径"
+// @Router    /fileUploadAndDownload/findFile [post]
+func (b *FileUploadAndDownloadApi) BreakpointContinueFinish(c *gin.Context) {
+	fileMd5 := c.Query("fileMd5")
+	fileName := c.Query("fileName")
+	filePath, err := utils.MakeFile(fileName, fileMd5)
+	if err != nil {
+		global.GVA_LOG.Error("文件创建失败!", zap.Error(err))
+		response.FailWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, "文件创建失败", c)
+	} else {
+		response.OkWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, "文件创建成功", c)
+	}
+}
+
+// RemoveChunk
+// @Tags      ExaFileUploadAndDownload
+// @Summary   删除切片
+// @Security  ApiKeyAuth
+// @accept    multipart/form-data
+// @Produce   application/json
+// @Param     file  formData  file                           true  "删除缓存切片"
+// @Success   200   {object}  response.Response{msg=string}  "删除切片"
+// @Router    /fileUploadAndDownload/removeChunk [post]
+func (b *FileUploadAndDownloadApi) RemoveChunk(c *gin.Context) {
+	var file example.ExaFile
+	err := c.ShouldBindJSON(&file)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.RemoveChunk(file.FileMd5)
+	if err != nil {
+		global.GVA_LOG.Error("缓存切片删除失败!", zap.Error(err))
+		return
+	}
+	err = fileUploadAndDownloadService.DeleteFileChunk(file.FileMd5, file.FilePath)
+	if err != nil {
+		global.GVA_LOG.Error(err.Error(), zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithMessage("缓存切片删除成功", c)
+}

+ 110 - 0
internal/api/v1/example/exa_file_upload_download.go

@@ -0,0 +1,110 @@
+package example
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/request"
+	"wails-app/internal/model/common/response"
+	exampleRes "wails-app/internal/model/example/response"
+)
+
+type FileUploadAndDownloadApi struct{}
+
+// UploadFile
+// @Tags      ExaFileUploadAndDownload
+// @Summary   上传文件示例
+// @Security  ApiKeyAuth
+// @accept    multipart/form-data
+// @Produce   application/json
+// @Param     file  formData  file                                                           true  "上传文件示例"
+// @Success   200   {object}  response.Response{data=exampleRes.ExaFileResponse,msg=string}  "上传文件示例,返回包括文件详情"
+// @Router    /fileUploadAndDownload/upload [post]
+func (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) {
+	var file dao.ExaFileUploadAndDownload
+	noSave := c.DefaultQuery("noSave", "0")
+	_, header, err := c.Request.FormFile("file")
+	if err != nil {
+		global.GVA_LOG.Error("接收文件失败!", zap.Error(err))
+		response.FailWithMessage("接收文件失败", c)
+		return
+	}
+	file, err = fileUploadAndDownloadService.UploadFile(header, noSave) // 文件上传后拿到文件路径
+	if err != nil {
+		global.GVA_LOG.Error("修改数据库链接失败!", zap.Error(err))
+		response.FailWithMessage("修改数据库链接失败", c)
+		return
+	}
+	response.OkWithDetailed(exampleRes.ExaFileResponse{File: file}, "上传成功", c)
+}
+
+// EditFileName 编辑文件名或者备注
+func (b *FileUploadAndDownloadApi) EditFileName(c *gin.Context) {
+	var file dao.ExaFileUploadAndDownload
+	err := c.ShouldBindJSON(&file)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = fileUploadAndDownloadService.EditFileName(file)
+	if err != nil {
+		global.GVA_LOG.Error("编辑失败!", zap.Error(err))
+		response.FailWithMessage("编辑失败", c)
+		return
+	}
+	response.OkWithMessage("编辑成功", c)
+}
+
+// DeleteFile
+// @Tags      ExaFileUploadAndDownload
+// @Summary   删除文件
+// @Security  ApiKeyAuth
+// @Produce   application/json
+// @Param     data  body      example.ExaFileUploadAndDownload  true  "传入文件里面id即可"
+// @Success   200   {object}  response.Response{msg=string}     "删除文件"
+// @Router    /fileUploadAndDownload/deleteFile [post]
+func (b *FileUploadAndDownloadApi) DeleteFile(c *gin.Context) {
+	var file dao.ExaFileUploadAndDownload
+	err := c.ShouldBindJSON(&file)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	if err := fileUploadAndDownloadService.DeleteFile(file); err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// GetFileList
+// @Tags      ExaFileUploadAndDownload
+// @Summary   分页文件列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.PageInfo                                        true  "页码, 每页大小"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页文件列表,返回包括列表,总数,页码,每页数量"
+// @Router    /fileUploadAndDownload/getFileList [post]
+func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := fileUploadAndDownloadService.GetFileRecordInfoList(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}

+ 11 - 0
internal/api/v1/owner/enter.go

@@ -0,0 +1,11 @@
+package owner
+
+// 车主相关API
+var ApiGroup = []interface{}{
+	CreateOwner,
+	UpdateOwner,
+	GetOwnerByID,
+	GetOwnerByPhone,
+	ListOwners,
+	DeleteOwner,
+}

+ 120 - 0
internal/api/v1/owner/owner.go

@@ -0,0 +1,120 @@
+package owner
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/owner/request"
+	ownerResponse "wails-app/internal/model/owner/response"
+	"wails-app/internal/service/owner"
+)
+
+// CreateOwner 创建车主信息
+func CreateOwner(c *gin.Context) {
+	var req request.OwnerCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := owner.OwnerService.CreateOwner(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+// UpdateOwner 更新车主信息
+func UpdateOwner(c *gin.Context) {
+	var req request.OwnerUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := owner.OwnerService.UpdateOwner(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetOwnerByID 根据ID获取车主信息
+func GetOwnerByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	ownerInfo, err := owner.OwnerService.GetOwnerByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(ownerInfo, c)
+}
+
+// GetOwnerByPhone 根据手机号获取车主信息
+func GetOwnerByPhone(c *gin.Context) {
+	phone := c.Query("phone")
+	if phone == "" {
+		response.FailWithMessage("手机号不能为空", c)
+		return
+	}
+
+	ownerInfo, err := owner.OwnerService.GetOwnerByPhone(phone)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(ownerInfo, c)
+}
+
+// ListOwners 获取车主列表
+func ListOwners(c *gin.Context) {
+	var req request.OwnerQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := owner.OwnerService.ListOwners(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(ownerResponse.OwnerList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+// DeleteOwner 删除车主
+func DeleteOwner(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = owner.OwnerService.DeleteOwner(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}

+ 121 - 0
internal/api/v1/parking/booth.go

@@ -0,0 +1,121 @@
+package parking
+
+import (
+	"fmt"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/parking/request"
+	parkingResponse "wails-app/internal/model/parking/response"
+
+	"github.com/gin-gonic/gin"
+)
+
+func CreateBooth(c *gin.Context) {
+	var req request.BoothCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := BoothService.CreateBooth(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+func UpdateBooth(c *gin.Context) {
+	var req request.BoothUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := BoothService.UpdateBooth(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+func GetBoothByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	booth, err := BoothService.GetBoothByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(booth, c)
+}
+
+func GetBoothByCode(c *gin.Context) {
+	code := c.Query("code")
+	if code == "" {
+		response.FailWithMessage("岗亭编码不能为空", c)
+		return
+	}
+
+	booth, err := BoothService.GetBoothByCode(code)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(booth, c)
+}
+
+func QueryAllBooths(c *gin.Context) {
+	booths, err := BoothService.QueryAllBooths()
+	if err != nil {
+		response.FailWithMessage("THE QUERY FAILED", c)
+	}
+	response.OkWithData(booths, c)
+}
+
+func ListBooths(c *gin.Context) {
+	var req request.BoothQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := BoothService.ListBooths(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(parkingResponse.BoothList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+func DeleteBooth(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = BoothService.DeleteBooth(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}

+ 122 - 0
internal/api/v1/parking/channel.go

@@ -0,0 +1,122 @@
+package parking
+
+import (
+	"fmt"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/parking/request"
+	parkingResponse "wails-app/internal/model/parking/response"
+
+	"github.com/gin-gonic/gin"
+)
+
+func CreateChannel(c *gin.Context) {
+	var req request.ChannelCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := ChannelService.CreateChannel(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+func UpdateChannel(c *gin.Context) {
+	var req request.ChannelUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := ChannelService.UpdateChannel(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+func GetChannelByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	channel, err := ChannelService.GetChannelByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(channel, c)
+}
+
+func GetChannelByCode(c *gin.Context) {
+	code := c.Query("code")
+	if code == "" {
+		response.FailWithMessage("通道编码不能为空", c)
+		return
+	}
+
+	channel, err := ChannelService.GetChannelByCode(code)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(channel, c)
+}
+
+func QueryAllChannels(c *gin.Context) {
+	channels, err := ChannelService.QueryAllChannels()
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithData(channels, c)
+}
+
+func ListChannels(c *gin.Context) {
+	var req request.ChannelQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := ChannelService.ListChannels(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(parkingResponse.ChannelList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+func DeleteChannel(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = ChannelService.DeleteChannel(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}

+ 7 - 0
internal/api/v1/parking/enter.go

@@ -0,0 +1,7 @@
+package parking
+
+import parkingService "wails-app/internal/service/parking"
+
+var BoothService = parkingService.NewBoothService()
+var ChannelService = parkingService.NewChannelService()
+var ParkingLotService = parkingService.NewParkingLotService()

+ 122 - 0
internal/api/v1/parking/parking_lot.go

@@ -0,0 +1,122 @@
+package parking
+
+import (
+	"fmt"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/parking/request"
+	parkingResponse "wails-app/internal/model/parking/response"
+
+	"github.com/gin-gonic/gin"
+)
+
+func CreateParkingLot(c *gin.Context) {
+	var req request.ParkingLotCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := ParkingLotService.CreateParkingLot(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+func UpdateParkingLot(c *gin.Context) {
+	var req request.ParkingLotUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := ParkingLotService.UpdateParkingLot(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+func GetParkingLotByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	lot, err := ParkingLotService.GetParkingLotByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(lot, c)
+}
+
+func GetParkingLotByCode(c *gin.Context) {
+	code := c.Query("code")
+	if code == "" {
+		response.FailWithMessage("区域编码不能为空", c)
+		return
+	}
+
+	lot, err := ParkingLotService.GetParkingLotByCode(code)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(lot, c)
+}
+
+func QueryAllParkingLots(c *gin.Context) {
+	lots, err := ParkingLotService.QueryAllParkingLots()
+	if err != nil {
+		response.FailWithMessage("THE QUERY FAILED", c)
+		return
+	}
+	response.OkWithData(lots, c)
+}
+
+func ListParkingLots(c *gin.Context) {
+	var req request.ParkingLotQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := ParkingLotService.ListParkingLots(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(parkingResponse.ParkingLotList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+func DeleteParkingLot(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = ParkingLotService.DeleteParkingLot(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}

+ 30 - 0
internal/api/v1/system/enter.go

@@ -0,0 +1,30 @@
+package system
+
+import "wails-app/internal/service"
+
+type ApiGroup struct {
+	JwtApi
+	BaseApi
+	CasbinApi
+	SystemApiApi
+	AuthorityApi
+	DictionaryApi
+	AuthorityMenuApi
+	OperationRecordApi
+	DictionaryDetailApi
+	AuthorityBtnApi
+}
+
+var (
+	apiService              = service.ServiceGroupApp.SystemServiceGroup.ApiService
+	jwtService              = service.ServiceGroupApp.SystemServiceGroup.JwtService
+	menuService             = service.ServiceGroupApp.SystemServiceGroup.MenuService
+	userService             = service.ServiceGroupApp.SystemServiceGroup.UserService
+	casbinService           = service.ServiceGroupApp.SystemServiceGroup.CasbinService
+	baseMenuService         = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService
+	authorityService        = service.ServiceGroupApp.SystemServiceGroup.AuthorityService
+	dictionaryService       = service.ServiceGroupApp.SystemServiceGroup.DictionaryService
+	operationRecordService  = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
+	dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
+	authorityBtnService     = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService
+)

+ 231 - 0
internal/api/v1/system/sys_api.go

@@ -0,0 +1,231 @@
+package system
+
+import (
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/request"
+	"wails-app/internal/model/common/response"
+	systemReq "wails-app/internal/model/system/request"
+	systemRes "wails-app/internal/model/system/response"
+	"wails-app/internal/utils"
+
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+)
+
+type SystemApiApi struct{}
+
+// CreateApi
+// @Tags      SysApi
+// @Summary   创建基础api
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysApi                  true  "api路径, api中文描述, api组, 方法"
+// @Success   200   {object}  response.Response{msg=string}  "创建基础api"
+// @Router    /api/createApi [post]
+func (s *SystemApiApi) CreateApi(c *gin.Context) {
+	var api dao.SysApi
+	err := c.ShouldBindJSON(&api)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(api, utils.ApiVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = apiService.CreateApi(api)
+	if err != nil {
+		global.GVA_LOG.Error("创建失败!", zap.Error(err))
+		response.FailWithMessage("创建失败", c)
+		return
+	}
+	response.OkWithMessage("创建成功", c)
+}
+
+// DeleteApi
+// @Tags      SysApi
+// @Summary   删除api
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysApi                  true  "ID"
+// @Success   200   {object}  response.Response{msg=string}  "删除api"
+// @Router    /api/deleteApi [post]
+func (s *SystemApiApi) DeleteApi(c *gin.Context) {
+	var api dao.SysApi
+	err := c.ShouldBindJSON(&api)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(api.GVA_MODEL, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = apiService.DeleteApi(api)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// GetApiList
+// @Tags      SysApi
+// @Summary   分页获取API列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      systemReq.SearchApiParams                               true  "分页获取API列表"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取API列表,返回包括列表,总数,页码,每页数量"
+// @Router    /api/getApiList [post]
+func (s *SystemApiApi) GetApiList(c *gin.Context) {
+	var pageInfo systemReq.SearchApiParams
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo.PageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := apiService.GetAPIInfoList(pageInfo.SysApi, pageInfo.PageInfo, pageInfo.OrderKey, pageInfo.Desc)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}
+
+// GetApiById
+// @Tags      SysApi
+// @Summary   根据id获取api
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.GetById                                   true  "根据id获取api"
+// @Success   200   {object}  response.Response{data=systemRes.SysAPIResponse}  "根据id获取api,返回包括api详情"
+// @Router    /api/getApiById [post]
+func (s *SystemApiApi) GetApiById(c *gin.Context) {
+	var idInfo request.GetById
+	err := c.ShouldBindJSON(&idInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(idInfo, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	api, err := apiService.GetApiById(idInfo.ID)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysAPIResponse{Api: api}, "获取成功", c)
+}
+
+// UpdateApi
+// @Tags      SysApi
+// @Summary   修改基础api
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysApi                  true  "api路径, api中文描述, api组, 方法"
+// @Success   200   {object}  response.Response{msg=string}  "修改基础api"
+// @Router    /api/updateApi [post]
+func (s *SystemApiApi) UpdateApi(c *gin.Context) {
+	var api dao.SysApi
+	err := c.ShouldBindJSON(&api)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(api, utils.ApiVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = apiService.UpdateApi(api)
+	if err != nil {
+		global.GVA_LOG.Error("修改失败!", zap.Error(err))
+		response.FailWithMessage("修改失败", c)
+		return
+	}
+	response.OkWithMessage("修改成功", c)
+}
+
+// GetAllApis
+// @Tags      SysApi
+// @Summary   获取所有的Api 不分页
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Success   200  {object}  response.Response{data=systemRes.SysAPIListResponse,msg=string}  "获取所有的Api 不分页,返回包括api列表"
+// @Router    /api/getAllApis [post]
+func (s *SystemApiApi) GetAllApis(c *gin.Context) {
+	apis, err := apiService.GetAllApis()
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysAPIListResponse{Apis: apis}, "获取成功", c)
+}
+
+// DeleteApisByIds
+// @Tags      SysApi
+// @Summary   删除选中Api
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.IdsReq                 true  "ID"
+// @Success   200   {object}  response.Response{msg=string}  "删除选中Api"
+// @Router    /api/deleteApisByIds [delete]
+func (s *SystemApiApi) DeleteApisByIds(c *gin.Context) {
+	var ids request.IdsReq
+	err := c.ShouldBindJSON(&ids)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = apiService.DeleteApisByIds(ids)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// FreshCasbin
+// @Tags      SysApi
+// @Summary   刷新casbin缓存
+// @accept    application/json
+// @Produce   application/json
+// @Success   200   {object}  response.Response{msg=string}  "刷新成功"
+// @Router    /api/freshCasbin [get]
+func (s *SystemApiApi) FreshCasbin(c *gin.Context) {
+	err := casbinService.FreshCasbin()
+	if err != nil {
+		global.GVA_LOG.Error("刷新失败!", zap.Error(err))
+		response.FailWithMessage("刷新失败", c)
+		return
+	}
+	response.OkWithMessage("刷新成功", c)
+}

+ 177 - 0
internal/api/v1/system/sys_authority.go

@@ -0,0 +1,177 @@
+package system
+
+import (
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/request"
+	"wails-app/internal/model/common/response"
+	systemRes "wails-app/internal/model/system/response"
+	"wails-app/internal/utils"
+
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+)
+
+type AuthorityApi struct{}
+
+// CreateAuthority
+// @Tags      Authority
+// @Summary   创建角色
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysAuthority                                                true  "权限id, 权限名, 父角色id"
+// @Success   200   {object}  response.Response{data=systemRes.SysAuthorityResponse,msg=string}  "创建角色,返回包括系统角色详情"
+// @Router    /authority/createAuthority [post]
+func (a *AuthorityApi) CreateAuthority(c *gin.Context) {
+	var authority, authBack dao.SysAuthority
+	var err error
+
+	if err = c.ShouldBindJSON(&authority); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	if err = utils.Verify(authority, utils.AuthorityVerify); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	if authBack, err = authorityService.CreateAuthority(authority); err != nil {
+		global.GVA_LOG.Error("创建失败!", zap.Error(err))
+		response.FailWithMessage("创建失败"+err.Error(), c)
+		return
+	}
+	err = casbinService.FreshCasbin()
+	if err != nil {
+		global.GVA_LOG.Error("创建成功,权限刷新失败。", zap.Error(err))
+		response.FailWithMessage("创建成功,权限刷新失败。"+err.Error(), c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "创建成功", c)
+}
+
+// DeleteAuthority
+// @Tags      Authority
+// @Summary   删除角色
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysAuthority            true  "删除角色"
+// @Success   200   {object}  response.Response{msg=string}  "删除角色"
+// @Router    /authority/deleteAuthority [post]
+func (a *AuthorityApi) DeleteAuthority(c *gin.Context) {
+	var authority dao.SysAuthority
+	var err error
+	if err = c.ShouldBindJSON(&authority); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	if err = utils.Verify(authority, utils.AuthorityIdVerify); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	// 删除角色之前需要判断是否有用户正在使用此角色
+	if err = authorityService.DeleteAuthority(&authority); err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败"+err.Error(), c)
+		return
+	}
+	_ = casbinService.FreshCasbin()
+	response.OkWithMessage("删除成功", c)
+}
+
+// UpdateAuthority
+// @Tags      Authority
+// @Summary   更新角色信息
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysAuthority                                                true  "权限id, 权限名, 父角色id"
+// @Success   200   {object}  response.Response{data=systemRes.SysAuthorityResponse,msg=string}  "更新角色信息,返回包括系统角色详情"
+// @Router    /authority/updateAuthority [post]
+func (a *AuthorityApi) UpdateAuthority(c *gin.Context) {
+	var auth dao.SysAuthority
+	err := c.ShouldBindJSON(&auth)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(auth, utils.AuthorityVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	authority, err := authorityService.UpdateAuthority(auth)
+	if err != nil {
+		global.GVA_LOG.Error("更新失败!", zap.Error(err))
+		response.FailWithMessage("更新失败"+err.Error(), c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authority}, "更新成功", c)
+}
+
+// GetAuthorityList
+// @Tags      Authority
+// @Summary   分页获取角色列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.PageInfo                                        true  "页码, 每页大小"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取角色列表,返回包括列表,总数,页码,每页数量"
+// @Router    /authority/getAuthorityList [post]
+func (a *AuthorityApi) GetAuthorityList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := authorityService.GetAuthorityInfoList(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败"+err.Error(), c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}
+
+// SetDataAuthority
+// @Tags      Authority
+// @Summary   设置角色资源权限
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysAuthority            true  "设置角色资源权限"
+// @Success   200   {object}  response.Response{msg=string}  "设置角色资源权限"
+// @Router    /authority/setDataAuthority [post]
+func (a *AuthorityApi) SetDataAuthority(c *gin.Context) {
+	var auth dao.SysAuthority
+	err := c.ShouldBindJSON(&auth)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(auth, utils.AuthorityIdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = authorityService.SetDataAuthority(auth)
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败"+err.Error(), c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}

+ 80 - 0
internal/api/v1/system/sys_authority_btn.go

@@ -0,0 +1,80 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/system/request"
+)
+
+type AuthorityBtnApi struct{}
+
+// GetAuthorityBtn
+// @Tags      AuthorityBtn
+// @Summary   获取权限按钮
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.SysAuthorityBtnReq                                      true  "菜单id, 角色id, 选中的按钮id"
+// @Success   200   {object}  response.Response{data=response.SysAuthorityBtnRes,msg=string}  "返回列表成功"
+// @Router    /authorityBtn/getAuthorityBtn [post]
+func (a *AuthorityBtnApi) GetAuthorityBtn(c *gin.Context) {
+	var req request.SysAuthorityBtnReq
+	err := c.ShouldBindJSON(&req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	res, err := authorityBtnService.GetAuthorityBtn(req)
+	if err != nil {
+		global.GVA_LOG.Error("查询失败!", zap.Error(err))
+		response.FailWithMessage("查询失败", c)
+		return
+	}
+	response.OkWithDetailed(res, "查询成功", c)
+}
+
+// SetAuthorityBtn
+// @Tags      AuthorityBtn
+// @Summary   设置权限按钮
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.SysAuthorityBtnReq     true  "菜单id, 角色id, 选中的按钮id"
+// @Success   200   {object}  response.Response{msg=string}  "返回列表成功"
+// @Router    /authorityBtn/setAuthorityBtn [post]
+func (a *AuthorityBtnApi) SetAuthorityBtn(c *gin.Context) {
+	var req request.SysAuthorityBtnReq
+	err := c.ShouldBindJSON(&req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = authorityBtnService.SetAuthorityBtn(req)
+	if err != nil {
+		global.GVA_LOG.Error("分配失败!", zap.Error(err))
+		response.FailWithMessage("分配失败", c)
+		return
+	}
+	response.OkWithMessage("分配成功", c)
+}
+
+// CanRemoveAuthorityBtn
+// @Tags      AuthorityBtn
+// @Summary   设置权限按钮
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Success   200  {object}  response.Response{msg=string}  "删除成功"
+// @Router    /authorityBtn/canRemoveAuthorityBtn [post]
+func (a *AuthorityBtnApi) CanRemoveAuthorityBtn(c *gin.Context) {
+	id := c.Query("id")
+	err := authorityBtnService.CanRemoveAuthorityBtn(id)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}

+ 70 - 0
internal/api/v1/system/sys_captcha.go

@@ -0,0 +1,70 @@
+package system
+
+import (
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/mojocn/base64Captcha"
+	"go.uber.org/zap"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+	systemRes "wails-app/internal/model/system/response"
+)
+
+// 当开启多服务器部署时,替换下面的配置,使用redis共享存储验证码
+// var store = captcha.NewDefaultRedisStore()
+var store = base64Captcha.DefaultMemStore
+
+type BaseApi struct{}
+
+// Captcha
+// @Tags      Base
+// @Summary   生成验证码
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Success   200  {object}  response.Response{data=systemRes.SysCaptchaResponse,msg=string}  "生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码"
+// @Router    /base/captcha [post]
+func (b *BaseApi) Captcha(c *gin.Context) {
+	// 判断验证码是否开启
+	openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha               // 是否开启防爆次数
+	openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
+	key := c.ClientIP()
+	v, ok := global.BlackCache.Get(key)
+	if !ok {
+		global.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))
+	}
+
+	var oc bool
+	if openCaptcha == 0 || openCaptcha < interfaceToInt(v) {
+		oc = true
+	}
+	// 字符,公式,验证码配置
+	// 生成默认数字的driver
+	driver := base64Captcha.NewDriverDigit(global.GVA_CONFIG.Captcha.ImgHeight, global.GVA_CONFIG.Captcha.ImgWidth, global.GVA_CONFIG.Captcha.KeyLong, 0.7, 80)
+	// cp := base64Captcha.NewCaptcha(driver, store.UseWithCtx(c))   // v8下使用redis
+	cp := base64Captcha.NewCaptcha(driver, store)
+	id, b64s, _, err := cp.Generate()
+	if err != nil {
+		global.GVA_LOG.Error("验证码获取失败!", zap.Error(err))
+		response.FailWithMessage("验证码获取失败", c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysCaptchaResponse{
+		CaptchaId:     id,
+		PicPath:       b64s,
+		CaptchaLength: global.GVA_CONFIG.Captcha.KeyLong,
+		OpenCaptcha:   oc,
+	}, "验证码获取成功", c)
+}
+
+// 类型转换
+func interfaceToInt(v interface{}) (i int) {
+	switch v := v.(type) {
+	case int:
+		i = v
+	default:
+		i = 0
+	}
+	return
+}

+ 68 - 0
internal/api/v1/system/sys_casbin.go

@@ -0,0 +1,68 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/system/request"
+	systemRes "wails-app/internal/model/system/response"
+	"wails-app/internal/utils"
+)
+
+type CasbinApi struct{}
+
+// UpdateCasbin
+// @Tags      Casbin
+// @Summary   更新角色api权限
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.CasbinInReceive        true  "权限id, 权限模型列表"
+// @Success   200   {object}  response.Response{msg=string}  "更新角色api权限"
+// @Router    /casbin/UpdateCasbin [post]
+func (cas *CasbinApi) UpdateCasbin(c *gin.Context) {
+	var cmr request.CasbinInReceive
+	err := c.ShouldBindJSON(&cmr)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(cmr, utils.AuthorityIdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = casbinService.UpdateCasbin(cmr.AuthorityId, cmr.CasbinInfos)
+	if err != nil {
+		global.GVA_LOG.Error("更新失败!", zap.Error(err))
+		response.FailWithMessage("更新失败", c)
+		return
+	}
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetPolicyPathByAuthorityId
+// @Tags      Casbin
+// @Summary   获取权限列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.CasbinInReceive                                          true  "权限id, 权限模型列表"
+// @Success   200   {object}  response.Response{data=systemRes.PolicyPathResponse,msg=string}  "获取权限列表,返回包括casbin详情列表"
+// @Router    /casbin/getPolicyPathByAuthorityId [post]
+func (cas *CasbinApi) GetPolicyPathByAuthorityId(c *gin.Context) {
+	var casbin request.CasbinInReceive
+	err := c.ShouldBindJSON(&casbin)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(casbin, utils.AuthorityIdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	paths := casbinService.GetPolicyPathByAuthorityId(casbin.AuthorityId)
+	response.OkWithDetailed(systemRes.PolicyPathResponse{Paths: paths}, "获取成功", c)
+}

+ 129 - 0
internal/api/v1/system/sys_dictionary.go

@@ -0,0 +1,129 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+)
+
+type DictionaryApi struct{}
+
+// CreateSysDictionary
+// @Tags      SysDictionary
+// @Summary   创建SysDictionary
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysDictionary           true  "SysDictionary模型"
+// @Success   200   {object}  response.Response{msg=string}  "创建SysDictionary"
+// @Router    /sysDictionary/createSysDictionary [post]
+func (s *DictionaryApi) CreateSysDictionary(c *gin.Context) {
+	var dictionary dao.SysDictionary
+	err := c.ShouldBindJSON(&dictionary)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = dictionaryService.CreateSysDictionary(dictionary)
+	if err != nil {
+		global.GVA_LOG.Error("创建失败!", zap.Error(err))
+		response.FailWithMessage("创建失败", c)
+		return
+	}
+	response.OkWithMessage("创建成功", c)
+}
+
+// DeleteSysDictionary
+// @Tags      SysDictionary
+// @Summary   删除SysDictionary
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysDictionary           true  "SysDictionary模型"
+// @Success   200   {object}  response.Response{msg=string}  "删除SysDictionary"
+// @Router    /sysDictionary/deleteSysDictionary [delete]
+func (s *DictionaryApi) DeleteSysDictionary(c *gin.Context) {
+	var dictionary dao.SysDictionary
+	err := c.ShouldBindJSON(&dictionary)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = dictionaryService.DeleteSysDictionary(dictionary)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// UpdateSysDictionary
+// @Tags      SysDictionary
+// @Summary   更新SysDictionary
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysDictionary           true  "SysDictionary模型"
+// @Success   200   {object}  response.Response{msg=string}  "更新SysDictionary"
+// @Router    /sysDictionary/updateSysDictionary [put]
+func (s *DictionaryApi) UpdateSysDictionary(c *gin.Context) {
+	var dictionary dao.SysDictionary
+	err := c.ShouldBindJSON(&dictionary)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = dictionaryService.UpdateSysDictionary(&dictionary)
+	if err != nil {
+		global.GVA_LOG.Error("更新失败!", zap.Error(err))
+		response.FailWithMessage("更新失败", c)
+		return
+	}
+	response.OkWithMessage("更新成功", c)
+}
+
+// FindSysDictionary
+// @Tags      SysDictionary
+// @Summary   用id查询SysDictionary
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  query     system.SysDictionary                                       true  "ID或字典英名"
+// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  "用id查询SysDictionary"
+// @Router    /sysDictionary/findSysDictionary [get]
+func (s *DictionaryApi) FindSysDictionary(c *gin.Context) {
+	var dictionary dao.SysDictionary
+	err := c.ShouldBindJSON(&dictionary)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	sysDictionary, err := dictionaryService.GetSysDictionary(dictionary.Type, dictionary.ID, dictionary.Status)
+	if err != nil {
+		global.GVA_LOG.Error("字典未创建或未开启!", zap.Error(err))
+		response.FailWithMessage("字典未创建或未开启", c)
+		return
+	}
+	response.OkWithDetailed(gin.H{"resysDictionary": sysDictionary}, "查询成功", c)
+}
+
+// GetSysDictionaryList
+// @Tags      SysDictionary
+// @Summary   分页获取SysDictionary列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量"
+// @Router    /sysDictionary/getSysDictionaryList [get]
+func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) {
+	list, err := dictionaryService.GetSysDictionaryInfoList()
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(list, "获取成功", c)
+}

+ 148 - 0
internal/api/v1/system/sys_dictionary_detail.go

@@ -0,0 +1,148 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/system/request"
+	"wails-app/internal/utils"
+)
+
+type DictionaryDetailApi struct{}
+
+// CreateSysDictionaryDetail
+// @Tags      SysDictionaryDetail
+// @Summary   创建SysDictionaryDetail
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysDictionaryDetail     true  "SysDictionaryDetail模型"
+// @Success   200   {object}  response.Response{msg=string}  "创建SysDictionaryDetail"
+// @Router    /sysDictionaryDetail/createSysDictionaryDetail [post]
+func (s *DictionaryDetailApi) CreateSysDictionaryDetail(c *gin.Context) {
+	var detail dao.SysDictionaryDetail
+	err := c.ShouldBindJSON(&detail)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = dictionaryDetailService.CreateSysDictionaryDetail(detail)
+	if err != nil {
+		global.GVA_LOG.Error("创建失败!", zap.Error(err))
+		response.FailWithMessage("创建失败", c)
+		return
+	}
+	response.OkWithMessage("创建成功", c)
+}
+
+// DeleteSysDictionaryDetail
+// @Tags      SysDictionaryDetail
+// @Summary   删除SysDictionaryDetail
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysDictionaryDetail     true  "SysDictionaryDetail模型"
+// @Success   200   {object}  response.Response{msg=string}  "删除SysDictionaryDetail"
+// @Router    /sysDictionaryDetail/deleteSysDictionaryDetail [delete]
+func (s *DictionaryDetailApi) DeleteSysDictionaryDetail(c *gin.Context) {
+	var detail dao.SysDictionaryDetail
+	err := c.ShouldBindJSON(&detail)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = dictionaryDetailService.DeleteSysDictionaryDetail(detail)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// UpdateSysDictionaryDetail
+// @Tags      SysDictionaryDetail
+// @Summary   更新SysDictionaryDetail
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysDictionaryDetail     true  "更新SysDictionaryDetail"
+// @Success   200   {object}  response.Response{msg=string}  "更新SysDictionaryDetail"
+// @Router    /sysDictionaryDetail/updateSysDictionaryDetail [put]
+func (s *DictionaryDetailApi) UpdateSysDictionaryDetail(c *gin.Context) {
+	var detail dao.SysDictionaryDetail
+	err := c.ShouldBindJSON(&detail)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = dictionaryDetailService.UpdateSysDictionaryDetail(&detail)
+	if err != nil {
+		global.GVA_LOG.Error("更新失败!", zap.Error(err))
+		response.FailWithMessage("更新失败", c)
+		return
+	}
+	response.OkWithMessage("更新成功", c)
+}
+
+// FindSysDictionaryDetail
+// @Tags      SysDictionaryDetail
+// @Summary   用id查询SysDictionaryDetail
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  query     system.SysDictionaryDetail                                 true  "用id查询SysDictionaryDetail"
+// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  "用id查询SysDictionaryDetail"
+// @Router    /sysDictionaryDetail/findSysDictionaryDetail [get]
+func (s *DictionaryDetailApi) FindSysDictionaryDetail(c *gin.Context) {
+	var detail dao.SysDictionaryDetail
+	err := c.ShouldBindJSON(&detail)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(detail, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	reSysDictionaryDetail, err := dictionaryDetailService.GetSysDictionaryDetail(detail.ID)
+	if err != nil {
+		global.GVA_LOG.Error("查询失败!", zap.Error(err))
+		response.FailWithMessage("查询失败", c)
+		return
+	}
+	response.OkWithDetailed(gin.H{"reSysDictionaryDetail": reSysDictionaryDetail}, "查询成功", c)
+}
+
+// GetSysDictionaryDetailList
+// @Tags      SysDictionaryDetail
+// @Summary   分页获取SysDictionaryDetail列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  query     request.SysDictionaryDetailSearch                       true  "页码, 每页大小, 搜索条件"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量"
+// @Router    /sysDictionaryDetail/getSysDictionaryDetailList [get]
+func (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) {
+	var pageInfo request.SysDictionaryDetailSearch
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := dictionaryDetailService.GetSysDictionaryDetailInfoList(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}

+ 33 - 0
internal/api/v1/system/sys_jwt_blacklist.go

@@ -0,0 +1,33 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/utils"
+)
+
+type JwtApi struct{}
+
+// JsonInBlacklist
+// @Tags      Jwt
+// @Summary   jwt加入黑名单
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Success   200  {object}  response.Response{msg=string}  "jwt加入黑名单"
+// @Router    /jwt/jsonInBlacklist [post]
+func (j *JwtApi) JsonInBlacklist(c *gin.Context) {
+	token := utils.GetToken(c)
+	jwt := dao.JwtBlacklist{Jwt: token}
+	err := jwtService.JsonInBlacklist(jwt)
+	if err != nil {
+		global.GVA_LOG.Error("jwt作废失败!", zap.Error(err))
+		response.FailWithMessage("jwt作废失败", c)
+		return
+	}
+	utils.ClearToken(c)
+	response.OkWithMessage("jwt作废成功", c)
+}

+ 278 - 0
internal/api/v1/system/sys_menu.go

@@ -0,0 +1,278 @@
+package system
+
+import (
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/request"
+	"wails-app/internal/model/common/response"
+	systemReq "wails-app/internal/model/system/request"
+	systemRes "wails-app/internal/model/system/response"
+	"wails-app/internal/utils"
+
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+)
+
+type AuthorityMenuApi struct{}
+
+// GetMenu
+// @Tags      AuthorityMenu
+// @Summary   获取用户动态路由
+// @Security  ApiKeyAuth
+// @Produce   application/json
+// @Param     data  body      request.Empty                                                  true  "空"
+// @Success   200   {object}  response.Response{data=systemRes.SysMenusResponse,msg=string}  "获取用户动态路由,返回包括系统菜单详情列表"
+// @Router    /menu/getMenu [post]
+func (a *AuthorityMenuApi) GetMenu(c *gin.Context) {
+	menus, err := menuService.GetMenuTree(utils.GetUserAuthorityId(c))
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	if menus == nil {
+		menus = []dao.SysMenu{}
+	}
+	response.OkWithDetailed(systemRes.SysMenusResponse{Menus: menus}, "获取成功", c)
+}
+
+// GetBaseMenuTree
+// @Tags      AuthorityMenu
+// @Summary   获取用户动态路由
+// @Security  ApiKeyAuth
+// @Produce   application/json
+// @Param     data  body      request.Empty                                                      true  "空"
+// @Success   200   {object}  response.Response{data=systemRes.SysBaseMenusResponse,msg=string}  "获取用户动态路由,返回包括系统菜单列表"
+// @Router    /menu/getBaseMenuTree [post]
+func (a *AuthorityMenuApi) GetBaseMenuTree(c *gin.Context) {
+	menus, err := menuService.GetBaseMenuTree()
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysBaseMenusResponse{Menus: menus}, "获取成功", c)
+}
+
+// AddMenuAuthority
+// @Tags      AuthorityMenu
+// @Summary   增加menu和角色关联关系
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      systemReq.AddMenuAuthorityInfo  true  "角色ID"
+// @Success   200   {object}  response.Response{msg=string}   "增加menu和角色关联关系"
+// @Router    /menu/addMenuAuthority [post]
+func (a *AuthorityMenuApi) AddMenuAuthority(c *gin.Context) {
+	var authorityMenu systemReq.AddMenuAuthorityInfo
+	err := c.ShouldBindJSON(&authorityMenu)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	if err := utils.Verify(authorityMenu, utils.AuthorityIdVerify); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	if err := menuService.AddMenuAuthority(authorityMenu.Menus, authorityMenu.AuthorityId); err != nil {
+		global.GVA_LOG.Error("添加失败!", zap.Error(err))
+		response.FailWithMessage("添加失败", c)
+	} else {
+		response.OkWithMessage("添加成功", c)
+	}
+}
+
+// GetMenuAuthority
+// @Tags      AuthorityMenu
+// @Summary   获取指定角色menu
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.GetAuthorityId                                     true  "角色ID"
+// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  "获取指定角色menu"
+// @Router    /menu/getMenuAuthority [post]
+func (a *AuthorityMenuApi) GetMenuAuthority(c *gin.Context) {
+	var param request.GetAuthorityId
+	err := c.ShouldBindJSON(&param)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(param, utils.AuthorityIdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	menus, err := menuService.GetMenuAuthority(&param)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithDetailed(systemRes.SysMenusResponse{Menus: menus}, "获取失败", c)
+		return
+	}
+	response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c)
+}
+
+// AddBaseMenu
+// @Tags      Menu
+// @Summary   新增菜单
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysBaseMenu             true  "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记"
+// @Success   200   {object}  response.Response{msg=string}  "新增菜单"
+// @Router    /menu/addBaseMenu [post]
+func (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) {
+	var menu dao.SysBaseMenu
+	err := c.ShouldBindJSON(&menu)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(menu, utils.MenuVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(menu.Meta, utils.MenuMetaVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = menuService.AddBaseMenu(menu)
+	if err != nil {
+		global.GVA_LOG.Error("添加失败!", zap.Error(err))
+		response.FailWithMessage("添加失败", c)
+		return
+	}
+	response.OkWithMessage("添加成功", c)
+}
+
+// DeleteBaseMenu
+// @Tags      Menu
+// @Summary   删除菜单
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.GetById                true  "菜单id"
+// @Success   200   {object}  response.Response{msg=string}  "删除菜单"
+// @Router    /menu/deleteBaseMenu [post]
+func (a *AuthorityMenuApi) DeleteBaseMenu(c *gin.Context) {
+	var menu request.GetById
+	err := c.ShouldBindJSON(&menu)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(menu, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = baseMenuService.DeleteBaseMenu(menu.ID)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// UpdateBaseMenu
+// @Tags      Menu
+// @Summary   更新菜单
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysBaseMenu             true  "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记"
+// @Success   200   {object}  response.Response{msg=string}  "更新菜单"
+// @Router    /menu/updateBaseMenu [post]
+func (a *AuthorityMenuApi) UpdateBaseMenu(c *gin.Context) {
+	var menu dao.SysBaseMenu
+	err := c.ShouldBindJSON(&menu)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(menu, utils.MenuVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(menu.Meta, utils.MenuMetaVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = baseMenuService.UpdateBaseMenu(menu)
+	if err != nil {
+		global.GVA_LOG.Error("更新失败!", zap.Error(err))
+		response.FailWithMessage("更新失败", c)
+		return
+	}
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetBaseMenuById
+// @Tags      Menu
+// @Summary   根据id获取菜单
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.GetById                                                   true  "菜单id"
+// @Success   200   {object}  response.Response{data=systemRes.SysBaseMenuResponse,msg=string}  "根据id获取菜单,返回包括系统菜单列表"
+// @Router    /menu/getBaseMenuById [post]
+func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) {
+	var idInfo request.GetById
+	err := c.ShouldBindJSON(&idInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(idInfo, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	menu, err := baseMenuService.GetBaseMenuById(idInfo.ID)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, "获取成功", c)
+}
+
+// GetMenuList
+// @Tags      Menu
+// @Summary   分页获取基础menu列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.PageInfo                                        true  "页码, 每页大小"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取基础menu列表,返回包括列表,总数,页码,每页数量"
+// @Router    /menu/getMenuList [post]
+func (a *AuthorityMenuApi) GetMenuList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	menuList, total, err := menuService.GetInfoList()
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     menuList,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}

+ 149 - 0
internal/api/v1/system/sys_operation_record.go

@@ -0,0 +1,149 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/request"
+	"wails-app/internal/model/common/response"
+	systemReq "wails-app/internal/model/system/request"
+	"wails-app/internal/utils"
+)
+
+type OperationRecordApi struct{}
+
+// CreateSysOperationRecord
+// @Tags      SysOperationRecord
+// @Summary   创建SysOperationRecord
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysOperationRecord      true  "创建SysOperationRecord"
+// @Success   200   {object}  response.Response{msg=string}  "创建SysOperationRecord"
+// @Router    /sysOperationRecord/createSysOperationRecord [post]
+func (s *OperationRecordApi) CreateSysOperationRecord(c *gin.Context) {
+	var sysOperationRecord dao.SysOperationRecord
+	err := c.ShouldBindJSON(&sysOperationRecord)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = operationRecordService.CreateSysOperationRecord(sysOperationRecord)
+	if err != nil {
+		global.GVA_LOG.Error("创建失败!", zap.Error(err))
+		response.FailWithMessage("创建失败", c)
+		return
+	}
+	response.OkWithMessage("创建成功", c)
+}
+
+// DeleteSysOperationRecord
+// @Tags      SysOperationRecord
+// @Summary   删除SysOperationRecord
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysOperationRecord      true  "SysOperationRecord模型"
+// @Success   200   {object}  response.Response{msg=string}  "删除SysOperationRecord"
+// @Router    /sysOperationRecord/deleteSysOperationRecord [delete]
+func (s *OperationRecordApi) DeleteSysOperationRecord(c *gin.Context) {
+	var sysOperationRecord dao.SysOperationRecord
+	err := c.ShouldBindJSON(&sysOperationRecord)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = operationRecordService.DeleteSysOperationRecord(sysOperationRecord)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// DeleteSysOperationRecordByIds
+// @Tags      SysOperationRecord
+// @Summary   批量删除SysOperationRecord
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.IdsReq                 true  "批量删除SysOperationRecord"
+// @Success   200   {object}  response.Response{msg=string}  "批量删除SysOperationRecord"
+// @Router    /sysOperationRecord/deleteSysOperationRecordByIds [delete]
+func (s *OperationRecordApi) DeleteSysOperationRecordByIds(c *gin.Context) {
+	var IDS request.IdsReq
+	err := c.ShouldBindJSON(&IDS)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = operationRecordService.DeleteSysOperationRecordByIds(IDS)
+	if err != nil {
+		global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
+		response.FailWithMessage("批量删除失败", c)
+		return
+	}
+	response.OkWithMessage("批量删除成功", c)
+}
+
+// FindSysOperationRecord
+// @Tags      SysOperationRecord
+// @Summary   用id查询SysOperationRecord
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  query     system.SysOperationRecord                                  true  "Id"
+// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  "用id查询SysOperationRecord"
+// @Router    /sysOperationRecord/findSysOperationRecord [get]
+func (s *OperationRecordApi) FindSysOperationRecord(c *gin.Context) {
+	var sysOperationRecord dao.SysOperationRecord
+	err := c.ShouldBindJSON(&sysOperationRecord)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(sysOperationRecord, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	reSysOperationRecord, err := operationRecordService.GetSysOperationRecord(sysOperationRecord.ID)
+	if err != nil {
+		global.GVA_LOG.Error("查询失败!", zap.Error(err))
+		response.FailWithMessage("查询失败", c)
+		return
+	}
+	response.OkWithDetailed(gin.H{"reSysOperationRecord": reSysOperationRecord}, "查询成功", c)
+}
+
+// GetSysOperationRecordList
+// @Tags      SysOperationRecord
+// @Summary   分页获取SysOperationRecord列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  query     request.SysOperationRecordSearch                        true  "页码, 每页大小, 搜索条件"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量"
+// @Router    /sysOperationRecord/getSysOperationRecordList [get]
+func (s *OperationRecordApi) GetSysOperationRecordList(c *gin.Context) {
+	var pageInfo systemReq.SysOperationRecordSearch
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := operationRecordService.GetSysOperationRecordInfoList(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}

+ 473 - 0
internal/api/v1/system/sys_user.go

@@ -0,0 +1,473 @@
+package system
+
+import (
+	"wails-app/internal/dao"
+	"strconv"
+	"time"
+
+	"wails-app/internal/global"
+	"wails-app/internal/model/common/request"
+	"wails-app/internal/model/common/response"
+	systemReq "wails-app/internal/model/system/request"
+	systemRes "wails-app/internal/model/system/response"
+	"wails-app/internal/utils"
+
+	"github.com/gin-gonic/gin"
+	"github.com/redis/go-redis/v9"
+	"go.uber.org/zap"
+)
+
+// 框架自带的登录无加密 返回公钥
+
+func (b *BaseApi) ReturnPublicKey(c *gin.Context) {
+	rsa := utils.RSA{}
+	publicKey := rsa.ReturnPublicKey()
+	response.OkWithData(gin.H{"publicKey": publicKey}, c)
+}
+
+// Login
+// @Tags     Base
+// @Summary  用户登录
+// @Produce   application/json
+// @Param    data  body      systemReq.Login                                             true  "用户名, 密码, 验证码"
+// @Success  200   {object}  response.Response{data=systemRes.LoginResponse,msg=string}  "返回包括用户信息,token,过期时间"
+// @Router   /base/login [post]
+func (b *BaseApi) Login(c *gin.Context) {
+	var l systemReq.Login
+	err := c.ShouldBindJSON(&l)
+	key := c.ClientIP()
+
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(l, utils.LoginVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	// 判断验证码是否开启
+	openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha               // 是否开启防爆次数
+	openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
+	v, ok := global.BlackCache.Get(key)
+	if !ok {
+		global.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))
+	}
+
+	var oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v)
+
+	//解密
+	rsa := utils.RSA{}
+	l.Password = rsa.Encryption(l.Password)
+
+	if !oc || (l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true)) {
+		u := &dao.SysUser{Username: l.Username, Password: l.Password}
+		user, err := userService.Login(u)
+		if err != nil {
+			global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
+			// 验证码次数+1
+			global.BlackCache.Increment(key, 1)
+			response.FailWithMessage("用户名不存在或者密码错误", c)
+			return
+		}
+		if user.Enable != 1 {
+			global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
+			// 验证码次数+1
+			global.BlackCache.Increment(key, 1)
+			response.FailWithMessage("用户被禁止登录", c)
+			return
+		}
+		b.TokenNext(c, *user)
+		return
+	}
+	// 验证码次数+1
+	global.BlackCache.Increment(key, 1)
+	response.FailWithMessage("验证码错误", c)
+}
+
+// TokenNext 登录以后签发jwt
+func (b *BaseApi) TokenNext(c *gin.Context, user dao.SysUser) {
+	j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+	claims := j.CreateClaims(systemReq.BaseClaims{
+		UUID:        user.UUID,
+		ID:          user.ID,
+		NickName:    user.NickName,
+		Username:    user.Username,
+		AuthorityId: user.AuthorityId,
+	})
+	token, err := j.CreateToken(claims)
+	if err != nil {
+		global.GVA_LOG.Error("获取token失败!", zap.Error(err))
+		response.FailWithMessage("获取token失败", c)
+		return
+	}
+	if !global.GVA_CONFIG.System.UseMultipoint {
+		utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
+		response.OkWithDetailed(systemRes.LoginResponse{
+			User:      user,
+			Token:     token,
+			ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
+		}, "登录成功", c)
+		return
+	}
+
+	if jwtStr, err := jwtService.GetRedisJWT(user.Username); err == redis.Nil {
+		if err := jwtService.SetRedisJWT(token, user.Username); err != nil {
+			global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
+			response.FailWithMessage("设置登录状态失败", c)
+			return
+		}
+		utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
+		response.OkWithDetailed(systemRes.LoginResponse{
+			User:      user,
+			Token:     token,
+			ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
+		}, "登录成功", c)
+	} else if err != nil {
+		global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
+		response.FailWithMessage("设置登录状态失败", c)
+	} else {
+		var blackJWT dao.JwtBlacklist
+		blackJWT.Jwt = jwtStr
+		if err := jwtService.JsonInBlacklist(blackJWT); err != nil {
+			response.FailWithMessage("jwt作废失败", c)
+			return
+		}
+		if err := jwtService.SetRedisJWT(token, user.Username); err != nil {
+			response.FailWithMessage("设置登录状态失败", c)
+			return
+		}
+		utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
+		response.OkWithDetailed(systemRes.LoginResponse{
+			User:      user,
+			Token:     token,
+			ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
+		}, "登录成功", c)
+	}
+}
+
+// Register
+// @Tags     SysUser
+// @Summary  用户注册账号
+// @Produce   application/json
+// @Param    data  body      systemReq.Register                                            true  "用户名, 昵称, 密码, 角色ID"
+// @Success  200   {object}  response.Response{data=systemRes.SysUserResponse,msg=string}  "用户注册账号,返回包括用户信息"
+// @Router   /user/admin_register [post]
+func (b *BaseApi) Register(c *gin.Context) {
+	var r systemReq.Register
+	err := c.ShouldBindJSON(&r)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(r, utils.RegisterVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	user := &dao.SysUser{Username: r.Username, NickName: r.NickName, Password: r.Password, HeaderImg: r.HeaderImg, AuthorityId: r.AuthorityId, Enable: r.Enable, Phone: r.Phone, Email: r.Email}
+	userReturn, err := userService.Register(*user)
+	if err != nil {
+		global.GVA_LOG.Error("注册失败!", zap.Error(err))
+		response.FailWithDetailed(systemRes.SysUserResponse{User: userReturn}, "注册失败", c)
+		return
+	}
+	response.OkWithDetailed(systemRes.SysUserResponse{User: userReturn}, "注册成功", c)
+}
+
+// ChangePassword
+// @Tags      SysUser
+// @Summary   用户修改密码
+// @Security  ApiKeyAuth
+// @Produce  application/json
+// @Param     data  body      systemReq.ChangePasswordReq    true  "用户名, 原密码, 新密码"
+// @Success   200   {object}  response.Response{msg=string}  "用户修改密码"
+// @Router    /user/changePassword [post]
+func (b *BaseApi) ChangePassword(c *gin.Context) {
+	var req systemReq.ChangePasswordReq
+	err := c.ShouldBindJSON(&req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(req, utils.ChangePasswordVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	uid := utils.GetUserID(c)
+	u := &dao.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password}
+	_, err = userService.ChangePassword(u, req.NewPassword)
+	if err != nil {
+		global.GVA_LOG.Error("修改失败!", zap.Error(err))
+		response.FailWithMessage("修改失败,原密码与当前账户不符", c)
+		return
+	}
+	response.OkWithMessage("修改成功", c)
+}
+
+// GetUserList
+// @Tags      SysUser
+// @Summary   分页获取用户列表
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.PageInfo                                        true  "页码, 每页大小"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取用户列表,返回包括列表,总数,页码,每页数量"
+// @Router    /user/getUserList [post]
+func (b *BaseApi) GetUserList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := userService.GetUserInfoList(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}
+
+// SetUserAuthority
+// @Tags      SysUser
+// @Summary   更改用户权限
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      systemReq.SetUserAuth          true  "用户UUID, 角色ID"
+// @Success   200   {object}  response.Response{msg=string}  "设置用户权限"
+// @Router    /user/setUserAuthority [post]
+func (b *BaseApi) SetUserAuthority(c *gin.Context) {
+	var sua systemReq.SetUserAuth
+	err := c.ShouldBindJSON(&sua)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	if UserVerifyErr := utils.Verify(sua, utils.SetUserAuthorityVerify); UserVerifyErr != nil {
+		response.FailWithMessage(UserVerifyErr.Error(), c)
+		return
+	}
+	userID := utils.GetUserID(c)
+	err = userService.SetUserAuthority(userID, sua.AuthorityId)
+	if err != nil {
+		global.GVA_LOG.Error("修改失败!", zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	claims := utils.GetUserInfo(c)
+	j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+	claims.AuthorityId = sua.AuthorityId
+	if token, err := j.CreateToken(*claims); err != nil {
+		global.GVA_LOG.Error("修改失败!", zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+	} else {
+		c.Header("new-token", token)
+		c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))
+		utils.SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60))
+		response.OkWithMessage("修改成功", c)
+	}
+}
+
+// SetUserAuthorities
+// @Tags      SysUser
+// @Summary   设置用户权限
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      systemReq.SetUserAuthorities   true  "用户UUID, 角色ID"
+// @Success   200   {object}  response.Response{msg=string}  "设置用户权限"
+// @Router    /user/setUserAuthorities [post]
+func (b *BaseApi) SetUserAuthorities(c *gin.Context) {
+	var sua systemReq.SetUserAuthorities
+	err := c.ShouldBindJSON(&sua)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = userService.SetUserAuthorities(sua.ID, sua.AuthorityId)
+	if err != nil {
+		global.GVA_LOG.Error("修改失败!", zap.Error(err))
+		response.FailWithMessage("修改失败", c)
+		return
+	}
+	response.OkWithMessage("修改成功", c)
+}
+
+// DeleteUser
+// @Tags      SysUser
+// @Summary   删除用户
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      request.GetById                true  "用户ID"
+// @Success   200   {object}  response.Response{msg=string}  "删除用户"
+// @Router    /user/deleteUser [delete]
+func (b *BaseApi) DeleteUser(c *gin.Context) {
+	var reqId request.GetById
+	err := c.ShouldBindJSON(&reqId)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(reqId, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	jwtId := utils.GetUserID(c)
+	if jwtId == uint(reqId.ID) {
+		response.FailWithMessage("删除失败, 自杀失败", c)
+		return
+	}
+	err = userService.DeleteUser(reqId.ID)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}
+
+// SetUserInfo
+// @Tags      SysUser
+// @Summary   设置用户信息
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysUser                                             true  "ID, 用户名, 昵称, 头像链接"
+// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  "设置用户信息"
+// @Router    /user/setUserInfo [put]
+func (b *BaseApi) SetUserInfo(c *gin.Context) {
+	var user systemReq.ChangeUserInfo
+	err := c.ShouldBindJSON(&user)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(user, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	if user.AuthorityIds != 0 {
+		err = userService.SetUserAuthorities(user.ID, user.AuthorityIds)
+		if err != nil {
+			global.GVA_LOG.Error("设置失败!", zap.Error(err))
+			response.FailWithMessage("设置失败", c)
+			return
+		}
+	}
+	err = userService.SetUserInfo(dao.SysUser{
+		GVA_MODEL: global.GVA_MODEL{
+			ID: user.ID,
+		},
+		NickName:  user.NickName,
+		HeaderImg: user.HeaderImg,
+		Phone:     user.Phone,
+		Email:     user.Email,
+		SideMode:  user.SideMode,
+		Enable:    user.Enable,
+	})
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败", c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}
+
+// SetSelfInfo
+// @Tags      SysUser
+// @Summary   设置用户信息
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Param     data  body      system.SysUser                                             true  "ID, 用户名, 昵称, 头像链接"
+// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  "设置用户信息"
+// @Router    /user/SetSelfInfo [put]
+func (b *BaseApi) SetSelfInfo(c *gin.Context) {
+	var user systemReq.ChangeUserInfo
+	err := c.ShouldBindJSON(&user)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	user.ID = utils.GetUserID(c)
+	err = userService.SetSelfInfo(dao.SysUser{
+		GVA_MODEL: global.GVA_MODEL{
+			ID: user.ID,
+		},
+		NickName:  user.NickName,
+		HeaderImg: user.HeaderImg,
+		Phone:     user.Phone,
+		Email:     user.Email,
+		SideMode:  user.SideMode,
+		Enable:    user.Enable,
+	})
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败", c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}
+
+// GetUserInfo
+// @Tags      SysUser
+// @Summary   获取用户信息
+// @Security  ApiKeyAuth
+// @accept    application/json
+// @Produce   application/json
+// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  "获取用户信息"
+// @Router    /user/getUserInfo [get]
+func (b *BaseApi) GetUserInfo(c *gin.Context) {
+	uuid := utils.GetUserUuid(c)
+	ReqUser, err := userService.GetUserInfo(uuid)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(gin.H{"userInfo": ReqUser}, "获取成功", c)
+}
+
+// ResetPassword
+// @Tags      SysUser
+// @Summary   重置用户密码
+// @Security  ApiKeyAuth
+// @Produce  application/json
+// @Param     data  body      system.SysUser                 true  "ID"
+// @Success   200   {object}  response.Response{msg=string}  "重置用户密码"
+// @Router    /user/resetPassword [post]
+func (b *BaseApi) ResetPassword(c *gin.Context) {
+	var user dao.SysUser
+	err := c.ShouldBindJSON(&user)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = userService.ResetPassword(user.ID)
+	if err != nil {
+		global.GVA_LOG.Error("重置失败!", zap.Error(err))
+		response.FailWithMessage("重置失败"+err.Error(), c)
+		return
+	}
+	response.OkWithMessage("重置成功", c)
+}

+ 192 - 0
internal/api/v1/uhf/reader.go

@@ -0,0 +1,192 @@
+package uhf
+
+import (
+	"fmt"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/uhf/request"
+	"wails-app/internal/service/uhf"
+
+	"github.com/gin-gonic/gin"
+)
+
+func CreateReader(c *gin.Context) {
+	var req request.UHFReaderCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := uhf.UHFService.CreateReader(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+func UpdateReader(c *gin.Context) {
+	var req request.UHFReaderUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := uhf.UHFService.UpdateReader(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+func ConnectedDevice(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = uhf.UHFService.ConnectedDevice(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.Ok(c)
+}
+
+func GetReaderByCode(c *gin.Context) {
+	code := c.Query("code")
+	if code == "" {
+		response.FailWithMessage("设备编码不能为空", c)
+		return
+	}
+
+	reader, err := uhf.UHFService.GetReaderByCode(code)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(reader, c)
+}
+
+func QueryAllReaders(c *gin.Context) {
+	readers, err := uhf.UHFService.QueryAllReaders()
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithData(readers, c)
+}
+
+func ListReaders(c *gin.Context) {
+	var req request.UHFReaderQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	list, total, err := uhf.UHFService.ListReaders(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     req.Page,
+		PageSize: req.PageSize,
+	}, "获取成功", c)
+}
+
+func DeleteReader(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = uhf.UHFService.DeleteReader(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}
+
+func ConnectReader(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = uhf.UHFService.ConnectReader(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("连接成功", c)
+}
+
+func DisconnectReader(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = uhf.UHFService.DisconnectReader(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("断开连接成功", c)
+}
+
+func SetReaderPower(c *gin.Context) {
+	var req request.UHFReaderPower
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := uhf.UHFService.SetReaderPower(req.ID, int(req.Power))
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("功率设置成功", c)
+}
+
+func ReportTag(c *gin.Context) {
+	var req request.UHFReaderTagReport
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := uhf.UHFService.ReportTag(req.DeviceCode, req.Tag)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("标签上报成功", c)
+}

+ 35 - 0
internal/api/v1/vehicle/enter.go

@@ -0,0 +1,35 @@
+package vehicle
+
+import "wails-app/internal/service"
+
+// 车辆相关API
+//var ApiGroup = []interface{}{
+//	CreateVehicle,
+//	UpdateVehicle,
+//	GetVehicleByID,
+//	GetVehicleByPlateNumber,
+//	ListVehicles,
+//	DeleteVehicle,
+//	VehicleEntry,
+//	VehicleExit,
+//	CreateFeeConfig,
+//	UpdateFeeConfig,
+//	GetFeeConfigByID,
+//	ListFeeConfigs,
+//	DeleteFeeConfig,
+//	GetVehicleRecordByID,
+//	ListVehicleRecordsByPlateNumber,
+//	ListVehicleRecords,
+//}
+
+type ApiGroup struct {
+	ShortlistApi
+}
+
+var (
+	shortlistService     = service.ServiceGroupApp.VehicleServiceGroup.ShortlistService
+	vehicleService       = service.ServiceGroupApp.VehicleServiceGroup.VehicleService
+	vehicleRecordService = service.ServiceGroupApp.VehicleServiceGroup.VehicleRecordService
+	feeConfigService     = service.ServiceGroupApp.VehicleServiceGroup.FeeConfigService
+	vehicleTypeService   = service.ServiceGroupApp.VehicleServiceGroup.VehicleTypeService
+)

+ 101 - 0
internal/api/v1/vehicle/fee_config.go

@@ -0,0 +1,101 @@
+package vehicle
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/vehicle/request"
+	vehicleResponse "wails-app/internal/model/vehicle/response"
+)
+
+// CreateFeeConfig 创建收费配置
+func CreateFeeConfig(c *gin.Context) {
+	var req request.FeeConfigCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := feeConfigService.CreateFeeConfig(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+// UpdateFeeConfig 更新收费配置
+func UpdateFeeConfig(c *gin.Context) {
+	var req request.FeeConfigUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := feeConfigService.UpdateFeeConfig(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetFeeConfigByID 根据ID获取收费配置
+func GetFeeConfigByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	config, err := feeConfigService.GetFeeConfigByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(config, c)
+}
+
+// ListFeeConfigs 获取收费配置列表
+func ListFeeConfigs(c *gin.Context) {
+	var req request.FeeConfigQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := feeConfigService.ListFeeConfigs(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleResponse.FeeConfigList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+// DeleteFeeConfig 删除收费配置
+func DeleteFeeConfig(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = feeConfigService.DeleteFeeConfig(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}

+ 86 - 0
internal/api/v1/vehicle/shortlist.go

@@ -0,0 +1,86 @@
+package vehicle
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"wails-app/internal/dao"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/vehicle/request"
+	"strconv"
+)
+
+type ShortlistApi struct{}
+
+func (sa *ShortlistApi) QueryAllShortlists(c *gin.Context) {
+	shortlists, err := shortlistService.QueryAllShortlists()
+	if err != nil {
+		response.FailWithMessage("查询失败", c)
+		return
+	}
+	response.OkWithData(shortlists, c)
+}
+
+func (sa *ShortlistApi) QueryShortlistList(c *gin.Context) {
+	var info request.ShortlistRequest
+	err := c.ShouldBindJSON(&info)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	fmt.Println(info)
+	list, total, err := shortlistService.QueryShortlistList(info)
+	if err != nil {
+		response.FailWithMessage("查询失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     info.Page,
+		PageSize: info.PageSize,
+	}, "获取成功", c)
+}
+
+func (sa *ShortlistApi) CreateShortlist(c *gin.Context) {
+	var shortlist dao.Shortlist
+	err := c.ShouldBindJSON(&shortlist)
+	if err != nil {
+		response.FailWithMessage("参数错误"+err.Error(), c)
+		return
+	}
+	err = shortlistService.CreateShortlist(shortlist)
+	if err != nil {
+		response.FailWithMessage("新增失败", c)
+		return
+	}
+	response.OkWithMessage("新增成功", c)
+}
+
+func (sa *ShortlistApi) UpdateShortlist(c *gin.Context) {
+	var shortlist dao.Shortlist
+	err := c.ShouldBindJSON(&shortlist)
+	if err != nil {
+		response.FailWithMessage("参数错误"+err.Error(), c)
+		return
+	}
+	err = shortlistService.UpdateShortlist(shortlist)
+	if err != nil {
+		response.FailWithMessage("修改失败", c)
+		return
+	}
+	response.OkWithMessage("修改成功", c)
+}
+
+func (sa *ShortlistApi) DeleteShortlist(c *gin.Context) {
+	id, err := strconv.Atoi(c.Query("id"))
+	if err != nil {
+		response.FailWithMessage("参数错误", c)
+		return
+	}
+	err = shortlistService.DeleteShortlist(id)
+	if err != nil {
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}

+ 160 - 0
internal/api/v1/vehicle/vehicle.go

@@ -0,0 +1,160 @@
+package vehicle
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/vehicle/request"
+	vehicleResponse "wails-app/internal/model/vehicle/response"
+)
+
+// CreateVehicle 创建车辆信息
+func CreateVehicle(c *gin.Context) {
+	var req request.VehicleCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err := vehicleService.CreateVehicle(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+// UpdateVehicle 更新车辆信息
+func UpdateVehicle(c *gin.Context) {
+	var req request.VehicleUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := vehicleService.UpdateVehicle(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetVehicleByID 根据ID获取车辆信息
+func GetVehicleByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	vehicleInfo, err := vehicleService.GetVehicleByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleInfo, c)
+}
+
+// GetVehicleByPlateNumber 根据车牌号获取车辆信息
+func GetVehicleByPlateNumber(c *gin.Context) {
+	plateNumber := c.Query("plate_number")
+	if plateNumber == "" {
+		response.FailWithMessage("车牌号不能为空", c)
+		return
+	}
+
+	vehicleInfo, err := vehicleService.GetVehicleByPlateNumber(plateNumber, "")
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleInfo, c)
+}
+
+func QueryAllVehicles(c *gin.Context) {
+	vehicles, err := vehicleService.QueryAllVehicles()
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithData(vehicles, c)
+}
+
+// ListVehicles 获取车辆列表
+func ListVehicles(c *gin.Context) {
+	var req request.VehicleQuery
+	if err := c.ShouldBindQuery(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := vehicleService.ListVehicles(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleResponse.VehicleList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+// DeleteVehicle 删除车辆
+func DeleteVehicle(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = vehicleService.DeleteVehicle(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}
+
+// VehicleEntry 车辆入场
+func VehicleEntry(c *gin.Context) {
+	var req request.VehicleEntry
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	resp, err := vehicleService.VehicleEntry(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(resp, c)
+}
+
+// VehicleExit 车辆出场
+func VehicleExit(c *gin.Context) {
+	var req request.VehicleExit
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	resp, err := vehicleService.VehicleExit(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(resp, c)
+}

+ 78 - 0
internal/api/v1/vehicle/vehicle_record.go

@@ -0,0 +1,78 @@
+package vehicle
+
+import (
+	"fmt"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/vehicle/request"
+	vehicleResponse "wails-app/internal/model/vehicle/response"
+
+	"github.com/gin-gonic/gin"
+)
+
+// GetVehicleRecordByID 根据ID获取车辆记录
+func GetVehicleRecordByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	record, err := vehicleRecordService.GetVehicleRecordByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(record, c)
+}
+
+// ListVehicleRecordsByPlateNumber 根据车牌号获取车辆记录列表
+func ListVehicleRecordsByPlateNumber(c *gin.Context) {
+	plateNumber := c.Query("plate_number")
+	if plateNumber == "" {
+		response.FailWithMessage("车牌号不能为空", c)
+		return
+	}
+
+	page := 1
+	pageSize := 10
+	if p := c.Query("page"); p != "" {
+		fmt.Sscanf(p, "%d", &page)
+	}
+	if ps := c.Query("page_size"); ps != "" {
+		fmt.Sscanf(ps, "%d", &pageSize)
+	}
+
+	total, list, err := vehicleRecordService.ListVehicleRecordsByPlateNumber(plateNumber, page, pageSize)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleResponse.VehicleRecordList{
+		Total: total,
+		List:  list,
+	}, c)
+}
+
+// ListVehicleRecords 获取所有车辆记录列表
+func ListVehicleRecords(c *gin.Context) {
+	var info request.VehicleRecordRequest
+
+	err := c.ShouldBindJSON(&info)
+
+	list, total, err := vehicleRecordService.ListVehicleRecords(info)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     info.Page,
+		PageSize: info.PageSize,
+	}, "获取成功", c)
+}

+ 113 - 0
internal/api/v1/vehicle/vehicle_type.go

@@ -0,0 +1,113 @@
+package vehicle
+
+import (
+	"fmt"
+	"wails-app/internal/model/common/response"
+	"wails-app/internal/model/vehicle/request"
+
+	"github.com/gin-gonic/gin"
+)
+
+// CreateVehicleType 创建车辆类型
+func CreateVehicleType(c *gin.Context) {
+	var req request.VehicleTypeCreate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err := vehicleTypeService.CreateVehicleType(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("创建成功", c)
+}
+
+// UpdateVehicleType 更新车辆类型
+func UpdateVehicleType(c *gin.Context) {
+	var req request.VehicleTypeUpdate
+	if err := c.ShouldBindJSON(&req); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err := vehicleTypeService.UpdateVehicleType(req)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("更新成功", c)
+}
+
+// GetVehicleTypeByID 根据ID获取车辆类型
+func GetVehicleTypeByID(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	vehicleTypeInfo, err := vehicleTypeService.GetVehicleTypeByID(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleTypeInfo, c)
+}
+
+// ListVehicleTypes 获取车辆类型列表
+func ListVehicleTypes(c *gin.Context) {
+	var info request.VehicleTypeQuery
+	if err := c.ShouldBindJSON(&info); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	total, list, err := vehicleTypeService.ListVehicleTypes(info)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     info.Page,
+		PageSize: info.PageSize,
+	}, "获取成功", c)
+}
+
+// DeleteVehicleType 删除车辆类型
+func DeleteVehicleType(c *gin.Context) {
+	id := c.Query("id")
+	uintID := uint(0)
+	_, err := fmt.Sscanf(id, "%d", &uintID)
+	if err != nil {
+		response.FailWithMessage("无效的ID", c)
+		return
+	}
+
+	err = vehicleTypeService.DeleteVehicleType(uintID)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithMessage("删除成功", c)
+}
+
+// GetAllVehicleTypes 获取所有车辆类型(用于下拉选择)
+func GetAllVehicleTypes(c *gin.Context) {
+	vehicleTypes, err := vehicleTypeService.GetAllVehicleTypes()
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	response.OkWithData(vehicleTypes, c)
+}

+ 220 - 0
internal/config.docker.yaml

@@ -0,0 +1,220 @@
+# github.com/flipped-aurora/gin-vue-admin/server Global Configuration
+
+# jwt configuration
+jwt:
+  signing-key: qmPlus
+  expires-time: 7d
+  buffer-time: 1d
+  issuer: qmPlus
+# zap logger configuration
+zap:
+  level: info
+  format: console
+  prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
+  director: log
+  show-line: true
+  encode-level: LowercaseColorLevelEncoder
+  stacktrace-key: stacktrace
+  log-in-console: true
+
+# redis configuration
+redis:
+  db: 0
+  addr: 177.7.0.14:6379
+  password: ""
+
+# mongo configuration
+mongo:
+  coll: ''
+  options: ''
+  database: ''
+  username: ''
+  password: ''
+  min-pool-size: 0
+  max-pool-size: 100
+  socket-timeout-ms: 0
+  connect-timeout-ms: 0
+  is-zap: false
+  hosts:
+    - host: ''
+      port: ''
+
+# email configuration
+email:
+  to: xxx@qq.com
+  port: 465
+  from: xxx@163.com
+  host: smtp.163.com
+  is-ssl: true
+  secret: xxx
+  nickname: test
+
+# system configuration
+system:
+  env: public  # Change to "develop" to skip authentication for development mode
+  addr: 8888
+  db-type: mysql
+  oss-type: local    # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
+  use-redis: false     # 使用redis
+  use-mongo: false     # 使用mongo
+  use-multipoint: false
+  # IP限制次数 一个小时15000次
+  iplimit-count: 15000
+  #  IP限制一个小时
+  iplimit-time: 3600
+
+# captcha configuration
+captcha:
+  key-long: 6
+  img-width: 240
+  img-height: 80
+  open-captcha: 0 # 0代表一直开启,大于0代表限制次数
+  open-captcha-timeout: 3600 # open-captcha大于0时才生效
+
+# mysql connect configuration
+# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
+mysql:
+  path: ""
+  port: ""
+  config: ""
+  db-name: ""
+  username: ""
+  password: ""
+  max-idle-conns: 10
+  max-open-conns: 100
+  log-mode: ""
+  log-zap: false
+
+# pgsql connect configuration
+# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
+pgsql:
+  path: ""
+  port: ""
+  config: ""
+  db-name: ""
+  username: ""
+  password: ""
+  max-idle-conns: 10
+  max-open-conns: 100
+  log-mode: ""
+  log-zap: false
+
+db-list:
+  - disable: true # 是否禁用
+    type: "" # 数据库的类型,目前支持mysql、pgsql
+    alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
+    path: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    log-mode: ""
+    log-zap: false
+
+
+# local configuration
+local:
+  path: uploads/file
+  store-path: uploads/file
+
+# autocode configuration
+autocode:
+  transfer-restart: true
+  # root 自动适配项目根目录
+  # 请不要手动配置,他会在项目加载的时候识别出根路径
+  root: ""
+  server: /server
+  server-plug: /plugin/%s
+  server-api: /api/v1/%s
+  server-initialize: /initialize
+  server-model: /model/%s
+  server-request: /model/%s/request/
+  server-router: /router/%s
+  server-service: /service/%s
+  web: /web/src
+  web-api: /api
+  web-form: /view
+  web-table: /view
+
+# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
+qiniu:
+  zone: ZoneHuaDong
+  bucket: ""
+  img-path: ""
+  use-https: false
+  access-key: ""
+  secret-key: ""
+  use-cdn-domains: false
+
+# aliyun oss configuration
+aliyun-oss:
+  endpoint: yourEndpoint
+  access-key-id: yourAccessKeyId
+  access-key-secret: yourAccessKeySecret
+  bucket-name: yourBucketName
+  bucket-url: yourBucketUrl
+  base-path: yourBasePath
+
+# tencent cos configuration
+tencent-cos:
+  bucket: xxxxx-10005608
+  region: ap-shanghai
+  secret-id: your-secret-id
+  secret-key: your-secret-key
+  base-url: https://gin.vue.admin
+  path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+
+# aws s3 configuration (minio compatible)
+aws-s3:
+  bucket: xxxxx-10005608
+  region: ap-shanghai
+  endpoint: ""
+  s3-force-path-style: false
+  disable-ssl: false
+  secret-id: your-secret-id
+  secret-key: your-secret-key
+  base-url: https://gin.vue.admin
+  path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+
+# huawei obs configuration
+hua-wei-obs:
+  path: you-path
+  bucket: you-bucket
+  endpoint: you-endpoint
+  access-key: you-access-key
+  secret-key: you-secret-key
+
+# excel configuration
+excel:
+  dir: ./resource/excel/
+
+# timer task db clear table
+Timer:
+  start: true
+  spec: "@daily"  # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3
+  detail:
+    - tableName: sys_operation_records
+      compareField: created_at
+      interval: 2160h
+    - tableName: jwt_blacklists
+      compareField: created_at
+      interval: 168h
+
+# 跨域配置
+# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
+cors:
+  mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
+  whitelist:
+    - allow-origin: example1.com
+      allow-headers: content-type
+      allow-methods: GET, POST
+      expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+      allow-credentials: true # 布尔值
+    - allow-origin: example2.com
+      allow-headers: content-type
+      allow-methods: GET, POST
+      expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+      allow-credentials: true # 布尔值

+ 222 - 0
internal/config.yaml

@@ -0,0 +1,222 @@
+aliyun-oss:
+    endpoint: yourEndpoint
+    access-key-id: yourAccessKeyId
+    access-key-secret: yourAccessKeySecret
+    bucket-name: yourBucketName
+    bucket-url: yourBucketUrl
+    base-path: yourBasePath
+autocode:
+    server-model: /model/%s
+    server-router: /router/%s
+    server: /server
+    server-api: /api/v1/%s
+    server-plug: /plugin/%s
+    server-initialize: /initialize
+    root: E:\GoWorks\src\lc_finance
+    web-table: /view
+    web: /web/src
+    server-service: /service/%s
+    server-request: /model/%s/request/
+    web-api: /api
+    web-form: /view
+    transfer-restart: true
+aws-s3:
+    bucket: xxxxx-10005608
+    region: ap-shanghai
+    endpoint: ""
+    secret-id: your-secret-id
+    secret-key: your-secret-key
+    base-url: https://gin.vue.admin
+    path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+    s3-force-path-style: false
+    disable-ssl: false
+captcha:
+    key-long: 6
+    img-width: 240
+    img-height: 80
+    open-captcha: 0
+    open-captcha-timeout: 3600
+cors:
+    mode: strict-whitelist
+    whitelist:
+        - allow-origin: example1.com
+          allow-methods: POST, GET
+          allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
+          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+          allow-credentials: true
+        - allow-origin: example2.com
+          allow-methods: GET, POST
+          allow-headers: content-type
+          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+          allow-credentials: true
+db-list:
+    - type: ""
+      alias-name: ""
+      prefix: ""
+      port: ""
+      config: ""
+      db-name: ""
+      username: ""
+      password: ""
+      path: ""
+      engine: ""
+      log-mode: ""
+      max-idle-conns: 10
+      max-open-conns: 100
+      singular: false
+      log-zap: false
+      disable: true
+email:
+    to: xxx@qq.com
+    from: xxx@163.com
+    host: smtp.163.com
+    secret: xxx
+    nickname: test
+    port: 465
+    is-ssl: true
+excel:
+    dir: ./resource/excel/
+hua-wei-obs:
+    path: you-path
+    bucket: you-bucket
+    endpoint: you-endpoint
+    access-key: you-access-key
+    secret-key: you-secret-key
+jwt:
+    signing-key: 23cb7052-8c96-4acf-9136-578347b2d4c8
+    expires-time: 7d
+    buffer-time: 1d
+    issuer: qmPlus
+local:
+    path: uploads/file
+    store-path: uploads/file
+mongo:
+    coll: ""
+    options: ""
+    database: ""
+    username: ""
+    password: ""
+    auth-source: ""
+    min-pool-size: 0
+    max-pool-size: 100
+    socket-timeout-ms: 0
+    connect-timeout-ms: 0
+    is-zap: false
+    hosts:
+        - host: ""
+          port: ""
+mssql:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+mysql:
+    prefix: ""
+    port: "3306"
+    config: charset=utf8mb4&parseTime=True&loc=Local
+    db-name: lc_garage
+    username: root
+    password: root
+
+    path: 127.0.0.1
+    engine: ""
+    log-mode: error
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+oracle:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+pgsql:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+qiniu:
+    zone: ZoneHuaDong
+    bucket: ""
+    img-path: ""
+    access-key: ""
+    secret-key: ""
+    use-https: false
+    use-cdn-domains: false
+redis:
+    addr: 127.0.0.1:6379
+    password: ""
+    db: 0
+    useCluster: false
+    clusterAddrs:
+        - 172.21.0.3:7000
+        - 172.21.0.4:7001
+        - 172.21.0.2:7002
+sqlite:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+system:
+    db-type: mysql
+    oss-type: local
+    router-prefix: ""
+    addr: 8888
+    iplimit-count: 15000
+    iplimit-time: 3600
+    use-multipoint: false
+    use-redis: false
+    use-mongo: false
+tencent-cos:
+    bucket: xxxxx-10005608
+    region: ap-shanghai
+    secret-id: your-secret-id
+    secret-key: your-secret-key
+    base-url: https://gin.vue.admin
+    path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+zap:
+    level: info
+    prefix: '[github.com/flipped-aurora/gin-vue-admin/server]'
+    format: console
+    director: log
+    encode-level: LowercaseColorLevelEncoder
+    stacktrace-key: stacktrace
+    max-age: 0
+    show-line: true
+    log-in-console: true

+ 9 - 0
internal/config/captcha.go

@@ -0,0 +1,9 @@
+package config
+
+type Captcha struct {
+	KeyLong            int `mapstructure:"key-long" json:"key-long" yaml:"key-long"`                                     // 验证码长度
+	ImgWidth           int `mapstructure:"img-width" json:"img-width" yaml:"img-width"`                                  // 验证码宽度
+	ImgHeight          int `mapstructure:"img-height" json:"img-height" yaml:"img-height"`                               // 验证码高度
+	OpenCaptcha        int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"`                         // 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码
+	OpenCaptchaTimeOut int `mapstructure:"open-captcha-timeout" json:"open-captcha-timeout" yaml:"open-captcha-timeout"` // 防爆破验证码超时时间,单位:s(秒)
+}

+ 30 - 0
internal/config/config.go

@@ -0,0 +1,30 @@
+package config
+
+type Server struct {
+	JWT     JWT     `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
+	Zap     Zap     `mapstructure:"zap" json:"zap" yaml:"zap"`
+	Redis   Redis   `mapstructure:"redis" json:"redis" yaml:"redis"`
+	Mongo   Mongo   `mapstructure:"mongo" json:"mongo" yaml:"mongo"`
+	Email   Email   `mapstructure:"email" json:"email" yaml:"email"`
+	System  System  `mapstructure:"system" json:"system" yaml:"system"`
+	Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
+	// gorm
+	Mysql  Mysql           `mapstructure:"mysql" json:"mysql" yaml:"mysql"`
+	Mssql  Mssql           `mapstructure:"mssql" json:"mssql" yaml:"mssql"`
+	Pgsql  Pgsql           `mapstructure:"pgsql" json:"pgsql" yaml:"pgsql"`
+	Oracle Oracle          `mapstructure:"oracle" json:"oracle" yaml:"oracle"`
+	Sqlite Sqlite          `mapstructure:"sqlite" json:"sqlite" yaml:"sqlite"`
+	DBList []SpecializedDB `mapstructure:"db-list" json:"db-list" yaml:"db-list"`
+	// oss
+	Local      Local      `mapstructure:"local" json:"local" yaml:"local"`
+	Qiniu      Qiniu      `mapstructure:"qiniu" json:"qiniu" yaml:"qiniu"`
+	AliyunOSS  AliyunOSS  `mapstructure:"aliyun-oss" json:"aliyun-oss" yaml:"aliyun-oss"`
+	HuaWeiObs  HuaWeiObs  `mapstructure:"hua-wei-obs" json:"hua-wei-obs" yaml:"hua-wei-obs"`
+	TencentCOS TencentCOS `mapstructure:"tencent-cos" json:"tencent-cos" yaml:"tencent-cos"`
+	AwsS3      AwsS3      `mapstructure:"aws-s3" json:"aws-s3" yaml:"aws-s3"`
+
+	Excel Excel `mapstructure:"excel" json:"excel" yaml:"excel"`
+
+	// 跨域配置
+	Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"`
+}

+ 14 - 0
internal/config/cors.go

@@ -0,0 +1,14 @@
+package config
+
+type CORS struct {
+	Mode      string          `mapstructure:"mode" json:"mode" yaml:"mode"`
+	Whitelist []CORSWhitelist `mapstructure:"whitelist" json:"whitelist" yaml:"whitelist"`
+}
+
+type CORSWhitelist struct {
+	AllowOrigin      string `mapstructure:"allow-origin" json:"allow-origin" yaml:"allow-origin"`
+	AllowMethods     string `mapstructure:"allow-methods" json:"allow-methods" yaml:"allow-methods"`
+	AllowHeaders     string `mapstructure:"allow-headers" json:"allow-headers" yaml:"allow-headers"`
+	ExposeHeaders    string `mapstructure:"expose-headers" json:"expose-headers" yaml:"expose-headers"`
+	AllowCredentials bool   `mapstructure:"allow-credentials" json:"allow-credentials" yaml:"allow-credentials"`
+}

+ 32 - 0
internal/config/db_list.go

@@ -0,0 +1,32 @@
+package config
+
+type DsnProvider interface {
+	Dsn() string
+}
+
+// Embeded 结构体可以压平到上一层,从而保持 config 文件的结构和原来一样
+// 见 playground: https://go.dev/play/p/KIcuhqEoxmY
+
+// GeneralDB 也被 Pgsql 和 Mysql 原样使用
+type GeneralDB struct {
+	Prefix       string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`
+	Port         string `mapstructure:"port" json:"port" yaml:"port"`
+	Config       string `mapstructure:"config" json:"config" yaml:"config"`       // 高级配置
+	Dbname       string `mapstructure:"db-name" json:"db-name" yaml:"db-name"`    // 数据库名
+	Username     string `mapstructure:"username" json:"username" yaml:"username"` // 数据库密码
+	Password     string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
+	Path         string `mapstructure:"path" json:"path" yaml:"path"`
+	Engine       string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"`        //数据库引擎,默认InnoDB
+	LogMode      string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"`                   // 是否开启Gorm全局日志
+	MaxIdleConns int    `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` // 空闲中的最大连接数
+	MaxOpenConns int    `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
+	Singular     bool   `mapstructure:"singular" json:"singular" yaml:"singular"`                   //是否开启全局禁用复数,true表示开启
+	LogZap       bool   `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"`                      // 是否通过zap写入日志文件
+}
+
+type SpecializedDB struct {
+	Type      string `mapstructure:"type" json:"type" yaml:"type"`
+	AliasName string `mapstructure:"alias-name" json:"alias-name" yaml:"alias-name"`
+	GeneralDB `yaml:",inline" mapstructure:",squash"`
+	Disable   bool `mapstructure:"disable" json:"disable" yaml:"disable"`
+}

+ 11 - 0
internal/config/email.go

@@ -0,0 +1,11 @@
+package config
+
+type Email struct {
+	To       string `mapstructure:"to" json:"to" yaml:"to"`                   // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
+	From     string `mapstructure:"from" json:"from" yaml:"from"`             // 发件人  你自己要发邮件的邮箱
+	Host     string `mapstructure:"host" json:"host" yaml:"host"`             // 服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议
+	Secret   string `mapstructure:"secret" json:"secret" yaml:"secret"`       // 密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
+	Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称    发件人昵称 通常为自己的邮箱
+	Port     int    `mapstructure:"port" json:"port" yaml:"port"`             // 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
+	IsSSL    bool   `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"`       // 是否SSL   是否开启SSL
+}

+ 5 - 0
internal/config/excel.go

@@ -0,0 +1,5 @@
+package config
+
+type Excel struct {
+	Dir string `mapstructure:"dir" json:"dir" yaml:"dir"`
+}

+ 14 - 0
internal/config/gorm_mssql.go

@@ -0,0 +1,14 @@
+package config
+
+type Mssql struct {
+	GeneralDB `yaml:",inline" mapstructure:",squash"`
+}
+
+//dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
+func (m *Mssql) Dsn() string {
+	return "sqlserver://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "?database=" + m.Dbname + "&encrypt=disable"
+}
+
+func (m *Mssql) GetLogMode() string {
+	return m.LogMode
+}

+ 13 - 0
internal/config/gorm_mysql.go

@@ -0,0 +1,13 @@
+package config
+
+type Mysql struct {
+	GeneralDB `yaml:",inline" mapstructure:",squash"`
+}
+
+func (m *Mysql) Dsn() string {
+	return m.Username + ":" + m.Password + "@tcp(" + m.Path + ":" + m.Port + ")/" + m.Dbname + "?" + m.Config
+}
+
+func (m *Mysql) GetLogMode() string {
+	return m.LogMode
+}

+ 14 - 0
internal/config/gorm_oracle.go

@@ -0,0 +1,14 @@
+package config
+
+type Oracle struct {
+	GeneralDB `yaml:",inline" mapstructure:",squash"`
+}
+
+func (m *Oracle) Dsn() string {
+	return "oracle://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "/" + m.Dbname + "?" + m.Config
+
+}
+
+func (m *Oracle) GetLogMode() string {
+	return m.LogMode
+}

+ 21 - 0
internal/config/gorm_pgsql.go

@@ -0,0 +1,21 @@
+package config
+
+type Pgsql struct {
+	GeneralDB `yaml:",inline" mapstructure:",squash"`
+}
+
+// Dsn 基于配置文件获取 dsn
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (p *Pgsql) Dsn() string {
+	return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + p.Dbname + " port=" + p.Port + " " + p.Config
+}
+
+// LinkDsn 根据 dbname 生成 dsn
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (p *Pgsql) LinkDsn(dbname string) string {
+	return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + dbname + " port=" + p.Port + " " + p.Config
+}
+
+func (m *Pgsql) GetLogMode() string {
+	return m.LogMode
+}

+ 17 - 0
internal/config/gorm_sqlite.go

@@ -0,0 +1,17 @@
+package config
+
+import (
+	"path/filepath"
+)
+
+type Sqlite struct {
+	GeneralDB `yaml:",inline" mapstructure:",squash"`
+}
+
+func (s *Sqlite) Dsn() string {
+	return filepath.Join(s.Path, s.Dbname+".db")
+}
+
+func (s *Sqlite) GetLogMode() string {
+	return s.LogMode
+}

+ 8 - 0
internal/config/jwt.go

@@ -0,0 +1,8 @@
+package config
+
+type JWT struct {
+	SigningKey  string `mapstructure:"signing-key" json:"signing-key" yaml:"signing-key"`    // jwt签名
+	ExpiresTime string `mapstructure:"expires-time" json:"expires-time" yaml:"expires-time"` // 过期时间
+	BufferTime  string `mapstructure:"buffer-time" json:"buffer-time" yaml:"buffer-time"`    // 缓冲时间
+	Issuer      string `mapstructure:"issuer" json:"issuer" yaml:"issuer"`                   // 签发者
+}

+ 41 - 0
internal/config/mongo.go

@@ -0,0 +1,41 @@
+package config
+
+import (
+	"fmt"
+	"strings"
+)
+
+type Mongo struct {
+	Coll             string       `json:"coll" yaml:"coll" mapstructure:"coll"`                                           // collection name
+	Options          string       `json:"options" yaml:"options" mapstructure:"options"`                                  // mongodb options
+	Database         string       `json:"database" yaml:"database" mapstructure:"database"`                               // database name
+	Username         string       `json:"username" yaml:"username" mapstructure:"username"`                               // 用户名
+	Password         string       `json:"password" yaml:"password" mapstructure:"password"`                               // 密码
+	AuthSource       string       `json:"auth-source" yaml:"auth-source" mapstructure:"auth-source"`                      // 验证数据库
+	MinPoolSize      uint64       `json:"min-pool-size" yaml:"min-pool-size" mapstructure:"min-pool-size"`                // 最小连接池
+	MaxPoolSize      uint64       `json:"max-pool-size" yaml:"max-pool-size" mapstructure:"max-pool-size"`                // 最大连接池
+	SocketTimeoutMs  int64        `json:"socket-timeout-ms" yaml:"socket-timeout-ms" mapstructure:"socket-timeout-ms"`    // socket超时时间
+	ConnectTimeoutMs int64        `json:"connect-timeout-ms" yaml:"connect-timeout-ms" mapstructure:"connect-timeout-ms"` // 连接超时时间
+	IsZap            bool         `json:"is-zap" yaml:"is-zap" mapstructure:"is-zap"`                                     // 是否开启zap日志
+	Hosts            []*MongoHost `json:"hosts" yaml:"hosts" mapstructure:"hosts"`                                        // 主机列表
+}
+
+type MongoHost struct {
+	Host string `json:"host" yaml:"host" mapstructure:"host"` // ip地址
+	Port string `json:"port" yaml:"port" mapstructure:"port"` // 端口
+}
+
+// Uri .
+func (x *Mongo) Uri() string {
+	length := len(x.Hosts)
+	hosts := make([]string, 0, length)
+	for i := 0; i < length; i++ {
+		if x.Hosts[i].Host != "" && x.Hosts[i].Port != "" {
+			hosts = append(hosts, x.Hosts[i].Host+":"+x.Hosts[i].Port)
+		}
+	}
+	if x.Options != "" {
+		return fmt.Sprintf("mongodb://%s/%s?%s", strings.Join(hosts, ","), x.Database, x.Options)
+	}
+	return fmt.Sprintf("mongodb://%s/%s", strings.Join(hosts, ","), x.Database)
+}

+ 10 - 0
internal/config/oss_aliyun.go

@@ -0,0 +1,10 @@
+package config
+
+type AliyunOSS struct {
+	Endpoint        string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
+	AccessKeyId     string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
+	AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"`
+	BucketName      string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
+	BucketUrl       string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"`
+	BasePath        string `mapstructure:"base-path" json:"base-path" yaml:"base-path"`
+}

+ 13 - 0
internal/config/oss_aws.go

@@ -0,0 +1,13 @@
+package config
+
+type AwsS3 struct {
+	Bucket           string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
+	Region           string `mapstructure:"region" json:"region" yaml:"region"`
+	Endpoint         string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
+	SecretID         string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"`
+	SecretKey        string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
+	BaseURL          string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
+	PathPrefix       string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"`
+	S3ForcePathStyle bool   `mapstructure:"s3-force-path-style" json:"s3-force-path-style" yaml:"s3-force-path-style"`
+	DisableSSL       bool   `mapstructure:"disable-ssl" json:"disable-ssl" yaml:"disable-ssl"`
+}

+ 9 - 0
internal/config/oss_huawei.go

@@ -0,0 +1,9 @@
+package config
+
+type HuaWeiObs struct {
+	Path      string `mapstructure:"path" json:"path" yaml:"path"`
+	Bucket    string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
+	Endpoint  string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
+	AccessKey string `mapstructure:"access-key" json:"access-key" yaml:"access-key"`
+	SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
+}

+ 6 - 0
internal/config/oss_local.go

@@ -0,0 +1,6 @@
+package config
+
+type Local struct {
+	Path      string `mapstructure:"path" json:"path" yaml:"path"`                   // 本地文件访问路径
+	StorePath string `mapstructure:"store-path" json:"store-path" yaml:"store-path"` // 本地文件存储路径
+}

+ 11 - 0
internal/config/oss_qiniu.go

@@ -0,0 +1,11 @@
+package config
+
+type Qiniu struct {
+	Zone          string `mapstructure:"zone" json:"zone" yaml:"zone"`                                  // 存储区域
+	Bucket        string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`                            // 空间名称
+	ImgPath       string `mapstructure:"img-path" json:"img-path" yaml:"img-path"`                      // CDN加速域名
+	AccessKey     string `mapstructure:"access-key" json:"access-key" yaml:"access-key"`                // 秘钥AK
+	SecretKey     string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`                // 秘钥SK
+	UseHTTPS      bool   `mapstructure:"use-https" json:"use-https" yaml:"use-https"`                   // 是否使用https
+	UseCdnDomains bool   `mapstructure:"use-cdn-domains" json:"use-cdn-domains" yaml:"use-cdn-domains"` // 上传是否使用CDN上传加速
+}

+ 10 - 0
internal/config/oss_tencent.go

@@ -0,0 +1,10 @@
+package config
+
+type TencentCOS struct {
+	Bucket     string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
+	Region     string `mapstructure:"region" json:"region" yaml:"region"`
+	SecretID   string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"`
+	SecretKey  string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
+	BaseURL    string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
+	PathPrefix string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"`
+}

+ 9 - 0
internal/config/redis.go

@@ -0,0 +1,9 @@
+package config
+
+type Redis struct {
+	Addr         string   `mapstructure:"addr" json:"addr" yaml:"addr"`                         // 服务器地址:端口
+	Password     string   `mapstructure:"password" json:"password" yaml:"password"`             // 密码
+	DB           int      `mapstructure:"db" json:"db" yaml:"db"`                               // 单实例模式下redis的哪个数据库
+	UseCluster   bool     `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"`       // 是否使用集群模式
+	ClusterAddrs []string `mapstructure:"clusterAddrs" json:"clusterAddrs" yaml:"clusterAddrs"` // 集群模式下的节点地址列表
+}

+ 13 - 0
internal/config/system.go

@@ -0,0 +1,13 @@
+package config
+
+type System struct {
+	DbType        string `mapstructure:"db-type" json:"db-type" yaml:"db-type"`    // 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql
+	OssType       string `mapstructure:"oss-type" json:"oss-type" yaml:"oss-type"` // Oss类型
+	RouterPrefix  string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"`
+	Addr          int    `mapstructure:"addr" json:"addr" yaml:"addr"` // 端口值
+	LimitCountIP  int    `mapstructure:"iplimit-count" json:"iplimit-count" yaml:"iplimit-count"`
+	LimitTimeIP   int    `mapstructure:"iplimit-time" json:"iplimit-time" yaml:"iplimit-time"`
+	UseMultipoint bool   `mapstructure:"use-multipoint" json:"use-multipoint" yaml:"use-multipoint"` // 多点登录拦截
+	UseRedis      bool   `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"`                // 使用redis
+	UseMongo      bool   `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"`                // 使用mongo
+}

+ 60 - 0
internal/config/zap.go

@@ -0,0 +1,60 @@
+package config
+
+import (
+	"go.uber.org/zap/zapcore"
+	"strings"
+)
+
+type Zap struct {
+	Level         string `mapstructure:"level" json:"level" yaml:"level"`                            // 级别
+	Prefix        string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`                         // 日志前缀
+	Format        string `mapstructure:"format" json:"format" yaml:"format"`                         // 输出
+	Director      string `mapstructure:"director" json:"director"  yaml:"director"`                  // 日志文件夹
+	EncodeLevel   string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"`       // 编码级
+	StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` // 栈名
+
+	MaxAge       int  `mapstructure:"max-age" json:"max-age" yaml:"max-age"`                      // 日志留存时间
+	ShowLine     bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"`                // 显示行
+	LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` // 输出控制台
+}
+
+// ZapEncodeLevel 根据 EncodeLevel 返回 zapcore.LevelEncoder
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *Zap) ZapEncodeLevel() zapcore.LevelEncoder {
+	switch {
+	case z.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
+		return zapcore.LowercaseLevelEncoder
+	case z.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
+		return zapcore.LowercaseColorLevelEncoder
+	case z.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
+		return zapcore.CapitalLevelEncoder
+	case z.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
+		return zapcore.CapitalColorLevelEncoder
+	default:
+		return zapcore.LowercaseLevelEncoder
+	}
+}
+
+// TransportLevel 根据字符串转化为 zapcore.Level
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *Zap) TransportLevel() zapcore.Level {
+	z.Level = strings.ToLower(z.Level)
+	switch z.Level {
+	case "debug":
+		return zapcore.DebugLevel
+	case "info":
+		return zapcore.InfoLevel
+	case "warn":
+		return zapcore.WarnLevel
+	case "error":
+		return zapcore.ErrorLevel
+	case "dpanic":
+		return zapcore.DPanicLevel
+	case "panic":
+		return zapcore.PanicLevel
+	case "fatal":
+		return zapcore.FatalLevel
+	default:
+		return zapcore.DebugLevel
+	}
+}

+ 9 - 0
internal/core/internal/constant.go

@@ -0,0 +1,9 @@
+package internal
+
+const (
+	ConfigEnv         = "GVA_CONFIG"
+	ConfigDefaultFile = "config.yaml"
+	ConfigTestFile    = "config.test.yaml"
+	ConfigDebugFile   = "config.debug.yaml"
+	ConfigReleaseFile = "config.release.yaml"
+)

+ 97 - 0
internal/core/internal/cutter.go

@@ -0,0 +1,97 @@
+package internal
+
+import (
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+)
+
+type Cutter struct {
+	level    string        // 日志级别(debug, info, warn, error, dpanic, panic, fatal)
+	format   string        // 时间格式(2006-01-02)
+	Director string        // 日志文件夹
+	file     *os.File      // 文件句柄
+	mutex    *sync.RWMutex // 读写锁
+}
+
+type CutterOption func(*Cutter)
+
+// WithCutterFormat 设置时间格式
+func WithCutterFormat(format string) CutterOption {
+	return func(c *Cutter) {
+		c.format = format
+	}
+}
+
+func NewCutter(director string, level string, options ...CutterOption) *Cutter {
+	rotate := &Cutter{
+		level:    level,
+		Director: director,
+		mutex:    new(sync.RWMutex),
+	}
+	for i := 0; i < len(options); i++ {
+		options[i](rotate)
+	}
+	return rotate
+}
+
+// Write satisfies the io.Writer interface. It writes to the
+// appropriate file handle that is currently being used.
+// If we have reached rotation time, the target file gets
+// automatically rotated, and also purged if necessary.
+func (c *Cutter) Write(bytes []byte) (n int, err error) {
+	c.mutex.Lock()
+	defer func() {
+		if c.file != nil {
+			_ = c.file.Close()
+			c.file = nil
+		}
+		c.mutex.Unlock()
+	}()
+	var business string
+	if strings.Contains(string(bytes), "business") {
+		var compile *regexp.Regexp
+		compile, err = regexp.Compile(`{"business": "([^,]+)"}`)
+		if err != nil {
+			return 0, err
+		}
+		if compile.Match(bytes) {
+			finds := compile.FindSubmatch(bytes)
+			business = string(finds[len(finds)-1])
+			bytes = compile.ReplaceAll(bytes, []byte(""))
+		}
+		compile, err = regexp.Compile(`"business": "([^,]+)"`)
+		if err != nil {
+			return 0, err
+		}
+		if compile.Match(bytes) {
+			finds := compile.FindSubmatch(bytes)
+			business = string(finds[len(finds)-1])
+			bytes = compile.ReplaceAll(bytes, []byte(""))
+		}
+	}
+	format := time.Now().Format(c.format)
+	formats := make([]string, 0, 4)
+	formats = append(formats, c.Director)
+	if format != "" {
+		formats = append(formats, format)
+	}
+	if business != "" {
+		formats = append(formats, business)
+	}
+	formats = append(formats, c.level+".log")
+	filename := filepath.Join(formats...)
+	dirname := filepath.Dir(filename)
+	err = os.MkdirAll(dirname, 0755)
+	if err != nil {
+		return 0, err
+	}
+	c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+	if err != nil {
+		return 0, err
+	}
+	return c.file.Write(bytes)
+}

+ 21 - 0
internal/core/internal/file_rotatelogs.go

@@ -0,0 +1,21 @@
+package internal
+
+import (
+	"go.uber.org/zap/zapcore"
+	"os"
+	"wails-app/internal/global"
+)
+
+var FileRotatelogs = new(fileRotatelogs)
+
+type fileRotatelogs struct{}
+
+// GetWriteSyncer 获取 zapcore.WriteSyncer
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (r *fileRotatelogs) GetWriteSyncer(level string) zapcore.WriteSyncer {
+	fileWriter := NewCutter(global.GVA_CONFIG.Zap.Director, level, WithCutterFormat("2006-01-02"))
+	if global.GVA_CONFIG.Zap.LogInConsole {
+		return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter))
+	}
+	return zapcore.AddSync(fileWriter)
+}

+ 101 - 0
internal/core/internal/zap.go

@@ -0,0 +1,101 @@
+package internal
+
+import (
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"wails-app/internal/global"
+	"time"
+)
+
+var Zap = new(_zap)
+
+type _zap struct{}
+
+// GetEncoder 获取 zapcore.Encoder
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetEncoder() zapcore.Encoder {
+	if global.GVA_CONFIG.Zap.Format == "json" {
+		return zapcore.NewJSONEncoder(z.GetEncoderConfig())
+	}
+	return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
+}
+
+// GetEncoderConfig 获取zapcore.EncoderConfig
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
+	return zapcore.EncoderConfig{
+		MessageKey:     "message",
+		LevelKey:       "level",
+		TimeKey:        "time",
+		NameKey:        "logger",
+		CallerKey:      "caller",
+		StacktraceKey:  global.GVA_CONFIG.Zap.StacktraceKey,
+		LineEnding:     zapcore.DefaultLineEnding,
+		EncodeLevel:    global.GVA_CONFIG.Zap.ZapEncodeLevel(),
+		EncodeTime:     z.CustomTimeEncoder,
+		EncodeDuration: zapcore.SecondsDurationEncoder,
+		EncodeCaller:   zapcore.FullCallerEncoder,
+	}
+}
+
+// GetEncoderCore 获取Encoder的 zapcore.Core
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
+	writer := FileRotatelogs.GetWriteSyncer(l.String()) // 日志分割
+	return zapcore.NewCore(z.GetEncoder(), writer, level)
+}
+
+// CustomTimeEncoder 自定义日志输出时间格式
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
+	encoder.AppendString(global.GVA_CONFIG.Zap.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
+}
+
+// GetZapCores 根据配置文件的Level获取 []zapcore.Core
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetZapCores() []zapcore.Core {
+	cores := make([]zapcore.Core, 0, 7)
+	for level := global.GVA_CONFIG.Zap.TransportLevel(); level <= zapcore.FatalLevel; level++ {
+		cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
+	}
+	return cores
+}
+
+// GetLevelPriority 根据 zapcore.Level 获取 zap.LevelEnablerFunc
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
+	switch level {
+	case zapcore.DebugLevel:
+		return func(level zapcore.Level) bool { // 调试级别
+			return level == zap.DebugLevel
+		}
+	case zapcore.InfoLevel:
+		return func(level zapcore.Level) bool { // 日志级别
+			return level == zap.InfoLevel
+		}
+	case zapcore.WarnLevel:
+		return func(level zapcore.Level) bool { // 警告级别
+			return level == zap.WarnLevel
+		}
+	case zapcore.ErrorLevel:
+		return func(level zapcore.Level) bool { // 错误级别
+			return level == zap.ErrorLevel
+		}
+	case zapcore.DPanicLevel:
+		return func(level zapcore.Level) bool { // dpanic级别
+			return level == zap.DPanicLevel
+		}
+	case zapcore.PanicLevel:
+		return func(level zapcore.Level) bool { // panic级别
+			return level == zap.PanicLevel
+		}
+	case zapcore.FatalLevel:
+		return func(level zapcore.Level) bool { // 终止级别
+			return level == zap.FatalLevel
+		}
+	default:
+		return func(level zapcore.Level) bool { // 调试级别
+			return level == zap.DebugLevel
+		}
+	}
+}

+ 40 - 0
internal/core/server.go

@@ -0,0 +1,40 @@
+package core
+
+import (
+	"fmt"
+	"go.uber.org/zap"
+	"wails-app/internal/global"
+	"wails-app/internal/initialize"
+	"wails-app/internal/service/system"
+)
+
+type server interface {
+	ListenAndServe() error
+}
+
+func RunWindowsServer() {
+	if global.GVA_CONFIG.System.UseMultipoint || global.GVA_CONFIG.System.UseRedis {
+		// 初始化redis服务
+		initialize.Redis()
+	}
+	if global.GVA_CONFIG.System.UseMongo {
+		err := initialize.Mongo.Initialization()
+		if err != nil {
+			zap.L().Error(fmt.Sprintf("%+v", err))
+		}
+	}
+	// 从db加载jwt数据
+	if global.GVA_DB != nil {
+		system.LoadAll()
+	}
+
+	Router := initialize.Routers()
+	Router.Static("/form-generator", "./resource/page")
+
+	address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr)
+	s := initServer(address, Router)
+
+	global.GVA_LOG.Info("server run success on ", zap.String("address", address))
+	fmt.Println("龙弛")
+	global.GVA_LOG.Error(s.ListenAndServe().Error())
+}

+ 19 - 0
internal/core/server_other.go

@@ -0,0 +1,19 @@
+//go:build !windows
+// +build !windows
+
+package core
+
+import (
+	"time"
+
+	"github.com/fvbock/endless"
+	"github.com/gin-gonic/gin"
+)
+
+func initServer(address string, router *gin.Engine) server {
+	s := endless.NewServer(address, router)
+	s.ReadHeaderTimeout = 20 * time.Second
+	s.WriteTimeout = 20 * time.Second
+	s.MaxHeaderBytes = 1 << 20
+	return s
+}

+ 21 - 0
internal/core/server_win.go

@@ -0,0 +1,21 @@
+//go:build windows
+// +build windows
+
+package core
+
+import (
+	"net/http"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+func initServer(address string, router *gin.Engine) server {
+	return &http.Server{
+		Addr:           address,
+		Handler:        router,
+		ReadTimeout:    20 * time.Second,
+		WriteTimeout:   20 * time.Second,
+		MaxHeaderBytes: 1 << 20,
+	}
+}

+ 84 - 0
internal/core/viper.go

@@ -0,0 +1,84 @@
+package core
+
+import (
+	"flag"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"os"
+	"path/filepath"
+	"wails-app/internal/core/internal"
+
+	"github.com/fsnotify/fsnotify"
+	"github.com/spf13/viper"
+
+	"wails-app/internal/global"
+	_ "wails-app/internal/packfile"
+)
+
+// Viper //
+// 优先级: 命令行 > 环境变量 > 默认值
+// Author [SliverHorn](https://github.com/SliverHorn)
+func Viper(path ...string) *viper.Viper {
+	var config string
+
+	if len(path) == 0 {
+		flag.StringVar(&config, "c", "", "choose config file.")
+		flag.Parse()
+		if config == "" { // 判断命令行参数是否为空
+			if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空
+				switch gin.Mode() {
+				case gin.DebugMode:
+					config = internal.ConfigDefaultFile
+					fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigDefaultFile)
+				case gin.ReleaseMode:
+					config = internal.ConfigReleaseFile
+					fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigReleaseFile)
+				case gin.TestMode:
+					config = internal.ConfigTestFile
+					fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigTestFile)
+				}
+			} else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config
+				config = configEnv
+				fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", internal.ConfigEnv, config)
+			}
+		} else { // 命令行参数不为空 将值赋值于config
+			fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config)
+		}
+	} else { // 函数传递的可变参数的第一个值赋值于config
+		config = path[0]
+		fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config)
+	}
+
+	v := viper.New()
+	v.SetConfigFile(config)
+	v.SetConfigType("yaml")
+	err := v.ReadInConfig()
+	// If config.yaml not found in CWD, try next to the executable
+	if err != nil {
+		if exePath, exeErr := os.Executable(); exeErr == nil {
+			exeDir := filepath.Dir(exePath)
+			fallbackConfig := filepath.Join(exeDir, config)
+			if _, statErr := os.Stat(fallbackConfig); statErr == nil {
+				fmt.Printf("使用可执行文件目录的config: %s\n", fallbackConfig)
+				v.SetConfigFile(fallbackConfig)
+				err = v.ReadInConfig()
+			}
+		}
+	}
+	if err != nil {
+		panic(fmt.Errorf("Fatal error config file: %s \n", err))
+	}
+	v.WatchConfig()
+
+	v.OnConfigChange(func(e fsnotify.Event) {
+		fmt.Println("config file changed:", e.Name)
+		if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
+			fmt.Println(err)
+		}
+	})
+	if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
+		panic(err)
+	}
+
+	return v
+}

+ 28 - 0
internal/core/zap.go

@@ -0,0 +1,28 @@
+package core
+
+import (
+	"fmt"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"os"
+	"wails-app/internal/core/internal"
+	"wails-app/internal/global"
+	"wails-app/internal/utils"
+)
+
+// Zap 获取 zap.Logger
+// Author [SliverHorn](https://github.com/SliverHorn)
+func Zap() (logger *zap.Logger) {
+	if ok, _ := utils.PathExists(global.GVA_CONFIG.Zap.Director); !ok { // 判断是否有Director文件夹
+		fmt.Printf("create %v directory\n", global.GVA_CONFIG.Zap.Director)
+		_ = os.Mkdir(global.GVA_CONFIG.Zap.Director, os.ModePerm)
+	}
+
+	cores := internal.Zap.GetZapCores()
+	logger = zap.New(zapcore.NewTee(cores...))
+
+	if global.GVA_CONFIG.Zap.ShowLine {
+		logger = logger.WithOptions(zap.AddCaller())
+	}
+	return logger
+}

+ 19 - 0
internal/dao/booth.go

@@ -0,0 +1,19 @@
+package dao
+
+import "wails-app/internal/global"
+
+// Booth 岗亭模型
+type Booth struct {
+	global.GVA_MODEL
+	BoothCode    string      `gorm:"size:50;not null;uniqueIndex" json:"booth_code"` // 岗亭编码
+	BoothName    string      `gorm:"size:100;not null" json:"booth_name"`            // 岗亭名称
+	IPAddress    string      `gorm:"size:50" json:"ip_address"`                      // IP地址
+	Description  string      `gorm:"size:200" json:"description"`                    // 备注
+	ParkingLotID uint        `gorm:"index;comment:所属区域ID" json:"parking_lot_id"`     // 所属区域ID
+	ParkingLot   *ParkingLot `gorm:"foreignkey:parking_lot_id" json:"parking_lot"`
+	Channels     []Channel   `gorm:"foreignKey:BoothID" json:"channels"`
+}
+
+func (Booth) TableName() string {
+	return "booth"
+}

+ 22 - 0
internal/dao/camera.go

@@ -0,0 +1,22 @@
+package dao
+
+import "wails-app/internal/global"
+
+// Camera 摄像头模型
+type Camera struct {
+	global.GVA_MODEL
+	DeviceCode  string `gorm:"size:50;not null;uniqueIndex" json:"device_code"` // 设备编码
+	DeviceName  string `gorm:"size:100;not null" json:"device_name"`            // 设备名称
+	IPAddress   string `gorm:"size:50;not null" json:"ip_address"`              // IP地址
+	Port        int    `gorm:"not null" json:"port"`                            // 端口
+	Username    string `gorm:"size:50" json:"username"`                         // 用户名
+	Password    string `gorm:"size:50" json:"password"`                         // 密码
+	Channel     int    `gorm:"default:1" json:"channel"`                        // 通道号
+	Status      string `gorm:"size:20;default:'offline'" json:"status"`         // 连接状态
+	IsActive    bool   `gorm:"default:true" json:"is_active"`                   // 是否启用
+	Description string `gorm:"size:200" json:"description"`                     // 备注
+}
+
+func (Camera) TableName() string {
+	return "camera"
+}

+ 23 - 0
internal/dao/channel.go

@@ -0,0 +1,23 @@
+package dao
+
+import "wails-app/internal/global"
+
+// Channel 通道模型
+type Channel struct {
+	global.GVA_MODEL
+	ChannelCode    string `gorm:"size:50;not null;uniqueIndex" json:"channel_code"` // 通道编码
+	ChannelName    string `gorm:"size:100;not null" json:"channel_name"`            // 通道名称
+	Direction      string `gorm:"size:20;not null" json:"direction"`                // 进出方向 in/out/inout
+	AllowTemporary bool   `gorm:"not null" json:"allow_temporary"`                  // 是否允许临时车进出
+	Description    string `gorm:"size:200" json:"description"`                      // 备注
+	ParkingLotID   uint   `json:"parking_lot_id"`                                   // 所属区域ID
+
+	BoothID uint   `gorm:"index;comment:所属岗亭ID" json:"booth_id"`
+	Booth   *Booth `gorm:"foreignKey:BoothID;references:ID" json:"booth"`
+
+	UHFReaders []UHFReader `gorm:"foreignKey:ChannelID" json:"uhf_reader"`
+}
+
+func (Channel) TableName() string {
+	return "channel"
+}

+ 62 - 0
internal/dao/exa_file_upload_download.go

@@ -0,0 +1,62 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+)
+
+type ExaFileUploadAndDownload struct {
+	global.GVA_MODEL
+	Name string `json:"name" gorm:"comment:文件名"` // 文件名
+	Url  string `json:"url" gorm:"comment:文件地址"` // 文件地址
+	Tag  string `json:"tag" gorm:"comment:文件标签"` // 文件标签
+	Key  string `json:"key" gorm:"comment:编号"`   // 编号
+}
+
+func (ExaFileUploadAndDownload) TableName() string {
+	return "exa_file_upload_and_downloads"
+}
+
+// TODO:文件查询
+
+// QueryFileById 查询文件 按id
+func QueryFileById(id uint) (exaFileUploadAndDownload ExaFileUploadAndDownload, err error) {
+	err = global.GVA_DB.Where("id = ?", id).First(&exaFileUploadAndDownload).Error
+	return exaFileUploadAndDownload, err
+}
+
+// GetFileRecordInfoList 分页获取数据
+func GetFileRecordInfoList(limit, offset int, keyword string) (fileLists []ExaFileUploadAndDownload, total int64, err error) {
+
+	db := global.GVA_DB.Model(&ExaFileUploadAndDownload{})
+	if len(keyword) > 0 {
+		db = db.Where("name LIKE ?", "%"+keyword+"%")
+	}
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Order("updated_at desc").Find(&fileLists).Error
+	return fileLists, total, err
+}
+
+// TODO:文件新增
+
+// CreateFile 创建文件
+func (e ExaFileUploadAndDownload) CreateFile() error {
+	return global.GVA_DB.Create(&e).Error
+}
+
+// TODO:文件修改
+
+// EditFileName 编辑文件名或者备注
+func (e ExaFileUploadAndDownload) EditFileName() (fileFromDb ExaFileUploadAndDownload, err error) {
+	err = global.GVA_DB.Where("id = ?", e.ID).First(&fileFromDb).Update("name", e.Name).Error
+	return
+}
+
+// TODO:文件删除
+
+// DeleteFile 删除文件
+func (e ExaFileUploadAndDownload) DeleteFile() error {
+	return global.GVA_DB.Where("id = ?", e.ID).Unscoped().Delete(&e).Error
+}

+ 21 - 0
internal/dao/fee_config.go

@@ -0,0 +1,21 @@
+package dao
+
+import "wails-app/internal/global"
+
+type FeeConfig struct {
+	global.GVA_MODEL
+	VehicleTypeID uint         `gorm:"not null;uniqueIndex" json:"vehicle_type_id"`  // 车辆类型ID
+	VehicleType   *VehicleType `gorm:"foreignKey:VehicleTypeID" json:"vehicle_type"` // 车辆类型关联
+	StartTime     int          `json:"start_time"`                                   // 起始时间(分钟)
+	StartFee      float64      `json:"start_fee"`                                    // 起始费用
+	UnitTime      int          `json:"unit_time"`                                    // 单位时间(分钟)
+	UnitFee       float64      `json:"unit_fee"`                                     // 单位费用
+	DailyMaxFee   float64      `json:"daily_max_fee"`                                // 每日最高费用
+	IsVIPFree     bool         `gorm:"default:false" json:"is_vip_free"`             // VIP是否免费
+	VIPDiscount   float64      `gorm:"default:1.0" json:"vip_discount"`              // VIP折扣
+	Description   string       `gorm:"size:200" json:"description"`                  // 描述
+}
+
+func (FeeConfig) TableName() string {
+	return "fee_config"
+}

+ 20 - 0
internal/dao/owner.go

@@ -0,0 +1,20 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+	"time"
+)
+
+type Owner struct {
+	global.GVA_MODEL
+	OwnerName     string    `gorm:"size:50;" json:"owner_name"`
+	OwnerSurname  string    `gorm:"size:50;" json:"owner_surname"`
+	OwnerPhone    string    `gorm:"size:20;" json:"owner_phone"`
+	Vehicles      []Vehicle `gorm:"foreignKey:OwnerId" json:"vehicles"`
+	IsVip         bool      `gorm:"size:1;" json:"is_vip"`
+	VipExpireTime time.Time `json:"vip_expire_time"  gorm:"default:null"`
+}
+
+func (Owner) TableName() string {
+	return "owner"
+}

+ 20 - 0
internal/dao/parking_lot.go

@@ -0,0 +1,20 @@
+package dao
+
+import "wails-app/internal/global"
+
+// ParkingLot 停车区域
+type ParkingLot struct {
+	global.GVA_MODEL
+	LotCode     string      `gorm:"size:50;not null;uniqueIndex" json:"lot_code"` // 区域编码
+	LotName     string      `gorm:"size:100;not null" json:"lot_name"`            // 区域名称
+	Capacity    int         `gorm:"not null" json:"capacity"`                     // 车位数量
+	Available   int         `gorm:"not null" json:"available"`                    // 可用车位
+	Description string      `gorm:"size:200" json:"description"`                  // 备注
+	Booths      []Booth     `gorm:"foreignKey:ParkingLotID" json:"booths"`        // 岗亭列表(一对多)
+	Channels    []Channel   `gorm:"foreignKey:ParkingLotID" json:"channels"`      // 通道列表(一对多)
+	UHFReaders  []UHFReader `gorm:"foreignKey:ParkingLotID" json:"uhf_readers"`   // UHF读写器列表(一对多)
+}
+
+func (ParkingLot) TableName() string {
+	return "parking_lot"
+}

+ 75 - 0
internal/dao/shortlist.go

@@ -0,0 +1,75 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+	"time"
+)
+
+type Shortlist struct {
+	global.GVA_MODEL
+	ListType       string     `json:"list_type" gorm:"comment:名单类型"`
+	VehicleId      int        `json:"vehicle_id" gorm:"comment:车辆ID"`
+	Vehicle        *Vehicle   `json:"vehicle" gorm:"foreignKey:VehicleId"`
+	ExpirationTime *time.Time `json:"expiration_time" gorm:"comment:过期时间;default:null"`
+}
+
+func (Shortlist) TableName() string {
+	return "shortlist"
+}
+
+func QueryShortlistList(limit, offset int, listType, plateNumber, rfidTag string) (list []Shortlist, total int64, err error) {
+	db := global.GVA_DB.Model(&Shortlist{}).Preload("Vehicle").Preload("Vehicle.VehicleType") // 预加载车辆信息
+
+	// 1. 按名单类型筛选:black / white
+	if listType != "" {
+		db = db.Where("list_type = ?", listType)
+	}
+
+	// 2. 车牌号模糊查询
+	if plateNumber != "" {
+		db = db.Joins("Vehicle").Where("Vehicle.plate_number LIKE ?", "%"+plateNumber+"%")
+	}
+
+	// 3. RFID标签精确查询
+	if rfidTag != "" {
+		db = db.Joins("Vehicle").Where("Vehicle.rfid_tag = ?", rfidTag)
+	}
+
+	// 查询总数
+	err = db.Count(&total).Error
+	if err != nil {
+		return nil, 0, err
+	}
+
+	// 查询列表
+	err = db.Limit(limit).Offset(offset).Find(&list).Error
+	return list, total, err
+}
+
+func QueryAllShortlists() (list []Shortlist, err error) {
+	err = global.GVA_DB.Model(&Shortlist{}).Find(&list).Error
+
+	return list, err
+}
+
+func QueryShortlistByRFIDTag(rfidTag string) (shortlist Shortlist, err error) {
+	err = global.GVA_DB.Model(&Shortlist{}).
+		Preload("Vehicle").
+		Joins("LEFT JOIN vehicle ON shortlist.vehicle_id = vehicle.id").
+		Where("vehicle.rfid_tag = ?", rfidTag).
+		First(&shortlist).Error
+
+	return
+}
+
+func CreateShortlist(shortlist Shortlist) error {
+	return global.GVA_DB.Create(&shortlist).Error
+}
+
+func UpdateShortlist(shortlist Shortlist) error {
+	return global.GVA_DB.Where("id = ?", shortlist.ID).Updates(shortlist).Error
+}
+
+func DeleteShortlist(id int) error {
+	return global.GVA_DB.Where("id = ?", id).Unscoped().Delete(&Shortlist{}).Error
+}

+ 113 - 0
internal/dao/sys_api.go

@@ -0,0 +1,113 @@
+package dao
+
+import (
+	"fmt"
+	"wails-app/internal/global"
+)
+
+type SysApi struct {
+	global.GVA_MODEL
+	Path        string `json:"path" gorm:"comment:api路径"`             // api路径
+	Description string `json:"description" gorm:"comment:api中文描述"`    // api中文描述
+	ApiGroup    string `json:"apiGroup" gorm:"comment:api组"`          // api组
+	Method      string `json:"method" gorm:"default:POST;comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE
+}
+
+func (SysApi) TableName() string {
+	return "sys_apis"
+}
+
+// TODO:Api查询
+
+// QueryApiByPathMethod 查询api 按路径和方法
+func QueryApiByPathMethod(path, method string) (sysApi SysApi, err error) {
+	err = global.GVA_DB.Where("path = ? AND method = ?", path, method).First(&sysApi).Error
+	return
+}
+
+// QueryApiById 查询api 按id
+func QueryApiById(id uint) (sysApi SysApi, err error) {
+	err = global.GVA_DB.First(&sysApi, "id = ?", id).Error
+	return
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetAPIInfoList
+//@description: 分页获取数据,
+//@param: api model.SysApi, info request.PageInfo, order string, desc bool
+//@return: list interface{}, total int64, err error
+
+func (api SysApi) GetAPIInfoList(limit, offset int, order string, desc bool) (apiList []SysApi, total int64, err error) {
+
+	db := global.GVA_DB.Model(&SysApi{})
+
+	if api.Path != "" {
+		db = db.Where("path LIKE ?", "%"+api.Path+"%")
+	}
+
+	if api.Description != "" {
+		db = db.Where("description LIKE ?", "%"+api.Description+"%")
+	}
+
+	if api.Method != "" {
+		db = db.Where("method = ?", api.Method)
+	}
+
+	if api.ApiGroup != "" {
+		db = db.Where("api_group = ?", api.ApiGroup)
+	}
+
+	err = db.Count(&total).Error
+
+	if err != nil {
+		return apiList, total, err
+	}
+
+	db = db.Limit(limit).Offset(offset)
+	OrderStr := "id desc"
+	if order != "" {
+		orderMap := make(map[string]bool, 5)
+		orderMap["id"] = true
+		orderMap["path"] = true
+		orderMap["api_group"] = true
+		orderMap["description"] = true
+		orderMap["method"] = true
+		if !orderMap[order] {
+			err = fmt.Errorf("非法的排序字段: %v", order)
+			return apiList, total, err
+		}
+		OrderStr = order
+		if desc {
+			OrderStr = order + " desc"
+		}
+	}
+	err = db.Order(OrderStr).Find(&apiList).Error
+	return apiList, total, err
+}
+
+// GetAllApis 查询所有api
+func GetAllApis() (apis []SysApi, err error) {
+	err = global.GVA_DB.Find(&apis).Error
+	return
+}
+
+// TODO:Api新增
+
+// CreateApi 新增api
+func (api SysApi) CreateApi() error {
+	return global.GVA_DB.Create(&api).Error
+}
+
+// TODO:Api修改
+
+// SaveApi 更新api
+func (api SysApi) SaveApi() error {
+	return global.GVA_DB.Save(&api).Error
+}
+
+// TODO:Api删除
+
+// DeleteApi 删除api
+func (api SysApi) DeleteApi() error {
+	return global.GVA_DB.Delete(&api).Error
+}

+ 60 - 0
internal/dao/sys_authority.go

@@ -0,0 +1,60 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+	"time"
+)
+
+type SysAuthority struct {
+	CreatedAt       time.Time       // 创建时间
+	UpdatedAt       time.Time       // 更新时间
+	DeletedAt       *time.Time      `sql:"index"`
+	AuthorityId     uint            `json:"authorityId" gorm:"not null;unique;primary_key;comment:角色ID;size:90"` // 角色ID
+	AuthorityName   string          `json:"authorityName" gorm:"comment:角色名"`                                    // 角色名
+	ParentId        *uint           `json:"parentId" gorm:"comment:父角色ID"`                                       // 父角色ID
+	DataAuthorityId []*SysAuthority `json:"dataAuthorityId" gorm:"many2many:sys_data_authority_id;"`
+	Children        []SysAuthority  `json:"children" gorm:"-"`
+	SysBaseMenus    []SysBaseMenu   `json:"menus" gorm:"many2many:sys_authority_menus;"`
+	Users           []SysUser       `json:"-" gorm:"many2many:sys_user_authority;"`
+	DefaultRouter   string          `json:"defaultRouter" gorm:"comment:默认菜单;default:dashboard"` // 默认菜单(默认dashboard)
+}
+
+func (SysAuthority) TableName() string {
+	return "sys_authorities"
+}
+
+// TODO:角色查询
+
+// QueryAuthorityById 查询角色 按id
+func QueryAuthorityById(authorityId uint) (sysAuthority SysAuthority, err error) {
+	err = global.GVA_DB.Where("authority_id = ?", authorityId).First(&sysAuthority).Error
+	return
+}
+
+// GetAuthorityInfoList 分页获取角色列表
+func GetAuthorityInfoList(limit, offset int) (sysAuthority []SysAuthority, total int64, err error) {
+	db := global.GVA_DB.Model(&SysAuthority{})
+	if err = db.Where("parent_id = ?", "0").Count(&total).Error; total == 0 || err != nil {
+		return
+	}
+	var authority []SysAuthority
+	err = db.Limit(limit).Offset(offset).Preload("DataAuthorityId").Where("parent_id = ?", "0").Find(&authority).Error
+
+	return authority, total, err
+}
+
+// GetAuthorityInfo 获取所有角色信息
+func (sa SysAuthority) GetAuthorityInfo() (sysAuthority SysAuthority, err error) {
+	err = global.GVA_DB.Preload("DataAuthorityId").Where("authority_id = ?", sa.AuthorityId).First(&sysAuthority).Error
+	return sysAuthority, err
+}
+
+// TODO:角色新增
+// TODO:角色修改
+
+// UpdateAuthority 修改角色
+func (sa SysAuthority) UpdateAuthority() error {
+	return global.GVA_DB.Model(&SysAuthority{}).Updates(&sa).Error
+}
+
+// TODO:角色删除

+ 62 - 0
internal/dao/sys_authority_btn.go

@@ -0,0 +1,62 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"wails-app/internal/global"
+)
+
+type SysAuthorityBtn struct {
+	AuthorityId      uint           `gorm:"comment:角色ID"`
+	SysMenuID        uint           `gorm:"comment:菜单ID"`
+	SysBaseMenuBtnID uint           `gorm:"comment:菜单按钮ID"`
+	SysBaseMenuBtn   SysBaseMenuBtn ` gorm:"comment:按钮详情"`
+}
+
+// TODO:按钮查询
+
+// QueryAuthorityBtnAndSysBaseMenuBtnById 查询角色按钮和菜单按钮 按id
+func QueryAuthorityBtnAndSysBaseMenuBtnById(authorityId uint) (btns []SysAuthorityBtn, err error) {
+	err = global.GVA_DB.Where("authority_id = ?", authorityId).Preload("SysBaseMenuBtn").Find(&btns).Error
+	return btns, err
+}
+
+// QueryAuthorityBtnByAuthorityIdMenuId 查询角色按钮 按角色id和菜单id
+func QueryAuthorityBtnByAuthorityIdMenuId(authorityId, sysMenuId uint) (authorityBtn []SysAuthorityBtn, err error) {
+	err = global.GVA_DB.Find(&authorityBtn, "authority_id = ? and sys_menu_id = ?", authorityId, sysMenuId).Error
+	return
+}
+
+// QueryAuthorityBtn 查询角色按钮 按菜单按钮id global.GVA_DB.First(&dao.SysAuthorityBtn{}, "sys_base_menu_btn_id = ?", ID).Error
+func QueryAuthorityBtn(Id string) (sysAuthorityBtn SysAuthorityBtn, err error) {
+	err = global.GVA_DB.Model(&SysAuthorityBtn{}).First(sysAuthorityBtn, "sys_base_menu_btn_id =?", Id).Error
+	return
+}
+
+// TODO:按钮新增
+// TODO:按钮修改
+
+func SetAuthorityBtn(menuId, authorityId uint, selected []uint) (err error) {
+	return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		var authorityBtn []SysAuthorityBtn
+		err = tx.Delete(&[]SysAuthorityBtn{}, "authority_id = ? and sys_menu_id = ?", authorityId, menuId).Error
+		if err != nil {
+			return err
+		}
+		for _, v := range selected {
+			authorityBtn = append(authorityBtn, SysAuthorityBtn{
+				AuthorityId:      authorityId,
+				SysMenuID:        menuId,
+				SysBaseMenuBtnID: v,
+			})
+		}
+		if len(authorityBtn) > 0 {
+			err = tx.Create(&authorityBtn).Error
+		}
+		if err != nil {
+			return err
+		}
+		return err
+	})
+}
+
+// TODO:按钮删除

+ 39 - 0
internal/dao/sys_authority_menu.go

@@ -0,0 +1,39 @@
+package dao
+
+import "wails-app/internal/global"
+
+type SysMenu struct {
+	SysBaseMenu
+	MenuId      uint                   `json:"menuId" gorm:"comment:菜单ID"`
+	AuthorityId uint                   `json:"-" gorm:"comment:角色ID"`
+	Children    []SysMenu              `json:"children" gorm:"-"`
+	Parameters  []SysBaseMenuParameter `json:"parameters" gorm:"foreignKey:SysBaseMenuID;references:MenuId"`
+	Btns        map[string]uint        `json:"btns" gorm:"-"`
+}
+
+type SysAuthorityMenu struct {
+	MenuId      string `json:"menuId" gorm:"comment:菜单ID;column:sys_base_menu_id"`
+	AuthorityId string `json:"-" gorm:"comment:角色ID;column:sys_authority_authority_id"`
+}
+
+func (s SysAuthorityMenu) TableName() string {
+	return "sys_authority_menus"
+}
+
+// TODO:菜单查询
+
+// QueryAuthorityMenuByAuthorityId 查询角色菜单 按角色id
+func QueryAuthorityMenuByAuthorityId(authorityId uint) (SysAuthorityMenus []SysAuthorityMenu, err error) {
+	err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&SysAuthorityMenus).Error
+	return SysAuthorityMenus, err
+}
+
+// QuerySysBaseMenuIdById 查询菜单id 按角色id
+func QuerySysBaseMenuIdById(authorityId uint) (menuIds []string, err error) {
+	err = global.GVA_DB.Model(&SysAuthorityMenu{}).Where("sys_authority_authority_id = ?", authorityId).Pluck("sys_base_menu_id", &menuIds).Error
+	return
+}
+
+// TODO:菜单新增
+// TODO:菜单修改
+// TODO:菜单删除

+ 139 - 0
internal/dao/sys_base_menu.go

@@ -0,0 +1,139 @@
+package dao
+
+import (
+	"errors"
+	"gorm.io/gorm"
+	"wails-app/internal/global"
+)
+
+type SysBaseMenu struct {
+	global.GVA_MODEL
+	MenuLevel     uint                                       `json:"-"`
+	ParentId      uint                                       `json:"parentId" gorm:"comment:父菜单ID"`     // 父菜单ID
+	Path          string                                     `json:"path" gorm:"comment:路由path"`        // 路由path
+	Name          string                                     `json:"name" gorm:"comment:路由name"`        // 路由name
+	Hidden        bool                                       `json:"hidden" gorm:"comment:是否在列表隐藏"`     // 是否在列表隐藏
+	Component     string                                     `json:"component" gorm:"comment:对应前端文件路径"` // 对应前端文件路径
+	Sort          int                                        `json:"sort" gorm:"comment:排序标记"`          // 排序标记
+	Meta          `json:"meta" gorm:"embedded;comment:附加属性"` // 附加属性
+	SysAuthoritys []SysAuthority                             `json:"authoritys" gorm:"many2many:sys_authority_menus;"`
+	Children      []SysBaseMenu                              `json:"children" gorm:"-"`
+	Parameters    []SysBaseMenuParameter                     `json:"parameters"`
+	MenuBtn       []SysBaseMenuBtn                           `json:"menuBtn"`
+}
+
+type Meta struct {
+	ActiveName  string `json:"activeName" gorm:"comment:高亮菜单"`
+	KeepAlive   bool   `json:"keepAlive" gorm:"comment:是否缓存"`           // 是否缓存
+	DefaultMenu bool   `json:"defaultMenu" gorm:"comment:是否是基础路由(开发中)"` // 是否是基础路由(开发中)
+	Title       string `json:"title" gorm:"comment:菜单名"`                // 菜单名
+	Icon        string `json:"icon" gorm:"comment:菜单图标"`                // 菜单图标
+	CloseTab    bool   `json:"closeTab" gorm:"comment:自动关闭tab"`         // 自动关闭tab
+}
+
+type SysBaseMenuParameter struct {
+	global.GVA_MODEL
+	SysBaseMenuID uint
+	Type          string `json:"type" gorm:"comment:地址栏携带参数为params还是query"` // 地址栏携带参数为params还是query
+	Key           string `json:"key" gorm:"comment:地址栏携带参数的key"`            // 地址栏携带参数的key
+	Value         string `json:"value" gorm:"comment:地址栏携带参数的值"`            // 地址栏携带参数的值
+}
+
+func (SysBaseMenu) TableName() string {
+	return "sys_base_menus"
+}
+
+// TODO:菜单查询
+
+// QueryBaseMenuAndParametersByMenuId 查询菜单和参数 按菜单id
+func QueryBaseMenuAndParametersByMenuId(menuIds []string) (baseMenu []SysBaseMenu, err error) {
+	err = global.GVA_DB.Where("id in (?)", menuIds).Order("sort").Preload("Parameters").Find(&baseMenu).Error
+	return
+}
+
+// QueryBaseMenuByName 查询菜单 按名字
+func QueryBaseMenuByName(name string) (baseMenu SysBaseMenu, err error) {
+	err = global.GVA_DB.Where("name = ?", name).First(&baseMenu).Error
+	return
+}
+
+// QueryBaseMenuAndMenuBtnAndParameters 查询菜单和菜单按钮和参数
+func QueryBaseMenuAndMenuBtnAndParameters() (allMenus []SysBaseMenu, err error) {
+	err = global.GVA_DB.Order("sort").Preload("MenuBtn").Preload("Parameters").Find(&allMenus).Error
+	return
+}
+
+// QueryBaseMenuByIdAndDefaultRouter 查询菜单 按菜单id和默认路由
+func QueryBaseMenuByIdAndDefaultRouter(defaultRouter string, menuIds []string) (allMenus []SysBaseMenu, err error) {
+	err = global.GVA_DB.First(&allMenus, "name = ? and id in (?)", defaultRouter, menuIds).Error
+	return
+}
+
+func QueryBaseMenuAndMenuBtnAndParameterById(id int) (menu SysBaseMenu, err error) {
+	err = global.GVA_DB.Preload("MenuBtn").Preload("Parameters").Where("id = ?", id).First(&menu).Error
+	return
+}
+
+// TODO:菜单新增
+
+// CreateBaseMenu 创建菜单
+func (bm SysBaseMenu) CreateBaseMenu() error {
+	return global.GVA_DB.Create(&bm).Error
+}
+
+// TODO:菜单修改
+
+func (bm SysBaseMenu) UpdateBaseMenu() (err error) {
+	var oldMenu SysBaseMenu
+
+	err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		tx.Where("id = ?", bm.ID).Find(&oldMenu)
+		if oldMenu.Name != bm.Name {
+			if !errors.Is(tx.Where("id <> ? AND name = ?", bm.ID, bm.Name).First(&SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
+				global.GVA_LOG.Debug("存在相同name修改失败")
+				return errors.New("存在相同name修改失败")
+			}
+		}
+		txErr := tx.Unscoped().Delete(&SysBaseMenuParameter{}, "sys_base_menu_id = ?", bm.ID).Error
+		if txErr != nil {
+			global.GVA_LOG.Debug(txErr.Error())
+			return txErr
+		}
+		txErr = tx.Unscoped().Delete(&SysBaseMenuBtn{}, "sys_base_menu_id = ?", bm.ID).Error
+		if txErr != nil {
+			global.GVA_LOG.Debug(txErr.Error())
+			return txErr
+		}
+		if len(bm.Parameters) > 0 {
+			for k := range bm.Parameters {
+				bm.Parameters[k].SysBaseMenuID = bm.ID
+			}
+			txErr = tx.Create(&bm.Parameters).Error
+			if txErr != nil {
+				global.GVA_LOG.Debug(txErr.Error())
+				return txErr
+			}
+		}
+
+		if len(bm.MenuBtn) > 0 {
+			for k := range bm.MenuBtn {
+				bm.MenuBtn[k].SysBaseMenuID = bm.ID
+			}
+			txErr = tx.Create(&bm.MenuBtn).Error
+			if txErr != nil {
+				global.GVA_LOG.Debug(txErr.Error())
+				return txErr
+			}
+		}
+
+		txErr = tx.Model(&oldMenu).Updates(bm).Error
+		if txErr != nil {
+			global.GVA_LOG.Debug(txErr.Error())
+			return txErr
+		}
+		return nil
+	})
+	return err
+}
+
+// TODO:菜单删除

+ 70 - 0
internal/dao/sys_dictionary.go

@@ -0,0 +1,70 @@
+// 自动生成模板SysDictionary
+package dao
+
+import (
+	"gorm.io/gorm"
+	"wails-app/internal/global"
+)
+
+// SysDictionary 如果含有time.Time 请自行import time包
+type SysDictionary struct {
+	global.GVA_MODEL
+	Name                 string                `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"`   // 字典名(中)
+	Type                 string                `json:"type" form:"type" gorm:"column:type;comment:字典名(英)"`   // 字典名(英)
+	Status               *bool                 `json:"status" form:"status" gorm:"column:status;comment:状态"` // 状态
+	Desc                 string                `json:"desc" form:"desc" gorm:"column:desc;comment:描述"`       // 描述
+	SysDictionaryDetails []SysDictionaryDetail `json:"sysDictionaryDetails" form:"sysDictionaryDetails"`
+}
+
+func (SysDictionary) TableName() string {
+	return "sys_dictionaries"
+}
+
+// TODO:字典查询
+
+// QueryDictionaryByType 查询字典 按类型
+func QueryDictionaryByType(t string) (sysDictionary SysDictionary, err error) {
+	err = global.GVA_DB.First(&SysDictionary{}, "type = ?", t).Error
+	return sysDictionary, err
+}
+
+// QueryDictionaryAndDetailsById 查询字典和详情 按字典id
+func QueryDictionaryAndDetailsById(id uint) (sysDictionary SysDictionary, err error) {
+	err = global.GVA_DB.Where("id =?", id).Preload("SysDictionaryDetails").First(&sysDictionary).Error
+	return sysDictionary, err
+}
+
+// GetSysDictionary 根据id或者type获取字典单条数据
+func GetSysDictionary(Type string, Id uint, flag bool) (sysDictionary SysDictionary, err error) {
+	err = global.GVA_DB.Where("(type = ? OR id = ?) and status = ?", Type, Id, flag).Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB {
+		return db.Where("status = ?", true).Order("sort")
+	}).First(&sysDictionary).Error
+	return
+}
+
+// GetSysDictionaryInfoList 分页获取字典列表
+func GetSysDictionaryInfoList() (sysDictionaries []SysDictionary, err error) {
+	err = global.GVA_DB.Find(&sysDictionaries).Error
+	return
+}
+
+// TODO:字典新增
+
+// CreateDictionary 创建字典
+func (sd SysDictionary) CreateDictionary() error {
+	return global.GVA_DB.Create(&sd).Error
+}
+
+// TODO:字典修改
+
+// UpdateDictionary 修改字典
+func (sd SysDictionary) UpdateDictionary() error {
+	return global.GVA_DB.Model(&SysDictionary{}).Updates(&sd).Error
+}
+
+// TODO:字典删除
+
+// DeleteDictionary 删除字典
+func (sd SysDictionary) DeleteDictionary() error {
+	return global.GVA_DB.Delete(&sd).Error
+}

+ 101 - 0
internal/dao/sys_dictionary_detail.go

@@ -0,0 +1,101 @@
+// 自动生成模板SysDictionaryDetail
+package dao
+
+import (
+	"wails-app/internal/global"
+)
+
+// 如果含有time.Time 请自行import time包
+type SysDictionaryDetail struct {
+	global.GVA_MODEL
+	Label           string `json:"label" form:"label" gorm:"column:label;comment:展示值"`                                  // 展示值
+	Value           string `json:"value" form:"value" gorm:"column:value;comment:字典值"`                                  // 字典值
+	Extend          string `json:"extend" form:"extend" gorm:"column:extend;comment:扩展值"`                               // 扩展值
+	Status          *bool  `json:"status" form:"status" gorm:"column:status;comment:启用状态"`                              // 启用状态
+	Sort            int    `json:"sort" form:"sort" gorm:"column:sort;comment:排序标记"`                                    // 排序标记
+	SysDictionaryID int    `json:"sysDictionaryID" form:"sysDictionaryID" gorm:"column:sys_dictionary_id;comment:关联标记"` // 关联标记
+}
+
+func (SysDictionaryDetail) TableName() string {
+	return "sys_dictionary_details"
+}
+
+// TODO:字典详情查询
+
+// GetSysDictionaryDetailById 查询字典详情 按id
+func GetSysDictionaryDetailById(id uint) (sysDictionaryDetail SysDictionaryDetail, err error) {
+	err = global.GVA_DB.Where("id = ?", id).First(&sysDictionaryDetail).Error
+	return sysDictionaryDetail, err
+}
+
+// GetSysDictionaryDetailInfoList 分页获取字典详情列表
+func (sdd SysDictionaryDetail) GetSysDictionaryDetailInfoList(limit, offset int) (sysDictionaryDetails []SysDictionaryDetail, total int64, err error) {
+	// 创建db
+	db := global.GVA_DB.Model(&SysDictionaryDetail{})
+	// 如果有条件搜索 下方会自动创建搜索语句
+	if sdd.Label != "" {
+		db = db.Where("label LIKE ?", "%"+sdd.Label+"%")
+	}
+	if sdd.Value != "" {
+		db = db.Where("value = ?", sdd.Value)
+	}
+	if sdd.Status != nil {
+		db = db.Where("status = ?", sdd.Status)
+	}
+	if sdd.SysDictionaryID != 0 {
+		db = db.Where("sys_dictionary_id = ?", sdd.SysDictionaryID)
+	}
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Order("sort").Find(&sysDictionaryDetails).Error
+	return sysDictionaryDetails, total, err
+}
+
+// GetDictionaryListByDictionaryId 查询字典列表 按字典id
+func GetDictionaryListByDictionaryId(dictionaryID uint) (sysDictionaryDetails []SysDictionaryDetail, err error) {
+	err = global.GVA_DB.Find(&sysDictionaryDetails, "sys_dictionary_id = ?", dictionaryID).Error
+	return sysDictionaryDetails, err
+}
+
+// GetDictionaryListByType 查询字典列表 按字典类型
+func GetDictionaryListByType(t string) (sysDictionaryDetails []SysDictionaryDetail, err error) {
+	db := global.GVA_DB.Model(&SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id")
+	err = db.Debug().Find(&sysDictionaryDetails, "type = ?", t).Error
+	return sysDictionaryDetails, err
+}
+
+// GetDictionaryInfoByValue 查询字典列表 按字典内容
+func GetDictionaryInfoByValue(dictionaryID uint, value uint) (sysDictionaryDetail SysDictionaryDetail, err error) {
+	err = global.GVA_DB.First(&sysDictionaryDetail, "sys_dictionary_id = ? and value = ?", dictionaryID, value).Error
+	return sysDictionaryDetail, err
+}
+
+// GetDictionaryInfoByTypeValue 查询字典列表 按字典类型和字典内容
+func GetDictionaryInfoByTypeValue(t string, value uint) (sysDictionaryDetail SysDictionaryDetail, err error) {
+	db := global.GVA_DB.Model(&SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id")
+	err = db.First(&sysDictionaryDetail, "sys_dictionaries.type = ? and sys_dictionary_details.value = ?", t, value).Error
+	return sysDictionaryDetail, err
+}
+
+// TODO:字典详情新增
+
+// CreateSysDictionaryDetail 创建字典详情数据
+func (sdd SysDictionaryDetail) CreateSysDictionaryDetail() error {
+	return global.GVA_DB.Create(&sdd).Error
+}
+
+// TODO:字典详情修改
+
+// UpdateSysDictionaryDetail 更新字典数据
+func (sdd SysDictionaryDetail) UpdateSysDictionaryDetail() error {
+	return global.GVA_DB.Save(&sdd).Error
+}
+
+// TODO:字典详情删除
+
+// DeleteSysDictionaryDetail 删除字典详情
+func (sdd SysDictionaryDetail) DeleteSysDictionaryDetail() error {
+	return global.GVA_DB.Delete(&sdd).Error
+}

+ 21 - 0
internal/dao/sys_jwt_blacklist.go

@@ -0,0 +1,21 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+)
+
+type JwtBlacklist struct {
+	global.GVA_MODEL
+	Jwt string `gorm:"type:text;comment:jwt"`
+}
+
+// TODO:JwtBlack查询
+// TODO:JwtBlack新增
+
+// CreateJwtBlack 创建jwt黑名单
+func (jbl JwtBlacklist) CreateJwtBlack() error {
+	return global.GVA_DB.Create(&jbl).Error
+}
+
+// TODO:JwtBlack修改
+// TODO:JwtBlack删除

+ 10 - 0
internal/dao/sys_menu_btn.go

@@ -0,0 +1,10 @@
+package dao
+
+import "wails-app/internal/global"
+
+type SysBaseMenuBtn struct {
+	global.GVA_MODEL
+	Name          string `json:"name" gorm:"comment:按钮关键key"`
+	Desc          string `json:"desc" gorm:"按钮备注"`
+	SysBaseMenuID uint   `json:"sysBaseMenuID" gorm:"comment:菜单ID"`
+}

+ 76 - 0
internal/dao/sys_operation_record.go

@@ -0,0 +1,76 @@
+// 自动生成模板SysOperationRecord
+package dao
+
+import (
+	"time"
+
+	"wails-app/internal/global"
+)
+
+// 如果含有time.Time 请自行import time包
+type SysOperationRecord struct {
+	global.GVA_MODEL
+	Ip           string        `json:"ip" form:"ip" gorm:"column:ip;comment:请求ip"`                                   // 请求ip
+	Method       string        `json:"method" form:"method" gorm:"column:method;comment:请求方法"`                       // 请求方法
+	Path         string        `json:"path" form:"path" gorm:"column:path;comment:请求路径"`                             // 请求路径
+	Status       int           `json:"status" form:"status" gorm:"column:status;comment:请求状态"`                       // 请求状态
+	Latency      time.Duration `json:"latency" form:"latency" gorm:"column:latency;comment:延迟" swaggertype:"string"` // 延迟
+	Agent        string        `json:"agent" form:"agent" gorm:"type:text;column:agent;comment:代理"`                  // 代理
+	ErrorMessage string        `json:"error_message" form:"error_message" gorm:"column:error_message;comment:错误信息"`  // 错误信息
+	Body         string        `json:"body" form:"body" gorm:"type:text;column:body;comment:请求Body"`                 // 请求Body
+	Resp         string        `json:"resp" form:"resp" gorm:"type:text;column:resp;comment:响应Body"`                 // 响应Body
+	UserID       int           `json:"user_id" form:"user_id" gorm:"column:user_id;comment:用户id"`                    // 用户id
+	User         SysUser       `json:"user"`
+}
+
+// TODO:操作查询
+
+// GetSysOperationRecord 根据id获取单条操作记录
+func GetSysOperationRecord(id uint) (sysOperationRecord SysOperationRecord, err error) {
+	err = global.GVA_DB.Where("id = ?", id).First(&sysOperationRecord).Error
+	return
+}
+
+// GetSysOperationRecordInfoList 分页获取操作记录列表
+func (sor SysOperationRecord) GetSysOperationRecordInfoList(limit, offset int) (sysOperationRecords []SysOperationRecord, total int64, err error) {
+	// 创建db
+	db := global.GVA_DB.Model(&SysOperationRecord{})
+	// 如果有条件搜索 下方会自动创建搜索语句
+	if sor.Method != "" {
+		db = db.Where("method = ?", sor.Method)
+	}
+	if sor.Path != "" {
+		db = db.Where("path LIKE ?", "%"+sor.Path+"%")
+	}
+	if sor.Status != 0 {
+		db = db.Where("status = ?", sor.Status)
+	}
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Order("id desc").Limit(limit).Offset(offset).Preload("User").Find(&sysOperationRecords).Error
+	return sysOperationRecords, total, err
+}
+
+// TODO:操作新增
+
+func (sor SysOperationRecord) CreateSysOperationRecord() error {
+	return global.GVA_DB.Create(&sor).Error
+}
+
+// TODO:操作修改
+
+// TODO:操作删除
+
+// DeleteSysOperationRecordByIds 删除操作记录按id
+func DeleteSysOperationRecordByIds(ids []int) (err error) {
+	err = global.GVA_DB.Delete(&[]SysOperationRecord{}, "id in (?)", ids).Error
+	return err
+}
+
+// DeleteSysOperationRecord 删除操作记录
+func (sor SysOperationRecord) DeleteSysOperationRecord() (err error) {
+	err = global.GVA_DB.Delete(&sor).Error
+	return err
+}

+ 155 - 0
internal/dao/sys_user.go

@@ -0,0 +1,155 @@
+package dao
+
+import (
+	"errors"
+	"github.com/gofrs/uuid/v5"
+	"gorm.io/gorm"
+	"wails-app/internal/global"
+)
+
+type SysUser struct {
+	global.GVA_MODEL
+	UUID        uuid.UUID    `json:"uuid" gorm:"index;comment:用户UUID"`                                                     // 用户UUID
+	Username    string       `json:"userName" gorm:"index;comment:用户登录名"`                                                  // 用户登录名
+	Password    string       `json:"-"  gorm:"comment:用户登录密码"`                                                             // 用户登录密码
+	NickName    string       `json:"nickName" gorm:"default:系统用户;comment:用户昵称"`                                            // 用户昵称
+	SideMode    string       `json:"sideMode" gorm:"default:dark;comment:用户侧边主题"`                                          // 用户侧边主题
+	HeaderImg   string       `json:"headerImg" gorm:"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像"` // 用户头像
+	BaseColor   string       `json:"baseColor" gorm:"default:#fff;comment:基础颜色"`                                           // 基础颜色
+	ActiveColor string       `json:"activeColor" gorm:"default:#1890ff;comment:活跃颜色"`                                      // 活跃颜色
+	AuthorityId uint         `json:"authorityId" gorm:"default:888;comment:用户角色ID"`                                        // 用户角色ID
+	Authority   SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"`
+	Phone       string       `json:"phone"  gorm:"comment:用户手机号"`                     // 用户手机号
+	Email       string       `json:"email"  gorm:"comment:用户邮箱"`                      // 用户邮箱
+	Enable      int          `json:"enable" gorm:"default:1;comment:用户是否被冻结 1正常 2冻结"` //用户是否被冻结 1正常 2冻结
+}
+
+func (SysUser) TableName() string {
+	return "sys_users"
+}
+
+// TODO:查询
+// TODO:新增
+// TODO:修改
+// TODO:删除
+
+// TODO: 用户查询
+
+// QueryUserByUserName  按用户名查询用户
+func QueryUserByUserName(userName string) (SysUser, error) {
+	var user SysUser
+	err := global.GVA_DB.Where("username =?", userName).First(&user).Error
+	return user, err
+}
+
+// QueryUserByUserId  按用户Id查询用户
+func QueryUserByUserId(id uint) (SysUser, error) {
+	var user SysUser
+	err := global.GVA_DB.Where("id =?", id).First(&user).Error
+	return user, err
+}
+
+// QueryUserAndAuthorityByUserName 按用户名查询用户和角色
+func QueryUserAndAuthorityByUserName(userName string) (user SysUser, err error) {
+	err = global.GVA_DB.Where("username = ?", userName).Preload("Authority").First(&user).Error
+	return
+}
+
+// QueryUserInfoList 条件查询用户信息列表
+func QueryUserInfoList(limit, offset int) (userList []SysUser, total int64, err error) {
+	db := global.GVA_DB.Model(&SysUser{})
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Preload("Authority").Find(&userList).Error
+	return
+}
+
+// QueryUserAuthority 查询用户角色
+func QueryUserAuthority(id, authorityId uint) (authority SysAuthority, err error) {
+	err = global.GVA_DB.Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityId).First(&authority).Error
+	return
+}
+
+// GetUserInfoByUUID 获取用户信息 按UUID
+func GetUserInfoByUUID(uuid uuid.UUID) (user SysUser, err error) {
+	err = global.GVA_DB.Preload("Authority").First(&user, "uuid = ?", uuid).Error
+	return user, err
+}
+
+// TODO: 用户新增
+
+// CreateUser 创建用户
+func (u SysUser) CreateUser() error {
+	return global.GVA_DB.Create(&u).Error
+}
+
+// TODO: 用户修改
+
+// UpdateUser 修改用户
+func (u SysUser) UpdateUser() error {
+	return global.GVA_DB.Save(&u).Error
+}
+
+// UpdateUserAuthority 修改用户权限
+func UpdateUserAuthority(id, authorityId uint) error {
+	return global.GVA_DB.Model(&SysUser{}).Where("id = ?", id).Update("authority_id", authorityId).Error
+}
+
+// SetUserAuthorities 设置用户权限
+func SetUserAuthorities(id uint, authorityId uint) (err error) {
+	return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		var user SysUser
+		TxErr := tx.Where("id = ?", id).First(&user).Error
+		if TxErr != nil {
+			global.GVA_LOG.Debug(TxErr.Error())
+			return errors.New("查询用户数据失败")
+		}
+		TxErr = tx.Delete(&[]SysUserAuthority{}, "sys_user_id = ?", id).Error
+		if TxErr != nil {
+			return TxErr
+		}
+
+		useAuthority := SysUserAuthority{
+			SysUserId: id, SysAuthorityAuthorityId: authorityId,
+		}
+		TxErr = tx.Create(&useAuthority).Error
+		if TxErr != nil {
+			return TxErr
+		}
+		TxErr = tx.Model(&user).Update("authority_id", authorityId).Error
+		if TxErr != nil {
+			return TxErr
+		}
+		// 返回 nil 提交事务
+		return nil
+	})
+}
+
+// SetUserInfo 修改用户信息
+func (u SysUser) SetUserInfo() error {
+	return global.GVA_DB.Model(&SysUser{}).Updates(&u).Error
+}
+
+// SetUserInfoById 修改用户信息 按id
+func (u SysUser) SetUserInfoById() error {
+	return global.GVA_DB.Model(&SysUser{}).
+		Where("id=?", u.ID).
+		Updates(&u).Error
+}
+
+// TODO: 用户删除
+
+// DeleteUser 删除用户
+func DeleteUser(id int) (err error) {
+	return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		if err := tx.Where("id = ?", id).Delete(&SysUser{}).Error; err != nil {
+			return err
+		}
+		if err := tx.Delete(&[]SysUserAuthority{}, "sys_user_id = ?", id).Error; err != nil {
+			return err
+		}
+		return nil
+	})
+}

+ 11 - 0
internal/dao/sys_user_authority.go

@@ -0,0 +1,11 @@
+package dao
+
+// SysUserAuthority 是 sysUser 和 sysAuthority 的连接表
+type SysUserAuthority struct {
+	SysUserId               uint `gorm:"column:sys_user_id"`
+	SysAuthorityAuthorityId uint `gorm:"column:sys_authority_authority_id"`
+}
+
+func (s *SysUserAuthority) TableName() string {
+	return "sys_user_authority"
+}

+ 39 - 0
internal/dao/uhf_reader.go

@@ -0,0 +1,39 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+	"time"
+)
+
+type UHFReader struct {
+	global.GVA_MODEL
+
+	DeviceCode string `gorm:"size:50;not null;uniqueIndex" json:"device_code"`
+	DeviceName string `gorm:"size:100;not null" json:"device_name"`
+	DeviceType string `gorm:"size:100;not null" json:"device_type"`
+
+	// ========== 连接方式配置 ==========
+	ConnectType string `gorm:"size:20;not null;default:'tcp'" json:"connect_type"` // tcp / serial
+
+	// TCP 配置
+	IPAddress string `gorm:"size:50" json:"ip_address"`
+	Port      int    `gorm:"default:0" json:"port"`
+
+	// Serial(RS232) 配置
+	COMPort  string `gorm:"size:50" json:"com_port"`       // COM3 /dev/ttyUSB0
+	BaudRate int    `gorm:"default:9600" json:"baud_rate"` // 波特率
+
+	Status         string     `gorm:"size:20;default:'offline'" json:"status"`
+	IsActive       bool       `gorm:"default:true" json:"is_active"`
+	Description    string     `gorm:"size:200" json:"description"`
+	ParkingLotID   uint       `json:"parking_lot_id"`
+	ParkingLot     ParkingLot `gorm:"foreignkey:ParkingLotID" json:"parking_lot"`
+	LastOnlineTime *time.Time `json:"last_online_time" gorm:"comment:最后上线时间;default:null"`
+
+	ChannelID uint     `gorm:"comment:绑定通道ID" json:"channel_id"`
+	Channel   *Channel `gorm:"foreignKey:ChannelID;references:ID" json:"channel"`
+}
+
+func (UHFReader) TableName() string {
+	return "uhf_reader"
+}

+ 26 - 0
internal/dao/vehicle.go

@@ -0,0 +1,26 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+	"time"
+)
+
+type Vehicle struct {
+	global.GVA_MODEL
+	PlateNumber   string       `gorm:"size:20;not null;uniqueIndex" json:"plate_number"` // 车牌号
+	RFIDTag       string       `gorm:"size:40;column:rfid_tag" json:"rfid_tag"`          // 电子标签
+	VehicleTypeID uint         `gorm:"not null" json:"vehicle_type_id"`                  // 车辆类型ID
+	VehicleType   *VehicleType `gorm:"foreignKey:VehicleTypeID" json:"vehicle_type"`     // 车辆类型关联
+	VehicleBrand  string       `gorm:"size:50" json:"vehicle_brand"`                     // 车辆品牌
+	VehicleColor  string       `gorm:"size:20" json:"vehicle_color"`                     // 车辆颜色
+	OwnerId       *uint        `json:"owner_id"`                                         // 车主id,可为空(临时车辆)
+	Owner         *Owner       `gorm:"foreignKey:OwnerId" json:"owner"`
+	LastEntryTime *time.Time   `json:"last_entry_time" gorm:"default:null"` // 最近入场时间
+	LastExitTime  *time.Time   `json:"last_exit_time"  gorm:"default:null"` // 最近出场时间
+	TotalStayTime int64        `json:"total_stay_time" gorm:"default:null"` // 总停留时间
+	TotalFee      float64      `json:"total_fee"`                           // 总费用
+}
+
+func (Vehicle) TableName() string {
+	return "vehicle"
+}

+ 25 - 0
internal/dao/vehicle_record.go

@@ -0,0 +1,25 @@
+package dao
+
+import (
+	"wails-app/internal/global"
+	"time"
+)
+
+type VehicleRecord struct {
+	global.GVA_MODEL
+	PlateNumber    string     `gorm:"size:20;" json:"plate_number"`                   // 车牌号
+	RFIDTag        string     `gorm:"size:40;column:rfid_tag" json:"rfid_tag"`        // 标签
+	EntryTime      time.Time  `json:"entry_time" gorm:"default:null"`                 // 入场时间
+	ExitTime       *time.Time `json:"exit_time" gorm:"default:null"`                  // 出场时间
+	StayTime       int64      `json:"stay_time"`                                      // 停留时间(分钟)
+	Fee            float64    `json:"fee"`                                            // 费用
+	PaymentStatus  string     `gorm:"size:20;default:'unpaid'" json:"payment_status"` // 支付状态
+	EntryImage     string     `gorm:"size:255" json:"entry_image"`                    // 入场图片
+	ExitImage      string     `gorm:"size:255" json:"exit_image"`                     // 出场图片
+	ParkingLotID   uint       `json:"parking_lot_id"`                                 // 停车场ID
+	ParkingSpaceID uint       `json:"parking_space_id"`                               // 停车位ID
+}
+
+func (VehicleRecord) TableName() string {
+	return "vehicle_record"
+}

+ 13 - 0
internal/dao/vehicle_type.go

@@ -0,0 +1,13 @@
+package dao
+
+import "wails-app/internal/global"
+
+type VehicleType struct {
+	global.GVA_MODEL
+	Name    string `form:"name" json:"name" gorm:"comment:名称"`
+	Remarks string `form:"remarks" json:"remarks" gorm:"comment:备注"`
+}
+
+func (VehicleType) TableName() string {
+	return "vehicle_type"
+}

File diff suppressed because it is too large
+ 7018 - 0
internal/docs/docs.go


File diff suppressed because it is too large
+ 6992 - 0
internal/docs/swagger.json


File diff suppressed because it is too large
+ 4279 - 0
internal/docs/swagger.yaml


+ 53 - 0
internal/global/global.go

@@ -0,0 +1,53 @@
+package global
+
+import (
+	"github.com/qiniu/qmgo"
+	"sync"
+
+	"github.com/songzhibin97/gkit/cache/local_cache"
+	"wails-app/internal/utils/timer"
+
+	"golang.org/x/sync/singleflight"
+
+	"go.uber.org/zap"
+
+	"wails-app/internal/config"
+
+	"github.com/redis/go-redis/v9"
+	"github.com/spf13/viper"
+	"gorm.io/gorm"
+)
+
+var (
+	GVA_DB     *gorm.DB
+	GVA_DBList map[string]*gorm.DB
+	GVA_REDIS  redis.UniversalClient
+	GVA_MONGO  *qmgo.QmgoClient
+	GVA_CONFIG config.Server
+	GVA_VP     *viper.Viper
+	// GVA_LOG    *oplogging.Logger
+	GVA_LOG                 *zap.Logger
+	GVA_Timer               timer.Timer = timer.NewTimerTask()
+	GVA_Concurrency_Control             = &singleflight.Group{}
+
+	BlackCache local_cache.Cache
+	lock       sync.RWMutex
+)
+
+// GetGlobalDBByDBName 通过名称获取db list中的db
+func GetGlobalDBByDBName(dbname string) *gorm.DB {
+	lock.RLock()
+	defer lock.RUnlock()
+	return GVA_DBList[dbname]
+}
+
+// MustGetGlobalDBByDBName 通过名称获取db 如果不存在则panic
+func MustGetGlobalDBByDBName(dbname string) *gorm.DB {
+	lock.RLock()
+	defer lock.RUnlock()
+	db, ok := GVA_DBList[dbname]
+	if !ok || db == nil {
+		panic("db no init")
+	}
+	return db
+}

+ 14 - 0
internal/global/model.go

@@ -0,0 +1,14 @@
+package global
+
+import (
+	"time"
+
+	"gorm.io/gorm"
+)
+
+type GVA_MODEL struct {
+	ID        uint           `gorm:"primarykey" json:"ID"` // 主键ID
+	CreatedAt time.Time      // 创建时间
+	UpdatedAt time.Time      // 更新时间
+	DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间
+}

+ 36 - 0
internal/initialize/db_list.go

@@ -0,0 +1,36 @@
+package initialize
+
+import (
+	"gorm.io/gorm"
+	"wails-app/internal/config"
+	"wails-app/internal/global"
+)
+
+const sys = "system"
+
+func DBList() {
+	dbMap := make(map[string]*gorm.DB)
+	for _, info := range global.GVA_CONFIG.DBList {
+		if info.Disable {
+			continue
+		}
+		switch info.Type {
+		case "mysql":
+			dbMap[info.AliasName] = GormMysqlByConfig(config.Mysql{GeneralDB: info.GeneralDB})
+		case "mssql":
+			dbMap[info.AliasName] = GormMssqlByConfig(config.Mssql{GeneralDB: info.GeneralDB})
+		case "pgsql":
+			dbMap[info.AliasName] = GormPgSqlByConfig(config.Pgsql{GeneralDB: info.GeneralDB})
+		case "oracle":
+			dbMap[info.AliasName] = GormOracleByConfig(config.Oracle{GeneralDB: info.GeneralDB})
+		default:
+			continue
+		}
+	}
+	// 做特殊判断,是否有迁移
+	// 适配低版本迁移多数据库版本
+	if sysDB, ok := dbMap[sys]; ok {
+		global.GVA_DB = sysDB
+	}
+	global.GVA_DBList = dbMap
+}

+ 67 - 0
internal/initialize/gorm.go

@@ -0,0 +1,67 @@
+package initialize
+
+import (
+	"os"
+	"wails-app/internal/dao"
+
+	"go.uber.org/zap"
+	"gorm.io/gorm"
+	"wails-app/internal/global"
+	"wails-app/internal/model/example"
+)
+
+func Gorm() *gorm.DB {
+	switch global.GVA_CONFIG.System.DbType {
+	case "mysql":
+		return GormMysql()
+	case "pgsql":
+		return GormPgSql()
+	case "oracle":
+		return GormOracle()
+	case "mssql":
+		return GormMssql()
+	case "sqlite":
+		return GormSqlite()
+	default:
+		return GormMysql()
+	}
+}
+
+func RegisterTables() {
+	db := global.GVA_DB
+	err := db.AutoMigrate(
+
+		dao.SysApi{},
+		dao.SysUser{},
+		dao.SysBaseMenu{},
+		dao.JwtBlacklist{},
+		dao.SysAuthority{},
+		dao.SysDictionary{},
+		dao.SysOperationRecord{},
+		dao.SysDictionaryDetail{},
+		dao.SysBaseMenuParameter{},
+		dao.SysBaseMenuBtn{},
+		dao.SysAuthorityBtn{},
+
+		example.ExaFile{},
+		example.ExaFileChunk{},
+		dao.ExaFileUploadAndDownload{},
+
+		dao.Vehicle{},
+		dao.VehicleType{},
+		dao.Owner{},
+		dao.FeeConfig{},
+		dao.Booth{},
+		dao.Channel{},
+		dao.ParkingLot{},
+		dao.VehicleRecord{},
+		dao.UHFReader{},
+		dao.Camera{},
+		dao.Shortlist{},
+	)
+	if err != nil {
+		global.GVA_LOG.Error("register table failed", zap.Error(err))
+		os.Exit(0)
+	}
+	global.GVA_LOG.Info("register table success")
+}

+ 59 - 0
internal/initialize/gorm_mssql.go

@@ -0,0 +1,59 @@
+/*
+ * @Author: 逆光飞翔 191180776@qq.com
+ * @Date: 2022-12-08 17:25:49
+ * @LastEditors: 逆光飞翔 191180776@qq.com
+ * @LastEditTime: 2022-12-08 18:00:00
+ * @FilePath: \server\initialize\gorm_mssql.go
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+package initialize
+
+import (
+	"gorm.io/driver/sqlserver"
+	"gorm.io/gorm"
+	"wails-app/internal/config"
+	"wails-app/internal/global"
+	"wails-app/internal/initialize/internal"
+)
+
+// GormMssql 初始化Mssql数据库
+// Author [LouisZhang](191180776@qq.com)
+func GormMssql() *gorm.DB {
+	m := global.GVA_CONFIG.Mssql
+	if m.Dbname == "" {
+		return nil
+	}
+	mssqlConfig := sqlserver.Config{
+		DSN:               m.Dsn(), // DSN data source name
+		DefaultStringSize: 191,     // string 类型字段的默认长度
+	}
+	if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
+		return nil
+	} else {
+		db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
+		sqlDB, _ := db.DB()
+		sqlDB.SetMaxIdleConns(m.MaxIdleConns)
+		sqlDB.SetMaxOpenConns(m.MaxOpenConns)
+		return db
+	}
+}
+
+// GormMssqlByConfig 初始化Mysql数据库用过传入配置
+func GormMssqlByConfig(m config.Mssql) *gorm.DB {
+	if m.Dbname == "" {
+		return nil
+	}
+	mssqlConfig := sqlserver.Config{
+		DSN:               m.Dsn(), // DSN data source name
+		DefaultStringSize: 191,     // string 类型字段的默认长度
+	}
+	if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
+		panic(err)
+	} else {
+		db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
+		sqlDB, _ := db.DB()
+		sqlDB.SetMaxIdleConns(m.MaxIdleConns)
+		sqlDB.SetMaxOpenConns(m.MaxOpenConns)
+		return db
+	}
+}

+ 0 - 0
internal/initialize/gorm_mysql.go


Some files were not shown because too many files changed in this diff