├── .DS_Store
├── .gitignore
├── .idea
├── .gitignore
├── .name
├── dataSources.xml
├── git_toolbox_blame.xml
├── git_toolbox_prj.xml
├── modules.xml
├── vcs.xml
├── watcherTasks.xml
└── wike-go.iml
├── common
└── common.go
├── config.yaml
├── db
└── core.db
├── go.mod
├── go.sum
├── logs
└── app.log
├── main.go
├── model.conf
├── pkg
├── core
│ ├── casbin.go
│ ├── core.go
│ ├── cron.go
│ ├── ctl.go
│ ├── db.go
│ ├── helper.go
│ ├── http.go
│ ├── infra.go
│ ├── interface.go
│ └── middleware.go
├── doc
│ └── doc.go
├── func
│ ├── bloomfilter
│ │ └── bloomfilter.go
│ ├── breaker
│ │ └── breaker.go
│ ├── cache
│ │ ├── interface.go
│ │ ├── memory
│ │ │ ├── cache.go
│ │ │ └── cache_test.go
│ │ └── redis
│ │ │ ├── cache.go
│ │ │ └── cache_test.go
│ ├── config
│ │ └── config.go
│ ├── cron
│ │ └── cron.go
│ ├── ctl
│ │ ├── base.go
│ │ ├── const.go
│ │ ├── curd.go
│ │ ├── error.go
│ │ ├── fastRouter
│ │ │ └── core.go
│ │ ├── func.go
│ │ ├── json.go
│ │ ├── page.go
│ │ ├── repeatCheck.go
│ │ ├── returnType.go
│ │ ├── time.go
│ │ └── ws.go
│ ├── hash
│ │ └── consistent.go
│ ├── jwt
│ │ ├── jwt.go
│ │ └── jwt_test.go
│ ├── log
│ │ └── zap.go
│ ├── memorylog
│ │ ├── log.go
│ │ └── log_test.go
│ ├── rateLimiter
│ │ └── rateLimiter.go
│ └── retry
│ │ └── retry.go
├── modules
│ └── ants_service
│ │ └── ants.go
├── os
│ └── serverMetrics.go
├── tpl
│ └── tpl.go
└── utils
│ ├── copy.go
│ ├── duration.go
│ ├── ip.go
│ ├── lock.go
│ ├── map.go
│ ├── md5.go
│ ├── number.go
│ ├── password.go
│ ├── queue.go
│ ├── random.go
│ ├── set.go
│ └── validator.go
├── plugin
└── repeatCheck.go
├── policy.csv
├── readme.md
├── server
├── api
│ ├── api.go
│ ├── build.go
│ ├── dictionary.go
│ ├── job.go
│ ├── log.go
│ ├── role.go
│ └── system.go
├── model
│ ├── SysDictionary.go
│ ├── api.go
│ ├── casbin.go
│ └── job.go
└── service
│ └── service.go
├── web
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .vscode
│ └── extensions.json
├── README.md
├── eslint.config.mjs
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.vue
│ ├── assets
│ │ └── vue.svg
│ ├── axios
│ │ └── common.js
│ ├── components
│ │ ├── HelloWorld.vue
│ │ └── MenuItem.vue
│ ├── main.ts
│ ├── pages
│ │ ├── api
│ │ │ └── index.vue
│ │ ├── cronJob
│ │ │ └── index.vue
│ │ ├── dict
│ │ │ └── index.vue
│ │ ├── dictDetail
│ │ │ └── index.vue
│ │ ├── home
│ │ │ └── index.vue
│ │ ├── log
│ │ │ └── index.vue
│ │ ├── role
│ │ │ └── index.vue
│ │ ├── rule
│ │ │ └── index.vue
│ │ └── settings
│ │ │ └── index.vue
│ ├── router
│ │ └── index.js
│ ├── style.scss
│ ├── tools
│ │ └── index.js
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
└── 接口文档.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wike2019/wike_go/ed6f7064c11f9709f437634b6634c70c62b44cae/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ./logs/
2 | ./main.go
3 | ./config.yaml
4 | ./viper.go
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | middleware.go
--------------------------------------------------------------------------------
/.idea/dataSources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | sqlite.xerial
6 | true
7 | org.sqlite.JDBC
8 | jdbc:sqlite:$PROJECT_DIR$/core.db
9 | $ProjectFileDir$
10 |
11 |
12 | sqlite.xerial
13 | true
14 | org.sqlite.JDBC
15 | jdbc:sqlite:$PROJECT_DIR$/db/core.db
16 | $ProjectFileDir$
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/git_toolbox_blame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/git_toolbox_prj.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/wike-go.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/common/common.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | // 根据id 搜索
4 | type IDSearch struct {
5 | ID uint `json:"id" form:"id"`
6 | }
7 |
8 | // 根据关键字搜索
9 | type KeyWordsSearch struct {
10 | KeyWords string `json:"keywords" form:"keywords"`
11 | }
12 |
13 | // 空结构,一般用于参数为空
14 | type Empty struct {
15 | }
16 |
17 | func (this IDSearch) GetID() uint {
18 | return this.ID
19 | }
20 |
21 | type IDSearcher interface {
22 | GetID() uint
23 | }
24 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | port: 9999
2 | development: true
3 | mysql: "root:QAZwsx123123...@tcp(192.168.3.2:3307)/kf_vue?charset=utf8mb4&parseTime=True&loc=Local"
4 | redis: "192.168.3.2:6379"
5 | logPath: "./logs/app.log"
6 | text: "仅限于奇荃网络相关问题处理使用,他用无效。"
7 | LRULimit: 4096
8 | LimitRate: 128
9 | LimitBucket: 512
--------------------------------------------------------------------------------
/db/core.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wike2019/wike_go/ed6f7064c11f9709f437634b6634c70c62b44cae/db/core.db
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/wike2019/wike_go
2 |
3 | go 1.23
4 |
5 | require (
6 | github.com/avast/retry-go v3.0.0+incompatible
7 | github.com/bsm/redislock v0.9.4
8 | github.com/casbin/casbin/v2 v2.100.0
9 | github.com/casbin/gorm-adapter/v3 v3.32.0
10 | github.com/gin-contrib/gzip v0.0.6
11 | github.com/gin-contrib/timeout v1.0.0
12 | github.com/gin-gonic/gin v1.9.1
13 | github.com/glebarez/sqlite v1.7.0
14 | github.com/goccy/go-json v0.10.2
15 | github.com/golang-jwt/jwt/v4 v4.5.0
16 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
17 | github.com/google/uuid v1.3.1
18 | github.com/gorilla/websocket v1.5.1
19 | github.com/hugh2632/bloomfilter v0.0.0-20220107050508-533b6738df0f
20 | github.com/panjf2000/ants/v2 v2.10.0
21 | github.com/patrickmn/go-cache v2.1.0+incompatible
22 | github.com/redis/go-redis/v9 v9.3.0
23 | github.com/robfig/cron v1.2.0
24 | github.com/shirou/gopsutil/v3 v3.24.5
25 | github.com/shopspring/decimal v1.3.1
26 | github.com/sony/gobreaker v0.5.0
27 | github.com/spf13/viper v1.16.0
28 | github.com/stathat/consistent v1.0.0
29 | go.uber.org/fx v1.23.0
30 | go.uber.org/zap v1.26.0
31 | golang.org/x/crypto v0.21.0
32 | golang.org/x/time v0.5.0
33 | gopkg.in/natefinch/lumberjack.v2 v2.2.1
34 | gorm.io/gorm v1.25.12
35 | )
36 |
37 | require (
38 | github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
39 | github.com/bytedance/sonic v1.11.3 // indirect
40 | github.com/casbin/govaluate v1.2.0 // indirect
41 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
42 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
43 | github.com/chenzhuoyu/iasm v0.9.1 // indirect
44 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
45 | github.com/dustin/go-humanize v1.0.1 // indirect
46 | github.com/fsnotify/fsnotify v1.6.0 // indirect
47 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect
48 | github.com/gin-contrib/sse v0.1.0 // indirect
49 | github.com/glebarez/go-sqlite v1.20.3 // indirect
50 | github.com/go-ole/go-ole v1.2.6 // indirect
51 | github.com/go-playground/locales v0.14.1 // indirect
52 | github.com/go-playground/universal-translator v0.18.1 // indirect
53 | github.com/go-playground/validator/v10 v10.19.0 // indirect
54 | github.com/go-redis/redis/v8 v8.8.3 // indirect
55 | github.com/go-sql-driver/mysql v1.7.0 // indirect
56 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
57 | github.com/golang-sql/sqlexp v0.1.0 // indirect
58 | github.com/hashicorp/hcl v1.0.0 // indirect
59 | github.com/jackc/pgpassfile v1.0.0 // indirect
60 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
61 | github.com/jackc/pgx/v5 v5.5.5 // indirect
62 | github.com/jackc/puddle/v2 v2.2.1 // indirect
63 | github.com/jinzhu/inflection v1.0.0 // indirect
64 | github.com/jinzhu/now v1.1.5 // indirect
65 | github.com/json-iterator/go v1.1.12 // indirect
66 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
67 | github.com/leodido/go-urn v1.4.0 // indirect
68 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
69 | github.com/magiconair/properties v1.8.7 // indirect
70 | github.com/mattn/go-isatty v0.0.20 // indirect
71 | github.com/microsoft/go-mssqldb v1.6.0 // indirect
72 | github.com/mitchellh/mapstructure v1.5.0 // indirect
73 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
74 | github.com/modern-go/reflect2 v1.0.2 // indirect
75 | github.com/onsi/ginkgo v1.16.5 // indirect
76 | github.com/onsi/gomega v1.25.0 // indirect
77 | github.com/pelletier/go-toml/v2 v2.2.0 // indirect
78 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
79 | github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect
80 | github.com/shoenig/go-m1cpu v0.1.6 // indirect
81 | github.com/spf13/afero v1.9.5 // indirect
82 | github.com/spf13/cast v1.5.1 // indirect
83 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
84 | github.com/spf13/pflag v1.0.5 // indirect
85 | github.com/subosito/gotenv v1.4.2 // indirect
86 | github.com/tklauser/go-sysconf v0.3.12 // indirect
87 | github.com/tklauser/numcpus v0.6.1 // indirect
88 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
89 | github.com/ugorji/go/codec v1.2.12 // indirect
90 | github.com/yusufpapurcu/wmi v1.2.4 // indirect
91 | go.opentelemetry.io/otel v0.20.0 // indirect
92 | go.opentelemetry.io/otel/metric v0.20.0 // indirect
93 | go.opentelemetry.io/otel/trace v0.20.0 // indirect
94 | go.uber.org/dig v1.18.0 // indirect
95 | go.uber.org/multierr v1.10.0 // indirect
96 | golang.org/x/arch v0.7.0 // indirect
97 | golang.org/x/net v0.22.0 // indirect
98 | golang.org/x/sync v0.8.0 // indirect
99 | golang.org/x/sys v0.20.0 // indirect
100 | golang.org/x/text v0.14.0 // indirect
101 | google.golang.org/protobuf v1.33.0 // indirect
102 | gopkg.in/ini.v1 v1.67.0 // indirect
103 | gopkg.in/yaml.v3 v3.0.1 // indirect
104 | gorm.io/driver/mysql v1.5.7 // indirect
105 | gorm.io/driver/postgres v1.5.9 // indirect
106 | gorm.io/driver/sqlserver v1.5.3 // indirect
107 | gorm.io/plugin/dbresolver v1.5.3 // indirect
108 | modernc.org/libc v1.22.2 // indirect
109 | modernc.org/mathutil v1.5.0 // indirect
110 | modernc.org/memory v1.5.0 // indirect
111 | modernc.org/sqlite v1.20.3 // indirect
112 | stathat.com/c/consistent v1.0.0 // indirect
113 | )
114 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "github.com/wike2019/wike_go/pkg/core"
7 | "github.com/wike2019/wike_go/server/api"
8 | "github.com/wike2019/wike_go/server/service"
9 | "log"
10 | )
11 |
12 | func MyViper(core *core.GCore) *viper.Viper {
13 | //fmt.Println(core)
14 | viper.SetDefault("port", "8888")
15 | viper.SetDefault("logPath", "./logs/app.log")
16 | viper.SetDefault("development", true)
17 | viper.SetConfigFile("config.yaml") // 指定配置文件路径
18 | viper.SetConfigName("config") // 配置文件名称(无扩展名)
19 | viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项
20 | viper.AddConfigPath(".") // 还可以在工作目录中查找配置
21 | err := viper.ReadInConfig() // 查找并读取配置文件
22 | if err != nil { // 处理读取配置文件的错误
23 | log.Fatalf("Fatal error config file: %s \n", err.Error())
24 | }
25 | //业务参数
26 | viper.SetDefault("Timeout", 3000)
27 | core.Stop(func() error {
28 | fmt.Println("这里是viper清理")
29 | return nil
30 | })
31 | return viper.GetViper()
32 | }
33 | func main() {
34 | g := core.God()
35 | g.Stop(func() error {
36 | fmt.Println("这里做全局清理")
37 | return nil
38 | })
39 | g.Default().GlobalUse(core.CORSMiddleware()) //选择redis作为缓存服务的存储
40 | g.Provide(MyViper).Provide(service.CoreService).MountWithEmpty(api.NewRouter).Run()
41 | }
42 |
--------------------------------------------------------------------------------
/model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
12 | [matchers]
13 | m = r.sub == "root" ||(g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*"))
--------------------------------------------------------------------------------
/pkg/core/casbin.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/casbin/casbin/v2"
5 | "github.com/casbin/gorm-adapter/v3"
6 | "github.com/wike2019/wike_go/server/model"
7 | "go.uber.org/zap"
8 | "log"
9 | "os"
10 | )
11 |
12 | /*
13 | *
14 | 配置文件模版
15 | */
16 | const modelconfig = `
17 | [request_definition]
18 | r = sub, obj, act
19 |
20 | [policy_definition]
21 | p = sub, obj, act
22 |
23 | [role_definition]
24 | g = _, _
25 |
26 | [policy_effect]
27 | e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
28 | [matchers]
29 | m = r.sub == "root" ||(g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*"))
30 | `
31 |
32 | /*
33 | *
34 | 鉴权系统初始化方法,默认使用sqlite作为数据存储
35 | */
36 | func NewEnforcer(db *CoreDb) *casbin.Enforcer {
37 | _, err := os.Stat("model.conf")
38 | if os.IsNotExist(err) {
39 | os.WriteFile("model.conf", []byte(modelconfig), os.ModePerm)
40 | }
41 | adapter, err := gormadapter.NewAdapterByDB(db.DB)
42 | if err != nil {
43 | log.Fatalf("Failed to create NewAdapterByDB: %v", err)
44 | }
45 |
46 | // 2. 创建 Casbin Enforcer,加载模型和策略
47 | enforcer, err := casbin.NewEnforcer("model.conf", adapter)
48 | if err != nil {
49 | log.Fatalf("Failed to create enforcer: %v", err)
50 | }
51 |
52 | // 3. 加载策略(如果已经存在)
53 | err = enforcer.LoadPolicy()
54 | if err != nil {
55 | log.Fatalf("Failed to load policy: %v", err)
56 | }
57 | return enforcer
58 | }
59 |
60 | // 核心鉴权系统
61 | type RoleCtl struct {
62 | E *casbin.Enforcer
63 | zap *zap.Logger
64 | }
65 |
66 | func NewCtl(e *casbin.Enforcer, zap *zap.Logger) *RoleCtl {
67 | return &RoleCtl{E: e, zap: zap}
68 | }
69 |
70 | // 添加规则
71 | func (this *RoleCtl) AddRule(role string, path string, method string) {
72 | _, err := this.E.AddPolicy(role, path, method)
73 | if err != nil {
74 | this.zap.Error("添加鉴权规则失败:" + err.Error())
75 | }
76 | err = this.E.SavePolicy()
77 | if err != nil {
78 | this.zap.Error("保存鉴权规则失败:" + err.Error())
79 | }
80 | }
81 |
82 | // 添加角色
83 | func (this *RoleCtl) AddRole(role string, parentRole string) {
84 | _, err := this.E.AddGroupingPolicy(role, parentRole)
85 | if err != nil {
86 | this.zap.Error("添加继承关系失败:" + err.Error())
87 | }
88 | err = this.E.SavePolicy()
89 | if err != nil {
90 | this.zap.Error("保存继承规则失败:" + err.Error())
91 | }
92 | }
93 |
94 | func (this *RoleCtl) GetAllParentRoles(role string) []string {
95 | parentRoles := make(map[string]bool) // 使用 map 防止重复
96 | var dfs func(r string)
97 |
98 | dfs = func(r string) {
99 | roles, _ := this.E.GetRolesForUser(r) // 获取直接父角色
100 | for _, parentRole := range roles {
101 | if !parentRoles[parentRole] { // 防止重复访问
102 | parentRoles[parentRole] = true
103 | dfs(parentRole) // 递归获取父角色
104 | }
105 | }
106 | }
107 | dfs(role)
108 | // 将 map 转为 slice 返回
109 | result := make([]string, 0, len(parentRoles))
110 | for r := range parentRoles {
111 | result = append(result, r)
112 | }
113 | return result
114 | }
115 |
116 | func (this *RoleCtl) GetRulesForRole(role string) []model.Rule {
117 | // 使用 GetFilteredPolicy 按照第一个字段(角色/主体)筛选规则
118 | res := make([]model.Rule, 0)
119 | rules, _ := this.E.GetFilteredPolicy(0, role)
120 | for _, item := range rules {
121 | res = append(res, model.Rule{
122 | RuleSearch: model.RuleSearch{Role: item[0]},
123 | Path: item[1],
124 | Method: item[2],
125 | })
126 | }
127 | return res
128 | }
129 |
130 | func (this *RoleCtl) GetRulesForInheritRole(role string) []model.AggregatedRule {
131 | list := this.GetAllParentRoles(role)
132 | res := make([]model.Rule, 0)
133 | for _, item := range list {
134 | res = append(res, this.GetRulesForRole(item)...)
135 | }
136 | return this.AggregateRules(res)
137 | }
138 | func (this *RoleCtl) AggregateRules(rules []model.Rule) []model.AggregatedRule {
139 | // 使用 map 去重,key 是 path + method,value 是角色数组
140 | aggregatedMap := make(map[string]model.AggregatedRule)
141 |
142 | for _, rule := range rules {
143 | // 组合 Path 和 Method 作为 key
144 | key := rule.Path + "|" + rule.Method
145 |
146 | // 检查是否已经存在
147 | if aggregatedRule, exists := aggregatedMap[key]; exists {
148 | // 如果已存在,将 Role 添加到角色数组中(避免重复)
149 | exists := false
150 | for _, r := range aggregatedRule.Roles {
151 | if r == rule.Role {
152 | exists = true
153 | break
154 | }
155 | }
156 | if !exists {
157 | aggregatedRule.Roles = append(aggregatedRule.Roles, rule.Role)
158 | }
159 | aggregatedMap[key] = aggregatedRule
160 | } else {
161 | // 如果不存在,新建 AggregatedRule
162 | aggregatedMap[key] = model.AggregatedRule{
163 | Path: rule.Path,
164 | Method: rule.Method,
165 | Roles: []string{rule.Role},
166 | }
167 | }
168 | }
169 |
170 | // 将 map 转换为 slice
171 | result := make([]model.AggregatedRule, 0, len(aggregatedMap))
172 | for _, aggregatedRule := range aggregatedMap {
173 | result = append(result, aggregatedRule)
174 | }
175 |
176 | return result
177 | }
178 |
179 | func (this *RoleCtl) DeleteRulesForRole(role string) error {
180 | // 使用 RemoveFilteredPolicy 删除与指定角色相关的规则
181 | this.E.RemoveFilteredPolicy(0, role)
182 | // 保存更改到持久化存储
183 | err := this.E.SavePolicy()
184 | return err
185 | }
186 |
187 | func (this *RoleCtl) DeleteRoleInheritance(childRole string, parentRole string) error {
188 | // 删除该角色的所有继承关系
189 | this.E.RemoveGroupingPolicy(childRole, parentRole)
190 | // 保存到持久化存储
191 | err := this.E.SavePolicy()
192 | return err
193 | }
194 |
--------------------------------------------------------------------------------
/pkg/core/core.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/spf13/viper"
6 | "go.uber.org/fx"
7 | "go.uber.org/zap"
8 | )
9 |
10 | type GCore struct {
11 | gin *gin.Engine //gin引擎
12 | app *fx.App //依赖注入
13 | provides []interface{}
14 | supply []interface{}
15 | invokes []interface{}
16 | Controller []interface{}
17 | globalMiddleware []gin.HandlerFunc
18 | CronFunc []map[string]func()
19 | RoleCtl *RoleCtl
20 | StopRun []func() error
21 | Reject bool
22 | Zap *zap.Logger
23 | cfg *viper.Viper
24 | db *CoreDb
25 | }
26 |
27 | const IRoutes = "routes"
28 |
29 | func God() *GCore {
30 | //初始化核心对象
31 | return &GCore{
32 | gin: nil,
33 | Controller: make([]interface{}, 0),
34 | provides: make([]interface{}, 0),
35 | invokes: make([]interface{}, 0),
36 | globalMiddleware: make([]gin.HandlerFunc, 0),
37 | CronFunc: make([]map[string]func(), 0),
38 | StopRun: make([]func() error, 0),
39 | Zap: nil,
40 | cfg: nil,
41 | }
42 | }
43 | func (this *GCore) Run() {
44 | //通过依赖注入调用启动函数
45 | this.app = fx.New(
46 | Module,
47 | fx.NopLogger,
48 | fx.Provide(fx.Annotate(
49 | this.NewHTTPServer,
50 | fx.ParamTags(CreateGroup(IRoutes)), //将路由接口组注入进来
51 | )),
52 | fx.Supply(this.supply...), //注册supply
53 | fx.Provide(this.provides...), //注册 provides
54 | fx.Invoke(this.invokes...), //注册 invokes
55 | fx.Provide(this.Controller...), //注册 路由
56 | fx.Supply(this),
57 | )
58 | this.app.Run() //启动app
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/core/cron.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/wike2019/wike_go/pkg/func/ctl"
5 | )
6 |
7 | func (this *GCore) DefaultTask() {
8 | this.Cron("0 0 3 * * *", func() {
9 | // 在这里写入具体的任务逻辑
10 | ctl.ClearChan <- struct{}{}
11 | }, "每天清除数据任务")
12 | }
13 |
--------------------------------------------------------------------------------
/pkg/core/ctl.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/wike2019/wike_go/pkg/doc"
7 | "github.com/wike2019/wike_go/pkg/func/ctl"
8 | "net/http"
9 | "reflect"
10 | )
11 |
12 | // 权限注册函数
13 | func (this *GCore) GetWithRbac(r gin.IRoutes, group Controller, groupName string, path string, handler func(c *gin.Context) interface{}, name string) {
14 | query, body, header, output := group.GetInnerData()
15 | this.db.ApiTable(name, groupName, doc.Input(query, body, header), doc.Output(output), group.Path()+path, http.MethodGet)
16 | r.GET(path, WrapCtxHandler(handler))
17 |
18 | }
19 | func (this *GCore) PostWithRbac(r gin.IRoutes, group Controller, groupName string, path string, handler func(c *gin.Context) interface{}, name string) {
20 | query, body, header, output := group.GetInnerData()
21 | this.db.ApiTable(name, groupName, doc.Input(query, body, header), doc.Output(output), group.Path()+path, http.MethodPost)
22 | r.POST(path, WrapCtxHandler(handler))
23 | }
24 | func (this *GCore) DelWithRbac(r gin.IRoutes, group Controller, groupName string, path string, handler func(c *gin.Context) interface{}, name string) {
25 | query, body, header, output := group.GetInnerData()
26 | this.db.ApiTable(name, groupName, doc.Input(query, body, header), doc.Output(output), group.Path()+path, http.MethodDelete)
27 | r.DELETE(path, WrapCtxHandler(handler))
28 | }
29 | func (this *GCore) PutWithRbac(r gin.IRoutes, group Controller, groupName string, path string, handler func(c *gin.Context) interface{}, name string) {
30 | query, body, header, output := group.GetInnerData()
31 | this.db.ApiTable(name, groupName, doc.Input(query, body, header), doc.Output(output), group.Path()+path, http.MethodPut)
32 | r.PUT(path, WrapCtxHandler(handler))
33 | }
34 | func WrapCtxHandler(fn func(c *gin.Context) interface{}) gin.HandlerFunc {
35 | return func(c *gin.Context) {
36 | result := fn(c)
37 | if reflect.TypeOf(result).Kind() == reflect.String {
38 | str := fmt.Sprintf("%v", result)
39 | ctl.OKWithMsg(c, str)
40 | } else {
41 | ctl.OK(c, result)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/core/db.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/glebarez/sqlite"
5 | "github.com/wike2019/wike_go/server/model"
6 | "gorm.io/gorm"
7 | )
8 |
9 | type CoreDb struct {
10 | DB *gorm.DB
11 | }
12 |
13 | func InitDb() *CoreDb {
14 | dbSqlite, err := gorm.Open(sqlite.Open("./db/core.db"), &gorm.Config{})
15 | if err != nil {
16 | panic("failed to connect database")
17 | }
18 | err = dbSqlite.AutoMigrate(&model.API{}, &model.SysDictionary{}, &model.SysDictionaryDetail{}, &model.Job{}, &model.Rule{}, &model.Role{})
19 | if err != nil {
20 | panic("failed to migrate database")
21 | }
22 | dbSqlite.Model(model.API{}).Where("1=1").Update("status", 2)
23 | dbSqlite.Model(model.Job{}).Where("1=1").Delete(&model.Job{})
24 | return &CoreDb{
25 | DB: dbSqlite,
26 | }
27 | }
28 |
29 | func (this *CoreDb) ApiTable(name string, group string, input string, output string, path string, method string) *CoreDb {
30 | res := &model.API{
31 | APISearch: model.APISearch{
32 | Name: name,
33 | Group: group,
34 | Path: path,
35 | Method: method,
36 | },
37 | Input: input,
38 | Output: output,
39 |
40 | Status: 1,
41 | }
42 | historyApi := &model.API{}
43 | err := this.DB.Where("method=? and path=?", method, path).First(historyApi).Error
44 | if err != nil {
45 | this.DB.Create(res)
46 | } else {
47 | historyApi.Input = input
48 | historyApi.Output = output
49 | historyApi.Name = name
50 | historyApi.Group = group
51 | historyApi.Status = 1
52 | this.DB.Save(historyApi)
53 | }
54 |
55 | return this
56 | }
57 |
58 | func (this *CoreDb) GetData() map[string]model.APIGroup {
59 | var list []model.API
60 | this.DB.Find(&list)
61 |
62 | rs := make(map[string]model.APIGroup)
63 |
64 | for _, item := range list {
65 | // 检查分组是否存在
66 | group, ok := rs[item.Group]
67 | if !ok {
68 | // 如果分组不存在,初始化
69 | group = model.APIGroup{
70 | Group: item.Group,
71 | APIs: make([]model.API, 0),
72 | }
73 | }
74 |
75 | // 修改分组中的 API 列表
76 | group.APIs = append(group.APIs, item)
77 |
78 | // 将修改后的分组放回 map
79 | rs[item.Group] = group
80 | }
81 |
82 | return rs
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/core/helper.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "go.uber.org/fx"
6 | )
7 |
8 | // 将路由控制器转换成依赖注入模式
9 |
10 | func Create(fn interface{}, resultTag string, param []string) interface{} {
11 | if len(param) > 0 {
12 | return fx.Annotate(fn, fx.ParamTags(param...), fx.ResultTags(resultTag))
13 | } else {
14 | return fx.Annotate(fn, fx.ResultTags(resultTag))
15 | }
16 | }
17 | func CreateInterFace(fn interface{}, I interface{}, resultTag string, param []string) interface{} {
18 | if len(param) > 0 {
19 | return fx.Annotate(fn, fx.As(I), fx.ParamTags(param...), fx.ResultTags(resultTag))
20 | } else {
21 | return fx.Annotate(fn, fx.As(I), fx.ResultTags(resultTag))
22 | }
23 | }
24 | func ParamList(param ...string) []string {
25 | return param
26 | }
27 | func CreateTag(tag string) string {
28 | return fmt.Sprintf(` name:"%s" `, tag)
29 | }
30 | func CreateGroup(tag string) string {
31 | return fmt.Sprintf(` group:"%s" `, tag)
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/core/http.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "compress/gzip"
5 | "context"
6 | "fmt"
7 | ginGzip "github.com/gin-contrib/gzip"
8 | "github.com/gin-gonic/gin"
9 | "github.com/google/uuid"
10 | "github.com/spf13/viper"
11 | cronInit "github.com/wike2019/wike_go/pkg/func/cron"
12 | "github.com/wike2019/wike_go/pkg/tpl"
13 | "go.uber.org/fx"
14 | "go.uber.org/zap"
15 | "net"
16 | "net/http"
17 | "os"
18 | "text/template"
19 | "time"
20 | )
21 |
22 | func (this *GCore) NewHTTPServer(ControllerList []Controller, db *CoreDb, lc fx.Lifecycle, zap *zap.Logger, cfg *viper.Viper, defaultCron *cronInit.DefaultCron, roleCtl *RoleCtl) *http.Server {
23 |
24 | this.RoleCtl = roleCtl
25 | this.Zap = zap
26 | this.db = db
27 | this.cfg = cfg
28 | r := gin.New()
29 | this.gin = r //缓存gin
30 | this.gin.MaxMultipartMemory = 32 << 20 // 32 MiB //单次上传总文件最大大小
31 | this.gin.Use(this.globalMiddleware...)
32 | //健康检查路由
33 | r.GET("/api/v1/health", func(c *gin.Context) {
34 | c.String(200, "ok")
35 | })
36 | //获取唯一token路由
37 | r.GET("/api/v1/token", func(c *gin.Context) {
38 | token := uuid.NewString()
39 | c.String(200, token)
40 | })
41 | for _, route := range ControllerList {
42 | //注册路由
43 | group := r.Group(route.Path())
44 | route.Build(group, this)
45 | }
46 | srv := &http.Server{
47 | Addr: ":" + cfg.GetString("port"),
48 | Handler: this.gin,
49 | }
50 |
51 | lc.Append(fx.Hook{
52 | OnStart: func(ctx context.Context) error {
53 | ln, err := net.Listen("tcp", srv.Addr)
54 | if err != nil {
55 | zap.Error(err.Error())
56 | return err
57 | }
58 | zap.Debug(fmt.Sprintf("Starting HTTP server at %s", srv.Addr))
59 | go func() {
60 | if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
61 | zap.Error(fmt.Sprintf("HTTP server listen: %s\n", err))
62 | }
63 | }()
64 | this.DefaultTask()
65 | for _, item := range this.CronFunc {
66 | for k, v := range item {
67 | defaultCron.AddFunc(k, v)
68 | }
69 | }
70 |
71 | go func() {
72 | defaultCron.Start()
73 | }()
74 |
75 | tmpl, err := template.New("markdown").Parse(tpl.MdTemplate)
76 | if err != nil {
77 | zap.Fatal(err.Error())
78 | }
79 |
80 | // 创建 Markdown 文件
81 | file, err := os.Create("接口文档.md")
82 | if err != nil {
83 | zap.Fatal(err.Error())
84 | }
85 |
86 | // 渲染模板到文件
87 | err = tmpl.Execute(file, this.db.GetData())
88 | if err != nil {
89 | zap.Fatal(err.Error())
90 | }
91 |
92 | err = file.Close()
93 | if err != nil {
94 | zap.Fatal(err.Error())
95 | }
96 | return nil
97 | },
98 | OnStop: func(ctx context.Context) error {
99 | //清理资源
100 | defer zap.Sync()
101 | ctx, cancel := context.WithTimeout(ctx, time.Second*10)
102 | defer cancel()
103 | this.Reject = true
104 | defaultCron.Stop()
105 | srv.Shutdown(ctx)
106 | for _, job := range this.StopRun {
107 | err := job()
108 | if err != nil {
109 | zap.Error(fmt.Sprintf("stop func is error: %s\n", err))
110 | }
111 | }
112 | return nil
113 | },
114 | })
115 | return srv
116 | }
117 |
118 | func (this *GCore) Default() *GCore {
119 | this.GlobalUse(ginGzip.Gzip(gzip.DefaultCompression)) //开启压缩
120 | this.GlobalUse(Reject(this)) //优雅关闭
121 | this.GlobalUse(AddTrace())
122 | this.GlobalUse(CustomRecover(this)) //添加recover中间件和traceId中间件
123 | this.GlobalUse(AccessLog(this)) //添加访问日志中间件
124 | this.GlobalUse(LimitBodySize(32 << 20))
125 | return this //添加body数据长度限制中间件
126 | //全局中间件 //注册用户自定义全局中间件
127 | //this.gin.Use(rateLimiter.RateLimiter(rateLimiterCache, cfg)) //设置接口根据ip限流
128 | }
129 |
--------------------------------------------------------------------------------
/pkg/core/infra.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | cronInit "github.com/wike2019/wike_go/pkg/func/cron"
6 | zaplog "github.com/wike2019/wike_go/pkg/func/log"
7 | "github.com/wike2019/wike_go/server/model"
8 | "go.uber.org/fx"
9 | "net/http"
10 | "reflect"
11 | "runtime"
12 | )
13 |
14 | var Module = fx.Module("infra",
15 | fx.Provide(zaplog.GetLogger), //默认日志
16 | fx.Provide(cronInit.NewDefaultCron, NewEnforcer, NewCtl), //定时器任务 rbac权限
17 | fx.Invoke(func(*http.Server) {}),
18 | fx.Provide(InitDb),
19 | )
20 |
21 | // 用于没有参数的依赖注入
22 | func (this *GCore) Config(cfgs ...interface{}) *GCore {
23 | for _, cfg := range cfgs {
24 | t := reflect.TypeOf(cfg)
25 | if t.Kind() != reflect.Ptr {
26 | panic("required ptr object") //必须是指针对象
27 | }
28 | if t.Elem().Kind() != reflect.Struct {
29 | continue
30 | } //处理依赖注入 (new)
31 | v := reflect.ValueOf(cfg)
32 | for i := 0; i < t.NumMethod(); i++ {
33 | method := v.Method(i)
34 | callRet := method.Call(nil)
35 |
36 | if callRet != nil && len(callRet) == 1 {
37 | this.supply = append(this.supply, callRet[0].Interface())
38 | }
39 | }
40 | }
41 | return this
42 | }
43 |
44 | // 用于有注入参数的依赖注入
45 | func (this *GCore) Provide(list ...interface{}) *GCore {
46 | this.provides = append(this.provides, list...)
47 | return this
48 | }
49 |
50 | // 用于主动调用
51 | func (this *GCore) Invokes(list ...interface{}) *GCore {
52 | this.invokes = append(this.invokes, list...)
53 | return this
54 | }
55 |
56 | // 用于注册全局中间件
57 | func (this *GCore) GlobalUse(middleware ...gin.HandlerFunc) *GCore {
58 | this.globalMiddleware = append(this.globalMiddleware, middleware...)
59 | return this
60 | }
61 |
62 | // 用于挂载带参数控制器
63 | func (this *GCore) Mount(class interface{}, params []string) *GCore {
64 | this.Controller = append(this.Controller, CreateInterFace(class, new(Controller), CreateGroup("routes"), params))
65 | return this
66 | }
67 |
68 | // 用于挂载不带参数控制器
69 | func (this *GCore) MountWithEmpty(class interface{}) *GCore {
70 | this.Controller = append(this.Controller, CreateInterFace(class, new(Controller), CreateGroup("routes"), []string{}))
71 | return this
72 | }
73 |
74 | // 用于挂载已经存在带对象
75 | func (this *GCore) Supply(supply ...interface{}) *GCore {
76 | this.supply = append(this.supply, supply...)
77 | return this
78 | }
79 |
80 | // 用于添加定时任务
81 | func (this *GCore) Cron(spec string, cmd func(), Job string) *GCore {
82 | this.CronFunc = append(this.CronFunc, map[string]func(){spec: cmd})
83 | JobTask := &model.Job{
84 | JobSearch: model.JobSearch{
85 | Name: Job,
86 | },
87 | Cron: spec,
88 | Func: runtime.FuncForPC(reflect.ValueOf(cmd).Pointer()).Name(),
89 | }
90 | this.db.DB.Create(JobTask)
91 | return this
92 | }
93 | func (this *GCore) Stop(job func() error) *GCore {
94 | this.StopRun = append(this.StopRun, job)
95 | return this
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/core/interface.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | type Controller interface {
8 | Build(r *gin.RouterGroup, GCore *GCore)
9 | Path() string
10 | GetInnerData() (interface{}, interface{}, interface{}, interface{})
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/core/middleware.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "bytes"
5 | "github.com/casbin/casbin/v2"
6 | "github.com/gin-contrib/timeout"
7 | "github.com/gin-gonic/gin"
8 | "github.com/google/uuid"
9 | controller "github.com/wike2019/wike_go/pkg/func/ctl"
10 | "github.com/wike2019/wike_go/pkg/func/rateLimiter"
11 | "github.com/wike2019/wike_go/pkg/utils"
12 | "go.uber.org/zap"
13 | "golang.org/x/time/rate"
14 | "io"
15 | "net/http"
16 | "strconv"
17 | "time"
18 | )
19 |
20 | // 优雅关闭中间件
21 | func Reject(god *GCore) gin.HandlerFunc {
22 | return func(context *gin.Context) {
23 | if god.Reject {
24 | context.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"message": "服务暂时关闭", "code": 503})
25 | return
26 | }
27 | context.Next()
28 | }
29 | }
30 |
31 | // 请求追中中间件
32 | func AddTrace() gin.HandlerFunc {
33 | return func(context *gin.Context) {
34 | traceId := ""
35 | traceIdHeader := context.Request.Header.Get("trace_id")
36 | traceIdQuery := context.Query("trace_id")
37 | repeatCheck := context.Query("__check__")
38 | if repeatCheck == "check" && traceIdQuery == "" {
39 | context.AbortWithStatusJSON(200, gin.H{"message": "非法数据", "code": 200, "trace_id": "0"})
40 | return
41 | }
42 | if traceIdQuery != "" {
43 | traceId = traceIdQuery
44 | } else {
45 | traceId = traceIdHeader
46 | }
47 | if traceId == "" {
48 | traceId = uuid.NewString()
49 | }
50 | context.Set("trace_id", traceId)
51 | context.Next()
52 | }
53 | }
54 |
55 | // 日志中间件
56 | func AccessLog(god *GCore) gin.HandlerFunc {
57 | return func(ctx *gin.Context) {
58 | start := time.Now()
59 | path := ctx.Request.URL.Path
60 | raw := ctx.Request.URL.RawQuery
61 | var reqBody []byte
62 | if ctx.Request.Body != nil {
63 | reqBody, _ = io.ReadAll(ctx.Request.Body)
64 | ctx.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
65 | }
66 | ctx.Next()
67 | latency := time.Now().Sub(start)
68 | clientIP := ctx.ClientIP()
69 | method := ctx.Request.Method
70 | statusCode := ctx.Writer.Status()
71 | if raw != "" {
72 | path = path + "?" + raw
73 | }
74 | if ctx.Query("log") != "false" {
75 | god.Zap.Info("接口访问日志",
76 | zap.String("path", path),
77 | zap.String("method", method),
78 | zap.String("http_host", ctx.Request.Host),
79 | zap.String("ua", ctx.Request.UserAgent()),
80 | zap.String("remote_addr", ctx.Request.RemoteAddr),
81 | zap.Int("request_body_size", len(reqBody)),
82 | zap.Int("status_code", statusCode),
83 | zap.Int("error_code", ctx.GetInt("error_code")),
84 | zap.String("error_msg", ctx.GetString("error_code")),
85 | zap.String("client_ip", clientIP),
86 | zap.Duration("latency", latency),
87 | )
88 | }
89 |
90 | }
91 | }
92 |
93 | // 跨域中间
94 | func CORSMiddleware() gin.HandlerFunc {
95 | return func(c *gin.Context) {
96 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
97 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
98 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With,token")
99 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
100 |
101 | if c.Request.Method == "OPTIONS" {
102 | c.AbortWithStatus(http.StatusNoContent)
103 | return
104 | }
105 |
106 | c.Next()
107 | }
108 | }
109 |
110 | // 异常恢复中间件
111 | func CustomRecover(god *GCore) gin.HandlerFunc {
112 | return func(c *gin.Context) {
113 | defer func() {
114 | if err := recover(); err != nil {
115 | check, ok := err.(controller.StatusError)
116 | if ok {
117 | god.Zap.Warn("主动抛出的错误", zap.String("path", c.Request.URL.Path), zap.String("error", check.Msg), zap.Int("code", check.Code))
118 | c.AbortWithStatusJSON(check.Code, gin.H{"message": check.Msg, "code": check.Code, "trace_id": c.GetString("trace_id")})
119 | return
120 | }
121 | god.Zap.Error("接口错误", zap.String("path", c.Request.URL.Path), zap.String("error", err.(error).Error()))
122 | c.AbortWithStatusJSON(500, gin.H{"message": "Internal Server Error", "code": 500, "trace_id": c.GetString("trace_id")})
123 | return
124 | }
125 | }()
126 | c.Next()
127 | }
128 | }
129 |
130 | // body数据大小限制中间件
131 | func LimitBodySize(maxSize int64) gin.HandlerFunc {
132 | return func(c *gin.Context) {
133 | contentLength := c.Request.Header.Get("Content-Length")
134 | length, err := strconv.ParseInt(contentLength, 10, 64)
135 | if err != nil {
136 | c.Next()
137 | return
138 | }
139 | if length > maxSize {
140 | c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"error": "请求体内容太长", "trace_id": c.GetString("trace_id")})
141 | return
142 | }
143 | c.Next()
144 | }
145 | }
146 |
147 | // 超时中间件
148 | func TimeoutMiddleware(duration time.Duration) gin.HandlerFunc {
149 | return timeout.New(
150 | timeout.WithTimeout(duration),
151 | timeout.WithHandler(func(c *gin.Context) {
152 | c.Next()
153 | }),
154 | timeout.WithResponse(TimeOutResponse),
155 | )
156 | }
157 |
158 | // 超时响应
159 | func TimeOutResponse(c *gin.Context) {
160 | c.JSON(http.StatusGatewayTimeout, gin.H{
161 | "code": http.StatusGatewayTimeout,
162 | "msg": "系统超时了",
163 | "trace_id": c.GetString("trace_id"),
164 | })
165 | }
166 |
167 | // RBAC中间件
168 | func Authorizer(e *casbin.Enforcer) gin.HandlerFunc {
169 | return func(c *gin.Context) {
170 | role := c.GetString("role")
171 | ok, _ := e.Enforce(role, c.FullPath(), c.Request.Method)
172 | if !ok {
173 | c.JSON(http.StatusNonAuthoritativeInfo, gin.H{
174 | "code": http.StatusNonAuthoritativeInfo,
175 | "msg": "当前角色没有访问权限",
176 | "trace_id": c.GetString("trace_id"),
177 | })
178 | c.Abort()
179 | }
180 | }
181 | }
182 | func Root() gin.HandlerFunc {
183 | return func(c *gin.Context) {
184 | c.Set("role", "root")
185 | }
186 | }
187 |
188 | func RateLimiter(rateLimiterCache rateLimiter.GetLimit, r float64, b int) gin.HandlerFunc {
189 | return func(c *gin.Context) {
190 | limiter := rateLimiterCache.GetLimiter(utils.GetClientIP(c), rate.Limit(r), b)
191 | if limiter.Allow() == false {
192 | c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
193 | "code": http.StatusTooManyRequests,
194 | "error": "请求太频繁了",
195 | "trace_id": c.GetString("trace_id"),
196 | })
197 | return
198 | }
199 | c.Next()
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/pkg/doc/doc.go:
--------------------------------------------------------------------------------
1 | package doc
2 |
3 | import (
4 | "fmt"
5 | "gorm.io/gorm"
6 | "reflect"
7 | "strings"
8 | )
9 |
10 | func Input(query interface{}, body interface{}, header interface{}) string {
11 | str := ""
12 | str += "query参数\n"
13 | str += "---\n"
14 | str += generateStructTable(query, 2, 1, false, true)
15 | str += "---\n"
16 | str += "body参数\n"
17 | str += "---\n"
18 | str += generateStructTable(body, 1, 1, false, true)
19 | str += "---\n"
20 | str += "header参数\n"
21 | str += "---\n"
22 | str += generateStructTable(header, 3, 1, false, true)
23 | str += "---\n"
24 | return str
25 | }
26 | func Output(body interface{}) string {
27 | return generateStructTable(body, 1, 1, false, false) + "---\n"
28 | }
29 |
30 | // 生成表格的函数
31 | func generateStructTableIn(visited map[string]bool, data interface{}, dataType int, level int, Anonymous bool, short bool) string {
32 | if data == nil {
33 | return ""
34 | }
35 | stack := make([]string, 0)
36 |
37 | // 限制递归的最大深度
38 | const maxDepth = 7
39 | if level > maxDepth {
40 | return ""
41 | }
42 |
43 | t := reflect.TypeOf(data)
44 | v := reflect.ValueOf(data)
45 |
46 | // 如果是指针,解引用
47 | if t.Kind() == reflect.Ptr {
48 | t = t.Elem()
49 | if v.IsValid() && !v.IsNil() {
50 | v = v.Elem()
51 | }
52 | }
53 |
54 | // 如果不是结构体,返回空
55 | if t.Kind() != reflect.Struct {
56 | return ""
57 | }
58 |
59 | md := strings.Builder{}
60 | if !Anonymous {
61 | if level > 5 {
62 | level = 5
63 | }
64 | // 添加标题
65 | md.WriteString(fmt.Sprintf("%s## %s\n\n", strings.Repeat("#", level), t.Name()))
66 | }
67 |
68 | // 根据 dataType 确定标签名称
69 | defaultData := "| 字段名 | 类型 | 标签 (json) | 描述 | 是否必填 | 是否搜索 |\n"
70 | switch dataType {
71 | case 2:
72 | defaultData = "| 字段名 | 类型 | 标签 (form) | 描述 | 是否必填 | 是否搜索 |\n"
73 | case 3:
74 | defaultData = "| 字段名 | 类型 | 标签 (header) | 描述 | 是否必填 | 是否搜索 |\n"
75 | }
76 | defaultDataLine := "|--------|------|------------|------|------|------|\n"
77 | if short == false {
78 | defaultData = "| 字段名 | 类型 | 标签 (json) | 描述 | \n"
79 | defaultDataLine = "|--------|------|------------|------| \n"
80 | }
81 | if !Anonymous {
82 | // 表格标题
83 | md.WriteString(defaultData)
84 | md.WriteString(defaultDataLine)
85 | }
86 |
87 | // 遍历结构体字段
88 | for i := 0; i < t.NumField(); i++ {
89 | field := t.Field(i)
90 | fieldValue := v.Field(i)
91 |
92 | // 如果是匿名字段,将其字段展开到当前结构体
93 | if field.Anonymous {
94 | if fieldValue.Kind() == reflect.Struct || fieldValue.Kind() == reflect.Array || fieldValue.Kind() == reflect.Slice {
95 | // 使用结构体类型的名称来追踪已经访问过的结构体
96 | typeName := field.Type.String()
97 | if visited[typeName] && field.Tag.Get("deep") != "true" {
98 | // 如果该结构体已经访问过,则跳过
99 | continue
100 | }
101 | visited[typeName] = true
102 | }
103 | // 如果匿名字段是指针,需要解引用
104 | if fieldValue.Kind() == reflect.Ptr && !fieldValue.IsNil() {
105 | stack = append(stack, generateStructTableIn(visited, fieldValue.Elem().Interface(), dataType, level+1, field.Anonymous, short))
106 | } else if fieldValue.Kind() == reflect.Struct {
107 | stack = append(stack, generateStructTableIn(visited, fieldValue.Interface(), dataType, level+1, field.Anonymous, short))
108 | }
109 | continue
110 | }
111 |
112 | // 获取字段信息
113 | name := field.Name
114 | fieldType := field.Type.String()
115 | tag := field.Tag.Get("json")
116 | if dataType == 2 {
117 | tag = field.Tag.Get("form")
118 | } else if dataType == 3 {
119 | tag = field.Tag.Get("header")
120 | }
121 | description := field.Tag.Get("desc")
122 | if description == "" {
123 | description = field.Tag.Get("comment") // 支持 `comment` 标签
124 | }
125 | required := field.Tag.Get("required")
126 | require := "否"
127 | if required == "true" {
128 | require = "是"
129 | }
130 | search := "-"
131 | searched := field.Tag.Get("search")
132 | if searched != "" {
133 | search = "是"
134 | }
135 |
136 | // 判断是否是结构体或者数组类型
137 |
138 | if short == false {
139 | md.WriteString(fmt.Sprintf("| %s | %s | %s | %s | \n", name, fieldType, tag, description))
140 | } else {
141 | // 添加字段到表格
142 | md.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s | \n", name, fieldType, tag, description, require, search))
143 | }
144 |
145 | // 检查是否是标准库类型,如果是则跳过递归
146 | if isStandardLibraryType(field.Type) {
147 |
148 | continue
149 | }
150 | if fieldValue.Kind() == reflect.Struct || fieldValue.Kind() == reflect.Array || fieldValue.Kind() == reflect.Slice {
151 | // 使用结构体类型的名称来追踪已经访问过的结构体
152 | typeName := field.Type.String()
153 | if visited[typeName] && field.Tag.Get("deep") != "true" {
154 | // 如果该结构体已经访问过,则跳过
155 | continue
156 | }
157 | visited[typeName] = true
158 | }
159 | // 判断是否是可寻址的值类型
160 |
161 | //ptrAddr := fieldValue.Addr().Pointer()
162 | // 如果字段是指针类型,递归处理
163 | if fieldValue.Kind() == reflect.Ptr {
164 | // 如果是指针类型,解引用后递归处理
165 | if !fieldValue.IsNil() {
166 | stack = append(stack, generateStructTableIn(visited, fieldValue.Elem().Interface(), dataType, level+1, field.Anonymous, short))
167 | } else {
168 | if short == false {
169 | md.WriteString(fmt.Sprintf("| %s | %s | 空指针 | - | \n", name, fieldType))
170 | } else {
171 | // 添加字段到表格
172 | md.WriteString(fmt.Sprintf("| %s | %s | - | 空指针 | - |\n", name, fieldType))
173 | }
174 | }
175 | } else if fieldValue.Kind() == reflect.Struct {
176 | // 如果是结构体类型,递归处理
177 | stack = append(stack, generateStructTableIn(visited, fieldValue.Interface(), dataType, level+1, field.Anonymous, short))
178 | } else if fieldValue.Kind() == reflect.Slice {
179 | // 如果是切片,递归处理第一个元素
180 | elementType := field.Type.Elem() // 获取切片的元素类型
181 | if fieldValue.Len() > 0 {
182 | element := fieldValue.Index(0).Interface()
183 | if short == false {
184 | md.WriteString(fmt.Sprintf("| %s | []%s | %s | %s | \n", name, elementType.Name(), tag, description))
185 | } else {
186 | // 添加字段到表格
187 | md.WriteString(fmt.Sprintf("| %s | []%s | %s | %s | %s | %s | \n", name, elementType.Name(), tag, description, require, search))
188 | }
189 | stack = append(stack, generateStructTableIn(visited, element, dataType, level+1, field.Anonymous, short))
190 | } else {
191 | // 如果切片为空,递归生成切片元素类型
192 | emptyElement := reflect.New(elementType).Elem().Interface()
193 | stack = append(stack, generateStructTableIn(visited, emptyElement, dataType, level+1, field.Anonymous, short))
194 | }
195 | continue
196 | }
197 |
198 | }
199 |
200 | // 收集并返回最终结果
201 | res := md.String()
202 | for _, item := range stack {
203 | res += item
204 | }
205 | return res
206 | }
207 |
208 | // 生成表格的函数
209 | func generateStructTable(data interface{}, dataType int, level int, Anonymous bool, short bool) string {
210 | if data == nil {
211 | return ""
212 | }
213 | stack := make([]string, 0)
214 | visited := make(map[string]bool) // 用于记录已访问过的结构体类型名称
215 |
216 | // 限制递归的最大深度
217 | const maxDepth = 7
218 | if level > maxDepth {
219 | return ""
220 | }
221 |
222 | t := reflect.TypeOf(data)
223 | v := reflect.ValueOf(data)
224 |
225 | // 如果是指针,解引用
226 | if t.Kind() == reflect.Ptr {
227 | t = t.Elem()
228 | if v.IsValid() && !v.IsNil() {
229 | v = v.Elem()
230 | }
231 | }
232 |
233 | // 如果不是结构体,返回空
234 | if t.Kind() != reflect.Struct {
235 | return ""
236 | }
237 |
238 | md := strings.Builder{}
239 | if !Anonymous {
240 | if level > 5 {
241 | level = 5
242 | }
243 | // 添加标题
244 | md.WriteString(fmt.Sprintf("%s## %s\n\n", strings.Repeat("#", level), t.Name()))
245 | }
246 |
247 | // 根据 dataType 确定标签名称
248 | defaultData := "| 字段名 | 类型 | 标签 (json) | 描述 | 是否必填 | 是否搜索 |\n"
249 | switch dataType {
250 | case 2:
251 | defaultData = "| 字段名 | 类型 | 标签 (form) | 描述 | 是否必填 | 是否搜索 |\n"
252 | case 3:
253 | defaultData = "| 字段名 | 类型 | 标签 (header) | 描述 | 是否必填 | 是否搜索 |\n"
254 | }
255 | defaultDataLine := "|--------|------|------------|------|------|------|\n"
256 | if short == false {
257 | defaultData = "| 字段名 | 类型 | 标签 (json) | 描述 | \n"
258 | defaultDataLine = "|--------|------|------------|------| \n"
259 | }
260 | if !Anonymous {
261 | // 表格标题
262 | md.WriteString(defaultData)
263 | md.WriteString(defaultDataLine)
264 | }
265 |
266 | // 遍历结构体字段
267 | for i := 0; i < t.NumField(); i++ {
268 | field := t.Field(i)
269 | fieldValue := v.Field(i)
270 |
271 | // 如果是匿名字段,将其字段展开到当前结构体
272 | if field.Anonymous {
273 | if fieldValue.Kind() == reflect.Struct || fieldValue.Kind() == reflect.Array || fieldValue.Kind() == reflect.Slice {
274 | // 使用结构体类型的名称来追踪已经访问过的结构体
275 | typeName := field.Type.String()
276 | if visited[typeName] && field.Tag.Get("deep") != "true" {
277 | // 如果该结构体已经访问过,则跳过
278 | continue
279 | }
280 | visited[typeName] = true
281 | }
282 | // 如果匿名字段是指针,需要解引用
283 | if fieldValue.Kind() == reflect.Ptr && !fieldValue.IsNil() {
284 | stack = append(stack, generateStructTableIn(visited, fieldValue.Elem().Interface(), dataType, level+1, field.Anonymous, short))
285 | } else if fieldValue.Kind() == reflect.Struct {
286 | stack = append(stack, generateStructTableIn(visited, fieldValue.Interface(), dataType, level+1, field.Anonymous, short))
287 | }
288 | continue
289 | }
290 |
291 | // 获取字段信息
292 | name := field.Name
293 | fieldType := field.Type.String()
294 | tag := field.Tag.Get("json")
295 | if dataType == 2 {
296 | tag = field.Tag.Get("form")
297 | } else if dataType == 3 {
298 | tag = field.Tag.Get("header")
299 | }
300 | description := field.Tag.Get("desc")
301 | if description == "" {
302 | description = field.Tag.Get("comment") // 支持 `comment` 标签
303 | }
304 | required := field.Tag.Get("required")
305 | require := "否"
306 | if required == "true" {
307 | require = "是"
308 | }
309 | search := "-"
310 | searched := field.Tag.Get("search")
311 | if searched != "" {
312 | search = "是"
313 | }
314 |
315 | // 判断是否是结构体或者数组类型
316 |
317 | if short == false {
318 | md.WriteString(fmt.Sprintf("| %s | %s | %s | %s | \n", name, fieldType, tag, description))
319 | } else {
320 | // 添加字段到表格
321 | md.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s | \n", name, fieldType, tag, description, require, search))
322 | }
323 |
324 | // 检查是否是标准库类型,如果是则跳过递归
325 | if isStandardLibraryType(field.Type) {
326 |
327 | continue
328 | }
329 | if fieldValue.Kind() == reflect.Struct || fieldValue.Kind() == reflect.Array || fieldValue.Kind() == reflect.Slice {
330 | // 使用结构体类型的名称来追踪已经访问过的结构体
331 | typeName := field.Type.String()
332 | if visited[typeName] && field.Tag.Get("deep") != "true" {
333 | // 如果该结构体已经访问过,则跳过
334 | continue
335 | }
336 | visited[typeName] = true
337 | }
338 | // 判断是否是可寻址的值类型
339 |
340 | //ptrAddr := fieldValue.Addr().Pointer()
341 | // 如果字段是指针类型,递归处理
342 | if fieldValue.Kind() == reflect.Ptr {
343 | // 如果是指针类型,解引用后递归处理
344 | if !fieldValue.IsNil() {
345 | stack = append(stack, generateStructTableIn(visited, fieldValue.Elem().Interface(), dataType, level+1, field.Anonymous, short))
346 | } else {
347 | if short == false {
348 | md.WriteString(fmt.Sprintf("| %s | %s | 空指针 | - | \n", name, fieldType))
349 | } else {
350 | // 添加字段到表格
351 | md.WriteString(fmt.Sprintf("| %s | %s | - | 空指针 | - |\n", name, fieldType))
352 | }
353 | }
354 | } else if fieldValue.Kind() == reflect.Struct {
355 | // 如果是结构体类型,递归处理
356 | stack = append(stack, generateStructTableIn(visited, fieldValue.Interface(), dataType, level+1, field.Anonymous, short))
357 | } else if fieldValue.Kind() == reflect.Slice {
358 | // 如果是切片,递归处理第一个元素
359 | elementType := field.Type.Elem() // 获取切片的元素类型
360 | if fieldValue.Len() > 0 {
361 | element := fieldValue.Index(0).Interface()
362 | if short == false {
363 | md.WriteString(fmt.Sprintf("| %s | []%s | %s | %s | \n", name, elementType.Name(), tag, description))
364 | } else {
365 | // 添加字段到表格
366 | md.WriteString(fmt.Sprintf("| %s | []%s | %s | %s | %s | %s | \n", name, elementType.Name(), tag, description, require, search))
367 | }
368 | stack = append(stack, generateStructTableIn(visited, element, dataType, level+1, field.Anonymous, short))
369 | } else {
370 | // 如果切片为空,递归生成切片元素类型
371 | emptyElement := reflect.New(elementType).Elem().Interface()
372 | stack = append(stack, generateStructTableIn(visited, emptyElement, dataType, level+1, field.Anonymous, short))
373 | }
374 | continue
375 | }
376 |
377 | }
378 |
379 | // 收集并返回最终结果
380 | res := md.String()
381 | for _, item := range stack {
382 | res += item
383 | }
384 | return res
385 | }
386 |
387 | func isStandardLibraryType(t reflect.Type) bool {
388 | // 如果类型是 nil,直接返回 false
389 | if t == nil {
390 | return false
391 | }
392 | if t == reflect.TypeOf(gorm.DeletedAt{}) {
393 | return true
394 | }
395 | // 对切片类型的元素类型检查
396 | if t.Kind() == reflect.Slice {
397 | t = t.Elem()
398 | }
399 |
400 | // 获取包路径
401 | pkgPath := t.PkgPath()
402 |
403 | // Go 标准库的类型通常在 "" 或 "time" 等包路径中
404 | if pkgPath == "" || pkgPath == "time" {
405 | return true
406 | }
407 |
408 | return false
409 | }
410 |
--------------------------------------------------------------------------------
/pkg/func/bloomfilter/bloomfilter.go:
--------------------------------------------------------------------------------
1 | package bloomfilter
2 |
3 | import (
4 | "github.com/hugh2632/bloomfilter"
5 | )
6 |
7 | // 布隆过滤器
8 | type Bloom struct {
9 | Bloom bloomfilter.IFilter
10 | }
11 |
12 | // 默认布隆过滤器
13 | func NewBloom() *Bloom {
14 | return &Bloom{Bloom: bloomfilter.NewMemoryFilter(make([]byte, 10240), bloomfilter.DefaultHash...)}
15 | }
16 |
17 | func (this *Bloom) Exists(key string) bool {
18 | return this.Bloom.Exists([]byte(key))
19 | }
20 | func (this *Bloom) Clear() error {
21 | return this.Bloom.Clear()
22 | }
23 | func (this *Bloom) Add(key string) {
24 | this.Bloom.Push([]byte(key))
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/func/breaker/breaker.go:
--------------------------------------------------------------------------------
1 | package breaker
2 |
3 | import (
4 | "github.com/sony/gobreaker"
5 | "time"
6 | )
7 |
8 | var DefaultToTrip = func(counts gobreaker.Counts) bool {
9 | failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
10 | return counts.Requests >= 6 && failureRatio >= 0.6
11 | }
12 |
13 | func NewCircuitBreaker(name string, MaxRequests uint32, Interval time.Duration, Timeout time.Duration) *gobreaker.CircuitBreaker {
14 | var cbSettings gobreaker.Settings
15 | cbSettings.Name = name
16 | cbSettings.MaxRequests = MaxRequests
17 | cbSettings.Interval = Interval
18 | cbSettings.Timeout = Timeout
19 | cbSettings.ReadyToTrip = DefaultToTrip
20 | return gobreaker.NewCircuitBreaker(cbSettings)
21 | }
22 |
23 | func NewCircuitBreakerWithSettings(settings gobreaker.Settings) *gobreaker.CircuitBreaker {
24 | return gobreaker.NewCircuitBreaker(settings)
25 | }
26 |
27 | /*
28 | demo
29 | this.Breaker.Execute(this.Job)
30 | **/
31 |
--------------------------------------------------------------------------------
/pkg/func/cache/interface.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // 缓存接口
8 | type Cacher interface {
9 | Set(k string, x interface{}, d time.Duration)
10 | Get(k string, obj interface{}) bool
11 | Delete(k string) bool
12 | }
13 |
14 | // 实际缓存服务
15 | type Service struct {
16 | cache Cacher
17 | }
18 |
19 | func ServiceCache(cache Cacher) *Service {
20 | return &Service{cache: cache}
21 | }
22 |
23 | // 缓存业务函数
24 | func FindWithCallBack[T any](key string, time time.Duration, CacheService *Service, fn func() T) T {
25 | var data T
26 | ok := CacheService.cache.Get(key, &data)
27 | if ok {
28 | return data
29 | }
30 | data = fn()
31 | CacheService.cache.Set(key, data, time)
32 | return data
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/func/cache/memory/cache.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/patrickmn/go-cache"
6 | "time"
7 | )
8 |
9 | const (
10 | NoExpiration time.Duration = -1
11 | DefaultExpiration time.Duration = 0
12 | )
13 |
14 | // 内存缓存
15 | type Cache struct {
16 | MemoryCache *cache.Cache
17 | }
18 |
19 | func NewCache() *Cache {
20 | cache := cache.New(5*time.Minute, 10*time.Minute)
21 | return &Cache{MemoryCache: cache}
22 | }
23 |
24 | // 接口方法
25 | func (this *Cache) Get(k string, obj interface{}) bool {
26 | data, found := this.MemoryCache.Get(k)
27 | if found {
28 | err := json.Unmarshal(data.([]byte), obj)
29 | if err != nil {
30 | return false
31 | }
32 | return found
33 | }
34 | return false
35 | }
36 | func (this *Cache) Set(k string, x interface{}, d time.Duration) {
37 | b, _ := json.Marshal(x)
38 | this.MemoryCache.Set(k, b, d)
39 | }
40 |
41 | func (this *Cache) Delete(k string) bool {
42 | this.MemoryCache.Delete(k)
43 | return true
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/func/cache/memory/cache_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | type Test struct {
9 | Name string
10 | }
11 |
12 | func TestNewCache(t *testing.T) {
13 | c := NewCache()
14 | // Set the value of the key "foo" to "bar", with the default expiration time
15 |
16 | a := &Test{
17 | Name: "caasc",
18 | }
19 | c.Set("a", a, NoExpiration)
20 | c.Set("a1", "11131", NoExpiration)
21 | b := &Test{}
22 | c.Get("a", b)
23 | td := ""
24 | c.Get("a1", &td)
25 | fmt.Println(b.Name)
26 | fmt.Println(td)
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/func/cache/redis/cache.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "github.com/redis/go-redis/v9"
7 | "time"
8 | )
9 |
10 | // redis 缓存
11 | type Cache struct {
12 | redis *redis.Client
13 | }
14 |
15 | const (
16 | NoExpiration time.Duration = 0
17 | DefaultExpiration time.Duration = 5 * time.Minute
18 | )
19 |
20 | func NewCache(redisCache *redis.Client) *Cache {
21 | return &Cache{
22 | redis: redisCache,
23 | }
24 | }
25 |
26 | // 接口方法
27 | func (this *Cache) Set(k string, x interface{}, d time.Duration) {
28 | b, _ := json.Marshal(x)
29 | this.redis.Set(
30 | context.Background(),
31 | k,
32 | b,
33 | d,
34 | )
35 | }
36 | func (this *Cache) Get(k string, obj interface{}) bool {
37 | b, err := this.redis.Get(context.Background(), k).Result()
38 | if err == nil {
39 | err = json.Unmarshal([]byte(b), obj)
40 | if err == nil {
41 | return true
42 | }
43 | }
44 | return false
45 | }
46 |
47 | func (this *Cache) Delete(k string) bool {
48 | err := this.redis.Del(context.Background(), k).Err()
49 | if err == nil {
50 | return true
51 | }
52 | return false
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/func/cache/redis/cache_test.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | type Test struct {
8 | Name string
9 | }
10 |
11 | func TestNewCache(t *testing.T) {
12 |
13 | //c := NewCache()
14 | //// Set the value of the key "foo" to "bar", with the default expiration time
15 | //
16 | //a := &Test{
17 | // Name: "caasc",
18 | //}
19 | //c.Set("a", a, NoExpiration)
20 | //c.Set("a1", "1111", NoExpiration)
21 | //b := &Test{}
22 | //c.Get("a", b)
23 | //td := ""
24 | //c.Get("a1", &td)
25 | //fmt.Println(b.Name)
26 | //fmt.Println(td)
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/func/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "log"
6 | )
7 |
8 | // 配置中心
9 | func Config() *viper.Viper {
10 | viper.SetDefault("port", "8888")
11 | viper.SetDefault("logPath", "./logs/app.log")
12 | viper.SetDefault("development", true)
13 | viper.SetConfigFile("config.yaml") // 指定配置文件路径
14 | viper.SetConfigName("config") // 配置文件名称(无扩展名)
15 | viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项
16 | viper.AddConfigPath(".") // 还可以在工作目录中查找配置
17 | err := viper.ReadInConfig() // 查找并读取配置文件
18 | if err != nil { // 处理读取配置文件的错误
19 | log.Fatalf("Fatal error config file: %s \n", err.Error())
20 | }
21 | //业务参数
22 | viper.SetDefault("Timeout", 3000)
23 | viper.SetDefault("LimitRate", 512)
24 | viper.SetDefault("LimitBucket", 1024)
25 | viper.SetDefault("LRULimit", 4096*5)
26 | return viper.GetViper()
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/func/cron/cron.go:
--------------------------------------------------------------------------------
1 | package cronInit
2 |
3 | import (
4 | "github.com/robfig/cron"
5 | )
6 |
7 | type DefaultCron struct {
8 | *cron.Cron
9 | }
10 |
11 | // 定时任务函数
12 | func NewDefaultCron() *DefaultCron {
13 | return &DefaultCron{
14 | Cron: cron.New(),
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/func/ctl/base.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "errors"
5 | "github.com/gin-gonic/gin"
6 | "github.com/wike2019/wike_go/common"
7 | "io"
8 | "net/http"
9 | "strconv"
10 | )
11 |
12 | type Controller struct {
13 | Page
14 | *gin.Context
15 | query interface{}
16 | body interface{}
17 | header interface{}
18 | output interface{}
19 | Data []byte
20 | }
21 |
22 | func (r *Controller) clear() {
23 | r.query = nil
24 | r.body = nil
25 | r.header = nil
26 | r.output = nil
27 | r.Data = nil
28 | }
29 | func (r *Controller) SetDoc(input interface{}, output interface{}) {
30 | r.clear()
31 | r.getFuncMateInfo(input, output)
32 |
33 | }
34 | func (r *Controller) SetDocRaw(paramInstance interface{}, bodyInstance interface{}, headerInstance interface{}, returnInstance interface{}) {
35 | r.clear()
36 | r.query = paramInstance
37 | r.body = bodyInstance
38 | r.header = headerInstance
39 | r.output = returnInstance
40 | }
41 | func (r *Controller) SetEmpty() {
42 | r.clear()
43 | r.SetDocRaw(common.Empty{}, common.Empty{}, common.Empty{}, DataList[string]{})
44 | }
45 | func (r *Controller) SetEmptyWithHeader(header interface{}) {
46 | r.clear()
47 | r.SetDocRaw(common.Empty{}, common.Empty{}, header, DataList[string]{})
48 | }
49 | func (r *Controller) GetInnerData() (interface{}, interface{}, interface{}, interface{}) {
50 | defer r.clear()
51 | return r.query, r.body, r.header, r.output
52 | }
53 | func (r *Controller) PageInit() {
54 | queryStr := r.Context.Query("page")
55 | countStr := r.Context.Query("count")
56 | // 转换为 int
57 | if queryStr != "" {
58 | page, err := strconv.Atoi(queryStr)
59 | Error(err, 400)
60 | r.CurPage = page
61 | }
62 | if countStr != "" {
63 | count, err := strconv.Atoi(countStr)
64 | Error(err, 400)
65 | r.Count = count
66 | }
67 | if r.CurPage == 0 {
68 | r.CurPage = 1
69 | }
70 | if r.Count == 0 {
71 | r.Count = 10
72 | }
73 | r.Offset = (r.CurPage - 1) * r.Count
74 | }
75 | func (r *Controller) SetContext(ctx *gin.Context) *Controller {
76 | clone := new(Controller)
77 | body, err := io.ReadAll(ctx.Request.Body)
78 | if err != nil {
79 | Error(errors.New("参数错误"+err.Error()), 400)
80 | return nil
81 | }
82 | clone.Data = body
83 | clone.Context = ctx
84 | return clone
85 | }
86 | func (r *Controller) OK(data interface{}) {
87 | r.JSON(http.StatusOK, data)
88 | }
89 | func (r *Controller) OKWithMsg(msg string) {
90 | r.JSON(http.StatusOK, gin.H{
91 | "msg": msg,
92 | "traceId": r.GetString("trace_id"),
93 | "code": Success,
94 | })
95 | }
96 |
97 | func OK(r *gin.Context, data interface{}) {
98 | r.JSON(http.StatusOK, data)
99 | }
100 | func OKWithMsg(r *gin.Context, msg string) {
101 | r.JSON(http.StatusOK, gin.H{
102 | "msg": msg,
103 | "traceId": r.GetString("trace_id"),
104 | "code": Success,
105 | })
106 | }
107 | func List[T any](msg string, list []T, context *Controller) *PageList[T] {
108 | return &PageList[T]{
109 | Data: list,
110 | Page: context.Page,
111 | Msg: msg,
112 | TraceId: context.GetString("trace_id"),
113 | Code: Success,
114 | }
115 | }
116 | func Item[T any](msg string, list T, context *Controller) *DataList[T] {
117 | return &DataList[T]{
118 | Data: list,
119 | Msg: msg,
120 | TraceId: context.GetString("trace_id"),
121 | Code: Success,
122 | }
123 | }
124 |
125 | func (r *Controller) Failed(code errorCode, msg string) {
126 | errMsg := codeMsg[code] + ": " + msg
127 | if code != Success {
128 | r.Set("error_code", int(code))
129 | r.Set("error_msg", msg)
130 | }
131 | r.AbortWithStatusJSON(http.StatusOK, gin.H{
132 | "code": code,
133 | "msg": errMsg,
134 | "data": nil,
135 | "trace_id": r.GetString("trace_id"),
136 | })
137 | }
138 |
--------------------------------------------------------------------------------
/pkg/func/ctl/const.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | type errorCode int
4 |
5 | // 核心控制器基础方法
6 | const (
7 | Success errorCode = 200
8 | Failed errorCode = 500
9 | ParamError errorCode = 400
10 | NotFound errorCode = 404
11 | UnAuthorized errorCode = 401
12 | )
13 |
14 | var codeMsg = map[errorCode]string{
15 | Success: "正常",
16 | Failed: "系统异常",
17 | ParamError: "参数错误",
18 | NotFound: "记录不存在",
19 | UnAuthorized: "未授权",
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/func/ctl/curd.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "fmt"
5 | "gorm.io/gorm"
6 | "reflect"
7 | "regexp"
8 | "strings"
9 | )
10 |
11 | type DBChange func(db *gorm.DB) *gorm.DB
12 |
13 | // GetGormColumnMap 解析结构体并返回字段名到数据库列名的映射
14 | func GetGormColumnMap(obj interface{}, db *gorm.DB) (*gorm.DB, error) {
15 | columnMap := make(map[string]string)
16 |
17 | // 获取结构体的类型
18 | t := reflect.TypeOf(obj)
19 | v := reflect.ValueOf(obj)
20 | // 如果是指针,获取其元素
21 | if t.Kind() == reflect.Ptr {
22 | t = t.Elem()
23 | v = v.Elem()
24 | }
25 |
26 | // 确保传入的是结构体
27 | if t.Kind() != reflect.Struct {
28 | return nil, fmt.Errorf("传入的参数不是结构体")
29 | }
30 |
31 | // 遍历结构体的所有字段
32 | for i := 0; i < t.NumField(); i++ {
33 | field := t.Field(i)
34 | value := v.Field(i)
35 | // 跳过匿名字段
36 | if field.Anonymous {
37 | continue
38 | }
39 |
40 | // 获取字段名
41 | fieldName := field.Name
42 |
43 | // 获取 GORM 标签
44 | gormTag := field.Tag.Get("gorm")
45 |
46 | columnName := ""
47 |
48 | if gormTag != "" {
49 | // 解析标签,查找是否有 "column" 指定
50 | tags := strings.Split(gormTag, ";")
51 | for _, tag := range tags {
52 | tag = strings.TrimSpace(tag)
53 | if strings.HasPrefix(tag, "column:") {
54 | columnName = strings.TrimPrefix(tag, "column:")
55 | break
56 | }
57 | }
58 | }
59 |
60 | if columnName == "" {
61 | // 如果没有指定 column 标签,使用默认的命名规则
62 | columnName = CamelCaseToSnakeCase(fieldName)
63 | }
64 |
65 | //columnMap[fieldName] = columnName
66 | orderTag := field.Tag.Get("order")
67 | if orderTag != "" {
68 | db = db.Order(fmt.Sprintf(" `%s` %s ", columnName, orderTag))
69 | }
70 |
71 | searchTag := field.Tag.Get("search")
72 |
73 | op := field.Tag.Get("op")
74 | if op == "" {
75 | op = "="
76 | }
77 |
78 | if searchTag == "true" && !value.IsZero() {
79 | columnMap[fieldName] = columnName
80 | db = db.Where(fmt.Sprintf(" `%s` %s ?", columnName, op), value.Interface())
81 | }
82 | if searchTag == "or" && !value.IsZero() {
83 | columnMap[fieldName] = columnName
84 | db = db.Or(fmt.Sprintf(" `%s` %s ?", columnName, op), value.Interface())
85 | }
86 |
87 | if searchTag == "like" && !value.IsZero() {
88 | columnMap[fieldName] = columnName
89 | db = db.Where(fmt.Sprintf(" `%s` like ?", columnName), fmt.Sprintf("%%%v%%", value.Interface()))
90 | }
91 | }
92 |
93 | return db, nil
94 | }
95 |
96 | // CamelCaseToSnakeCase 将驼峰式命名转换为下划线式命名
97 | func CamelCaseToSnakeCase(str string) string {
98 | var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
99 | var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
100 |
101 | snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
102 | snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
103 | return strings.ToLower(snake)
104 | }
105 |
106 | func CreateItem[T any](db *gorm.DB, obj T) error {
107 | return db.Create(obj).Error
108 | }
109 | func UpdateItem[T any](db *gorm.DB, obj T, id uint) error {
110 | var model T
111 | return db.Model(model).Where("id=?", id).Debug().Updates(obj).Error
112 | }
113 | func DeleteItem[T any](db *gorm.DB, id uint) error {
114 | var model T
115 | return db.Model(model).Where("id=?", id).Debug().Delete(&model).Error
116 | }
117 |
118 | func ListItem[T any](db *gorm.DB, Offset, Count int, obj interface{}, call DBChange) ([]T, int64, error) {
119 | var list []T
120 | var model T
121 | count := int64(0)
122 | db = db.Model(model)
123 | db, err := GetGormColumnMap(obj, db)
124 | if err != nil {
125 | return list, 0, err
126 | }
127 | if call != nil {
128 | db = call(db)
129 | }
130 | err = db.Count(&count).Error
131 | if err != nil {
132 | return list, 0, err
133 | }
134 |
135 | err = db.Limit(Count).Offset(Offset).Debug().Find(&list).Error
136 | if err != nil {
137 | return list, 0, err
138 | }
139 | return list, count, nil
140 | }
141 | func GetItemByID[T any](db *gorm.DB, id int, call DBChange) (T, error) {
142 | var res T
143 | if call != nil {
144 | db = call(db)
145 | }
146 | err := db.Where("id=?", id).First(&res).Error
147 | return res, err
148 | }
149 |
150 | func GetItem[T any](db *gorm.DB, id uint, call DBChange) (T, error) {
151 | var res T
152 | var model T
153 | db = db.Model(model)
154 |
155 | if call != nil {
156 | db = call(db)
157 | }
158 | err := db.Debug().Where("id=?", id).First(&res).Error
159 |
160 | if err != nil {
161 | return res, err
162 | }
163 | return res, nil
164 | }
165 | func GetItemByFunc[T any](db *gorm.DB, call DBChange) (T, error) {
166 | var res T
167 | var model T
168 | db = db.Model(model)
169 |
170 | if call != nil {
171 | db = call(db)
172 | }
173 | err := db.Debug().First(&res).Error
174 |
175 | if err != nil {
176 | return res, err
177 | }
178 | return res, nil
179 | }
180 | func ListItemAll[T any](db *gorm.DB, obj interface{}, call DBChange) ([]T, error) {
181 | var list []T
182 | var model T
183 | db = db.Model(model)
184 | db, err := GetGormColumnMap(obj, db)
185 | if err != nil {
186 | return list, err
187 | }
188 | if call != nil {
189 | db = call(db)
190 | }
191 | err = db.Debug().Find(&list).Error
192 | if err != nil {
193 | return list, err
194 | }
195 | return list, nil
196 | }
197 |
--------------------------------------------------------------------------------
/pkg/func/ctl/error.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "errors"
5 | "gorm.io/gorm"
6 | )
7 |
8 | type StatusError struct {
9 | Msg string `json:"msg"`
10 | Code int `json:"code"`
11 | }
12 |
13 | func Error(err error, code int) {
14 | if err != nil {
15 | panic(StatusError{Msg: err.Error(), Code: code})
16 | }
17 | }
18 | func (r *Controller) IsExist(err error) bool {
19 | if errors.Is(err, gorm.ErrRecordNotFound) {
20 | return false
21 | }
22 | return true
23 | }
24 | func IsExist(err error) bool {
25 | if errors.Is(err, gorm.ErrRecordNotFound) {
26 | return false
27 | }
28 | return true
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/func/ctl/fastRouter/core.go:
--------------------------------------------------------------------------------
1 | package fastRouter
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gin-gonic/gin"
6 | "github.com/wike2019/wike_go/common"
7 | "github.com/wike2019/wike_go/pkg/core"
8 | "github.com/wike2019/wike_go/pkg/func/ctl"
9 | "gorm.io/gorm"
10 | )
11 |
12 | func FastRouter[Q common.IDSearcher, T any, H any](DB *gorm.DB, DBInstance *core.CoreDb, r *gin.RouterGroup, GCore *core.GCore, path string, name string, groupName string, prefix string) {
13 | res := &FastInstance[Q, T, H]{
14 | DB: DB,
15 | path: path,
16 | name: name,
17 | prefix: prefix,
18 | groupName: groupName,
19 | DBInstance: DBInstance,
20 | }
21 | res.Build(r, GCore)
22 | }
23 |
24 | type FastInstance[Q common.IDSearcher, T any, H any] struct {
25 | ctl.Controller
26 | DB *gorm.DB
27 | path string
28 | name string
29 | groupName string
30 | prefix string
31 | DBInstance *core.CoreDb
32 | }
33 |
34 | func (this *FastInstance[Q, T, H]) Build(r *gin.RouterGroup, GCore *core.GCore) {
35 | var query Q
36 | var data T
37 | var header H
38 | this.SetDocRaw(common.Empty{}, data, header, ctl.DataList[string]{})
39 | GCore.PostWithRbac(r, this, this.groupName, this.path+"/create", this.Create, "添加"+this.name)
40 | this.SetDocRaw(query, data, header, ctl.DataList[string]{})
41 | GCore.PostWithRbac(r, this, this.groupName, this.path+"/update", this.Update, "修改"+this.name)
42 | this.SetDocRaw(query, common.Empty{}, header, ctl.DataList[string]{})
43 | GCore.DelWithRbac(r, this, this.groupName, this.path+"/delete", this.Del, "删除"+this.name)
44 | }
45 |
46 | func (this *FastInstance[Q, T, H]) Path() string {
47 | if this.prefix[0] != '/' {
48 | this.prefix = "/" + this.prefix
49 | }
50 | return this.prefix
51 | }
52 |
53 | func (this *FastInstance[Q, T, H]) Create(context *gin.Context) interface{} {
54 | c := this.SetContext(context)
55 | var data T
56 | err := json.Unmarshal(c.Data, &data)
57 | ctl.Error(err, 400)
58 | err = ctl.CreateItem(this.DB, &data)
59 | ctl.Error(err, 400)
60 | return "添加" + this.name + "成功"
61 | }
62 | func (this *FastInstance[Q, T, H]) Update(context *gin.Context) interface{} {
63 | var query Q
64 | var data T
65 | err := context.ShouldBindQuery(&query)
66 | ctl.Error(err, 400)
67 | c := this.SetContext(context)
68 | err = json.Unmarshal(c.Data, &data)
69 | ctl.Error(err, 400)
70 | id := query.GetID()
71 | err = ctl.UpdateItem[T](this.DB, data, id)
72 | ctl.Error(err, 400)
73 | return "修改" + this.name + "成功"
74 | }
75 |
76 | func (this *FastInstance[Q, T, H]) Del(context *gin.Context) interface{} {
77 | var query Q
78 | err := context.ShouldBindQuery(&query)
79 | ctl.Error(err, 400)
80 | this.SetContext(context)
81 | id := query.GetID()
82 | err = ctl.DeleteItem[T](this.DB, id)
83 | ctl.Error(err, 400)
84 | return "删除" + this.name + "成功"
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/func/ctl/func.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | func (r *Controller) getFuncMateInfo(input interface{}, output interface{}) {
9 | // 获取函数的类型信息
10 | funcVal := reflect.ValueOf(output)
11 | funcType := funcVal.Type()
12 |
13 | funcInVal := reflect.ValueOf(input)
14 | funcInType := funcInVal.Type()
15 |
16 | // 确保传入的确实是一个函数
17 | if funcType.Kind() != reflect.Func {
18 | panic("类型错误")
19 | }
20 | if funcInType.Kind() != reflect.Func {
21 | panic("类型错误")
22 | }
23 |
24 | // 获取函数的参数类型和返回值类型
25 | if funcType.NumOut() != 2 {
26 | panic(fmt.Sprintf("%s-%d", "数据不合法", funcType.NumOut()))
27 | }
28 | if funcInType.NumOut() != 4 {
29 | panic(fmt.Sprintf("%s-%d", "数据不合法", funcInType.NumOut()))
30 | }
31 | // 获取参数类型(假设为结构体A)
32 | paramType := funcInType.Out(0).Elem()
33 | bodyType := funcInType.Out(1).Elem()
34 | headerType := funcInType.Out(2).Elem()
35 | // 获取返回值类型(假设为结构体B)
36 | returnType := funcType.Out(0).Elem()
37 |
38 | // 使用反射创建参数类型和返回值类型的实例
39 | paramInstance := reflect.New(paramType)
40 | bodyInstance := reflect.New(bodyType)
41 | headerInstance := reflect.New(headerType)
42 | returnInstance := reflect.New(returnType)
43 | r.query = paramInstance.Interface()
44 | r.body = bodyInstance.Interface()
45 | r.header = headerInstance.Interface()
46 | r.output = returnInstance.Interface()
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/func/ctl/json.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "database/sql/driver"
5 | "encoding/json"
6 | "errors"
7 | )
8 |
9 | type Arr []interface{}
10 |
11 | // MarshalJSON 自定义 JSON 序列化
12 | func (t Arr) MarshalJSON() ([]byte, error) {
13 | return json.Marshal([]interface{}(t))
14 | }
15 |
16 | // UnmarshalJSON 自定义 JSON 反序列化
17 | func (t *Arr) UnmarshalJSON(data []byte) error {
18 | var m []interface{}
19 | if err := json.Unmarshal(data, &m); err != nil {
20 | return err
21 | }
22 | *t = m
23 | return nil
24 | }
25 |
26 | // Value 将 Json 转换为数据库存储的字符串
27 | func (t Arr) Value() (driver.Value, error) {
28 | if t == nil {
29 | return "[]", nil
30 | }
31 | return json.Marshal(t)
32 | }
33 |
34 | // Scan 从数据库中加载 JSON 数据
35 | func (t *Arr) Scan(value interface{}) error {
36 | switch v := value.(type) {
37 | case string: // 如果是字符串
38 | return t.parseJsonString(v)
39 | case []byte: // 如果是字节数组
40 | return t.parseJsonString(string(v))
41 | default: // 其他类型无法处理
42 | return errors.New("failed to scan Json: value is not a valid JSON type")
43 | }
44 | }
45 |
46 | // 解析 JSON 字符串为 []interface{}
47 | func (t *Arr) parseJsonString(str string) error {
48 | var m []interface{}
49 | if err := json.Unmarshal([]byte(str), &m); err != nil {
50 | return err
51 | }
52 | *t = m
53 | return nil
54 | }
55 |
56 | // Json 定义为 map[string]interface{} 的别名
57 | type Json map[string]interface{}
58 |
59 | // MarshalJSON 自定义 JSON 序列化
60 | func (t Json) MarshalJSON() ([]byte, error) {
61 | return json.Marshal(map[string]interface{}(t))
62 | }
63 |
64 | // UnmarshalJSON 自定义 JSON 反序列化
65 | func (t *Json) UnmarshalJSON(data []byte) error {
66 | var m map[string]interface{}
67 | if err := json.Unmarshal(data, &m); err != nil {
68 | return err
69 | }
70 | *t = m
71 | return nil
72 | }
73 |
74 | // Value 将 Json 转换为数据库存储的字符串
75 | func (t Json) Value() (driver.Value, error) {
76 | if t == nil {
77 | return "{}", nil
78 | }
79 | return json.Marshal(t)
80 | }
81 |
82 | // Scan 从数据库中加载 JSON 数据
83 | func (t *Json) Scan(value interface{}) error {
84 | switch v := value.(type) {
85 | case string: // 如果是字符串
86 | return t.parseJsonString(v)
87 | case []byte: // 如果是字节数组
88 | return t.parseJsonString(string(v))
89 | default: // 其他类型无法处理
90 | return errors.New("failed to scan Json: value is not a valid JSON type")
91 | }
92 | }
93 |
94 | // 解析 JSON 字符串为 map[string]interface{}
95 | func (t *Json) parseJsonString(str string) error {
96 | var m map[string]interface{}
97 | if err := json.Unmarshal([]byte(str), &m); err != nil {
98 | return err
99 | }
100 | *t = m
101 | return nil
102 | }
103 |
--------------------------------------------------------------------------------
/pkg/func/ctl/page.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | type Page struct {
8 | SumPage int `json:"sumPage" desc:"总页数"` // 总页数
9 | SumCount int `json:"sumCount" desc:"总条数"` // 总条数
10 | CurPage int `json:"page" form:"page" desc:"当前页" required:"true" ` // 当前页
11 | Offset int `json:"-"` // 起始量
12 | Count int `json:"count" form:"count" desc:"每页返回数量" required:"true"` // 每页返回数量
13 | }
14 |
15 | // PageTime 适用 列表-分页-时间范围
16 | type Time struct {
17 | StartTime int64 `json:"startTime" form:"startTime"` // 开始时间
18 | EndTime int64 `json:"endTime" form:"endTime"` // 结束时间
19 | }
20 |
21 | // FormatPage 格式化分页数据-起始量
22 |
23 | // FormatTotal 格式化分页数据-总页数
24 | func (p *Page) FormatTotal(total int64) {
25 | p.SumCount = int(total)
26 | if p.Count > 0 {
27 | p.SumPage = int(math.Ceil(float64(p.SumCount) / float64(p.Count)))
28 | } else {
29 | p.SumPage = 1
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/func/ctl/repeatCheck.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/wike2019/wike_go/plugin"
6 | "gorm.io/gorm"
7 | "time"
8 | )
9 |
10 | // 一致性避免重试不幂等
11 | func RepeatCheck(context *gin.Context, tx *gorm.DB) error {
12 | traceId, _ := context.Get("trace_id")
13 | data := &plugin.RepeatCheck{
14 | TraceId: traceId.(string),
15 | }
16 | return tx.Create(data).Error
17 | }
18 |
19 | var ClearChan chan struct{}
20 |
21 | func init() {
22 | ClearChan = make(chan struct{}, 10)
23 | }
24 |
25 | // todo
26 | func Clear(db *gorm.DB) {
27 | for _ = range ClearChan {
28 | //定期清理老数据
29 | for {
30 | result := db.Where("time < ?", time.Now().AddDate(0, 0, -2)).Limit(5000).Delete(&plugin.RepeatCheck{})
31 | if result.Error != nil {
32 | continue
33 | }
34 | if result.RowsAffected < 5000 {
35 | break
36 | }
37 | }
38 | }
39 | }
40 |
41 | // 开启幂等服务
42 | func RepeatCheckStart(db *gorm.DB) {
43 | db.AutoMigrate(&plugin.RepeatCheck{})
44 | Clear(db)
45 | }
46 |
47 | // 关闭幂等服务
48 | func RepeatCheckStop() {
49 | close(ClearChan)
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/func/ctl/returnType.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | type PageList[T any] struct {
4 | Data []T `json:"data"`
5 | Page Page `json:"page"`
6 | Msg string `json:"msg"`
7 | TraceId string `json:"trace_id" desc:"追踪id"`
8 | Code errorCode `json:"code"`
9 | }
10 |
11 | type DataList[T any] struct {
12 | Msg string `json:"msg"`
13 | TraceId string `json:"trace_id" desc:"追踪id"`
14 | Code errorCode `json:"code"`
15 | Data T `json:"data"`
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/func/ctl/time.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "database/sql/driver"
5 | "encoding/json"
6 | "fmt"
7 | "strings"
8 | "time"
9 | )
10 |
11 | type LocalTimeInt int64
12 |
13 | func (t LocalTimeInt) MarshalJSON() ([]byte, error) {
14 | tTime := time.Unix(int64(t), 0)
15 | // 如果时间值是空或者0值 返回为null 如果写空字符串会报错
16 | if &t == nil || tTime.IsZero() {
17 | return []byte("null"), nil
18 | }
19 | return []byte(fmt.Sprintf("\"%s\"", tTime.Format("2006-01-02 15:04:05"))), nil
20 | }
21 |
22 | // UnmarshalJSON 为 LocalTimeInt 类型自定义 JSON 反序列化方法
23 | func (lti *LocalTimeInt) UnmarshalJSON(data []byte) error {
24 | // 将 JSON 中的数字解析为 int64
25 | var timestamp int64
26 | if err := json.Unmarshal(data, ×tamp); err != nil {
27 | return err
28 | }
29 |
30 | // 将 int64 的时间戳转换为 time.Time,然后更新 LocalTimeInt 的值
31 | *lti = LocalTimeInt(timestamp)
32 | return nil
33 | }
34 |
35 | // ToTime 方法将 LocalTimeInt 类型转换为 time.Time
36 | func (lti LocalTimeInt) ToTime() time.Time {
37 | return time.Unix(int64(lti), 0)
38 | }
39 |
40 | type LocalTime time.Time
41 |
42 | func (t LocalTime) Value() (driver.Value, error) {
43 | var zeroTime time.Time
44 | tlt := time.Time(t)
45 | if tlt.UnixNano() == zeroTime.UnixNano() {
46 | return nil, nil
47 | }
48 | return tlt, nil
49 | }
50 |
51 | func (t *LocalTime) Scan(v interface{}) error {
52 | if value, ok := v.(time.Time); ok {
53 | *t = LocalTime(value)
54 | return nil
55 | }
56 | return fmt.Errorf("can not convert %v to timestamp", v)
57 | }
58 |
59 | func (t *LocalTime) String() string {
60 | // 如果时间 null 那么我们需要把返回的值进行修改
61 | if t == nil || t.IsZero() {
62 | return ""
63 | }
64 | return fmt.Sprintf("%s", time.Time(*t).Format("2006-01-02 15:04:05"))
65 | }
66 |
67 | func (t *LocalTime) IsZero() bool {
68 | return time.Time(*t).IsZero()
69 | }
70 |
71 | func (t *LocalTime) UnmarshalJSON(data []byte) error {
72 |
73 | if string(data) == "null" {
74 | return nil
75 | }
76 | var err error
77 | //前端接收的时间字符串
78 | str := string(data)
79 | //去除接收的str收尾多余的"
80 | timeStr := strings.Trim(str, "\"")
81 |
82 | local, _ := time.LoadLocation("Asia/Shanghai")
83 |
84 | t1, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, local)
85 | *t = LocalTime(t1)
86 | return err
87 | }
88 |
89 | func (t LocalTime) MarshalJSON() ([]byte, error) {
90 | tTime := time.Time(t)
91 | // 如果时间值是空或者0值 返回为null 如果写空字符串会报错
92 | if &t == nil || t.IsZero() {
93 | return []byte("null"), nil
94 | }
95 | return []byte(fmt.Sprintf("\"%s\"", tTime.Format("2006-01-02 15:04:05"))), nil
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/func/ctl/ws.go:
--------------------------------------------------------------------------------
1 | package ctl
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/gorilla/websocket"
6 | "github.com/wike2019/wike_go/pkg/utils"
7 | "net/http"
8 | "sync"
9 | "time"
10 | )
11 |
12 | const Default = "default"
13 |
14 | // websocket 广播函数
15 | var wsUpGrader = websocket.Upgrader{
16 | ReadBufferSize: 1024,
17 | WriteBufferSize: 1024,
18 | // 允许跨域
19 | CheckOrigin: func(r *http.Request) bool {
20 | return true
21 | },
22 | }
23 |
24 | // websocket 注册函数
25 | func WsHandler(c *gin.Context) {
26 | roomId := c.DefaultQuery("room", Default)
27 | conn, err := wsUpGrader.Upgrade(c.Writer, c.Request, nil)
28 | if err != nil {
29 | http.NotFound(c.Writer, c.Request)
30 | return
31 | }
32 | record := WC.Data.Get(roomId)
33 | record = append(record, conn)
34 | WC.Data.Set(roomId, record)
35 | }
36 |
37 | var WC *WsClient
38 |
39 | func init() {
40 | WC = &WsClient{
41 | Data: utils.NewMap[[]*websocket.Conn](),
42 | }
43 | go func() {
44 | for {
45 | time.Sleep(10 * time.Minute)
46 | for _, k := range WC.Data.Keys() {
47 | ping(k)
48 | }
49 | }
50 | }()
51 | }
52 |
53 | type WsClient struct {
54 | Data *utils.MapSync[[]*websocket.Conn]
55 | lock sync.Mutex
56 | }
57 |
58 | func Broadcast(room string, data string) {
59 | // 创建一个新的切片存储仍然有效的连接
60 | WC.lock.Lock()
61 | defer WC.lock.Unlock()
62 | activeConns := make([]*websocket.Conn, 0)
63 | connList := WC.Data.Get(room)
64 | for _, conn := range connList {
65 | conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
66 | // 尝试向连接发送消息
67 | if err := conn.WriteMessage(websocket.TextMessage, []byte(data)); err != nil {
68 | conn.Close()
69 | } else {
70 | activeConns = append(activeConns, conn)
71 | }
72 | }
73 |
74 | WC.Data.Set(room, activeConns)
75 | }
76 | func ping(room string) {
77 | WC.lock.Lock()
78 | defer WC.lock.Unlock()
79 | // 创建一个新的切片存储仍然有效的连接
80 | activeConns := make([]*websocket.Conn, 0)
81 | connList := WC.Data.Get(room)
82 | for _, conn := range connList {
83 | conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
84 | // 尝试向连接发送消息
85 | if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
86 | conn.Close()
87 | } else {
88 | activeConns = append(activeConns, conn)
89 | }
90 | }
91 | WC.Data.Set(room, activeConns)
92 | }
93 |
--------------------------------------------------------------------------------
/pkg/func/hash/consistent.go:
--------------------------------------------------------------------------------
1 | package hash
2 |
3 | import "github.com/stathat/consistent"
4 |
5 | type Hash struct {
6 | *consistent.Consistent
7 | }
8 |
9 | func New() *Hash {
10 | return &Hash{
11 | Consistent: consistent.New(),
12 | }
13 | }
14 | func (this *Hash) Get(key string) (string, error) {
15 | return this.Consistent.Get(key)
16 | }
17 | func (this *Hash) Add(node string) {
18 | this.Consistent.Add(node)
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/func/jwt/jwt.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "fmt"
5 | "github.com/golang-jwt/jwt/v4"
6 | "time"
7 | )
8 |
9 | type InfoData[T any] struct {
10 | Core T
11 | jwt.RegisteredClaims
12 | }
13 |
14 | func Create[T any](info T, duration time.Duration, SECRET string, Issuer string) (string, error) {
15 | // Create a new token
16 | token := jwt.NewWithClaims(jwt.SigningMethodHS256,
17 | InfoData[T]{
18 | Core: info,
19 | RegisteredClaims: jwt.RegisteredClaims{
20 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(duration)),
21 | Issuer: Issuer,
22 | },
23 | })
24 |
25 | // Sign the token with a secret key
26 | tokenString, err := token.SignedString([]byte(SECRET))
27 | if err != nil {
28 | return "", err
29 | }
30 | return tokenString, nil
31 | }
32 | func Parse[T any](token string, SECRET string) (*T, error) {
33 | var res InfoData[T]
34 | // Parse the token
35 | info, err := jwt.ParseWithClaims(token, &res, func(token *jwt.Token) (interface{}, error) {
36 | return []byte(SECRET), nil
37 | })
38 | if err != nil {
39 | return nil, err
40 | }
41 | if claims, ok := info.Claims.(*InfoData[T]); ok && info.Valid {
42 | return &claims.Core, nil
43 | } else {
44 | return nil, fmt.Errorf("token不合法,或者已过期")
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/func/jwt/jwt_test.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | type Info struct {
10 | Name string
11 | }
12 |
13 | func TestJWT(t *testing.T) {
14 | core := Info{
15 | Name: "我是wike",
16 | }
17 | //生成一个token
18 | token, err := Create[Info](core, time.Second*10)
19 | fmt.Println(token, err)
20 | //根据token得到对象
21 | info, err := Parse[Info](token)
22 | fmt.Println(info, err)
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/func/log/zap.go:
--------------------------------------------------------------------------------
1 | package zaplog
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "github.com/wike2019/wike_go/pkg/func/memorylog"
6 | "go.uber.org/zap"
7 | "go.uber.org/zap/zapcore"
8 | "gopkg.in/natefinch/lumberjack.v2"
9 | "os"
10 | "sync"
11 | )
12 |
13 | var once sync.Once
14 | var logger *zap.Logger
15 |
16 | func GetLogger(*viper.Viper) *zap.Logger {
17 | once.Do(func() {
18 | encoderConfig := zapcore.EncoderConfig{
19 | // Keys can be anything except the empty string.
20 | TimeKey: "T",
21 | LevelKey: "L",
22 | NameKey: "N",
23 | CallerKey: "C",
24 | MessageKey: "M",
25 | StacktraceKey: "S",
26 | LineEnding: zapcore.DefaultLineEnding,
27 | EncodeLevel: zapcore.LowercaseLevelEncoder,
28 | EncodeTime: zapcore.ISO8601TimeEncoder,
29 | EncodeDuration: zapcore.SecondsDurationEncoder,
30 | EncodeCaller: zapcore.ShortCallerEncoder, // ShortCallerEncoder will print function name and line number: pkg/file.go:line
31 | }
32 | Level := zap.InfoLevel
33 | if viper.GetBool("development") {
34 | Level = zap.DebugLevel
35 | }
36 | // 创建一个写入文件的 zapcore.Core
37 | fileSyncWriter := zapcore.AddSync(&lumberjack.Logger{
38 | Filename: viper.GetString("logPath"),
39 | MaxSize: 100,
40 | MaxBackups: 3,
41 | MaxAge: 7,
42 | Compress: true,
43 | LocalTime: true,
44 | })
45 | fileCore := zapcore.NewCore(
46 | zapcore.NewConsoleEncoder(encoderConfig),
47 | fileSyncWriter,
48 | Level,
49 | )
50 |
51 | // 创建一个写入控制台的 zapcore.Core
52 | consoleSyncWriter := zapcore.AddSync(os.Stdout)
53 | consoleEncoderConfig := zap.NewDevelopmentEncoderConfig()
54 | consoleEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
55 | consoleCore := zapcore.NewCore(
56 | zapcore.NewConsoleEncoder(encoderConfig),
57 | consoleSyncWriter,
58 | Level,
59 | )
60 | memorylog.LogInfo = &memorylog.LogStruct{
61 | Log: make(memorylog.LogList, 0, 2000),
62 | }
63 | tempSyncWriter := zapcore.AddSync(memorylog.LogInfo)
64 | tempSyncCore := zapcore.NewCore(
65 | zapcore.NewConsoleEncoder(encoderConfig),
66 | tempSyncWriter,
67 | Level,
68 | )
69 |
70 | // 使用 zapcore.NewTee 创建一个包含两个 zapcore.Core 的 zapcore.Core
71 | core := zapcore.NewTee(fileCore, consoleCore, tempSyncCore)
72 |
73 | // 创建 zap 日志记录器
74 | logger = zap.New(core, zap.AddCaller())
75 | })
76 | return logger
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/func/memorylog/log.go:
--------------------------------------------------------------------------------
1 | package memorylog
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // 内存日志
8 | type Log struct {
9 | Text string
10 | }
11 |
12 | type LogList []*Log
13 |
14 | type LogStruct struct {
15 | Log LogList
16 | }
17 |
18 | var LogInfo *LogStruct
19 |
20 | func (this *LogStruct) Write(text []byte) (int, error) {
21 | var old LogList
22 | if len(this.Log) >= 2001 {
23 | old = this.Log[:2000]
24 | } else {
25 | old = this.Log
26 | }
27 | this.Log = append([]*Log{{Text: string(text)}}, old...)
28 | return len(text), nil
29 | }
30 |
31 | func (this *LogStruct) Show() {
32 | for _, item := range this.Log {
33 | fmt.Println(item.Text)
34 | }
35 | }
36 | func (this *LogStruct) All() LogList {
37 | return this.Log
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/func/memorylog/log_test.go:
--------------------------------------------------------------------------------
1 | package memorylog
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestLog(t *testing.T) {
9 | count := 0
10 | f := &LogStruct{}
11 | for {
12 | count = count + 1
13 |
14 | f.Write([]byte(fmt.Sprintf("%d", count)))
15 | //fmt.Println(count)
16 | if count == 3000 {
17 | break
18 | }
19 | }
20 | fmt.Println("ok")
21 |
22 | f.Show()
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/func/rateLimiter/rateLimiter.go:
--------------------------------------------------------------------------------
1 | package rateLimiter
2 |
3 | import (
4 | "github.com/golang/groupcache/lru"
5 | "golang.org/x/time/rate"
6 | "sync"
7 | )
8 |
9 | type LimiterCache struct {
10 | cache *lru.Cache
11 | mu sync.Mutex
12 | }
13 |
14 | func NewRateLimiterCache(maxEntries int) GetLimit {
15 | return &LimiterCache{
16 | cache: lru.New(maxEntries),
17 | }
18 | }
19 |
20 | func (rlc *LimiterCache) GetLimiter(key string, r rate.Limit, b int) *rate.Limiter {
21 | rlc.mu.Lock()
22 | defer rlc.mu.Unlock()
23 |
24 | if lim, ok := rlc.cache.Get(key); ok {
25 | return lim.(*rate.Limiter)
26 | }
27 |
28 | lim := rate.NewLimiter(r, b)
29 | rlc.cache.Add(key, lim)
30 | return lim
31 | }
32 | func RateLimit() GetLimit {
33 | return NewRateLimiterCache(40960)
34 | }
35 |
36 | type GetLimit interface {
37 | GetLimiter(key string, r rate.Limit, b int) *rate.Limiter
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/func/retry/retry.go:
--------------------------------------------------------------------------------
1 | package retry
2 |
3 | import (
4 | "github.com/avast/retry-go"
5 | "time"
6 | )
7 |
8 | type Retry struct {
9 | job func() error
10 | times int
11 | delay time.Duration
12 | }
13 |
14 | func NewRetry(job func() error) *Retry {
15 | return &Retry{job: job, times: 3, delay: time.Second * 1}
16 | }
17 | func (r *Retry) Do() error {
18 | return retry.Do(r.job, retry.Attempts(uint(r.times)), retry.Delay(r.delay), retry.LastErrorOnly(true), retry.DelayType(retry.BackOffDelay))
19 | }
20 | func (r *Retry) SetTimes(times int) *Retry {
21 | r.times = times
22 | return r
23 | }
24 | func (r *Retry) SetDelay(delay time.Duration) *Retry {
25 | r.delay = delay
26 | return r
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/modules/ants_service/ants.go:
--------------------------------------------------------------------------------
1 | package ants_service
2 |
3 | import (
4 | "github.com/panjf2000/ants/v2"
5 | "sync"
6 | )
7 |
8 | type AntsCtl struct {
9 | *ants.Pool
10 | *sync.WaitGroup
11 | Total int
12 | Ok int
13 | err error
14 | Fail int
15 | }
16 |
17 | func NewPool(size int) (*AntsCtl, error) {
18 | pool, err := ants.NewPool(size)
19 | return &AntsCtl{
20 | Pool: pool,
21 | WaitGroup: &sync.WaitGroup{},
22 | }, err
23 | }
24 | func (this *AntsCtl) SetTotal(total int) {
25 | this.Total = total
26 | this.Add(this.Total)
27 | }
28 | func (this *AntsCtl) Submit(task func() error) error {
29 | return this.Pool.Submit(func() {
30 | defer this.Done()
31 | err := task()
32 | if err != nil {
33 | this.Fail++
34 | if this.err == nil {
35 | this.err = err
36 | }
37 | return
38 | }
39 | this.Ok++
40 | })
41 | }
42 | func (this *AntsCtl) Error() error {
43 | return this.err
44 | }
45 | func (this *AntsCtl) Wait() {
46 | this.WaitGroup.Wait()
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/os/serverMetrics.go:
--------------------------------------------------------------------------------
1 | package os
2 |
3 | import (
4 | "github.com/shirou/gopsutil/v3/cpu"
5 | "github.com/shirou/gopsutil/v3/disk"
6 | "github.com/shirou/gopsutil/v3/mem"
7 | "runtime"
8 | "time"
9 | )
10 |
11 | const (
12 | B = 1
13 | KB = 1024 * B
14 | MB = 1024 * KB
15 | GB = 1024 * MB
16 | )
17 |
18 | type Server struct {
19 | Os Os `json:"os"`
20 | Cpu Cpu `json:"cpu"`
21 | Ram Ram `json:"ram"`
22 | Disk *Disk `json:"disk"`
23 | }
24 |
25 | type Os struct {
26 | GOOS string `json:"goos"`
27 | NumCPU int `json:"numCpu"`
28 | Compiler string `json:"compiler"`
29 | GoVersion string `json:"goVersion"`
30 | NumGoroutine int `json:"numGoroutine"`
31 | }
32 |
33 | type Cpu struct {
34 | Cpus []float64 `json:"cpus"`
35 | Cores int `json:"cores"`
36 | }
37 |
38 | type Ram struct {
39 | UsedMB int `json:"usedMb"`
40 | TotalMB int `json:"totalMb"`
41 | UsedPercent int `json:"usedPercent"`
42 | }
43 |
44 | type Disk struct {
45 | MountPoint string `json:"mountPoint"`
46 | UsedMB int `json:"usedMb"`
47 | UsedGB int `json:"usedGb"`
48 | TotalMB int `json:"totalMb"`
49 | TotalGB int `json:"totalGb"`
50 | UsedPercent int `json:"usedPercent"`
51 | }
52 |
53 | func InitOS() (o Os) {
54 | o.GOOS = runtime.GOOS
55 | o.NumCPU = runtime.NumCPU()
56 | o.Compiler = runtime.Compiler
57 | o.GoVersion = runtime.Version()
58 | o.NumGoroutine = runtime.NumGoroutine()
59 | return o
60 | }
61 |
62 | func InitCPU() (c Cpu, err error) {
63 | if cores, err := cpu.Counts(false); err != nil {
64 | return c, err
65 | } else {
66 | c.Cores = cores
67 | }
68 | if cpus, err := cpu.Percent(time.Duration(200)*time.Millisecond, true); err != nil {
69 | return c, err
70 | } else {
71 | c.Cpus = cpus
72 | }
73 | return c, nil
74 | }
75 |
76 | func InitRAM() (r Ram, err error) {
77 | if u, err := mem.VirtualMemory(); err != nil {
78 | return r, err
79 | } else {
80 | r.UsedMB = int(u.Used) / MB
81 | r.TotalMB = int(u.Total) / MB
82 | r.UsedPercent = int(u.UsedPercent)
83 | }
84 | return r, nil
85 | }
86 |
87 | func InitDisk(path string) (d *Disk, err error) {
88 | u, err := disk.Usage(path)
89 | if err != nil {
90 | return nil, err
91 | }
92 | return &Disk{
93 | MountPoint: path,
94 | UsedMB: int(u.Used) / MB,
95 | UsedGB: int(u.Used) / GB,
96 | TotalMB: int(u.Total) / MB,
97 | TotalGB: int(u.Total) / GB,
98 | UsedPercent: int(u.UsedPercent),
99 | }, nil
100 | }
101 |
--------------------------------------------------------------------------------
/pkg/tpl/tpl.go:
--------------------------------------------------------------------------------
1 | package tpl
2 |
3 | const MdTemplate = `# 接口文档
4 |
5 | {{- range . }}
6 | ## 接口分组:{{ .Group }}
7 |
8 | {{- range .APIs }}
9 |
10 | ### 接口名称:{{ .Name }} {{ if eq .Status 2 }}~~已废弃~~{{ end }}
11 |
12 | #### 请求路径:{{ .Path }}
13 |
14 | #### 请求方式:{{ .Method }}
15 |
16 | #### 请求参数:
17 |
18 | {{ .Input }}
19 |
20 | #### 请求结果:
21 |
22 | {{ .Output }}
23 |
24 | {{ end }}
25 | {{ end }}
26 | `
27 |
--------------------------------------------------------------------------------
/pkg/utils/copy.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "reflect"
7 | )
8 |
9 | // 对象拷贝
10 | func CopyProperties(dst, src interface{}) (err error) {
11 | // 防止意外panic
12 | defer func() {
13 | if e := recover(); e != nil {
14 | err = errors.New(fmt.Sprintf("%v", e))
15 | }
16 | }()
17 | dstType, dstValue := reflect.TypeOf(dst), reflect.ValueOf(dst)
18 | srcType, srcValue := reflect.TypeOf(src), reflect.ValueOf(src)
19 | // dst必须结构体指针类型
20 | if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct {
21 | return errors.New("dst type should be a struct pointer")
22 | }
23 | // src必须为结构体或者结构体指针
24 | if srcType.Kind() == reflect.Ptr {
25 | srcType, srcValue = srcType.Elem(), srcValue.Elem()
26 | }
27 | if srcType.Kind() != reflect.Struct {
28 | return errors.New("src type should be a struct or a struct pointer")
29 | }
30 | // 取具体内容
31 | dstType, dstValue = dstType.Elem(), dstValue.Elem()
32 | // 属性个数
33 | propertyNums := dstType.NumField()
34 | for i := 0; i < propertyNums; i++ {
35 | // 属性
36 | property := dstType.Field(i)
37 | // 待填充属性值
38 | propertyValue := srcValue.FieldByName(property.Name)
39 | // 无效,说明src没有这个属性 || 属性同名但类型不同
40 | if !propertyValue.IsValid() || property.Type != propertyValue.Type() {
41 | continue
42 | }
43 | if dstValue.Field(i).CanSet() {
44 | dstValue.Field(i).Set(propertyValue)
45 | }
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/utils/duration.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | "time"
7 | )
8 |
9 | func ParseDuration(d string) (time.Duration, error) {
10 | d = strings.TrimSpace(d)
11 | dr, err := time.ParseDuration(d)
12 | if err == nil {
13 | return dr, nil
14 | }
15 | if strings.Contains(d, "d") {
16 | index := strings.Index(d, "d")
17 |
18 | hour, _ := strconv.Atoi(d[:index])
19 | dr = time.Hour * 24 * time.Duration(hour)
20 | ndr, err := time.ParseDuration(d[index+1:])
21 | if err != nil {
22 | return dr, nil
23 | }
24 | return dr + ndr, nil
25 | }
26 |
27 | dv, err := strconv.ParseInt(d, 10, 64)
28 | return time.Duration(dv), err
29 | }
30 |
31 | //ParseDuration("1d5h20m")
32 | //ParseDuration("1d")
33 | //ParseDuration("5h20m")
34 |
--------------------------------------------------------------------------------
/pkg/utils/ip.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "strings"
6 | )
7 |
8 | func GetClientIP(c *gin.Context) string {
9 | ClientIP := c.ClientIP()
10 | RemoteIP := c.RemoteIP()
11 | ip := c.Request.Header.Get("X-Forwarded-For")
12 | if strings.Contains(ip, "127.0.0.1") || ip == "" {
13 | ip = c.Request.Header.Get("X-real-ip")
14 | }
15 | if ip == "" {
16 | ip = "127.0.0.1"
17 | }
18 | if RemoteIP != "127.0.0.1" {
19 | ip = RemoteIP
20 | }
21 | if ClientIP != "127.0.0.1" {
22 | ip = ClientIP
23 | }
24 | return ip
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/utils/lock.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "github.com/bsm/redislock"
6 | "github.com/redis/go-redis/v9"
7 | "time"
8 | )
9 |
10 | type RedisLock struct {
11 | client *redis.Client
12 | mutex *redislock.Client
13 | LockData *redislock.Lock
14 | }
15 |
16 | func NewRedisLock(client *redis.Client) *RedisLock {
17 | return &RedisLock{client: client}
18 | }
19 | func (RedisLock) String() string {
20 | return "redis"
21 | }
22 |
23 | func (r *RedisLock) Lock(key string, ttl int64, options *redislock.Options) (*redislock.Lock, error) {
24 | if r.mutex == nil {
25 | r.mutex = redislock.New(r.client)
26 | }
27 | lock, err := r.mutex.Obtain(context.TODO(), key, time.Duration(ttl)*time.Second, options)
28 | r.LockData = lock
29 | // 设置一个定时任务来自动续期锁
30 | go func() {
31 | for {
32 | time.Sleep(5 * time.Second) // 每 5 秒续期一次
33 | err := r.LockData.Refresh(context.Background(), 10*time.Second, options) // 延长锁的有效期
34 | if err != nil {
35 | return
36 | }
37 | }
38 | }()
39 | return r.LockData, err
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/utils/map.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "sync"
4 |
5 | // 并发安全map
6 | type MapSync[T any] struct {
7 | m map[string]T
8 | lock sync.RWMutex
9 | }
10 |
11 | func (this *MapSync[T]) Set(key string, val T) {
12 | this.lock.Lock()
13 | defer this.lock.Unlock()
14 | this.m[key] = val
15 | }
16 |
17 | func (this *MapSync[T]) Get(key string) T {
18 | this.lock.RLock()
19 | defer this.lock.RUnlock()
20 | return this.m[key]
21 | }
22 | func (this *MapSync[T]) Delete(key string) {
23 | this.lock.Lock()
24 | defer this.lock.Unlock()
25 | delete(this.m, key)
26 | }
27 |
28 | func (this *MapSync[T]) Keys() []string {
29 | this.lock.RLock()
30 | defer this.lock.RUnlock()
31 | keys := make([]string, 0)
32 | for k := range this.m {
33 | keys = append(keys, k)
34 | }
35 | return keys
36 | }
37 | func (this *MapSync[T]) Values() []T {
38 | this.lock.RLock()
39 | defer this.lock.RUnlock()
40 | values := make([]T, 0)
41 | for k := range this.m {
42 | values = append(values, this.m[k])
43 | }
44 | return values
45 | }
46 | func NewMap[T any]() *MapSync[T] {
47 | return &MapSync[T]{m: make(map[string]T)}
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/utils/md5.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | )
7 |
8 | func CheckMd5(content []byte, chunkMd5 string) (isSame bool) {
9 | fileMd5 := MD5V(content)
10 | if fileMd5 == chunkMd5 {
11 | return true // 可以继续上传
12 | } else {
13 | return false // 切片不完整,废弃
14 | }
15 | }
16 | func MD5V(str []byte, b ...byte) string {
17 | h := md5.New()
18 | h.Write(str)
19 | return hex.EncodeToString(h.Sum(b))
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/utils/number.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "github.com/shopspring/decimal"
6 | "strconv"
7 | )
8 |
9 | // 数学计算
10 | func Decimal(num float64) float64 {
11 | num, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", num), 64)
12 | return num
13 | }
14 |
15 | // AddDecimal 加法
16 | func AddDecimal(d1 decimal.Decimal, d2 decimal.Decimal) decimal.Decimal {
17 | return d1.Add(d2)
18 | }
19 |
20 | // SubDecimal 减法
21 | func SubDecimal(d1 decimal.Decimal, d2 decimal.Decimal) decimal.Decimal {
22 | return d1.Sub(d2)
23 | }
24 |
25 | // MulDecimal 乘法
26 | func MulDecimal(d1 decimal.Decimal, d2 decimal.Decimal) decimal.Decimal {
27 | return d1.Mul(d2)
28 | }
29 |
30 | // DivDecimal 除法
31 | func DivDecimal(d1 decimal.Decimal, d2 decimal.Decimal) decimal.Decimal {
32 | return d1.Div(d2)
33 | }
34 |
35 | // IntDecimal 转换成int
36 | func IntDecimal(d decimal.Decimal) int64 {
37 | return d.IntPart()
38 | }
39 |
40 | // DecimalFloat 转换成float
41 | func DecimalFloat(d decimal.Decimal) float64 {
42 | f, exact := d.Float64()
43 | if !exact {
44 | return f
45 | }
46 | return 0
47 | }
48 |
49 | // FloatDecimal 浮点型到decimal
50 | func FloatDecimal(d float64) decimal.Decimal {
51 | return decimal.NewFromFloat(d)
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/utils/password.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "golang.org/x/crypto/bcrypt"
4 |
5 | // 加解密 密码
6 | func PasswordHash(pwd string) (string, error) {
7 | bytes, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
8 | if err != nil {
9 | return "", err
10 | }
11 | return string(bytes), err
12 | }
13 |
14 | func PasswordVerify(pwd, hash string) bool {
15 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pwd))
16 | return err == nil
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/utils/queue.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "github.com/redis/go-redis/v9"
7 | )
8 |
9 | type RedisQueue struct {
10 | client *redis.Client
11 | }
12 |
13 | func NewRedisQueue(client *redis.Client) *RedisQueue {
14 | return &RedisQueue{client: client}
15 | }
16 |
17 | func (this *RedisQueue) Push(queueName string, message interface{}) error {
18 | msg, _ := json.Marshal(message)
19 | return this.client.LPush(context.Background(), queueName, msg).Err()
20 | }
21 |
22 | func (this *RedisQueue) Pop(queueName string, obj interface{}) error {
23 | message, err := this.client.BLPop(context.Background(), 0, queueName).Result()
24 | if err != nil {
25 | return err
26 | }
27 | err = json.Unmarshal([]byte(message[1]), obj)
28 | if err != nil {
29 | return err
30 | }
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/utils/random.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 | )
7 |
8 | var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
9 |
10 | // RandomString 生成随机长度的字符串
11 | func RandomString(length int64) string {
12 | b := make([]rune, length)
13 | r := rand.New(rand.NewSource(time.Now().UnixNano()))
14 | for i := range b {
15 | b[i] = letters[r.Intn(62)]
16 | }
17 | return string(b)
18 | }
19 |
20 | // GetRandomNum 生成随机数字(包括边界)
21 | func GetRandomNum(min, max int) int64 {
22 | rand.New(rand.NewSource(time.Now().UnixNano()))
23 | return int64(rand.Intn(max-min+1) + min)
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/utils/set.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // set 集合
8 | type Set struct {
9 | mapData *MapSync[Empty]
10 | }
11 | type Empty struct{}
12 |
13 | func NewSet() *Set {
14 | return &Set{
15 | mapData: NewMap[Empty](),
16 | }
17 | }
18 | func (this *Set) Add(value interface{}) error {
19 | b, err := json.Marshal(value)
20 | if err != nil {
21 | return err
22 | }
23 | this.mapData.Set(string(b), Empty{})
24 | return err
25 | }
26 | func (this *Set) List(dist interface{}) error {
27 | keys := this.mapData.Keys()
28 | res := make([]interface{}, len(keys))
29 | for i, item := range keys {
30 | var obj interface{}
31 | err := json.Unmarshal([]byte(item), &obj)
32 | if err != nil {
33 | return err
34 | }
35 | res[i] = obj
36 | }
37 | b, err := json.Marshal(res)
38 | if err != nil {
39 | return err
40 | }
41 | err = json.Unmarshal(b, dist)
42 | if err != nil {
43 | return err
44 | }
45 | return nil
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/utils/validator.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "regexp"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type Rules map[string][]string
12 |
13 | type RulesMap map[string]Rules
14 |
15 | var CustomizeMap = make(map[string]Rules)
16 |
17 | //@author: [piexlmax](https://github.com/piexlmax)
18 | //@function: RegisterRule
19 | //@description: 注册自定义规则方案建议在路由初始化层即注册
20 | //@param: key string, rule Rules
21 | //@return: err error
22 |
23 | func RegisterRule(key string, rule Rules) (err error) {
24 | if CustomizeMap[key] != nil {
25 | return errors.New(key + "已注册,无法重复注册")
26 | } else {
27 | CustomizeMap[key] = rule
28 | return nil
29 | }
30 | }
31 |
32 | //@author: [piexlmax](https://github.com/piexlmax)
33 | //@function: NotEmpty
34 | //@description: 非空 不能为其对应类型的0值
35 | //@return: string
36 |
37 | func NotEmpty() string {
38 | return "notEmpty"
39 | }
40 |
41 | // @author: [zooqkl](https://github.com/zooqkl)
42 | // @function: RegexpMatch
43 | // @description: 正则校验 校验输入项是否满足正则表达式
44 | // @param: rule string
45 | // @return: string
46 |
47 | func RegexpMatch(rule string) string {
48 | return "regexp=" + rule
49 | }
50 |
51 | //@author: [piexlmax](https://github.com/piexlmax)
52 | //@function: Lt
53 | //@description: 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
54 | //@param: mark string
55 | //@return: string
56 |
57 | func Lt(mark string) string {
58 | return "lt=" + mark
59 | }
60 |
61 | //@author: [piexlmax](https://github.com/piexlmax)
62 | //@function: Le
63 | //@description: 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
64 | //@param: mark string
65 | //@return: string
66 |
67 | func Le(mark string) string {
68 | return "le=" + mark
69 | }
70 |
71 | //@author: [piexlmax](https://github.com/piexlmax)
72 | //@function: Eq
73 | //@description: 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
74 | //@param: mark string
75 | //@return: string
76 |
77 | func Eq(mark string) string {
78 | return "eq=" + mark
79 | }
80 |
81 | //@author: [piexlmax](https://github.com/piexlmax)
82 | //@function: Ne
83 | //@description: 不等于入参(!=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
84 | //@param: mark string
85 | //@return: string
86 |
87 | func Ne(mark string) string {
88 | return "ne=" + mark
89 | }
90 |
91 | //@author: [piexlmax](https://github.com/piexlmax)
92 | //@function: Ge
93 | //@description: 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
94 | //@param: mark string
95 | //@return: string
96 |
97 | func Ge(mark string) string {
98 | return "ge=" + mark
99 | }
100 |
101 | //@author: [piexlmax](https://github.com/piexlmax)
102 | //@function: Gt
103 | //@description: 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
104 | //@param: mark string
105 | //@return: string
106 |
107 | func Gt(mark string) string {
108 | return "gt=" + mark
109 | }
110 |
111 | //
112 | //@author: [piexlmax](https://github.com/piexlmax)
113 | //@function: Verify
114 | //@description: 校验方法
115 | //@param: st interface{}, roleMap Rules(入参实例,规则map)
116 | //@return: err error
117 |
118 | func Verify(st interface{}, roleMap Rules) (err error) {
119 | compareMap := map[string]bool{
120 | "lt": true,
121 | "le": true,
122 | "eq": true,
123 | "ne": true,
124 | "ge": true,
125 | "gt": true,
126 | }
127 |
128 | typ := reflect.TypeOf(st)
129 | val := reflect.ValueOf(st) // 获取reflect.Type类型
130 |
131 | kd := val.Kind() // 获取到st对应的类别
132 | if kd != reflect.Struct {
133 | return errors.New("expect struct")
134 | }
135 | num := val.NumField()
136 | // 遍历结构体的所有字段
137 | for i := 0; i < num; i++ {
138 | tagVal := typ.Field(i)
139 | val := val.Field(i)
140 | if tagVal.Type.Kind() == reflect.Struct {
141 | if err = Verify(val.Interface(), roleMap); err != nil {
142 | return err
143 | }
144 | }
145 | if len(roleMap[tagVal.Name]) > 0 {
146 | for _, v := range roleMap[tagVal.Name] {
147 | switch {
148 | case v == "notEmpty":
149 | if isBlank(val) {
150 | return errors.New(tagVal.Name + "值不能为空")
151 | }
152 | case strings.Split(v, "=")[0] == "regexp":
153 | if !regexpMatch(strings.Split(v, "=")[1], val.String()) {
154 | return errors.New(tagVal.Name + "格式校验不通过")
155 | }
156 | case compareMap[strings.Split(v, "=")[0]]:
157 | if !compareVerify(val, v) {
158 | return errors.New(tagVal.Name + "长度或值不在合法范围," + v)
159 | }
160 | }
161 | }
162 | }
163 | }
164 | return nil
165 | }
166 |
167 | //@author: [piexlmax](https://github.com/piexlmax)
168 | //@function: compareVerify
169 | //@description: 长度和数字的校验方法 根据类型自动校验
170 | //@param: value reflect.Value, VerifyStr string
171 | //@return: bool
172 |
173 | func compareVerify(value reflect.Value, VerifyStr string) bool {
174 | switch value.Kind() {
175 | case reflect.String:
176 | return compare(len([]rune(value.String())), VerifyStr)
177 | case reflect.Slice, reflect.Array:
178 | return compare(value.Len(), VerifyStr)
179 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
180 | return compare(value.Uint(), VerifyStr)
181 | case reflect.Float32, reflect.Float64:
182 | return compare(value.Float(), VerifyStr)
183 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
184 | return compare(value.Int(), VerifyStr)
185 | default:
186 | return false
187 | }
188 | }
189 |
190 | //@author: [piexlmax](https://github.com/piexlmax)
191 | //@function: isBlank
192 | //@description: 非空校验
193 | //@param: value reflect.Value
194 | //@return: bool
195 |
196 | func isBlank(value reflect.Value) bool {
197 | switch value.Kind() {
198 | case reflect.String, reflect.Slice:
199 | return value.Len() == 0
200 | case reflect.Bool:
201 | return !value.Bool()
202 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
203 | return value.Int() == 0
204 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
205 | return value.Uint() == 0
206 | case reflect.Float32, reflect.Float64:
207 | return value.Float() == 0
208 | case reflect.Interface, reflect.Ptr:
209 | return value.IsNil()
210 | }
211 | return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
212 | }
213 |
214 | //@author: [piexlmax](https://github.com/piexlmax)
215 | //@function: compare
216 | //@description: 比较函数
217 | //@param: value interface{}, VerifyStr string
218 | //@return: bool
219 |
220 | func compare(value interface{}, VerifyStr string) bool {
221 | VerifyStrArr := strings.Split(VerifyStr, "=")
222 | val := reflect.ValueOf(value)
223 | switch val.Kind() {
224 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
225 | VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64)
226 | if VErr != nil {
227 | return false
228 | }
229 | switch {
230 | case VerifyStrArr[0] == "lt":
231 | return val.Int() < VInt
232 | case VerifyStrArr[0] == "le":
233 | return val.Int() <= VInt
234 | case VerifyStrArr[0] == "eq":
235 | return val.Int() == VInt
236 | case VerifyStrArr[0] == "ne":
237 | return val.Int() != VInt
238 | case VerifyStrArr[0] == "ge":
239 | return val.Int() >= VInt
240 | case VerifyStrArr[0] == "gt":
241 | return val.Int() > VInt
242 | default:
243 | return false
244 | }
245 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
246 | VInt, VErr := strconv.Atoi(VerifyStrArr[1])
247 | if VErr != nil {
248 | return false
249 | }
250 | switch {
251 | case VerifyStrArr[0] == "lt":
252 | return val.Uint() < uint64(VInt)
253 | case VerifyStrArr[0] == "le":
254 | return val.Uint() <= uint64(VInt)
255 | case VerifyStrArr[0] == "eq":
256 | return val.Uint() == uint64(VInt)
257 | case VerifyStrArr[0] == "ne":
258 | return val.Uint() != uint64(VInt)
259 | case VerifyStrArr[0] == "ge":
260 | return val.Uint() >= uint64(VInt)
261 | case VerifyStrArr[0] == "gt":
262 | return val.Uint() > uint64(VInt)
263 | default:
264 | return false
265 | }
266 | case reflect.Float32, reflect.Float64:
267 | VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64)
268 | if VErr != nil {
269 | return false
270 | }
271 | switch {
272 | case VerifyStrArr[0] == "lt":
273 | return val.Float() < VFloat
274 | case VerifyStrArr[0] == "le":
275 | return val.Float() <= VFloat
276 | case VerifyStrArr[0] == "eq":
277 | return val.Float() == VFloat
278 | case VerifyStrArr[0] == "ne":
279 | return val.Float() != VFloat
280 | case VerifyStrArr[0] == "ge":
281 | return val.Float() >= VFloat
282 | case VerifyStrArr[0] == "gt":
283 | return val.Float() > VFloat
284 | default:
285 | return false
286 | }
287 | default:
288 | return false
289 | }
290 | }
291 |
292 | func regexpMatch(rule, matchStr string) bool {
293 | return regexp.MustCompile(rule).MatchString(matchStr)
294 | }
295 |
--------------------------------------------------------------------------------
/plugin/repeatCheck.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "gorm.io/gorm"
5 | "time"
6 | )
7 |
8 | type RepeatCheck struct {
9 | ID uint64 `gorm:"primaryKey;column:id;comment:主键"`
10 | TraceId string `gorm:"column:trace_id;type:varchar(255);uniqueIndex:idx_id;comment:追踪id"`
11 | Time time.Time `gorm:"column:time;type:date;comment:插入日期"`
12 | }
13 |
14 | func (this *RepeatCheck) TableName() string {
15 | return "RepeatCheck"
16 | }
17 |
18 | // BeforeCreate Gorm钩子,在创建记录之前调用
19 | func (m *RepeatCheck) BeforeCreate(tx *gorm.DB) (err error) {
20 | m.Time = time.Now()
21 | return
22 | }
23 |
24 | // BeforeUpdate Gorm钩子,在更新记录之前调用
25 | func (m *RepeatCheck) BeforeUpdate(tx *gorm.DB) (err error) {
26 | m.Time = time.Now()
27 | return
28 | }
29 |
--------------------------------------------------------------------------------
/policy.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wike2019/wike_go/ed6f7064c11f9709f437634b6634c70c62b44cae/policy.csv
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # wike_go 是一款web框架,基于成熟的gin框架进行二次封装,提供了一些常用的功能,让开发者更专注于业务开发。
2 |
3 | ## 特性
4 |
5 | 1 基于gin完美兼容,支持更优化的api
6 |
7 | 2 依赖注入,基于fx的二次封装,更简单的使用
8 |
9 | 3 丰富的工具函数
10 |
11 | ## 申明
12 | 框架刚成立,需要很多测试,和完善,功能也会慢慢完善,欢迎大家提出宝贵的意见,一起完善这个框架
13 |
14 | ## 项目接口文档
15 | http://wg.ng2-oa.com/
16 |
17 | ### 待完善
18 |
--------------------------------------------------------------------------------
/server/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gin-gonic/gin"
6 | "github.com/wike2019/wike_go/common"
7 | "github.com/wike2019/wike_go/pkg/func/ctl"
8 | "github.com/wike2019/wike_go/server/model"
9 | )
10 |
11 | func (this CoreCtl) getApiParams(context *gin.Context) (*ctl.Page, *model.APISearch, *common.Empty, *ctl.Controller) {
12 | Item := &model.APISearch{}
13 | c := this.SetContext(context)
14 | c.PageInit()
15 | err := json.Unmarshal(c.Data, Item)
16 | ctl.Error(err, 400)
17 | return &c.Page, Item, &common.Empty{}, c
18 | }
19 | func (this CoreCtl) getApi(context *gin.Context) interface{} {
20 | res, err := this.service.GetApiService(this.getApiParams(context))
21 | ctl.Error(err, 400)
22 | return res
23 | }
24 |
25 | func (this CoreCtl) getApiFrontCommonParams(context *gin.Context) (*common.Empty, *model.APISearch, *common.Empty, *ctl.Controller) {
26 | c := this.SetContext(context)
27 | return &common.Empty{}, &model.APISearch{}, &common.Empty{}, c
28 | }
29 | func (this CoreCtl) getApiFront(context *gin.Context) interface{} {
30 | res, err := this.service.GetApiFrontService(this.getApiFrontCommonParams(context))
31 | ctl.Error(err, 400)
32 | return res
33 | }
34 |
--------------------------------------------------------------------------------
/server/api/build.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | model2 "github.com/wike2019/wike_go/common"
6 | "github.com/wike2019/wike_go/pkg/core"
7 | "github.com/wike2019/wike_go/pkg/func/ctl"
8 | "github.com/wike2019/wike_go/pkg/func/ctl/fastRouter"
9 | "github.com/wike2019/wike_go/pkg/os"
10 | "github.com/wike2019/wike_go/server/model"
11 | "github.com/wike2019/wike_go/server/service"
12 | )
13 |
14 | type CoreCtl struct {
15 | ctl.Controller
16 | DBInstance *core.CoreDb
17 | roleCtl *core.RoleCtl
18 | service *service.Service
19 | }
20 |
21 | func NewRouter(DB *core.CoreDb, roleCtl *core.RoleCtl, service *service.Service) *CoreCtl {
22 | return &CoreCtl{DBInstance: DB, roleCtl: roleCtl, service: service}
23 | }
24 | func (this CoreCtl) Path() string {
25 | return "/core"
26 | }
27 | func (this CoreCtl) CoreDb() *core.CoreDb {
28 | return this.DBInstance
29 | }
30 | func (this *CoreCtl) Build(r *gin.RouterGroup, GCore *core.GCore) {
31 | //t := r.Use(core.Root(), core.Authorizer(this.roleCtl.E))
32 | fastRouter.FastRouter[model2.IDSearch, model.SysDictionary, model2.Empty](this.DBInstance.DB, this.DBInstance, r, GCore, "/dictionary", "字典", "系统接口", "core")
33 | fastRouter.FastRouter[model2.IDSearch, model.SysDictionaryDetail, model2.Empty](this.DBInstance.DB, this.DBInstance, r, GCore, "/dictionaryDetail", "字典详细", "系统接口", "core")
34 | fastRouter.FastRouter[model2.IDSearch, model.Role, model2.Empty](this.DBInstance.DB, this.DBInstance, r, GCore, "/role", "角色", "系统接口", "core")
35 |
36 | this.SetDoc(this.getApiParams, this.service.GetApiService)
37 | GCore.PostWithRbac(r, this, "系统内部接口", "/api", this.getApi, "获取接口列表")
38 |
39 | this.SetDoc(this.getApiFrontCommonParams, this.service.GetApiFrontService)
40 | GCore.GetWithRbac(r, this, "系统内部接口", "/getApiFront", this.getApiFront, "获得接口列表")
41 |
42 | this.SetDoc(this.dictionaryListParams, this.service.DictionaryListService)
43 | GCore.PostWithRbac(r, this, "系统内部接口", "/dictionaryList", this.dictionaryList, "获取字典列表")
44 | this.SetDoc(this.dictionaryItemParams, this.service.DictionaryItemService)
45 | GCore.GetWithRbac(r, this, "系统内部接口", "/dictionaryItem", this.dictionaryItem, "模糊搜索字典")
46 |
47 | this.SetDoc(this.dictionarySearchParams, this.service.DictionarySearchService)
48 | GCore.GetWithRbac(r, this, "系统内部接口", "/dictionarySearch", this.dictionarySearch, "模糊搜索字典")
49 | this.SetDocRaw(nil, nil, nil, ctl.DataList[os.Server]{})
50 | GCore.GetWithRbac(r, this, "系统内部接口", "/systemInfo", this.SystemInfo, "获取服务器信息")
51 |
52 | this.SetDoc(this.roleListParams, this.service.RoleListService)
53 | GCore.GetWithRbac(r, this, "系统内部接口", "/roleList", this.roleList, "获取角色名称列表")
54 |
55 | this.SetDoc(this.ruleCreateParams, this.service.RuleCreateService)
56 | GCore.PostWithRbac(r, this, "系统内部接口", "/ruleCreate", this.ruleCreate, "创建规则")
57 | ////
58 | this.SetDoc(this.ruleInfoParams, this.service.RuleInfoService)
59 | GCore.GetWithRbac(r, this, "系统内部接口", "/ruleInfo", this.ruleInfo, "获得接口权限详细")
60 | ////
61 | this.SetEmpty()
62 | GCore.GetWithRbac(r, this, "系统内部接口", "/ruleSync", this.ruleSync, "同步权限")
63 | this.SetEmpty()
64 | GCore.GetWithRbac(r, this, "系统内部接口", "/log", this.log, "接口日志查询")
65 | this.SetEmptyWithHeader(&model2.Empty{})
66 | GCore.GetWithRbac(r, this, "系统内部接口", "/jobList", this.jobList, "定时任务查询")
67 |
68 | this.SetDocRaw(nil, nil, nil, ctl.DataList[[]model.Role]{})
69 | GCore.GetWithRbac(r, this, "系统内部接口", "/roleDataList", this.roleDataList, "获取角色列表")
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/server/api/dictionary.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gin-gonic/gin"
6 | "github.com/wike2019/wike_go/common"
7 | "github.com/wike2019/wike_go/pkg/func/ctl"
8 | "github.com/wike2019/wike_go/server/model"
9 | )
10 |
11 | func (this CoreCtl) dictionaryListParams(context *gin.Context) (*ctl.Page, *model.SysDictionarySearch, *common.Empty, *ctl.Controller) {
12 | Item := &model.SysDictionarySearch{}
13 | c := this.SetContext(context)
14 | c.PageInit()
15 | err := json.Unmarshal(c.Data, Item)
16 | ctl.Error(err, 400)
17 | return &c.Page, Item, &common.Empty{}, c
18 | }
19 |
20 | func (this CoreCtl) dictionaryList(context *gin.Context) interface{} {
21 | data, err := this.service.DictionaryListService(this.dictionaryListParams(context))
22 | ctl.Error(err, 400)
23 | return data
24 | }
25 |
26 | func (this CoreCtl) dictionaryItemParams(context *gin.Context) (*common.IDSearch, *common.Empty, *common.Empty, *ctl.Controller) {
27 | query := &common.IDSearch{}
28 | c := this.SetContext(context)
29 | err := c.ShouldBindQuery(query)
30 | ctl.Error(err, 400)
31 | return query, &common.Empty{}, &common.Empty{}, c
32 | }
33 | func (this CoreCtl) dictionaryItem(context *gin.Context) interface{} {
34 | data, err := this.service.DictionaryItemService(this.dictionaryItemParams(context))
35 | ctl.Error(err, 400)
36 | return data
37 | }
38 |
39 | func (this CoreCtl) dictionarySearchParams(context *gin.Context) (*common.KeyWordsSearch, *common.Empty, *common.Empty, *ctl.Controller) {
40 | query := &common.KeyWordsSearch{}
41 | c := this.SetContext(context)
42 | err := c.ShouldBindQuery(query)
43 | ctl.Error(err, 400)
44 | return query, &common.Empty{}, &common.Empty{}, c
45 | }
46 | func (this CoreCtl) dictionarySearch(context *gin.Context) interface{} {
47 | data, err := this.service.DictionarySearchService(this.dictionarySearchParams(context))
48 | ctl.Error(err, 400)
49 | return data
50 | }
51 |
--------------------------------------------------------------------------------
/server/api/job.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/wike2019/wike_go/pkg/func/ctl"
6 | "github.com/wike2019/wike_go/server/model"
7 | )
8 |
9 | func (this CoreCtl) jobList(context *gin.Context) interface{} {
10 | c := this.SetContext(context)
11 | Item := model.Job{}
12 | data, err := ctl.ListItemAll[model.Job](this.DBInstance.DB, Item, nil)
13 | ctl.Error(err, 400)
14 | res := ctl.Item("获取日志成功", data, c)
15 | return res
16 | }
17 |
--------------------------------------------------------------------------------
/server/api/log.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/wike2019/wike_go/pkg/func/ctl"
6 | "github.com/wike2019/wike_go/pkg/func/memorylog"
7 | )
8 |
9 | func (this CoreCtl) log(context *gin.Context) interface{} {
10 | c := this.SetContext(context)
11 | res := ctl.Item("获取日志成功", memorylog.LogInfo.All(), c)
12 | return res
13 | }
14 |
--------------------------------------------------------------------------------
/server/api/role.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gin-gonic/gin"
6 | "github.com/wike2019/wike_go/common"
7 | "github.com/wike2019/wike_go/pkg/func/ctl"
8 | "github.com/wike2019/wike_go/server/model"
9 | )
10 |
11 | //
12 | // func (this CoreCtl) roleCreate(context *gin.Context) {
13 | // Item := &model2.Role{}
14 | // c := this.SetContext(context)
15 | // err := json.Unmarshal(c.Data, Item)
16 | // ctl.Error(err, 400)
17 | // if Item.Parent == "" {
18 | // Item.Parent = "nologin"
19 | // }
20 | // err = ctl.CreateItem[*model2.Role](this.DB.DB, Item)
21 | // ctl.Error(err, 400)
22 | // c.Success("添加角色成功", Item)
23 | // }
24 |
25 | func (this CoreCtl) roleListParams(context *gin.Context) (*model.Role, *common.Empty, *common.Empty, *ctl.Controller) {
26 | query := &model.Role{}
27 | c := this.SetContext(context)
28 | err := c.ShouldBindQuery(query)
29 | ctl.Error(err, 400)
30 | return query, &common.Empty{}, &common.Empty{}, c
31 | }
32 | func (this CoreCtl) roleList(context *gin.Context) interface{} {
33 | list, err := this.service.RoleListService(this.roleListParams(context))
34 | ctl.Error(err, 400)
35 | return list
36 | }
37 |
38 | func (this CoreCtl) roleDataList(context *gin.Context) interface{} {
39 | c := this.SetContext(context)
40 | list, err := ctl.ListItemAll[*model.Role](this.DBInstance.DB, model.Role{}, nil)
41 | ctl.Error(err, 400)
42 | data := ctl.Item[[]*model.Role]("获取api列表成功", list, c)
43 | return data
44 | }
45 |
46 | //
47 | // func (this CoreCtl) roleDelete(context *gin.Context) {
48 | // Item := &common.IDSearch{}
49 | // c := this.SetContext(context)
50 | // err := c.ShouldBindQuery(Item)
51 | // ctl.Error(err, 400)
52 | // err = ctl.DeleteItem[*model2.Role](this.DB.DB, Item.ID)
53 | // ctl.Error(err, 400)
54 | // c.Success("删除角色成功", Item)
55 | // }
56 |
57 | func (this CoreCtl) ruleCreateParams(context *gin.Context) (*common.Empty, *model.RuleInput, *common.Empty, *ctl.Controller) {
58 | Item := &model.RuleInput{}
59 | c := this.SetContext(context)
60 | err := json.Unmarshal(c.Data, Item)
61 | ctl.Error(err, 400)
62 | return &common.Empty{}, Item, &common.Empty{}, c
63 | }
64 | func (this CoreCtl) ruleCreate(context *gin.Context) interface{} {
65 | data, err := this.service.RuleCreateService(this.ruleCreateParams(context))
66 | ctl.Error(err, 400)
67 | return data
68 | }
69 |
70 | func (this CoreCtl) ruleSync(context *gin.Context) interface{} {
71 | c := this.SetContext(context)
72 | err := this.DBInstance.DB.Exec("delete from casbin_rule").Error
73 | ctl.Error(err, 400)
74 | search := model.Rule{}
75 | data, err := ctl.ListItemAll[model.Rule](this.DBInstance.DB, search, nil)
76 | for _, item := range data {
77 | this.roleCtl.AddRule(item.Role, item.Path, item.Method)
78 | }
79 | search2 := model.Role{}
80 | data2, err := ctl.ListItemAll[model.Role](this.DBInstance.DB, search2, nil)
81 | for _, item := range data2 {
82 | this.roleCtl.AddRole(item.Child, item.Parent)
83 | }
84 | return ctl.Item[common.Empty]("信息同步成功", common.Empty{}, c)
85 | }
86 |
87 | func (this CoreCtl) ruleInfoParams(context *gin.Context) (*model.RoleInput, *common.Empty, *common.Empty, *ctl.Controller) {
88 | query := &model.RoleInput{}
89 | c := this.SetContext(context)
90 | err := c.Context.ShouldBindQuery(query)
91 | ctl.Error(err, 400)
92 | return query, &common.Empty{}, &common.Empty{}, c
93 | }
94 | func (this CoreCtl) ruleInfo(context *gin.Context) interface{} {
95 | res, err := this.service.RuleInfoService(this.ruleInfoParams(context))
96 | ctl.Error(err, 400)
97 | return res
98 | }
99 |
--------------------------------------------------------------------------------
/server/api/system.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/wike2019/wike_go/pkg/func/ctl"
6 | "github.com/wike2019/wike_go/pkg/os"
7 | )
8 |
9 | func (this CoreCtl) SystemInfo(context *gin.Context) interface{} {
10 | c := this.SetContext(context)
11 | path := c.DefaultQuery("path", "/")
12 | var s os.Server
13 | s.Os = os.InitOS()
14 | cpu, err := os.InitCPU()
15 | if err != nil {
16 | ctl.Error(err, 400)
17 | }
18 | s.Cpu = cpu
19 | ram, err := os.InitRAM()
20 | if err != nil {
21 | ctl.Error(err, 400)
22 | }
23 | s.Ram = ram
24 | disk, err := os.InitDisk(path)
25 | if err != nil {
26 | ctl.Error(err, 400)
27 | }
28 | s.Disk = disk
29 | return ctl.Item[os.Server]("获取服务器信息成功", s, c)
30 | }
31 |
--------------------------------------------------------------------------------
/server/model/SysDictionary.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | type SysDictionary struct {
6 | gorm.Model
7 | SysDictionarySearch
8 | Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:描述"` // 描述
9 | SysDictionaryDetails []SysDictionaryDetail `json:"sysDictionaryDetails" form:"sysDictionaryDetails"`
10 | }
11 | type SysDictionarySearch struct {
12 | Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)" search:"like"` // 字典名(中)
13 | Type string `json:"type" form:"type" gorm:"column:type;comment:分类;unique" search:"like"` // 分类,添加唯一索引
14 | Status int `json:"status" form:"status" gorm:"column:status;comment:状态" search:"true"` // 状态
15 | }
16 |
17 | func (this *SysDictionarySearch) TableName() string {
18 | return "sys_dictionaries"
19 | }
20 |
21 | func (SysDictionary) TableName() string {
22 | return "sys_dictionaries"
23 | }
24 |
25 | type SysDictionaryDetail struct {
26 | gorm.Model
27 | Label string `json:"label" form:"label" gorm:"column:label;comment:展示值"` // 展示值
28 | Value string `json:"value" form:"value" gorm:"column:value;comment:字典值"` // 字典值
29 | Extend string `json:"extend" form:"extend" gorm:"column:extend;comment:扩展值"` // 扩展值
30 | Status int `json:"status" form:"status" gorm:"column:status;comment:启用状态"` // 启用状态
31 | Sort int `json:"sort" form:"sort" gorm:"column:sort;comment:排序标记"` // 排序标记
32 | SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" gorm:"column:sys_dictionary_id;comment:关联标记"` // 关联标记
33 | Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:描述"` // 描述
34 | }
35 |
36 | func (SysDictionaryDetail) TableName() string {
37 | return "sys_dictionary_details"
38 | }
39 |
--------------------------------------------------------------------------------
/server/model/api.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | // api接口模型
6 | type API struct {
7 | gorm.Model // 主键
8 | Input string `gorm:"size:2000"` // 入参数
9 | Output string `gorm:"size:2000"` // 出参数
10 | Status int `gorm:"type:int"`
11 | APISearch
12 | }
13 |
14 | // 可以用于搜索的字段
15 | type APISearch struct {
16 | Group string `gorm:"size:300" search:"like"` // 路由分组
17 | Name string `gorm:"size:300" search:"like"` // 路由名称
18 | Path string `gorm:"size:300" search:"like"` // 路由路径
19 | Method string `gorm:"size:100" search:"true"` // 请求方法
20 | }
21 |
22 | func (this *API) TableName() string {
23 | return "apis"
24 | }
25 | func (this *APISearch) TableName() string {
26 | return "apis"
27 | }
28 |
29 | // 子路有
30 | type ChildAPi struct {
31 | Value uint `json:"value"`
32 | Label string `json:"label"`
33 | Path string
34 | Method string
35 | }
36 |
37 | // 前端路由列表
38 | type APIFront struct {
39 | Value uint `json:"value"`
40 | Label string `json:"label"`
41 | Children []ChildAPi `json:"children"`
42 | }
43 |
44 | // 前端路由权限列表
45 | type APIDataForRole struct {
46 | APIList []Rule `json:"apiList"`
47 | APIParentList []Rule `json:"apiParentList"`
48 | ApiIDs []uint `json:"apiIDs"`
49 | }
50 |
51 | // 接口分组结构
52 | type APIGroup struct {
53 | Group string
54 | APIs []API
55 | }
56 |
--------------------------------------------------------------------------------
/server/model/casbin.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | // 路由规则
6 | type Rule struct {
7 | gorm.Model
8 | RuleSearch
9 | Path string `json:"path"`
10 | Method string `json:"method"`
11 | APIId uint `json:"apiId"`
12 | }
13 |
14 | func (this *Rule) TableName() string {
15 | return "sys_rule"
16 | }
17 |
18 | // 路由搜索字段
19 | type RuleSearch struct {
20 | Role string `json:"role" search:"true"`
21 | }
22 |
23 | func (this *RuleSearch) TableName() string {
24 | return "sys_rule"
25 | }
26 |
27 | // 根据接口获取路由角色列表
28 | type AggregatedRule struct {
29 | Path string `json:"path"`
30 | Method string `json:"method"`
31 | Roles []string `json:"roles"`
32 | }
33 |
34 | // 角色列表
35 | type Role struct {
36 | gorm.Model
37 | Parent string `json:"parent"`
38 | Child string `json:"children"`
39 | }
40 | type RoleService struct {
41 | List []Role
42 | Names []string
43 | }
44 |
45 | func (this *Role) TableName() string {
46 | return "sys_role"
47 | }
48 |
49 | // 添加规则
50 | type RuleInput struct {
51 | Role string `json:"role"`
52 | APIId []uint `json:"apiId"`
53 | }
54 |
55 | // 添加角色
56 | type RoleInput struct {
57 | Role string `form:"role" search:"true"`
58 | }
59 |
--------------------------------------------------------------------------------
/server/model/job.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | type Job struct {
6 | gorm.Model // 主键
7 | JobSearch
8 | Cron string `gorm:"size:2000"`
9 | Func string `gorm:"size:2000"` // 入参数
10 | }
11 | type JobSearch struct {
12 | Name string `gorm:"size:300" search:"like"` // 路由名称
13 | }
14 |
15 | func (this *JobSearch) TableName() string {
16 | return "jobs"
17 | }
18 |
19 | func (this *Job) TableName() string {
20 | return "jobs"
21 | }
22 |
--------------------------------------------------------------------------------
/server/service/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | model2 "github.com/wike2019/wike_go/common"
5 | "github.com/wike2019/wike_go/pkg/core"
6 | "github.com/wike2019/wike_go/pkg/func/ctl"
7 | model "github.com/wike2019/wike_go/server/model"
8 | "gorm.io/gorm"
9 | )
10 |
11 | type Service struct {
12 | DB *core.CoreDb
13 | }
14 |
15 | func (this *Service) GetApiService(query *ctl.Page, body *model.APISearch, header *model2.Empty, context *ctl.Controller) (*ctl.PageList[model.API], error) {
16 | data, total, err := ctl.ListItem[model.API](this.DB.DB, query.Offset, query.Count, body, nil)
17 | if err != nil {
18 | return nil, err
19 | }
20 | context.FormatTotal(total)
21 |
22 | return ctl.List[model.API]("获取api列表成功", data, context), nil
23 | }
24 |
25 | func (this *Service) GetApiFrontService(query *model2.Empty, body *model.APISearch, header *model2.Empty, context *ctl.Controller) (*ctl.DataList[[]model.APIFront], error) {
26 | data, err := ctl.ListItemAll[model.API](this.DB.DB, body, nil)
27 | if err != nil {
28 | return nil, err
29 | }
30 | res := this.convertToAPIFront(data)
31 |
32 | return ctl.Item[[]model.APIFront]("获取api列表成功", res, context), nil
33 | }
34 | func CoreService(DB *core.CoreDb) *Service {
35 | return &Service{DB: DB}
36 | }
37 | func (this *Service) convertToAPIFront(apiList []model.API) []model.APIFront {
38 | groupedAPIs := make(map[string][]model.ChildAPi)
39 |
40 | // 按 Group 分类
41 | for _, api := range apiList {
42 | child := model.ChildAPi{
43 | Value: api.ID,
44 | Label: api.Name,
45 | Path: api.Path,
46 | Method: api.Method,
47 | }
48 | groupedAPIs[api.Group] = append(groupedAPIs[api.Group], child)
49 | }
50 |
51 | // 组装 APIFront 结构
52 | var apiFrontList []model.APIFront
53 | for group, children := range groupedAPIs {
54 | apiFrontList = append(apiFrontList, model.APIFront{
55 | Value: 0,
56 | Label: group,
57 | Children: children,
58 | })
59 | }
60 |
61 | return apiFrontList
62 | }
63 |
64 | func (this *Service) DictionaryListService(query *ctl.Page, body *model.SysDictionarySearch, header *model2.Empty, context *ctl.Controller) (*ctl.PageList[model.SysDictionary], error) {
65 | data, total, err := ctl.ListItem[model.SysDictionary](this.DB.DB, query.Offset, query.Count, body, nil)
66 | if err != nil {
67 | return nil, err
68 | }
69 | context.FormatTotal(total)
70 |
71 | return ctl.List[model.SysDictionary]("获取字典列表成功", data, context), nil
72 | }
73 | func (this *Service) DictionaryItemService(query *model2.IDSearch, body *model2.Empty, header *model2.Empty, context *ctl.Controller) (*ctl.DataList[model.SysDictionary], error) {
74 | res, err := ctl.GetItem[model.SysDictionary](this.DB.DB, query.ID, func(db *gorm.DB) *gorm.DB {
75 | return db.Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB {
76 | return db.Order("`sort` ASC") // 按 Author 的 name 字段排序
77 | })
78 | })
79 | if !ctl.IsExist(err) {
80 | return ctl.Item[model.SysDictionary]("获取字典列表成功", res, context), nil
81 | }
82 | return ctl.Item[model.SysDictionary]("获取字典列表成功", res, context), err
83 | }
84 | func (this *Service) DictionarySearchService(query *model2.KeyWordsSearch, body *model2.Empty, header *model2.Empty, context *ctl.Controller) (*ctl.DataList[model.SysDictionary], error) {
85 | res, err := ctl.GetItemByFunc[model.SysDictionary](this.DB.DB, func(db *gorm.DB) *gorm.DB {
86 | return db.Where("type=?", query.KeyWords).Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB {
87 | return db.Order("`sort` ASC").Where("status=2") // 按 Author 的 name 字段排序
88 | })
89 | })
90 | return ctl.Item[model.SysDictionary]("获取字典列表成功", res, context), err
91 | }
92 |
93 | func (this Service) RoleListService(query *model.Role, body *model2.Empty, header *model2.Empty, context *ctl.Controller) (*ctl.DataList[model.RoleService], error) {
94 | list, err := ctl.ListItemAll[model.Role](this.DB.DB, query, nil)
95 | res := make([]string, 0)
96 | for _, item := range list {
97 | res = append(res, item.Child)
98 | }
99 | return ctl.Item[model.RoleService]("获取api列表成功", model.RoleService{
100 | List: list,
101 | Names: res,
102 | }, context), err
103 | }
104 |
105 | func (this Service) RuleCreateService(query *model2.Empty, body *model.RuleInput, header *model2.Empty, context *ctl.Controller) (*ctl.DataList[model2.Empty], error) {
106 | err := this.DB.DB.Transaction(func(tx *gorm.DB) error {
107 | delObj := model.Rule{}
108 | err := tx.Where(" role =?", body.Role).Delete(&delObj).Error
109 | for _, item := range body.APIId {
110 | if item == 0 {
111 | continue
112 | }
113 | old := model.Rule{}
114 | err = tx.Where("api_id=? and role =?", item, body.Role).First(&old).Error
115 | if ctl.IsExist(err) {
116 | continue
117 | }
118 | api := model.API{}
119 | err = tx.Where("id=?", item).First(&api).Error
120 | if err != nil {
121 | return err
122 | }
123 | data := &model.Rule{}
124 | data.APIId = item
125 | data.Role = body.Role
126 | data.Path = api.Path
127 | data.Method = api.Method
128 | err = tx.Create(data).Error
129 | if err != nil {
130 | return err
131 | }
132 | }
133 | return nil
134 | })
135 | return ctl.Item[model2.Empty]("同步接口成功", model2.Empty{}, context), err
136 | }
137 |
138 | func (this Service) RuleInfoService(query *model.RoleInput, body *model2.Empty, header *model2.Empty, context *ctl.Controller) (*ctl.DataList[model.APIDataForRole], error) {
139 | data, err := ctl.ListItemAll[model.Rule](this.DB.DB, query, nil)
140 | if err != nil {
141 | return nil, err
142 | }
143 | ids := make([]uint, 0)
144 | for _, item := range data {
145 | ids = append(ids, item.APIId)
146 | }
147 | res := model.APIDataForRole{}
148 | res.ApiIDs = ids
149 | res.APIList = data
150 | var roleList []model.Role
151 | err = this.DB.DB.Raw(`WITH RECURSIVE parent_chain AS (
152 | -- 初始查询,找到child = 'admin'的记录
153 | SELECT id, parent, child
154 | FROM sys_role
155 | WHERE child = ?
156 |
157 | UNION ALL
158 |
159 | -- 递归查询,找到每个parent的parent
160 | SELECT sr.id, sr.parent, sr.child
161 | FROM sys_role sr
162 | INNER JOIN parent_chain pc ON sr.child = pc.parent
163 | )
164 | SELECT *
165 | FROM parent_chain;`, query.Role).Debug().Find(&roleList).Error
166 | if err != nil {
167 | return nil, err
168 | }
169 | roleStr := make([]string, 0)
170 | for _, item := range roleList {
171 | if item.Parent != "" {
172 | roleStr = append(roleStr, item.Parent)
173 | }
174 | }
175 | var apiList []model.Rule
176 | err = this.DB.DB.Raw(`SELECT *
177 | FROM (
178 | SELECT *, ROW_NUMBER() OVER (PARTITION BY api_id ORDER BY created_at ASC) AS rn
179 | FROM sys_rule
180 | WHERE role IN (?) and sys_rule.deleted_at IS NULL
181 |
182 | ) t
183 | WHERE t.rn = 1;`, roleStr).Debug().Find(&apiList).Error
184 | if err != nil {
185 | return nil, err
186 | }
187 | res.APIParentList = apiList
188 | return ctl.Item[model.APIDataForRole]("同步接口成功", res, context), err
189 | }
190 |
--------------------------------------------------------------------------------
/web/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-recommended',
8 | 'eslint:recommended',
9 | ],
10 | rules: {
11 | 'vue/multi-word-component-names': 'off', // 关闭多单词规则
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/web/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "printWidth": 80,
5 | "tabWidth": 2,
6 | "useTabs": false
7 | }
8 |
--------------------------------------------------------------------------------
/web/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # Vue 3 + TypeScript + Vite
2 |
3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `
12 |