正在显示
46 个修改的文件
包含
2607 行增加
和
0 行删除
.gitignore
0 → 100644
1 | +# Compiled Object codefiles, Static and Dynamic libs (Shared Objects) | ||
2 | +*.o | ||
3 | +*.a | ||
4 | +*.so | ||
5 | + | ||
6 | +# Folders | ||
7 | +_obj | ||
8 | +_test | ||
9 | + | ||
10 | +# Architecture specific extensions/prefixes | ||
11 | +*.[568vq] | ||
12 | +[568vq].out | ||
13 | + | ||
14 | +*.cgo1.go | ||
15 | +*.cgo2.c | ||
16 | +_cgo_defun.c | ||
17 | +_cgo_gotypes.go | ||
18 | +_cgo_export.* | ||
19 | + | ||
20 | +_testmain.go | ||
21 | + | ||
22 | +*.exe | ||
23 | +*.test | ||
24 | +.log | ||
25 | +.idea | ||
26 | +.vscode | ||
27 | + | ||
28 | +app.log | ||
29 | +go.sum | ||
30 | +lastupdate.tmp | ||
31 | +*.log | ||
32 | + | ||
33 | +public/* | ||
34 | +logs/ |
Makefile
0 → 100644
1 | +.PHONY: model | ||
2 | +model: | ||
3 | + goctl model mysql ddl -s .\deploy\database\table.sql -d cmd/discuss | ||
4 | + | ||
5 | +.PHONY: api | ||
6 | +api: | ||
7 | + goctl api go -api .\cmd\discuss\mini\dsl\core.api -dir cmd/discuss/api -style go_zero | ||
8 | + | ||
9 | +.PHONY: swagger | ||
10 | +swagger: | ||
11 | + goctl api plugin -plugin goctl-swagger="swagger -filename core.json" -api .\cmd\discuss\api\dsl\core.api -dir .\cmd\discuss\api\dsl |
README.md
0 → 100644
cmd/discuss/api/README.md
0 → 100644
1 | +## 生成模型 | ||
2 | + | ||
3 | +``` | ||
4 | +goctl model mysql ddl -s .\deploy\database\table.sql -d cmd/discuss | ||
5 | +``` | ||
6 | + | ||
7 | +## api生成 | ||
8 | + | ||
9 | +``` | ||
10 | +goctl api go -api .\cmd\discuss\mini\dsl\core.api -dir cmd/discuss/api -style go_zero | ||
11 | +``` | ||
12 | + | ||
13 | +## swagger 生成 | ||
14 | + | ||
15 | +``` | ||
16 | +goctl api plugin -plugin goctl-swagger="swagger -filename core.json" -api .\cmd\discuss\api\dsl\core.api -dir .\cmd\discuss\api\dsl | ||
17 | +``` |
cmd/discuss/api/core.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "flag" | ||
5 | + "github.com/zeromicro/go-zero/core/logx" | ||
6 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db" | ||
7 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain" | ||
8 | + "net/http" | ||
9 | + "strings" | ||
10 | + | ||
11 | + "github.com/golang-jwt/jwt/v4/request" | ||
12 | + "github.com/zeromicro/go-zero/core/conf" | ||
13 | + "github.com/zeromicro/go-zero/rest" | ||
14 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/config" | ||
15 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler" | ||
16 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc" | ||
17 | +) | ||
18 | + | ||
19 | +var configFile = flag.String("f", "etc/core.yaml", "the config file") | ||
20 | + | ||
21 | +func main() { | ||
22 | + flag.Parse() | ||
23 | + | ||
24 | + var c config.Config | ||
25 | + conf.MustLoad(*configFile, &c) | ||
26 | + | ||
27 | + // 默认的token头 Authorization 修改未 x-token | ||
28 | + request.AuthorizationHeaderExtractor = &request.PostExtractionFilter{ | ||
29 | + request.HeaderExtractor{"x-mmm-accesstoken"}, func(tok string) (string, error) { | ||
30 | + // Should be a bearer token | ||
31 | + if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " { | ||
32 | + return tok[7:], nil | ||
33 | + } | ||
34 | + return tok, nil | ||
35 | + }, | ||
36 | + } | ||
37 | + | ||
38 | + // 初始化Domain里面的配置 | ||
39 | + domain.ProjectName = c.Name | ||
40 | + | ||
41 | + opts := make([]rest.RunOption, 0) | ||
42 | + // cors | ||
43 | + opt := rest.WithCustomCors(func(header http.Header) { | ||
44 | + header.Set("Access-Control-Allow-Headers", "*") | ||
45 | + }, func(writer http.ResponseWriter) { | ||
46 | + | ||
47 | + }) | ||
48 | + opts = append(opts, opt) | ||
49 | + | ||
50 | + server := rest.MustNewServer(c.RestConf, opts...) | ||
51 | + defer server.Stop() | ||
52 | + | ||
53 | + ctx := svc.NewServiceContext(c) | ||
54 | + handler.RegisterHandlers(server, ctx) | ||
55 | + | ||
56 | + db.Migrate(ctx.DB) | ||
57 | + | ||
58 | + logx.Infof("Starting server at %s:%d... \n", c.Host, c.Port) | ||
59 | + server.Start() | ||
60 | +} |
cmd/discuss/api/dsl/core.api
0 → 100644
1 | +import "core/test.api" |
cmd/discuss/api/dsl/core.json
0 → 100644
1 | +{ | ||
2 | + "swagger": "2.0", | ||
3 | + "info": { | ||
4 | + "title": "", | ||
5 | + "version": "" | ||
6 | + }, | ||
7 | + "schemes": [ | ||
8 | + "http", | ||
9 | + "https" | ||
10 | + ], | ||
11 | + "consumes": [ | ||
12 | + "application/json" | ||
13 | + ], | ||
14 | + "produces": [ | ||
15 | + "application/json" | ||
16 | + ], | ||
17 | + "paths": { | ||
18 | + "v1/health": { | ||
19 | + "get": { | ||
20 | + "summary": "健康", | ||
21 | + "operationId": "miniHealth", | ||
22 | + "responses": { | ||
23 | + "200": { | ||
24 | + "description": "A successful response.", | ||
25 | + "schema": { | ||
26 | + "$ref": "#/definitions/MiniHealthResposne" | ||
27 | + } | ||
28 | + } | ||
29 | + }, | ||
30 | + "requestBody": {}, | ||
31 | + "tags": [ | ||
32 | + "tool" | ||
33 | + ] | ||
34 | + } | ||
35 | + } | ||
36 | + }, | ||
37 | + "definitions": { | ||
38 | + "MiniHealthRequest": { | ||
39 | + "type": "object", | ||
40 | + "title": "MiniHealthRequest" | ||
41 | + }, | ||
42 | + "MiniHealthResposne": { | ||
43 | + "type": "object", | ||
44 | + "properties": { | ||
45 | + "ok": { | ||
46 | + "type": "boolean", | ||
47 | + "format": "boolean" | ||
48 | + } | ||
49 | + }, | ||
50 | + "title": "MiniHealthResposne", | ||
51 | + "required": [ | ||
52 | + "ok" | ||
53 | + ] | ||
54 | + } | ||
55 | + }, | ||
56 | + "securityDefinitions": { | ||
57 | + "apiKey": { | ||
58 | + "type": "apiKey", | ||
59 | + "description": "Enter JWT Bearer token **_only_**", | ||
60 | + "name": "Authorization", | ||
61 | + "in": "header" | ||
62 | + } | ||
63 | + }, | ||
64 | + "security": [ | ||
65 | + { | ||
66 | + "apiKey": [] | ||
67 | + } | ||
68 | + ] | ||
69 | +} |
cmd/discuss/api/dsl/core/test.api
0 → 100644
1 | +syntax = "v1" | ||
2 | + | ||
3 | +info( | ||
4 | + title: "天联鹰蜓" | ||
5 | + desc: "天联鹰蜓" | ||
6 | + author: "小火箭" | ||
7 | + email: "email" | ||
8 | + version: "v1" | ||
9 | +) | ||
10 | + | ||
11 | +@server( | ||
12 | + prefix: v1 | ||
13 | + group: tool | ||
14 | +) | ||
15 | +service Core { | ||
16 | + @doc "健康" | ||
17 | + @handler miniHealth | ||
18 | + get /health (MiniHealthRequest) returns (MiniHealthResposne) | ||
19 | +} | ||
20 | + | ||
21 | +type( | ||
22 | + MiniHealthRequest struct{ | ||
23 | + | ||
24 | + } | ||
25 | + MiniHealthResposne struct{ | ||
26 | + Ok bool `json:"ok"` | ||
27 | + } | ||
28 | +) |
cmd/discuss/api/etc/core.yaml
0 → 100644
1 | +Name: discuss | ||
2 | +Host: 0.0.0.0 | ||
3 | +Port: 8081 | ||
4 | +Verbose: true | ||
5 | + | ||
6 | +Log: | ||
7 | + #Mode: file | ||
8 | + Encoding: plain | ||
9 | + Level: debug # info | ||
10 | + MaxSize: 1 # 2MB | ||
11 | + TimeFormat: 2006-01-02 15:04:05.000 | ||
12 | + | ||
13 | +JwtAuth: | ||
14 | + AccessSecret: digital-platform | ||
15 | + Expire: 360000 | ||
16 | + | ||
17 | +Redis: | ||
18 | + Host: 127.0.0.1:6379 | ||
19 | + Type: node | ||
20 | + Pass: | ||
21 | +DB: | ||
22 | + DataSource: host=114.55.200.59 user=postgres password=eagle1010 dbname=sumifcc-discuss-dev port=31543 sslmode=disable TimeZone=Asia/Shanghai |
cmd/discuss/api/internal/config/config.go
0 → 100644
1 | +package config | ||
2 | + | ||
3 | +import ( | ||
4 | + "github.com/zeromicro/go-zero/core/stores/redis" | ||
5 | + "github.com/zeromicro/go-zero/rest" | ||
6 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/config" | ||
7 | +) | ||
8 | + | ||
9 | +type Config struct { | ||
10 | + rest.RestConf | ||
11 | + config.Config | ||
12 | + Redis redis.RedisConf `json:",optional"` | ||
13 | +} |
cmd/discuss/api/internal/handler/routes.go
0 → 100644
1 | +// Code generated by goctl. DO NOT EDIT. | ||
2 | +package handler | ||
3 | + | ||
4 | +import ( | ||
5 | + "net/http" | ||
6 | + | ||
7 | + tool "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/tool" | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc" | ||
9 | + | ||
10 | + "github.com/zeromicro/go-zero/rest" | ||
11 | +) | ||
12 | + | ||
13 | +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { | ||
14 | + server.AddRoutes( | ||
15 | + []rest.Route{ | ||
16 | + { | ||
17 | + Method: http.MethodGet, | ||
18 | + Path: "/health", | ||
19 | + Handler: tool.MiniHealthHandler(serverCtx), | ||
20 | + }, | ||
21 | + }, | ||
22 | + rest.WithPrefix("/v1"), | ||
23 | + ) | ||
24 | +} |
1 | +package tool | ||
2 | + | ||
3 | +import ( | ||
4 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/result" | ||
5 | + "net/http" | ||
6 | + | ||
7 | + "github.com/zeromicro/go-zero/rest/httpx" | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/tool" | ||
9 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc" | ||
10 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types" | ||
11 | +) | ||
12 | + | ||
13 | +func MiniHealthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { | ||
14 | + return func(w http.ResponseWriter, r *http.Request) { | ||
15 | + var req types.MiniHealthRequest | ||
16 | + if err := httpx.Parse(r, &req); err != nil { | ||
17 | + httpx.ErrorCtx(r.Context(), w, err) | ||
18 | + return | ||
19 | + } | ||
20 | + | ||
21 | + l := tool.NewMiniHealthLogic(r.Context(), svcCtx) | ||
22 | + resp, err := l.MiniHealth(&req) | ||
23 | + result.HttpResult(r, w, resp, err) | ||
24 | + } | ||
25 | +} |
1 | +package tool | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction" | ||
6 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/xerr" | ||
7 | + | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc" | ||
9 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types" | ||
10 | + | ||
11 | + "github.com/zeromicro/go-zero/core/logx" | ||
12 | +) | ||
13 | + | ||
14 | +type MiniHealthLogic struct { | ||
15 | + logx.Logger | ||
16 | + ctx context.Context | ||
17 | + svcCtx *svc.ServiceContext | ||
18 | +} | ||
19 | + | ||
20 | +func NewMiniHealthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MiniHealthLogic { | ||
21 | + return &MiniHealthLogic{ | ||
22 | + Logger: logx.WithContext(ctx), | ||
23 | + ctx: ctx, | ||
24 | + svcCtx: svcCtx, | ||
25 | + } | ||
26 | +} | ||
27 | + | ||
28 | +func (l *MiniHealthLogic) MiniHealth(req *types.MiniHealthRequest) (resp *types.MiniHealthResposne, err error) { | ||
29 | + // 普通查询 | ||
30 | + var conn = l.svcCtx.DefaultDBConn() | ||
31 | + l.svcCtx.CommentRepository.FindOne(l.ctx, conn, 0) | ||
32 | + | ||
33 | + // 事务查询 | ||
34 | + if err = transaction.UseTrans(l.ctx, l.svcCtx.DB, func(ctx context.Context, conn transaction.Conn) error { | ||
35 | + l.svcCtx.CommentRepository.FindOne(ctx, l.svcCtx.DefaultDBConn(), 0) | ||
36 | + return nil | ||
37 | + }, true); err != nil { | ||
38 | + return nil, xerr.NewErrMsgErr("健康检查失败", err) | ||
39 | + } | ||
40 | + return | ||
41 | +} |
1 | +package svc | ||
2 | + | ||
3 | +import ( | ||
4 | + "github.com/zeromicro/go-zero/core/stores/redis" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/config" | ||
6 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/repository" | ||
7 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction" | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain" | ||
9 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/cache" | ||
10 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/database" | ||
11 | + "gorm.io/gorm" | ||
12 | +) | ||
13 | + | ||
14 | +type ServiceContext struct { | ||
15 | + Config config.Config | ||
16 | + DB *gorm.DB | ||
17 | + Redis *redis.Redis | ||
18 | + | ||
19 | + CommentRepository domain.CommentRepository | ||
20 | +} | ||
21 | + | ||
22 | +func NewServiceContext(c config.Config) *ServiceContext { | ||
23 | + | ||
24 | + db := database.OpenGormPGDB(c.DB.DataSource, c.Log.Mode) | ||
25 | + mlCache := cache.NewMultiLevelCache([]string{c.Redis.Host}, c.Redis.Pass) | ||
26 | + redis, _ := redis.NewRedis(redis.RedisConf{Host: c.Redis.Host, Pass: c.Redis.Pass, Type: "node"}) | ||
27 | + | ||
28 | + return &ServiceContext{ | ||
29 | + Config: c, | ||
30 | + DB: db, | ||
31 | + Redis: redis, | ||
32 | + CommentRepository: repository.NewCommentRepository(cache.NewCachedRepository(mlCache)), | ||
33 | + } | ||
34 | +} | ||
35 | + | ||
36 | +func (svc *ServiceContext) DefaultDBConn() transaction.Conn { | ||
37 | + return transaction.NewTransactionContext(svc.DB) | ||
38 | +} |
cmd/discuss/api/internal/types/types.go
0 → 100644
cmd/discuss/doc/dsl/api/comment.api
0 → 100644
1 | + | ||
2 | +syntax = "v1" | ||
3 | + | ||
4 | +info( | ||
5 | + title: "xx实例" | ||
6 | + desc: "xx实例" | ||
7 | + author: "author" | ||
8 | + email: "email" | ||
9 | + version: "v1" | ||
10 | +) | ||
11 | + | ||
12 | +@server( | ||
13 | + prefix: comment/v1 | ||
14 | + group: comment | ||
15 | + jwt: JwtAuth | ||
16 | +) | ||
17 | +service Core { | ||
18 | + @handler getComment | ||
19 | + post /comment/:id (CommentGetRequest) returns (CommentGetResponse) | ||
20 | + @handler saveComment | ||
21 | + post /comment (CommentSaveRequest) returns (CommentSaveResponse) | ||
22 | + @handler deleteComment | ||
23 | + delete /comment/:id (CommentDeleteRequest) returns (CommentDeleteResponse) | ||
24 | + @handler updateComment | ||
25 | + put /comment/:id (CommentUpdateRequest) returns (CommentUpdateResponse) | ||
26 | + @handler searchComment | ||
27 | + post /comment/search (CommentSearchRequest) returns (CommentSearchResponse) | ||
28 | +} | ||
29 | + | ||
30 | +type ( | ||
31 | + CommentGetRequest { | ||
32 | + Id int64 `path:"id"` | ||
33 | + } | ||
34 | + CommentGetResponse struct{ | ||
35 | + Comment CommentItem `json:"comment"` | ||
36 | + } | ||
37 | + | ||
38 | + CommentSaveRequest struct{ | ||
39 | + Comment CommentItem `json:"comment"` | ||
40 | + } | ||
41 | + CommentSaveResponse struct{} | ||
42 | + | ||
43 | + CommentDeleteRequest struct{ | ||
44 | + Id int64 `path:"id"` | ||
45 | + } | ||
46 | + CommentDeleteResponse struct{} | ||
47 | + | ||
48 | + CommentUpdateRequest struct{ | ||
49 | + Id int64 `path:"id"` | ||
50 | + Comment CommentItem `json:"comment"` | ||
51 | + } | ||
52 | + CommentUpdateResponse struct{} | ||
53 | + | ||
54 | + CommentSearchRequest struct{ | ||
55 | + Page int `json:"page"` | ||
56 | + Size int `json:"size"` | ||
57 | + } | ||
58 | + CommentSearchResponse{ | ||
59 | + List []CommentItem `json:"list"` | ||
60 | + Total int64 `json:"total"` | ||
61 | + } | ||
62 | + CommentItem struct{ | ||
63 | + | ||
64 | + } | ||
65 | +) |
cmd/discuss/doc/dsl/rpc/comment.proto
0 → 100644
1 | + | ||
2 | +syntax = "proto3"; | ||
3 | + | ||
4 | +option go_package ="./pb"; | ||
5 | + | ||
6 | +package pb; | ||
7 | + | ||
8 | +message CommentGetReq { | ||
9 | + int64 Id = 1; | ||
10 | +} | ||
11 | +message CommentGetResp{ | ||
12 | + CommentItem User = 1; | ||
13 | +} | ||
14 | + | ||
15 | +message CommentSaveReq { | ||
16 | + | ||
17 | +} | ||
18 | +message CommentSaveResp{ | ||
19 | + | ||
20 | +} | ||
21 | + | ||
22 | +message CommentDeleteReq { | ||
23 | + int64 Id = 1; | ||
24 | +} | ||
25 | +message CommentDeleteResp{ | ||
26 | + | ||
27 | +} | ||
28 | + | ||
29 | +message CommentUpdateReq { | ||
30 | + int64 Id = 1; | ||
31 | +} | ||
32 | +message CommentUpdateResp{ | ||
33 | + | ||
34 | +} | ||
35 | + | ||
36 | +message CommentSearchReq { | ||
37 | + int64 PageNumber = 1; | ||
38 | + int64 PageSize = 2; | ||
39 | +} | ||
40 | +message CommentSearchResp{ | ||
41 | + repeated CommentItem List =1; | ||
42 | + int64 Total =2; | ||
43 | +} | ||
44 | +message CommentItem { | ||
45 | + | ||
46 | +} | ||
47 | + | ||
48 | +service CommentService { | ||
49 | + rpc CommentGet(CommentGetReq) returns(CommentGetResp); | ||
50 | + rpc CommentSave(CommentSaveReq) returns(CommentSaveResp); | ||
51 | + rpc CommentDelete(CommentDeleteReq) returns(CommentDeleteResp); | ||
52 | + rpc CommentUpdate(CommentUpdateReq) returns(CommentUpdateResp); | ||
53 | + rpc CommentSearch(CommentSearchReq) returns(CommentSearchResp); | ||
54 | +} |
cmd/discuss/interanl/pkg/db/migrate.go
0 → 100644
1 | +package models | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain" | ||
6 | + "gorm.io/gorm" | ||
7 | + "gorm.io/plugin/soft_delete" | ||
8 | + "time" | ||
9 | +) | ||
10 | + | ||
11 | +type Comment struct { | ||
12 | + Id int64 // 唯一标识 | ||
13 | + | ||
14 | + CreatedAt int64 `json:",omitempty"` | ||
15 | + UpdatedAt int64 `json:",omitempty"` | ||
16 | + DeletedAt int64 `json:",omitempty"` | ||
17 | + Version int `json:",omitempty"` | ||
18 | + IsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"` | ||
19 | +} | ||
20 | + | ||
21 | +func (m *Comment) TableName() string { | ||
22 | + return "t_comment" | ||
23 | +} | ||
24 | + | ||
25 | +func (m *Comment) BeforeCreate(tx *gorm.DB) (err error) { | ||
26 | + m.CreatedAt = time.Now().Unix() | ||
27 | + m.UpdatedAt = time.Now().Unix() | ||
28 | + return | ||
29 | +} | ||
30 | + | ||
31 | +func (m *Comment) BeforeUpdate(tx *gorm.DB) (err error) { | ||
32 | + m.UpdatedAt = time.Now().Unix() | ||
33 | + return | ||
34 | +} | ||
35 | + | ||
36 | +func (m *Comment) CacheKeyFunc() string { | ||
37 | + if m.Id == 0 { | ||
38 | + return "" | ||
39 | + } | ||
40 | + return fmt.Sprintf("%v:cache:%v:id:%v", domain.ProjectName, m.TableName(), m.Id) | ||
41 | +} | ||
42 | + | ||
43 | +func (m *Comment) CacheKeyFuncByObject(obj interface{}) string { | ||
44 | + if v, ok := obj.(*Comment); ok { | ||
45 | + return v.CacheKeyFunc() | ||
46 | + } | ||
47 | + return "" | ||
48 | +} | ||
49 | + | ||
50 | +func (m *Comment) CachePrimaryKeyFunc() string { | ||
51 | + if len("") == 0 { | ||
52 | + return "" | ||
53 | + } | ||
54 | + return fmt.Sprintf("%v:cache:%v:primarykey:%v", domain.ProjectName, m.TableName(), "key") | ||
55 | +} |
1 | +package repository | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "github.com/jinzhu/copier" | ||
6 | + "github.com/pkg/errors" | ||
7 | + "github.com/tiptok/gocomm/pkg/cache" | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/models" | ||
9 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction" | ||
10 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain" | ||
11 | + "gorm.io/gorm" | ||
12 | +) | ||
13 | + | ||
14 | +type CommentRepository struct { | ||
15 | + *cache.CachedRepository | ||
16 | +} | ||
17 | + | ||
18 | +func (repository *CommentRepository) Insert(ctx context.Context, conn transaction.Conn, dm *domain.Comment) (*domain.Comment, error) { | ||
19 | + var ( | ||
20 | + err error | ||
21 | + m = &models.Comment{} | ||
22 | + tx = conn.DB() | ||
23 | + ) | ||
24 | + if m, err = repository.DomainModelToModel(dm); err != nil { | ||
25 | + return nil, err | ||
26 | + } | ||
27 | + if tx = tx.Model(m).Save(m); tx.Error != nil { | ||
28 | + return nil, tx.Error | ||
29 | + } | ||
30 | + dm.Id = m.Id | ||
31 | + return repository.ModelToDomainModel(m) | ||
32 | + | ||
33 | +} | ||
34 | + | ||
35 | +func (repository *CommentRepository) Update(ctx context.Context, conn transaction.Conn, dm *domain.Comment) (*domain.Comment, error) { | ||
36 | + var ( | ||
37 | + err error | ||
38 | + m *models.Comment | ||
39 | + tx = conn.DB() | ||
40 | + ) | ||
41 | + if m, err = repository.DomainModelToModel(dm); err != nil { | ||
42 | + return nil, err | ||
43 | + } | ||
44 | + queryFunc := func() (interface{}, error) { | ||
45 | + tx = tx.Model(m).Updates(m) | ||
46 | + return nil, tx.Error | ||
47 | + } | ||
48 | + if _, err = repository.Query(queryFunc, m.CacheKeyFunc()); err != nil { | ||
49 | + return nil, err | ||
50 | + } | ||
51 | + return repository.ModelToDomainModel(m) | ||
52 | +} | ||
53 | + | ||
54 | +func (repository *CommentRepository) UpdateWithVersion(ctx context.Context, transaction transaction.Conn, dm *domain.Comment) (*domain.Comment, error) { | ||
55 | + var ( | ||
56 | + err error | ||
57 | + m *models.Comment | ||
58 | + tx = transaction.DB() | ||
59 | + ) | ||
60 | + if m, err = repository.DomainModelToModel(dm); err != nil { | ||
61 | + return nil, err | ||
62 | + } | ||
63 | + oldVersion := dm.Version | ||
64 | + m.Version += 1 | ||
65 | + queryFunc := func() (interface{}, error) { | ||
66 | + tx = tx.Model(m).Select("*").Where("id = ?", m.Id).Where("version = ?", oldVersion).Updates(m) | ||
67 | + if tx.RowsAffected == 0 { | ||
68 | + return nil, domain.ErrUpdateFail | ||
69 | + } | ||
70 | + return nil, tx.Error | ||
71 | + } | ||
72 | + if _, err = repository.Query(queryFunc, m.CacheKeyFunc()); err != nil { | ||
73 | + return nil, err | ||
74 | + } | ||
75 | + return repository.ModelToDomainModel(m) | ||
76 | +} | ||
77 | + | ||
78 | +func (repository *CommentRepository) Delete(ctx context.Context, conn transaction.Conn, dm *domain.Comment) (*domain.Comment, error) { | ||
79 | + var ( | ||
80 | + tx = conn.DB() | ||
81 | + m = &models.Comment{Id: dm.Identify().(int64)} | ||
82 | + ) | ||
83 | + queryFunc := func() (interface{}, error) { | ||
84 | + tx = tx.Where("id = ?", m.Id).Delete(m) | ||
85 | + return m, tx.Error | ||
86 | + } | ||
87 | + if _, err := repository.Query(queryFunc, m.CacheKeyFunc()); err != nil { | ||
88 | + return dm, err | ||
89 | + } | ||
90 | + return repository.ModelToDomainModel(m) | ||
91 | +} | ||
92 | + | ||
93 | +func (repository *CommentRepository) FindOne(ctx context.Context, conn transaction.Conn, id int64) (*domain.Comment, error) { | ||
94 | + var ( | ||
95 | + err error | ||
96 | + tx = conn.DB() | ||
97 | + m = new(models.Comment) | ||
98 | + ) | ||
99 | + queryFunc := func() (interface{}, error) { | ||
100 | + tx = tx.Model(m).Where("id = ?", id).First(m) | ||
101 | + if errors.Is(tx.Error, gorm.ErrRecordNotFound) { | ||
102 | + return nil, domain.ErrNotFound | ||
103 | + } | ||
104 | + return m, tx.Error | ||
105 | + } | ||
106 | + cacheModel := new(models.Comment) | ||
107 | + cacheModel.Id = id | ||
108 | + if err = repository.QueryCache(cacheModel.CacheKeyFunc, m, queryFunc); err != nil { | ||
109 | + return nil, err | ||
110 | + } | ||
111 | + return repository.ModelToDomainModel(m) | ||
112 | +} | ||
113 | + | ||
114 | +func (repository *CommentRepository) Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*domain.Comment, error) { | ||
115 | + var ( | ||
116 | + tx = conn.DB() | ||
117 | + ms []*models.Comment | ||
118 | + dms = make([]*domain.Comment, 0) | ||
119 | + total int64 | ||
120 | + ) | ||
121 | + queryFunc := func() (interface{}, error) { | ||
122 | + tx = tx.Model(&ms).Order("id desc") | ||
123 | + if total, tx = transaction.PaginationAndCount(ctx, tx, queryOptions, &ms); tx.Error != nil { | ||
124 | + return dms, tx.Error | ||
125 | + } | ||
126 | + return dms, nil | ||
127 | + } | ||
128 | + | ||
129 | + if _, err := repository.Query(queryFunc); err != nil { | ||
130 | + return 0, nil, err | ||
131 | + } | ||
132 | + | ||
133 | + for _, item := range ms { | ||
134 | + if dm, err := repository.ModelToDomainModel(item); err != nil { | ||
135 | + return 0, dms, err | ||
136 | + } else { | ||
137 | + dms = append(dms, dm) | ||
138 | + } | ||
139 | + } | ||
140 | + return total, dms, nil | ||
141 | +} | ||
142 | + | ||
143 | +func (repository *CommentRepository) ModelToDomainModel(from *models.Comment) (*domain.Comment, error) { | ||
144 | + to := &domain.Comment{} | ||
145 | + err := copier.Copy(to, from) | ||
146 | + return to, err | ||
147 | +} | ||
148 | + | ||
149 | +func (repository *CommentRepository) DomainModelToModel(from *domain.Comment) (*models.Comment, error) { | ||
150 | + to := &models.Comment{} | ||
151 | + err := copier.Copy(to, from) | ||
152 | + return to, err | ||
153 | +} | ||
154 | + | ||
155 | +func NewCommentRepository(cache *cache.CachedRepository) domain.CommentRepository { | ||
156 | + return &CommentRepository{CachedRepository: cache} | ||
157 | +} |
1 | +package transaction | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "fmt" | ||
6 | + "gorm.io/gorm" | ||
7 | + "sync" | ||
8 | +) | ||
9 | + | ||
10 | +type Context struct { | ||
11 | + //启用事务标识 | ||
12 | + beginTransFlag bool | ||
13 | + db *gorm.DB | ||
14 | + session *gorm.DB | ||
15 | + lock sync.Mutex | ||
16 | +} | ||
17 | + | ||
18 | +func (transactionContext *Context) Begin() error { | ||
19 | + transactionContext.lock.Lock() | ||
20 | + defer transactionContext.lock.Unlock() | ||
21 | + transactionContext.beginTransFlag = true | ||
22 | + tx := transactionContext.db.Begin() | ||
23 | + transactionContext.session = tx | ||
24 | + return nil | ||
25 | +} | ||
26 | + | ||
27 | +func (transactionContext *Context) Commit() error { | ||
28 | + transactionContext.lock.Lock() | ||
29 | + defer transactionContext.lock.Unlock() | ||
30 | + if !transactionContext.beginTransFlag { | ||
31 | + return nil | ||
32 | + } | ||
33 | + tx := transactionContext.session.Commit() | ||
34 | + return tx.Error | ||
35 | +} | ||
36 | + | ||
37 | +func (transactionContext *Context) Rollback() error { | ||
38 | + transactionContext.lock.Lock() | ||
39 | + defer transactionContext.lock.Unlock() | ||
40 | + if !transactionContext.beginTransFlag { | ||
41 | + return nil | ||
42 | + } | ||
43 | + tx := transactionContext.session.Rollback() | ||
44 | + return tx.Error | ||
45 | +} | ||
46 | + | ||
47 | +func (transactionContext *Context) DB() *gorm.DB { | ||
48 | + if transactionContext.beginTransFlag && transactionContext.session != nil { | ||
49 | + return transactionContext.session | ||
50 | + } | ||
51 | + return transactionContext.db | ||
52 | +} | ||
53 | + | ||
54 | +func NewTransactionContext(db *gorm.DB) *Context { | ||
55 | + return &Context{ | ||
56 | + db: db, | ||
57 | + } | ||
58 | +} | ||
59 | + | ||
60 | +type Conn interface { | ||
61 | + Begin() error | ||
62 | + Commit() error | ||
63 | + Rollback() error | ||
64 | + DB() *gorm.DB | ||
65 | +} | ||
66 | + | ||
67 | +// UseTrans when beginTrans is true , it will begin a new transaction | ||
68 | +// to execute the function, recover when panic happen | ||
69 | +func UseTrans(ctx context.Context, | ||
70 | + db *gorm.DB, | ||
71 | + fn func(context.Context, Conn) error, beginTrans bool) (err error) { | ||
72 | + var tx Conn | ||
73 | + tx = NewTransactionContext(db) | ||
74 | + if beginTrans { | ||
75 | + if err = tx.Begin(); err != nil { | ||
76 | + return | ||
77 | + } | ||
78 | + } | ||
79 | + defer func() { | ||
80 | + if p := recover(); p != nil { | ||
81 | + if e := tx.Rollback(); e != nil { | ||
82 | + err = fmt.Errorf("recover from %#v, rollback failed: %w", p, e) | ||
83 | + } else { | ||
84 | + err = fmt.Errorf("recoveer from %#v", p) | ||
85 | + } | ||
86 | + } else if err != nil { | ||
87 | + if e := tx.Rollback(); e != nil { | ||
88 | + err = fmt.Errorf("transaction failed: %s, rollback failed: %w", err, e) | ||
89 | + } | ||
90 | + } else { | ||
91 | + err = tx.Commit() | ||
92 | + } | ||
93 | + }() | ||
94 | + | ||
95 | + return fn(ctx, tx) | ||
96 | +} | ||
97 | + | ||
98 | +func PaginationAndCount(ctx context.Context, tx *gorm.DB, params map[string]interface{}, dst interface{}) (int64, *gorm.DB) { | ||
99 | + var total int64 | ||
100 | + // 只返回数量 | ||
101 | + if v, ok := params["countOnly"]; ok && v.(bool) { | ||
102 | + tx = tx.Count(&total) | ||
103 | + return total, tx | ||
104 | + } | ||
105 | + // 只返回记录 | ||
106 | + if v, ok := params["findOnly"]; ok && v.(bool) { | ||
107 | + if v, ok := params["offset"]; ok { | ||
108 | + tx.Offset(v.(int)) | ||
109 | + } | ||
110 | + if v, ok := params["limit"]; ok { | ||
111 | + tx.Limit(v.(int)) | ||
112 | + } | ||
113 | + if tx = tx.Find(dst); tx.Error != nil { | ||
114 | + return 0, tx | ||
115 | + } | ||
116 | + return total, tx | ||
117 | + } | ||
118 | + tx = tx.Count(&total) | ||
119 | + if tx.Error != nil { | ||
120 | + return total, tx | ||
121 | + } | ||
122 | + if v, ok := params["offset"]; ok { | ||
123 | + tx.Offset(v.(int)) | ||
124 | + } | ||
125 | + if v, ok := params["limit"]; ok { | ||
126 | + tx.Limit(v.(int)) | ||
127 | + } | ||
128 | + if tx = tx.Find(dst); tx.Error != nil { | ||
129 | + return 0, tx | ||
130 | + } | ||
131 | + return total, tx | ||
132 | +} |
cmd/discuss/interanl/pkg/domain/comment.go
0 → 100644
1 | +package domain | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction" | ||
6 | +) | ||
7 | + | ||
8 | +type Comment struct { | ||
9 | + Id int64 // 唯一标识 | ||
10 | + | ||
11 | + CreatedAt int64 `json:",omitempty"` | ||
12 | + UpdatedAt int64 `json:",omitempty"` | ||
13 | + DeletedAt int64 `json:",omitempty"` | ||
14 | + Version int `json:",omitempty"` | ||
15 | +} | ||
16 | + | ||
17 | +type CommentRepository interface { | ||
18 | + Insert(ctx context.Context, conn transaction.Conn, dm *Comment) (*Comment, error) | ||
19 | + Update(ctx context.Context, conn transaction.Conn, dm *Comment) (*Comment, error) | ||
20 | + UpdateWithVersion(ctx context.Context, conn transaction.Conn, dm *Comment) (*Comment, error) | ||
21 | + Delete(ctx context.Context, conn transaction.Conn, dm *Comment) (*Comment, error) | ||
22 | + FindOne(ctx context.Context, conn transaction.Conn, id int64) (*Comment, error) | ||
23 | + Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*Comment, error) | ||
24 | +} | ||
25 | + | ||
26 | +func (m *Comment) Identify() interface{} { | ||
27 | + if m.Id == 0 { | ||
28 | + return nil | ||
29 | + } | ||
30 | + return m.Id | ||
31 | +} |
1 | +package domain | ||
2 | + | ||
3 | +import ( | ||
4 | + "reflect" | ||
5 | +) | ||
6 | + | ||
7 | +func OffsetLimit(page, size int) (offset int, limit int) { | ||
8 | + if page == 0 { | ||
9 | + page = 1 | ||
10 | + } | ||
11 | + if size == 0 { | ||
12 | + size = 20 | ||
13 | + } | ||
14 | + offset = (page - 1) * size | ||
15 | + limit = size | ||
16 | + return | ||
17 | +} | ||
18 | + | ||
19 | +type QueryOptions map[string]interface{} | ||
20 | + | ||
21 | +func NewQueryOptions() QueryOptions { | ||
22 | + options := make(map[string]interface{}) | ||
23 | + return options | ||
24 | +} | ||
25 | +func (options QueryOptions) WithOffsetLimit(page, size int) QueryOptions { | ||
26 | + offset, limit := OffsetLimit(page, size) | ||
27 | + options["offset"] = offset | ||
28 | + options["limit"] = limit | ||
29 | + return options | ||
30 | +} | ||
31 | + | ||
32 | +func (options QueryOptions) WithKV(key string, value interface{}) QueryOptions { | ||
33 | + if reflect.ValueOf(value).IsZero() { | ||
34 | + return options | ||
35 | + } | ||
36 | + options[key] = value | ||
37 | + return options | ||
38 | +} | ||
39 | + | ||
40 | +func (options QueryOptions) MustWithKV(key string, value interface{}) QueryOptions { | ||
41 | + options[key] = value | ||
42 | + return options | ||
43 | +} | ||
44 | + | ||
45 | +func (options QueryOptions) Copy() QueryOptions { | ||
46 | + newOptions := NewQueryOptions() | ||
47 | + for k, v := range options { | ||
48 | + newOptions[k] = v | ||
49 | + } | ||
50 | + return newOptions | ||
51 | +} | ||
52 | + | ||
53 | +type IndexQueryOptionFunc func() QueryOptions | ||
54 | + | ||
55 | +// 自定义的一些查询条件 | ||
56 | + | ||
57 | +func (options QueryOptions) WithCountOnly() QueryOptions { | ||
58 | + options["countOnly"] = true | ||
59 | + return options | ||
60 | +} | ||
61 | +func (options QueryOptions) WithFindOnly() QueryOptions { | ||
62 | + options["findOnly"] = true | ||
63 | + return options | ||
64 | +} |
cmd/discuss/interanl/pkg/domain/vars.go
0 → 100644
deploy/database/table.sql
0 → 100644
go.mod
0 → 100644
1 | +module gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss | ||
2 | + | ||
3 | +go 1.19 | ||
4 | + | ||
5 | +require ( | ||
6 | + github.com/golang-jwt/jwt/v4 v4.5.0 | ||
7 | + github.com/jinzhu/copier v0.4.0 | ||
8 | + github.com/jinzhu/now v1.1.5 | ||
9 | + github.com/pkg/errors v0.9.1 | ||
10 | + github.com/stretchr/testify v1.8.4 | ||
11 | + github.com/tiptok/gocomm v1.0.14 | ||
12 | + github.com/zeromicro/go-zero v1.5.5 | ||
13 | + google.golang.org/grpc v1.57.0 | ||
14 | + gorm.io/driver/mysql v1.5.1 | ||
15 | + gorm.io/driver/postgres v1.5.2 | ||
16 | + gorm.io/gorm v1.25.4 | ||
17 | + gorm.io/plugin/soft_delete v1.2.1 | ||
18 | +) | ||
19 | + | ||
20 | +require ( | ||
21 | + github.com/Shopify/sarama v1.37.2 // indirect | ||
22 | + github.com/beego/beego/v2 v2.0.1 // indirect | ||
23 | + github.com/beorn7/perks v1.0.1 // indirect | ||
24 | + github.com/cenkalti/backoff/v4 v4.2.0 // indirect | ||
25 | + github.com/cespare/xxhash/v2 v2.2.0 // indirect | ||
26 | + github.com/coreos/go-semver v0.3.1 // indirect | ||
27 | + github.com/coreos/go-systemd/v22 v22.5.0 // indirect | ||
28 | + github.com/davecgh/go-spew v1.1.1 // indirect | ||
29 | + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | ||
30 | + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||
31 | + github.com/eapache/go-resiliency v1.3.0 // indirect | ||
32 | + github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect | ||
33 | + github.com/eapache/queue v1.1.0 // indirect | ||
34 | + github.com/emicklei/go-restful/v3 v3.9.0 // indirect | ||
35 | + github.com/fatih/color v1.15.0 // indirect | ||
36 | + github.com/fsnotify/fsnotify v1.4.9 // indirect | ||
37 | + github.com/garyburd/redigo v1.6.3 // indirect | ||
38 | + github.com/gin-contrib/sse v0.1.0 // indirect | ||
39 | + github.com/gin-gonic/gin v1.5.0 // indirect | ||
40 | + github.com/go-logr/logr v1.2.3 // indirect | ||
41 | + github.com/go-logr/stdr v1.2.2 // indirect | ||
42 | + github.com/go-openapi/jsonpointer v0.19.6 // indirect | ||
43 | + github.com/go-openapi/jsonreference v0.20.1 // indirect | ||
44 | + github.com/go-openapi/swag v0.22.3 // indirect | ||
45 | + github.com/go-playground/locales v0.12.1 // indirect | ||
46 | + github.com/go-playground/universal-translator v0.16.0 // indirect | ||
47 | + github.com/go-redis/redis/v8 v8.11.5 // indirect | ||
48 | + github.com/go-sql-driver/mysql v1.7.1 // indirect | ||
49 | + github.com/gogo/protobuf v1.3.2 // indirect | ||
50 | + github.com/golang/mock v1.6.0 // indirect | ||
51 | + github.com/golang/protobuf v1.5.3 // indirect | ||
52 | + github.com/golang/snappy v0.0.4 // indirect | ||
53 | + github.com/google/gnostic v0.5.7-v3refs // indirect | ||
54 | + github.com/google/go-cmp v0.5.9 // indirect | ||
55 | + github.com/google/gofuzz v1.2.0 // indirect | ||
56 | + github.com/google/uuid v1.3.0 // indirect | ||
57 | + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect | ||
58 | + github.com/hashicorp/errwrap v1.1.0 // indirect | ||
59 | + github.com/hashicorp/go-multierror v1.1.1 // indirect | ||
60 | + github.com/hashicorp/go-uuid v1.0.3 // indirect | ||
61 | + github.com/hashicorp/hcl v1.0.0 // indirect | ||
62 | + github.com/jackc/pgpassfile v1.0.0 // indirect | ||
63 | + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect | ||
64 | + github.com/jackc/pgx/v5 v5.4.3 // indirect | ||
65 | + github.com/jcmturner/aescts/v2 v2.0.0 // indirect | ||
66 | + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect | ||
67 | + github.com/jcmturner/gofork v1.7.6 // indirect | ||
68 | + github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect | ||
69 | + github.com/jcmturner/rpc/v2 v2.0.3 // indirect | ||
70 | + github.com/jinzhu/inflection v1.0.0 // indirect | ||
71 | + github.com/josharian/intern v1.0.0 // indirect | ||
72 | + github.com/json-iterator/go v1.1.12 // indirect | ||
73 | + github.com/klauspost/compress v1.15.15 // indirect | ||
74 | + github.com/leodido/go-urn v1.1.0 // indirect | ||
75 | + github.com/magiconair/properties v1.8.0 // indirect | ||
76 | + github.com/mailru/easyjson v0.7.7 // indirect | ||
77 | + github.com/mattn/go-colorable v0.1.13 // indirect | ||
78 | + github.com/mattn/go-isatty v0.0.17 // indirect | ||
79 | + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect | ||
80 | + github.com/mitchellh/mapstructure v1.3.3 // indirect | ||
81 | + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
82 | + github.com/modern-go/reflect2 v1.0.2 // indirect | ||
83 | + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect | ||
84 | + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||
85 | + github.com/openzipkin/zipkin-go v0.4.1 // indirect | ||
86 | + github.com/pelletier/go-toml v1.8.1 // indirect | ||
87 | + github.com/pelletier/go-toml/v2 v2.0.9 // indirect | ||
88 | + github.com/pierrec/lz4/v4 v4.1.17 // indirect | ||
89 | + github.com/pmezard/go-difflib v1.0.0 // indirect | ||
90 | + github.com/prometheus/client_golang v1.16.0 // indirect | ||
91 | + github.com/prometheus/client_model v0.3.0 // indirect | ||
92 | + github.com/prometheus/common v0.42.0 // indirect | ||
93 | + github.com/prometheus/procfs v0.10.1 // indirect | ||
94 | + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect | ||
95 | + github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect | ||
96 | + github.com/spaolacci/murmur3 v1.1.0 // indirect | ||
97 | + github.com/spf13/afero v1.2.2 // indirect | ||
98 | + github.com/spf13/cast v1.3.0 // indirect | ||
99 | + github.com/spf13/jwalterweatherman v1.0.0 // indirect | ||
100 | + github.com/spf13/pflag v1.0.5 // indirect | ||
101 | + github.com/spf13/viper v1.4.0 // indirect | ||
102 | + github.com/ugorji/go/codec v1.1.7 // indirect | ||
103 | + go.etcd.io/etcd/api/v3 v3.5.9 // indirect | ||
104 | + go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect | ||
105 | + go.etcd.io/etcd/client/v3 v3.5.9 // indirect | ||
106 | + go.opentelemetry.io/otel v1.14.0 // indirect | ||
107 | + go.opentelemetry.io/otel/exporters/jaeger v1.14.0 // indirect | ||
108 | + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect | ||
109 | + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect | ||
110 | + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect | ||
111 | + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 // indirect | ||
112 | + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect | ||
113 | + go.opentelemetry.io/otel/exporters/zipkin v1.14.0 // indirect | ||
114 | + go.opentelemetry.io/otel/sdk v1.14.0 // indirect | ||
115 | + go.opentelemetry.io/otel/trace v1.14.0 // indirect | ||
116 | + go.opentelemetry.io/proto/otlp v0.19.0 // indirect | ||
117 | + go.uber.org/atomic v1.10.0 // indirect | ||
118 | + go.uber.org/automaxprocs v1.5.3 // indirect | ||
119 | + go.uber.org/multierr v1.9.0 // indirect | ||
120 | + go.uber.org/zap v1.24.0 // indirect | ||
121 | + golang.org/x/crypto v0.12.0 // indirect | ||
122 | + golang.org/x/net v0.14.0 // indirect | ||
123 | + golang.org/x/oauth2 v0.7.0 // indirect | ||
124 | + golang.org/x/sys v0.11.0 // indirect | ||
125 | + golang.org/x/term v0.11.0 // indirect | ||
126 | + golang.org/x/text v0.12.0 // indirect | ||
127 | + golang.org/x/time v0.3.0 // indirect | ||
128 | + google.golang.org/appengine v1.6.7 // indirect | ||
129 | + google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect | ||
130 | + google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect | ||
131 | + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect | ||
132 | + google.golang.org/protobuf v1.31.0 // indirect | ||
133 | + gopkg.in/go-playground/validator.v9 v9.29.1 // indirect | ||
134 | + gopkg.in/inf.v0 v0.9.1 // indirect | ||
135 | + gopkg.in/yaml.v2 v2.4.0 // indirect | ||
136 | + gopkg.in/yaml.v3 v3.0.1 // indirect | ||
137 | + k8s.io/api v0.26.3 // indirect | ||
138 | + k8s.io/apimachinery v0.27.0-alpha.3 // indirect | ||
139 | + k8s.io/client-go v0.26.3 // indirect | ||
140 | + k8s.io/klog/v2 v2.90.1 // indirect | ||
141 | + k8s.io/kube-openapi v0.0.0-20230307230338-69ee2d25a840 // indirect | ||
142 | + k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect | ||
143 | + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect | ||
144 | + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect | ||
145 | + sigs.k8s.io/yaml v1.3.0 // indirect | ||
146 | +) |
pkg/cache/multi_level_cache.go
0 → 100644
1 | +package cache | ||
2 | + | ||
3 | +import ( | ||
4 | + "github.com/tiptok/gocomm/pkg/cache" | ||
5 | + "github.com/tiptok/gocomm/pkg/cache/gzcache" | ||
6 | + "github.com/tiptok/gocomm/pkg/log" | ||
7 | + "github.com/zeromicro/go-zero/core/logx" | ||
8 | +) | ||
9 | + | ||
10 | +func NewMultiLevelCache(hosts []string, password string) *cache.MultiLevelCache { | ||
11 | + logx.Infof("starting multi level cache...") | ||
12 | + mlCache := cache.NewMultiLevelCacheNew(cache.WithDebugLog(true, func() log.Log { | ||
13 | + return log.DefaultLog | ||
14 | + })) | ||
15 | + mlCache.RegisterCache(gzcache.NewClusterCache(hosts, password)) | ||
16 | + return mlCache | ||
17 | +} | ||
18 | + | ||
19 | +func NewCachedRepository(c *cache.MultiLevelCache, options ...cache.QueryOption) *cache.CachedRepository { | ||
20 | + return cache.NewCachedRepository(c, options...) | ||
21 | +} |
pkg/config/config.go
0 → 100644
1 | +package config | ||
2 | + | ||
3 | +import ( | ||
4 | + "github.com/zeromicro/go-zero/core/stores/cache" | ||
5 | + "github.com/zeromicro/go-zero/zrpc" | ||
6 | + "time" | ||
7 | +) | ||
8 | + | ||
9 | +type JWT struct { | ||
10 | + Secret string `json:",optional"` | ||
11 | + Expires time.Duration `json:",optional"` | ||
12 | +} | ||
13 | +type JwtAuth struct { | ||
14 | + AccessSecret string | ||
15 | + Expire int64 | ||
16 | +} | ||
17 | +type Config struct { | ||
18 | + JwtAuth JwtAuth `json:",optional"` | ||
19 | + UserRpc zrpc.RpcClientConf `json:",optional"` | ||
20 | + AuthRpc zrpc.RpcClientConf `json:",optional"` | ||
21 | + PostRpc zrpc.RpcClientConf `json:",optional"` | ||
22 | + CommentRpc zrpc.RpcClientConf `json:",optional"` | ||
23 | + JWT JWT `json:",optional"` | ||
24 | + DB struct { | ||
25 | + DataSource string | ||
26 | + } `json:",optional"` | ||
27 | + Cache cache.CacheConf `json:",optional"` | ||
28 | + DTM DTM `json:",optional"` | ||
29 | + Sms Sms `json:",optional"` | ||
30 | + Oss Oss `json:",optional"` | ||
31 | + Wechat Wechat `json:",optional"` // 学员端微信 | ||
32 | + CoachClient Wechat `json:",optional"` // 教练端微信 | ||
33 | + OfficialAccount Wechat `json:",optional"` | ||
34 | + ThirdWechatApps []Wechat `json:",optional"` | ||
35 | +} | ||
36 | + | ||
37 | +type DTM struct { | ||
38 | + Server Server `json:",optional"` | ||
39 | +} | ||
40 | + | ||
41 | +type Server struct { | ||
42 | + Name string `json:",optional"` | ||
43 | + Host string `json:",optional"` | ||
44 | + GRPC GRPC `json:",optional"` | ||
45 | + HTTP HTTP `json:",optional"` | ||
46 | + Metrics Metrics `json:",optional"` | ||
47 | +} | ||
48 | + | ||
49 | +type HTTP struct { | ||
50 | + Port string | ||
51 | +} | ||
52 | + | ||
53 | +type GRPC struct { | ||
54 | + Port string | ||
55 | +} | ||
56 | + | ||
57 | +type Metrics struct { | ||
58 | + Port string | ||
59 | +} | ||
60 | + | ||
61 | +type Sms struct { | ||
62 | + Debug bool | ||
63 | + DebugCode string | ||
64 | + Expire int `json:",default=180"` | ||
65 | + MaxSendTime int `json:",default=5"` | ||
66 | + CompanyName string | ||
67 | + SecretId string | ||
68 | + SecretKey string | ||
69 | + SmsAppId string | ||
70 | + Sign string | ||
71 | + TemplateId string | ||
72 | +} | ||
73 | + | ||
74 | +type Oss struct { | ||
75 | + OssEndPoint string | ||
76 | + AccessKeyID string | ||
77 | + AccessKeySecret string | ||
78 | + BuckName string | ||
79 | + | ||
80 | + RegionID string | ||
81 | + RoleArn string | ||
82 | + | ||
83 | + CDN CDN | ||
84 | +} | ||
85 | + | ||
86 | +type Wechat struct { | ||
87 | + AppName string `json:",optional"` | ||
88 | + AppID string | ||
89 | + AppSecret string | ||
90 | + MsgTemplates []Template `json:",optional"` | ||
91 | +} | ||
92 | + | ||
93 | +func (wx Wechat) GetTemplate(code string) (Template, bool) { | ||
94 | + for _, temp := range wx.MsgTemplates { | ||
95 | + if temp.Code == code { | ||
96 | + return temp, true | ||
97 | + } | ||
98 | + } | ||
99 | + return Template{}, false | ||
100 | +} | ||
101 | + | ||
102 | +type CDN struct { | ||
103 | + HostPairs []string | ||
104 | +} | ||
105 | + | ||
106 | +type Template struct { | ||
107 | + ID string // 模板ID | ||
108 | + Name string // 模板名称 | ||
109 | + Code string // 模板编码 | ||
110 | +} |
pkg/contextdata/tenant.go
0 → 100644
pkg/contextdata/user_token.go
0 → 100644
1 | +package contextdata | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "encoding/json" | ||
6 | + "github.com/golang-jwt/jwt/v4" | ||
7 | + "github.com/zeromicro/go-zero/core/logx" | ||
8 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/config" | ||
9 | + "time" | ||
10 | +) | ||
11 | + | ||
12 | +var ( | ||
13 | + CtxKeyJwtUserId = "userId" | ||
14 | + CtxKeyJwtCompanyId = "companyId" | ||
15 | +) | ||
16 | + | ||
17 | +func GetInt64FromCtx(ctx context.Context, key string) int64 { | ||
18 | + var uid int64 | ||
19 | + if jsonUid, ok := ctx.Value(key).(json.Number); ok { | ||
20 | + if int64Uid, err := jsonUid.Int64(); err == nil { | ||
21 | + uid = int64Uid | ||
22 | + } else { | ||
23 | + logx.WithContext(ctx).Errorf("GetUidFromCtx err : %+v", err) | ||
24 | + } | ||
25 | + } | ||
26 | + return uid | ||
27 | +} | ||
28 | + | ||
29 | +func getStringFromCtx(ctx context.Context, key string) string { | ||
30 | + var uid string | ||
31 | + if jsonUid, ok := ctx.Value(key).(string); ok { | ||
32 | + return jsonUid | ||
33 | + } | ||
34 | + return uid | ||
35 | +} | ||
36 | + | ||
37 | +func getArrayInt64FromCtx(ctx context.Context, key string) []int64 { | ||
38 | + values := ctx.Value(key) | ||
39 | + var ids = make([]int64, 0) | ||
40 | + if values == nil { | ||
41 | + return ids | ||
42 | + } | ||
43 | + if list, ok := values.([]interface{}); ok { | ||
44 | + for _, item := range list { | ||
45 | + if jsonId, ok := item.(json.Number); ok { | ||
46 | + id, _ := jsonId.Int64() | ||
47 | + ids = append(ids, id) | ||
48 | + } | ||
49 | + } | ||
50 | + } | ||
51 | + return ids | ||
52 | +} | ||
53 | + | ||
54 | +func GetUserTokenFromCtx(ctx context.Context) UserToken { | ||
55 | + return UserToken{ | ||
56 | + UserId: GetInt64FromCtx(ctx, CtxKeyJwtUserId), | ||
57 | + CompanyId: GetInt64FromCtx(ctx, CtxKeyJwtCompanyId), | ||
58 | + } | ||
59 | +} | ||
60 | + | ||
61 | +type UserToken struct { | ||
62 | + UserId int64 `json:"userId"` | ||
63 | + CompanyId int64 `json:"companyId"` | ||
64 | +} | ||
65 | + | ||
66 | +func (tk UserToken) GenerateToken(jwtConfig config.JwtAuth) (string, error) { | ||
67 | + claims := make(jwt.MapClaims) | ||
68 | + claims["exp"] = time.Now().Unix() + jwtConfig.Expire | ||
69 | + claims["iat"] = time.Now().Unix() | ||
70 | + claims["UserId"] = tk.UserId | ||
71 | + token := jwt.New(jwt.SigningMethodHS256) | ||
72 | + token.Claims = claims | ||
73 | + | ||
74 | + return token.SignedString([]byte(jwtConfig.AccessSecret)) | ||
75 | +} | ||
76 | + | ||
77 | +func (tk *UserToken) ParseToken(jwtConfig config.JWT, str string) error { | ||
78 | + return nil | ||
79 | +} | ||
80 | + | ||
81 | +// CheckUserInfo 如果UserToken有效 返回:true 否则返回false | ||
82 | +func (tk *UserToken) CheckUserInfo() bool { | ||
83 | + return !(tk.UserId > 100000000 || tk.UserId <= 0) | ||
84 | +} |
pkg/database/db.go
0 → 100644
1 | +package database | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "fmt" | ||
6 | + "github.com/zeromicro/go-zero/core/logx" | ||
7 | + "gorm.io/driver/mysql" | ||
8 | + "gorm.io/driver/postgres" | ||
9 | + "gorm.io/gorm" | ||
10 | + "gorm.io/gorm/logger" | ||
11 | + "log" | ||
12 | + "os" | ||
13 | + "time" | ||
14 | +) | ||
15 | + | ||
16 | +func OpenGormDB(source string) *gorm.DB { | ||
17 | + newLogger := logger.New( | ||
18 | + log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer | ||
19 | + logger.Config{ | ||
20 | + SlowThreshold: time.Second, // Slow SQL threshold | ||
21 | + LogLevel: logger.Info, // Log level | ||
22 | + IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger | ||
23 | + Colorful: false, // Disable color | ||
24 | + }, | ||
25 | + ) | ||
26 | + fmt.Println("starting db...") | ||
27 | + db, err := gorm.Open(mysql.Open(source), &gorm.Config{ | ||
28 | + Logger: newLogger, | ||
29 | + }) | ||
30 | + if err != nil { | ||
31 | + panic(err) | ||
32 | + } | ||
33 | + return db | ||
34 | +} | ||
35 | + | ||
36 | +func OpenGormPGDB(source string, logMode string) *gorm.DB { | ||
37 | + logx.Infof("starting db...") | ||
38 | + db, err := gorm.Open(postgres.New(postgres.Config{ | ||
39 | + DSN: source, | ||
40 | + PreferSimpleProtocol: true, // disables implicit prepared statement usage | ||
41 | + }), &gorm.Config{ | ||
42 | + Logger: NewLogger(logMode), //newLogger, | ||
43 | + }) | ||
44 | + if err != nil { | ||
45 | + panic(err) | ||
46 | + } | ||
47 | + return db | ||
48 | +} | ||
49 | + | ||
50 | +func NewLogger(logType string) logger.Interface { | ||
51 | + if logType == "console" { | ||
52 | + return logger.New( | ||
53 | + log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer | ||
54 | + logger.Config{ | ||
55 | + SlowThreshold: time.Second, // Slow SQL threshold | ||
56 | + LogLevel: logger.Info, // Log level | ||
57 | + IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger | ||
58 | + Colorful: false, // Disable color | ||
59 | + }, | ||
60 | + ) | ||
61 | + } | ||
62 | + return ZeroLog{} | ||
63 | +} | ||
64 | + | ||
65 | +type ZeroLog struct { | ||
66 | +} | ||
67 | + | ||
68 | +func (l ZeroLog) LogMode(logger.LogLevel) logger.Interface { | ||
69 | + return l | ||
70 | +} | ||
71 | +func (l ZeroLog) Info(ctx context.Context, s string, values ...interface{}) { | ||
72 | + logx.Infof(s, values...) | ||
73 | +} | ||
74 | +func (l ZeroLog) Warn(ctx context.Context, s string, values ...interface{}) { | ||
75 | + logx.Errorf(s, values...) | ||
76 | +} | ||
77 | +func (l ZeroLog) Error(ctx context.Context, s string, values ...interface{}) { | ||
78 | + logx.Errorf(s, values...) | ||
79 | +} | ||
80 | +func (l ZeroLog) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { | ||
81 | + now := time.Now() | ||
82 | + sql, rows := fc() | ||
83 | + logx.Infof("[%v] [rows:%v] %s", now.Sub(begin).String(), rows, sql) | ||
84 | +} |
pkg/database/partition.go
0 → 100644
1 | +package database | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "github.com/jinzhu/now" | ||
6 | + "github.com/zeromicro/go-zero/core/stores/redis" | ||
7 | + "gorm.io/gorm" | ||
8 | + "gorm.io/gorm/clause" | ||
9 | + "gorm.io/gorm/migrator" | ||
10 | + "gorm.io/gorm/schema" | ||
11 | + "strings" | ||
12 | + "time" | ||
13 | +) | ||
14 | + | ||
15 | +var ( | ||
16 | + // PartitionByRangeTime 按unix时间戳分区 | ||
17 | + PartitionByRangeTime = 1 | ||
18 | + // PartitionByHash 按系统的hash值分区 | ||
19 | + PartitionByHash = 2 | ||
20 | + // PartitionByList 按List包含值分区 | ||
21 | + PartitionByList = 3 | ||
22 | +) | ||
23 | + | ||
24 | +type PartitionTable interface { | ||
25 | + TableName() string | ||
26 | +} | ||
27 | + | ||
28 | +type PartitionMigrator struct { | ||
29 | + ServiceName string | ||
30 | + DB *gorm.DB | ||
31 | + Redis *redis.Redis | ||
32 | +} | ||
33 | + | ||
34 | +func NewPartitionMigrator(serviceName string, db *gorm.DB, redis *redis.Redis) *PartitionMigrator { | ||
35 | + return &PartitionMigrator{ | ||
36 | + DB: db, | ||
37 | + ServiceName: serviceName, | ||
38 | + Redis: redis, | ||
39 | + } | ||
40 | +} | ||
41 | + | ||
42 | +func (c *PartitionMigrator) AutoMigrate(t PartitionTable, option ...PartitionOptionFunc) error { | ||
43 | + options := NewPartitionOptions() | ||
44 | + for i := range option { | ||
45 | + option[i](options) | ||
46 | + } | ||
47 | + | ||
48 | + tableName := t.TableName() | ||
49 | + if !c.DB.Migrator().HasTable(tableName) { | ||
50 | + migrator := Migrator{migrator.Migrator{ | ||
51 | + migrator.Config{ | ||
52 | + CreateIndexAfterCreateTable: true, | ||
53 | + DB: c.DB, | ||
54 | + Dialector: c.DB.Dialector, | ||
55 | + }, | ||
56 | + }} | ||
57 | + if err := migrator.CreatePartitionTable(options, t); err != nil { | ||
58 | + panic(err) | ||
59 | + } | ||
60 | + } | ||
61 | + | ||
62 | + rk := fmt.Sprintf("%s:auto-partition:%s", c.ServiceName, tableName) | ||
63 | + lock := redis.NewRedisLock(c.Redis, rk) | ||
64 | + ok, err := lock.Acquire() | ||
65 | + if !ok || err != nil { | ||
66 | + return nil | ||
67 | + } | ||
68 | + defer lock.Release() | ||
69 | + switch options.Type { | ||
70 | + case PartitionByRangeTime: | ||
71 | + begin := options.TimeBegin | ||
72 | + end := options.TimeEnd | ||
73 | + for { | ||
74 | + if begin.Unix() > end.Unix() { | ||
75 | + break | ||
76 | + } | ||
77 | + pTable := fmt.Sprintf("%s_%s", tableName, options.FormatTimeSubFunc(begin)) | ||
78 | + sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s PARTITION OF %s FOR VALUES FROM (%d) TO (%d);", | ||
79 | + pTable, tableName, begin.Unix(), begin.AddDate(0, options.TimeSpanMonth, 0).Unix()) | ||
80 | + tx := c.DB.Exec(sql) | ||
81 | + if tx.Error != nil { | ||
82 | + return tx.Error | ||
83 | + } | ||
84 | + c.log(t, pTable) | ||
85 | + begin = begin.AddDate(0, options.TimeSpanMonth, 0) | ||
86 | + } | ||
87 | + break | ||
88 | + case PartitionByHash: | ||
89 | + for i := 0; i < options.Modulus; i++ { | ||
90 | + pTable := fmt.Sprintf("%s_%d", tableName, i) | ||
91 | + sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s PARTITION OF %s FOR VALUES WITH (MODULUS %d, REMAINDER %d);", | ||
92 | + pTable, tableName, options.Modulus, i) | ||
93 | + tx := c.DB.Exec(sql) | ||
94 | + if tx.Error != nil { | ||
95 | + return tx.Error | ||
96 | + } | ||
97 | + c.log(t, pTable) | ||
98 | + } | ||
99 | + break | ||
100 | + case PartitionByList: | ||
101 | + for i := 0; i < len(options.ListRange); i++ { | ||
102 | + pTable := fmt.Sprintf("%s_%d", tableName, i) | ||
103 | + sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s PARTITION OF %s FOR VALUES IN %s;", | ||
104 | + pTable, tableName, InArgs(options.ListRange[i])) | ||
105 | + tx := c.DB.Exec(sql) | ||
106 | + if tx.Error != nil { | ||
107 | + return tx.Error | ||
108 | + } | ||
109 | + c.log(t, pTable) | ||
110 | + } | ||
111 | + break | ||
112 | + default: | ||
113 | + return nil | ||
114 | + } | ||
115 | + | ||
116 | + return nil | ||
117 | +} | ||
118 | + | ||
119 | +func (c *PartitionMigrator) log(t PartitionTable, pTable string) { | ||
120 | + fmt.Println("【自动分区】 create partition table", pTable, "on table", t.TableName()) | ||
121 | +} | ||
122 | + | ||
123 | +type PartitionOptions struct { | ||
124 | + // 分区类型 1:Hash 2:RangeTime | ||
125 | + Type int | ||
126 | + // 分区列 | ||
127 | + Column string | ||
128 | + | ||
129 | + // Hash分区 | ||
130 | + Modulus int | ||
131 | + | ||
132 | + // List 范围 | ||
133 | + ListRange []interface{} | ||
134 | + | ||
135 | + // Range时间分区 | ||
136 | + TimeBegin time.Time | ||
137 | + TimeEnd time.Time | ||
138 | + TimeSpanMonth int | ||
139 | + FormatTimeSubFunc func(time.Time) string | ||
140 | + | ||
141 | + // 禁用PrimaryKey生成 | ||
142 | + // 分区字段有函数表达式的,需要禁用掉PrimaryKey,使用自定义的唯一ID生成规则 | ||
143 | + DisablePrimaryKey bool | ||
144 | +} | ||
145 | + | ||
146 | +func NewPartitionOptions() *PartitionOptions { | ||
147 | + return &PartitionOptions{ | ||
148 | + Type: PartitionByRangeTime, | ||
149 | + FormatTimeSubFunc: func(t time.Time) string { | ||
150 | + return t.Format("200601") | ||
151 | + }, | ||
152 | + } | ||
153 | +} | ||
154 | + | ||
155 | +func (c *PartitionOptions) Sql() string { | ||
156 | + if c.Type == PartitionByHash { | ||
157 | + return fmt.Sprintf("PARTITION BY HASH(%s)", c.Column) | ||
158 | + } | ||
159 | + if c.Type == PartitionByRangeTime { | ||
160 | + return fmt.Sprintf("PARTITION BY RANGE(%s)", c.Column) | ||
161 | + } | ||
162 | + if c.Type == PartitionByList { | ||
163 | + return fmt.Sprintf("PARTITION BY LIST(%s)", c.Column) | ||
164 | + } | ||
165 | + return "" | ||
166 | +} | ||
167 | + | ||
168 | +type PartitionOptionFunc func(*PartitionOptions) | ||
169 | + | ||
170 | +func WithPartitionType(t int) PartitionOptionFunc { | ||
171 | + return func(options *PartitionOptions) { | ||
172 | + options.Type = t | ||
173 | + } | ||
174 | +} | ||
175 | + | ||
176 | +func WithPartitionColumn(c string) PartitionOptionFunc { | ||
177 | + return func(options *PartitionOptions) { | ||
178 | + options.Column = c | ||
179 | + } | ||
180 | +} | ||
181 | + | ||
182 | +func WithPartitionHash(modulus int) PartitionOptionFunc { | ||
183 | + return func(options *PartitionOptions) { | ||
184 | + options.Modulus = modulus | ||
185 | + } | ||
186 | +} | ||
187 | + | ||
188 | +func WithPartitionRangeTime(begin, end time.Time, spanMonth int) PartitionOptionFunc { | ||
189 | + return func(options *PartitionOptions) { | ||
190 | + options.TimeBegin = begin | ||
191 | + options.TimeEnd = end | ||
192 | + options.TimeSpanMonth = spanMonth | ||
193 | + } | ||
194 | +} | ||
195 | + | ||
196 | +func WithPartitionList(list ...interface{}) PartitionOptionFunc { | ||
197 | + return func(options *PartitionOptions) { | ||
198 | + options.ListRange = list | ||
199 | + } | ||
200 | +} | ||
201 | + | ||
202 | +func WithDisablePrimaryKey(disablePrimaryKey bool) PartitionOptionFunc { | ||
203 | + return func(options *PartitionOptions) { | ||
204 | + options.DisablePrimaryKey = disablePrimaryKey | ||
205 | + } | ||
206 | +} | ||
207 | + | ||
208 | +func Date(date string) time.Time { | ||
209 | + return now.MustParse(date) | ||
210 | +} | ||
211 | + | ||
212 | +type Migrator struct { | ||
213 | + migrator.Migrator | ||
214 | +} | ||
215 | + | ||
216 | +// CreatePartitionTable create table in database for values | ||
217 | +func (m Migrator) CreatePartitionTable(options *PartitionOptions, values ...interface{}) error { | ||
218 | + for _, value := range m.ReorderModels(values, false) { | ||
219 | + tx := m.DB.Session(&gorm.Session{}) | ||
220 | + if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) { | ||
221 | + var ( | ||
222 | + createTableSQL = "CREATE TABLE ? (" | ||
223 | + values = []interface{}{m.CurrentTable(stmt)} | ||
224 | + hasPrimaryKeyInDataType bool | ||
225 | + ) | ||
226 | + | ||
227 | + for _, dbName := range stmt.Schema.DBNames { | ||
228 | + field := stmt.Schema.FieldsByDBName[dbName] | ||
229 | + if !field.IgnoreMigration { | ||
230 | + createTableSQL += "? ?" | ||
231 | + hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(string(field.DataType)), "PRIMARY KEY") | ||
232 | + values = append(values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field)) | ||
233 | + createTableSQL += "," | ||
234 | + } | ||
235 | + } | ||
236 | + | ||
237 | + if !hasPrimaryKeyInDataType && len(stmt.Schema.PrimaryFields) > 0 && !options.DisablePrimaryKey { | ||
238 | + createTableSQL += "PRIMARY KEY ?," | ||
239 | + primaryKeys := []interface{}{} | ||
240 | + for _, field := range stmt.Schema.PrimaryFields { | ||
241 | + primaryKeys = append(primaryKeys, clause.Column{Name: field.DBName}) | ||
242 | + } | ||
243 | + | ||
244 | + values = append(values, primaryKeys) | ||
245 | + } | ||
246 | + | ||
247 | + for _, idx := range stmt.Schema.ParseIndexes() { | ||
248 | + if m.CreateIndexAfterCreateTable { | ||
249 | + defer func(value interface{}, name string) { | ||
250 | + if errr == nil { | ||
251 | + errr = tx.Migrator().CreateIndex(value, name) | ||
252 | + } | ||
253 | + }(value, idx.Name) | ||
254 | + } else { | ||
255 | + if idx.Class != "" { | ||
256 | + createTableSQL += idx.Class + " " | ||
257 | + } | ||
258 | + createTableSQL += "INDEX ? ?" | ||
259 | + | ||
260 | + if idx.Comment != "" { | ||
261 | + createTableSQL += fmt.Sprintf(" COMMENT '%s'", idx.Comment) | ||
262 | + } | ||
263 | + | ||
264 | + if idx.Option != "" { | ||
265 | + createTableSQL += " " + idx.Option | ||
266 | + } | ||
267 | + | ||
268 | + createTableSQL += "," | ||
269 | + values = append(values, clause.Column{Name: idx.Name}, tx.Migrator().(migrator.BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt)) | ||
270 | + } | ||
271 | + } | ||
272 | + | ||
273 | + if !m.DB.DisableForeignKeyConstraintWhenMigrating && !m.DB.IgnoreRelationshipsWhenMigrating { | ||
274 | + for _, rel := range stmt.Schema.Relationships.Relations { | ||
275 | + if rel.Field.IgnoreMigration { | ||
276 | + continue | ||
277 | + } | ||
278 | + if constraint := rel.ParseConstraint(); constraint != nil { | ||
279 | + if constraint.Schema == stmt.Schema { | ||
280 | + sql, vars := buildConstraint(constraint) | ||
281 | + createTableSQL += sql + "," | ||
282 | + values = append(values, vars...) | ||
283 | + } | ||
284 | + } | ||
285 | + } | ||
286 | + } | ||
287 | + | ||
288 | + for _, chk := range stmt.Schema.ParseCheckConstraints() { | ||
289 | + createTableSQL += "CONSTRAINT ? CHECK (?)," | ||
290 | + values = append(values, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}) | ||
291 | + } | ||
292 | + | ||
293 | + createTableSQL = strings.TrimSuffix(createTableSQL, ",") | ||
294 | + | ||
295 | + createTableSQL += ")" | ||
296 | + | ||
297 | + if options != nil { | ||
298 | + createTableSQL += options.Sql() | ||
299 | + } | ||
300 | + | ||
301 | + if tableOption, ok := m.DB.Get("gorm:table_options"); ok { | ||
302 | + createTableSQL += fmt.Sprint(tableOption) | ||
303 | + } | ||
304 | + | ||
305 | + errr = tx.Exec(createTableSQL, values...).Error | ||
306 | + return errr | ||
307 | + }); err != nil { | ||
308 | + return err | ||
309 | + } | ||
310 | + } | ||
311 | + return nil | ||
312 | +} | ||
313 | + | ||
314 | +func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) { | ||
315 | + sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??" | ||
316 | + if constraint.OnDelete != "" { | ||
317 | + sql += " ON DELETE " + constraint.OnDelete | ||
318 | + } | ||
319 | + | ||
320 | + if constraint.OnUpdate != "" { | ||
321 | + sql += " ON UPDATE " + constraint.OnUpdate | ||
322 | + } | ||
323 | + | ||
324 | + var foreignKeys, references []interface{} | ||
325 | + for _, field := range constraint.ForeignKeys { | ||
326 | + foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName}) | ||
327 | + } | ||
328 | + | ||
329 | + for _, field := range constraint.References { | ||
330 | + references = append(references, clause.Column{Name: field.DBName}) | ||
331 | + } | ||
332 | + results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references) | ||
333 | + return | ||
334 | +} |
pkg/database/query.go
0 → 100644
1 | +package database | ||
2 | + | ||
3 | +import ( | ||
4 | + "github.com/zeromicro/go-zero/core/mapping" | ||
5 | + "reflect" | ||
6 | +) | ||
7 | + | ||
8 | +func InArgs(args interface{}) string { | ||
9 | + bytes := make([]byte, 0) | ||
10 | + bytes = appendIn(bytes, reflect.ValueOf(args)) | ||
11 | + return string(bytes) | ||
12 | +} | ||
13 | + | ||
14 | +func Arg(args interface{}) string { | ||
15 | + bytes := make([]byte, 0) | ||
16 | + v := reflect.ValueOf(args) | ||
17 | + bytes = appendValue(bytes, v) | ||
18 | + return string(bytes) | ||
19 | +} | ||
20 | + | ||
21 | +func appendIn(b []byte, slice reflect.Value) []byte { | ||
22 | + sliceLen := slice.Len() | ||
23 | + b = append(b, '(') | ||
24 | + for i := 0; i < sliceLen; i++ { | ||
25 | + if i > 0 { | ||
26 | + b = append(b, ',') | ||
27 | + } | ||
28 | + | ||
29 | + elem := slice.Index(i) | ||
30 | + if elem.Kind() == reflect.Interface { | ||
31 | + elem = elem.Elem() | ||
32 | + } | ||
33 | + if elem.Kind() == reflect.Slice { | ||
34 | + //b = appendIn(b, elem) | ||
35 | + } else { | ||
36 | + b = appendValue(b, elem) | ||
37 | + } | ||
38 | + } | ||
39 | + b = append(b, ')') | ||
40 | + return b | ||
41 | +} | ||
42 | + | ||
43 | +func appendValue(b []byte, v reflect.Value) []byte { | ||
44 | + if v.Kind() == reflect.Ptr && v.IsNil() { | ||
45 | + | ||
46 | + return append(b, "NULL"...) | ||
47 | + } | ||
48 | + if v.Kind() == reflect.Int || v.Kind() == reflect.Int64 || v.Kind() == reflect.Float64 { | ||
49 | + return append(b, []byte(mapping.Repr(v.Interface()))...) | ||
50 | + } | ||
51 | + b = append(b, []byte("'")...) | ||
52 | + b = append(b, []byte(mapping.Repr(v.Interface()))...) | ||
53 | + b = append(b, []byte("'")...) | ||
54 | + return b | ||
55 | +} |
pkg/database/shardingsphere.go
0 → 100644
1 | +package database |
pkg/result/httpResult.go
0 → 100644
1 | +package result | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/xerr" | ||
6 | + "net/http" | ||
7 | + | ||
8 | + "github.com/pkg/errors" | ||
9 | + "github.com/zeromicro/go-zero/core/logx" | ||
10 | + "github.com/zeromicro/go-zero/rest/httpx" | ||
11 | + "google.golang.org/grpc/status" | ||
12 | +) | ||
13 | + | ||
14 | +// http返回 | ||
15 | +func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) { | ||
16 | + | ||
17 | + if err == nil { | ||
18 | + //成功返回 | ||
19 | + r := Success(resp) | ||
20 | + httpx.WriteJson(w, http.StatusOK, r) | ||
21 | + } else { | ||
22 | + //错误返回 | ||
23 | + errcode := xerr.SERVER_COMMON_ERROR | ||
24 | + errmsg := "服务器开小差啦,稍后再来试一试" | ||
25 | + internalErr := "" | ||
26 | + causeErr := errors.Cause(err) // err类型 | ||
27 | + if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型 | ||
28 | + //自定义CodeError | ||
29 | + errcode = e.GetErrCode() | ||
30 | + errmsg = e.GetErrMsg() | ||
31 | + if e.InternalError != nil { | ||
32 | + internalErr = e.InternalError.Error() | ||
33 | + } | ||
34 | + } else { | ||
35 | + if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 | ||
36 | + grpcCode := uint32(gstatus.Code()) | ||
37 | + if xerr.IsCodeErr(grpcCode) { //区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 | ||
38 | + errcode = grpcCode | ||
39 | + errmsg = gstatus.Message() | ||
40 | + } | ||
41 | + } | ||
42 | + } | ||
43 | + | ||
44 | + logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err) | ||
45 | + response := Error(errcode, errmsg) | ||
46 | + response.Error = internalErr | ||
47 | + httpx.WriteJson(w, http.StatusOK, response) | ||
48 | + } | ||
49 | +} | ||
50 | + | ||
51 | +// 授权的http方法 | ||
52 | +func AuthHttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) { | ||
53 | + | ||
54 | + if err == nil { | ||
55 | + //成功返回 | ||
56 | + r := Success(resp) | ||
57 | + httpx.WriteJson(w, http.StatusOK, r) | ||
58 | + } else { | ||
59 | + //错误返回 | ||
60 | + errcode := xerr.SERVER_COMMON_ERROR | ||
61 | + errmsg := "服务器开小差啦,稍后再来试一试" | ||
62 | + | ||
63 | + causeErr := errors.Cause(err) // err类型 | ||
64 | + if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型 | ||
65 | + //自定义CodeError | ||
66 | + errcode = e.GetErrCode() | ||
67 | + errmsg = e.GetErrMsg() | ||
68 | + } else { | ||
69 | + if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 | ||
70 | + grpcCode := uint32(gstatus.Code()) | ||
71 | + if xerr.IsCodeErr(grpcCode) { //区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 | ||
72 | + errcode = grpcCode | ||
73 | + errmsg = gstatus.Message() | ||
74 | + } | ||
75 | + } | ||
76 | + } | ||
77 | + | ||
78 | + logx.WithContext(r.Context()).Errorf("【GATEWAY-ERR】 : %+v ", err) | ||
79 | + | ||
80 | + httpx.WriteJson(w, http.StatusUnauthorized, Error(errcode, errmsg)) | ||
81 | + } | ||
82 | +} | ||
83 | + | ||
84 | +// http 参数错误返回 | ||
85 | +func ParamErrorResult(r *http.Request, w http.ResponseWriter, err error) { | ||
86 | + errMsg := fmt.Sprintf("%s ,%s", xerr.MapErrMsg(xerr.REUQEST_PARAM_ERROR), err.Error()) | ||
87 | + httpx.WriteJson(w, http.StatusBadRequest, Error(xerr.REUQEST_PARAM_ERROR, errMsg)) | ||
88 | +} |
pkg/result/jobResult.go
0 → 100644
1 | +package result | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/xerr" | ||
6 | + | ||
7 | + "github.com/pkg/errors" | ||
8 | + "github.com/zeromicro/go-zero/core/logx" | ||
9 | + "google.golang.org/grpc/status" | ||
10 | +) | ||
11 | + | ||
12 | +// job返回 | ||
13 | +func JobResult(ctx context.Context, resp interface{}, err error) { | ||
14 | + if err == nil { | ||
15 | + // 成功返回 ,只有dev环境下才会打印info,线上不显示 | ||
16 | + if resp != nil { | ||
17 | + logx.Infof("resp: %+v", resp) | ||
18 | + } | ||
19 | + return | ||
20 | + } else { | ||
21 | + errCode := xerr.SERVER_COMMON_ERROR | ||
22 | + errMsg := "服务器开小差啦,稍后再来试一试" | ||
23 | + | ||
24 | + // 错误返回 | ||
25 | + causeErr := errors.Cause(err) // err类型 | ||
26 | + if e, ok := causeErr.(*xerr.CodeError); ok { // 自定义错误类型 | ||
27 | + // 自定义CodeError | ||
28 | + errCode = e.GetErrCode() | ||
29 | + errMsg = e.GetErrMsg() | ||
30 | + } else { | ||
31 | + if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 | ||
32 | + grpcCode := uint32(gstatus.Code()) | ||
33 | + if xerr.IsCodeErr(grpcCode) { // 区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 | ||
34 | + errCode = grpcCode | ||
35 | + errMsg = gstatus.Message() | ||
36 | + } | ||
37 | + } | ||
38 | + } | ||
39 | + | ||
40 | + logx.WithContext(ctx).Errorf("【JOB-ERR】 : %+v ,errCode:%d , errMsg:%s ", err, errCode, errMsg) | ||
41 | + return | ||
42 | + } | ||
43 | +} |
pkg/result/responseBean.go
0 → 100644
1 | +package result | ||
2 | + | ||
3 | +type ResponseSuccessBean struct { | ||
4 | + Code uint32 `json:"code"` | ||
5 | + Msg string `json:"msg"` | ||
6 | + Data interface{} `json:"data"` | ||
7 | +} | ||
8 | +type NullJson struct{} | ||
9 | + | ||
10 | +func Success(data interface{}) *ResponseSuccessBean { | ||
11 | + return &ResponseSuccessBean{Code: 0, Msg: "OK", Data: data} | ||
12 | +} | ||
13 | + | ||
14 | +type ResponseErrorBean struct { | ||
15 | + Code uint32 `json:"code"` | ||
16 | + Msg string `json:"msg"` | ||
17 | + Error string `json:"err"` | ||
18 | +} | ||
19 | + | ||
20 | +func Error(errCode uint32, errMsg string) *ResponseErrorBean { | ||
21 | + return &ResponseErrorBean{Code: errCode, Msg: errMsg} | ||
22 | +} |
pkg/tool/encryption.go
0 → 100644
1 | +package tool | ||
2 | + | ||
3 | +import ( | ||
4 | + "crypto/md5" | ||
5 | + "fmt" | ||
6 | + "io" | ||
7 | +) | ||
8 | + | ||
9 | +/** 加密方式 **/ | ||
10 | + | ||
11 | +func Md5ByString(str string) string { | ||
12 | + m := md5.New() | ||
13 | + _, err := io.WriteString(m, str) | ||
14 | + if err != nil { | ||
15 | + panic(err) | ||
16 | + } | ||
17 | + arr := m.Sum(nil) | ||
18 | + return fmt.Sprintf("%x", arr) | ||
19 | +} | ||
20 | + | ||
21 | +func Md5ByBytes(b []byte) string { | ||
22 | + return fmt.Sprintf("%x", md5.Sum(b)) | ||
23 | +} |
pkg/tool/filetype_detect.go
0 → 100644
1 | +package tool | ||
2 | + | ||
3 | +import ( | ||
4 | + "path/filepath" | ||
5 | + "strings" | ||
6 | +) | ||
7 | + | ||
8 | +const ( | ||
9 | + Image = "image" | ||
10 | + Video = "video" | ||
11 | +) | ||
12 | + | ||
13 | +var TypeMap = map[string]string{ | ||
14 | + "jpg": Image, | ||
15 | + "png": Image, | ||
16 | + "gif": Image, | ||
17 | + "webp": Image, | ||
18 | + "cr2": Image, | ||
19 | + "tif": Image, | ||
20 | + "bmp": Image, | ||
21 | + "heif": Image, | ||
22 | + "jxr": Image, | ||
23 | + "psd": Image, | ||
24 | + "ico": Image, | ||
25 | + "dwg": Image, | ||
26 | + "avif": Image, | ||
27 | + | ||
28 | + "mp4": Video, | ||
29 | + "m4v": Video, | ||
30 | + "mkv": Video, | ||
31 | + "webm": Video, | ||
32 | + "mov": Video, | ||
33 | + "avi": Video, | ||
34 | + "wmv": Video, | ||
35 | + "mpg": Video, | ||
36 | + "flv": Video, | ||
37 | + "3gp": Video, | ||
38 | +} | ||
39 | +var DefaultFileTypeDetector = FileTypeDetector{} | ||
40 | + | ||
41 | +type FileTypeDetector struct { | ||
42 | +} | ||
43 | + | ||
44 | +func (c FileTypeDetector) Classify(medias []string, mediaType string) []string { | ||
45 | + result := make([]string, 0) | ||
46 | + for _, media := range medias { | ||
47 | + v, ok := TypeMap[strings.Trim(filepath.Ext(media), ".")] | ||
48 | + if !ok { | ||
49 | + continue | ||
50 | + } | ||
51 | + if v == mediaType { | ||
52 | + result = append(result, media) | ||
53 | + } | ||
54 | + } | ||
55 | + return result | ||
56 | +} |
pkg/tool/jwt.go
0 → 100644
1 | +package tool | ||
2 | + | ||
3 | +import ( | ||
4 | + jwt "github.com/golang-jwt/jwt/v4" | ||
5 | + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/config" | ||
6 | + "time" | ||
7 | +) | ||
8 | + | ||
9 | +type UserToken struct { | ||
10 | + UserId int64 `json:"userId"` | ||
11 | + CoachId int64 `json:"coach_id"` | ||
12 | + AdminId int64 `json:"adminId"` | ||
13 | + ClientType string `json:"clientType"` | ||
14 | + AccessShops []int64 `json:"accessShops"` | ||
15 | +} | ||
16 | + | ||
17 | +func (tk UserToken) GenerateToken(jwtConfig config.JwtAuth) (string, error) { | ||
18 | + claims := make(jwt.MapClaims) | ||
19 | + claims["exp"] = time.Now().Unix() + jwtConfig.Expire | ||
20 | + claims["iat"] = time.Now().Unix() | ||
21 | + claims["UserId"] = tk.UserId | ||
22 | + claims["CoachId"] = tk.CoachId | ||
23 | + claims["AdminId"] = tk.AdminId | ||
24 | + claims["ClientType"] = tk.ClientType | ||
25 | + claims["AccessShops"] = tk.AccessShops | ||
26 | + token := jwt.New(jwt.SigningMethodHS256) | ||
27 | + token.Claims = claims | ||
28 | + | ||
29 | + return token.SignedString([]byte(jwtConfig.AccessSecret)) | ||
30 | +} | ||
31 | + | ||
32 | +func (tk *UserToken) ParseToken(jwtConfig config.JWT, str string) error { | ||
33 | + //tokenClaims, err := jwt.ParseWithClaims( | ||
34 | + // str, | ||
35 | + // tk, | ||
36 | + // func(token *jwt.Token) (interface{}, error) { | ||
37 | + // return []byte(jwtConfig.Secret), nil | ||
38 | + // }) | ||
39 | + //if err != nil { | ||
40 | + // return err | ||
41 | + //} | ||
42 | + //if claim, ok := tokenClaims.Claims.(*UserToken); ok && tokenClaims.Valid { | ||
43 | + // *tk = *claim | ||
44 | + // return nil | ||
45 | + //} | ||
46 | + //return errors.New("token 解析失败") | ||
47 | + return nil | ||
48 | +} | ||
49 | + | ||
50 | +// CheckUserInfo 如果UserToken有效 返回:true 否则返回false | ||
51 | +func (tk *UserToken) CheckUserInfo() bool { | ||
52 | + return !(tk.UserId > 100000000 || tk.UserId <= 0) | ||
53 | +} |
pkg/tool/krand.go
0 → 100644
1 | +package tool | ||
2 | + | ||
3 | +import ( | ||
4 | + "math/rand" | ||
5 | + "time" | ||
6 | +) | ||
7 | + | ||
8 | +const ( | ||
9 | + KC_RAND_KIND_NUM = 0 // 纯数字 | ||
10 | + KC_RAND_KIND_LOWER = 1 // 小写字母 | ||
11 | + KC_RAND_KIND_UPPER = 2 // 大写字母 | ||
12 | + KC_RAND_KIND_ALL = 3 // 数字、大小写字母 | ||
13 | +) | ||
14 | + | ||
15 | +// 随机字符串 | ||
16 | +func Krand(size int, kind int) string { | ||
17 | + ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size) | ||
18 | + is_all := kind > 2 || kind < 0 | ||
19 | + rand.Seed(time.Now().UnixNano()) | ||
20 | + for i := 0; i < size; i++ { | ||
21 | + if is_all { // random ikind | ||
22 | + ikind = rand.Intn(3) | ||
23 | + } | ||
24 | + scope, base := kinds[ikind][0], kinds[ikind][1] | ||
25 | + result[i] = uint8(base + rand.Intn(scope)) | ||
26 | + } | ||
27 | + return string(result) | ||
28 | +} |
pkg/xcollection/tree.go
0 → 100644
1 | +package xcollection | ||
2 | + | ||
3 | +type TreeNode interface { | ||
4 | + PID() string | ||
5 | + ID() string | ||
6 | +} | ||
7 | + | ||
8 | +type Tree struct { | ||
9 | + Node TreeNode `json:"chart"` | ||
10 | + Nodes []*Tree `json:"charts"` | ||
11 | +} | ||
12 | + | ||
13 | +func NewTree(nodes []TreeNode) *Tree { | ||
14 | + var tree = &Tree{ | ||
15 | + Node: nil, | ||
16 | + Nodes: make([]*Tree, 0), | ||
17 | + } | ||
18 | + for i := range nodes { | ||
19 | + match := traverseAdd(tree, nodes[i]) | ||
20 | + if !match { | ||
21 | + tree.Nodes = append(tree.Nodes, newTree(nodes[i])) | ||
22 | + } | ||
23 | + } | ||
24 | + return tree | ||
25 | +} | ||
26 | + | ||
27 | +func newTree(node TreeNode) *Tree { | ||
28 | + return &Tree{ | ||
29 | + Node: node, | ||
30 | + Nodes: make([]*Tree, 0), | ||
31 | + } | ||
32 | +} | ||
33 | + | ||
34 | +func (tree *Tree) Root() TreeNode { | ||
35 | + if tree.Node != nil { | ||
36 | + return tree.Node | ||
37 | + } | ||
38 | + if len(tree.Nodes) > 0 { | ||
39 | + return tree.Nodes[0].Node | ||
40 | + } | ||
41 | + return nil | ||
42 | +} | ||
43 | + | ||
44 | +// TreeNodePaths returns all the parents of the current node 1->5->7 , use time n*O(n)(need performance optimization) | ||
45 | +func (tree *Tree) TreeNodePaths(node TreeNode) []TreeNode { | ||
46 | + treeNode := node | ||
47 | + result := make([]TreeNode, 0) | ||
48 | + for { | ||
49 | + if treeNode == nil { | ||
50 | + break | ||
51 | + } | ||
52 | + tmp := tree.find(treeNode, func(a, b TreeNode) bool { | ||
53 | + if a.ID() == b.PID() { | ||
54 | + return true | ||
55 | + } | ||
56 | + return false | ||
57 | + }) | ||
58 | + result = append(result, treeNode) | ||
59 | + if tmp == nil { | ||
60 | + break | ||
61 | + } | ||
62 | + treeNode = tmp.Node | ||
63 | + } | ||
64 | + reserveResult := make([]TreeNode, 0) | ||
65 | + for i := len(result) - 1; i >= 0; i-- { | ||
66 | + reserveResult = append(reserveResult, result[i]) | ||
67 | + } | ||
68 | + return reserveResult | ||
69 | +} | ||
70 | + | ||
71 | +// Add adds a node to the first matching parent tree if add success it return true | ||
72 | +func (tree *Tree) Add(node TreeNode) bool { | ||
73 | + return traverseAdd(tree, node) | ||
74 | +} | ||
75 | + | ||
76 | +// AllChildNode returns all child nodes under Node, including itself | ||
77 | +func (tree *Tree) AllChildNode(node TreeNode) []TreeNode { | ||
78 | + treeNode := tree.find(node, nil) | ||
79 | + if treeNode == nil { | ||
80 | + return []TreeNode{} | ||
81 | + } | ||
82 | + return tree.allChildNode(treeNode, nil) | ||
83 | +} | ||
84 | + | ||
85 | +// AllLeafNode returns all leaf node under Node ,if node is nil returns all leaf node under tree | ||
86 | +func (tree *Tree) AllLeafNode(node TreeNode) []TreeNode { | ||
87 | + treeNode := tree | ||
88 | + if node != nil { | ||
89 | + treeNode = tree.find(node, nil) | ||
90 | + } | ||
91 | + if treeNode == nil { | ||
92 | + return []TreeNode{} | ||
93 | + } | ||
94 | + return tree.allChildNode(treeNode, func(node *Tree) bool { | ||
95 | + if len(node.Nodes) == 0 { | ||
96 | + return true | ||
97 | + } | ||
98 | + return false | ||
99 | + }) | ||
100 | +} | ||
101 | + | ||
102 | +// Depth returns all child nodes under depth depth=[1:n] | ||
103 | +func (tree *Tree) Depth(depth int) []TreeNode { | ||
104 | + treeNode := tree.find(tree.Root(), nil) | ||
105 | + if treeNode == nil { | ||
106 | + return []TreeNode{} | ||
107 | + } | ||
108 | + return tree.allChildByDepth(treeNode, depth) | ||
109 | +} | ||
110 | + | ||
111 | +// AllChildNodeByDepth returns all child nodes under depth Node | ||
112 | +func (tree *Tree) AllChildNodeByDepth(node TreeNode, depth int) []TreeNode { | ||
113 | + treeNode := tree.find(node, nil) | ||
114 | + if treeNode == nil { | ||
115 | + return []TreeNode{} | ||
116 | + } | ||
117 | + return tree.allChildByDepth(treeNode, depth) | ||
118 | +} | ||
119 | + | ||
120 | +// Find query the node in this tree | ||
121 | +func (tree *Tree) Find(node TreeNode, compared func(a, b TreeNode) bool) *Tree { | ||
122 | + return tree.find(node, compared) | ||
123 | +} | ||
124 | + | ||
125 | +// find query the node in this tree | ||
126 | +func (tree *Tree) find(node TreeNode, compared func(a, b TreeNode) bool) *Tree { | ||
127 | + var stack []*Tree | ||
128 | + stack = append(stack, tree) | ||
129 | + var find *Tree | ||
130 | + for { | ||
131 | + if len(stack) == 0 { | ||
132 | + break | ||
133 | + } | ||
134 | + pop := stack[0] | ||
135 | + stack = stack[1:] | ||
136 | + stack = append(stack, pop.Nodes...) | ||
137 | + if pop == nil || pop.Node == nil { | ||
138 | + continue | ||
139 | + } | ||
140 | + if compared != nil { | ||
141 | + if compared(pop.Node, node) { | ||
142 | + find = pop | ||
143 | + break | ||
144 | + } | ||
145 | + continue | ||
146 | + } | ||
147 | + if pop.Node.ID() == node.ID() { | ||
148 | + find = pop | ||
149 | + break | ||
150 | + } | ||
151 | + } | ||
152 | + return find | ||
153 | +} | ||
154 | + | ||
155 | +// allChildNode 返回treeNode下所有子节点 | ||
156 | +func (tree *Tree) allChildNode(treeNode *Tree, filter func(node *Tree) bool) []TreeNode { | ||
157 | + var stack []*Tree | ||
158 | + stack = append(stack, treeNode) | ||
159 | + var res []TreeNode | ||
160 | + for { | ||
161 | + if len(stack) == 0 { | ||
162 | + break | ||
163 | + } | ||
164 | + pop := stack[0] | ||
165 | + stack = stack[1:] | ||
166 | + stack = append(stack, pop.Nodes...) | ||
167 | + if filter != nil && !filter(pop) { | ||
168 | + continue | ||
169 | + } | ||
170 | + res = append(res, pop.Node) | ||
171 | + } | ||
172 | + return res | ||
173 | +} | ||
174 | + | ||
175 | +// traverseAdd 递归添加 | ||
176 | +// | ||
177 | +// tree 当前树 | ||
178 | +// node 判断的节点 | ||
179 | +func traverseAdd(tree *Tree, node TreeNode) bool { | ||
180 | + list := tree.Nodes | ||
181 | + var match bool = false | ||
182 | + for i := range list { | ||
183 | + id, pid := list[i].Node.ID(), node.PID() | ||
184 | + if pid == id { | ||
185 | + list[i].Nodes = append(list[i].Nodes, newTree(node)) | ||
186 | + return true | ||
187 | + } | ||
188 | + if match || traverseAdd(list[i], node) { | ||
189 | + match = true | ||
190 | + break | ||
191 | + } | ||
192 | + } | ||
193 | + return match | ||
194 | +} | ||
195 | + | ||
196 | +// allChildByDepth 返回treeNode下指定深度的所有子节点 depth=[1:n] | ||
197 | +func (tree *Tree) allChildByDepth(treeNode *Tree, depth int) []TreeNode { | ||
198 | + var stack []*Tree | ||
199 | + stack = append(stack, treeNode) | ||
200 | + var res []TreeNode | ||
201 | + if depth <= 0 { | ||
202 | + return res | ||
203 | + } | ||
204 | + if treeNode.Root() != nil && depth == 1 { | ||
205 | + return []TreeNode{treeNode.Root()} | ||
206 | + } | ||
207 | + curDepth := 1 | ||
208 | + var depthStack []*Tree | ||
209 | + for { | ||
210 | + if len(stack) == 0 { | ||
211 | + break | ||
212 | + } | ||
213 | + pop := stack[0] | ||
214 | + stack = stack[1:] | ||
215 | + depthStack = append(depthStack, pop.Nodes...) | ||
216 | + if len(stack) == 0 { | ||
217 | + curDepth++ | ||
218 | + stack = depthStack[:] | ||
219 | + depthStack = []*Tree{} | ||
220 | + if curDepth == depth { | ||
221 | + for i := range stack { | ||
222 | + res = append(res, stack[i].Node) | ||
223 | + } | ||
224 | + break | ||
225 | + } | ||
226 | + } | ||
227 | + } | ||
228 | + return res | ||
229 | +} |
pkg/xcollection/tree_test.go
0 → 100644
1 | +package xcollection | ||
2 | + | ||
3 | +import ( | ||
4 | + "github.com/stretchr/testify/assert" | ||
5 | + "strconv" | ||
6 | + "testing" | ||
7 | +) | ||
8 | + | ||
9 | +func prepare() []struct { | ||
10 | + Input []TreeNode | ||
11 | + Text string | ||
12 | + Except []string | ||
13 | + Except2 []string | ||
14 | +} { | ||
15 | + return []struct { | ||
16 | + Input []TreeNode | ||
17 | + Text string | ||
18 | + Except []string | ||
19 | + Except2 []string | ||
20 | + }{ | ||
21 | + { | ||
22 | + Input: []TreeNode{ | ||
23 | + &st{Id: 1, Pid: 0}, | ||
24 | + &st{Id: 2, Pid: 1}, &st{Id: 3, Pid: 1}, &st{Id: 4, Pid: 1}, | ||
25 | + &st{Id: 5, Pid: 3}, | ||
26 | + &st{Id: 6, Pid: 5}, &st{Id: 7, Pid: 5}}, | ||
27 | + Text: ` | ||
28 | +树形结构: | ||
29 | + 1 | ||
30 | +2 3 4 | ||
31 | + 5 | ||
32 | + 6 7 | ||
33 | +`, | ||
34 | + Except: []string{"5", "6", "7"}, | ||
35 | + Except2: []string{"2", "4", "6", "7"}, | ||
36 | + }, | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | +func Test_Tree(t *testing.T) { | ||
41 | + table := prepare() | ||
42 | + for i := range table { | ||
43 | + tree := NewTree(table[i].Input) | ||
44 | + out := tree.AllChildNode(&st{Id: 5, Pid: 3}) | ||
45 | + var res []string = treeNodeResults(out) | ||
46 | + assert.Equal(t, res, table[i].Except) | ||
47 | + | ||
48 | + out = tree.AllLeafNode(nil) //tree.Root() | ||
49 | + res = treeNodeResults(out) | ||
50 | + assert.Equal(t, res, table[i].Except2) | ||
51 | + | ||
52 | + root := tree.Root() | ||
53 | + assert.Equal(t, root.ID(), "1") | ||
54 | + | ||
55 | + //tree.Add(&st{Id:10,Pid: 7}) | ||
56 | + // | ||
57 | + //out = tree.AllLeafNode(tree.Root()) | ||
58 | + //res = treeNodeResults(out) | ||
59 | + //assert.Equal(t, res, []string{"2", "4", "6", "10"}) | ||
60 | + | ||
61 | + out = tree.TreeNodePaths(&st{Id: 7, Pid: 5}) | ||
62 | + res = treeNodeResults(out) | ||
63 | + assert.Equal(t, res, []string{"1", "3", "5", "7"}) | ||
64 | + | ||
65 | + } | ||
66 | +} | ||
67 | + | ||
68 | +func Test_TreeNodeByDepth(t *testing.T) { | ||
69 | + input := []TreeNode{ | ||
70 | + &st{Id: 1, Pid: 0}, | ||
71 | + &st{Id: 2, Pid: 1}, &st{Id: 3, Pid: 1}, &st{Id: 4, Pid: 1}, | ||
72 | + &st{Id: 5, Pid: 3}, | ||
73 | + &st{Id: 6, Pid: 5}, &st{Id: 7, Pid: 5}, | ||
74 | + &st{Id: 8, Pid: 6}, &st{Id: 9, Pid: 6}, &st{Id: 10, Pid: 6}, &st{Id: 11, Pid: 7}, &st{Id: 12, Pid: 7}, | ||
75 | + } | ||
76 | + | ||
77 | + tree := NewTree(input) | ||
78 | + /* | ||
79 | + 树形结构: | ||
80 | + 1 | ||
81 | + 2 3 4 | ||
82 | + 5 | ||
83 | + 6 7 | ||
84 | + 8 9 10 11 12 | ||
85 | + */ | ||
86 | + var out []TreeNode | ||
87 | + var res []string | ||
88 | + out = tree.AllChildNodeByDepth(&st{Id: 5, Pid: 3}, 2) | ||
89 | + res = treeNodeResults(out) | ||
90 | + assert.Equal(t, []string{"6", "7"}, res) | ||
91 | + out = tree.AllChildNodeByDepth(tree.Root(), 1) | ||
92 | + res = treeNodeResults(out) | ||
93 | + assert.Equal(t, []string{"1"}, res) | ||
94 | + out = tree.AllChildNodeByDepth(tree.Root(), 2) | ||
95 | + res = treeNodeResults(out) | ||
96 | + assert.Equal(t, []string{"2", "3", "4"}, res) | ||
97 | + out = tree.AllChildNodeByDepth(tree.Root(), 3) | ||
98 | + res = treeNodeResults(out) | ||
99 | + assert.Equal(t, []string{"5"}, res) | ||
100 | + out = tree.AllChildNodeByDepth(tree.Root(), 4) | ||
101 | + res = treeNodeResults(out) | ||
102 | + assert.Equal(t, []string{"6", "7"}, res) | ||
103 | + out = tree.AllChildNodeByDepth(tree.Root(), 5) | ||
104 | + res = treeNodeResults(out) | ||
105 | + assert.Equal(t, []string{"8", "9", "10", "11", "12"}, res) | ||
106 | +} | ||
107 | + | ||
108 | +type st struct { | ||
109 | + Id int | ||
110 | + Pid int | ||
111 | +} | ||
112 | + | ||
113 | +func (t *st) PID() string { | ||
114 | + return strconv.Itoa(t.Pid) | ||
115 | +} | ||
116 | +func (t *st) ID() string { | ||
117 | + return strconv.Itoa(t.Id) | ||
118 | +} | ||
119 | + | ||
120 | +func treeNodeResults(nodes []TreeNode) []string { | ||
121 | + var res []string | ||
122 | + for i := range nodes { | ||
123 | + res = append(res, nodes[i].ID()) | ||
124 | + } | ||
125 | + return res | ||
126 | +} |
pkg/xerr/errCode.go
0 → 100644
1 | +package xerr | ||
2 | + | ||
3 | +// 成功返回 | ||
4 | +const OK uint32 = 200 | ||
5 | + | ||
6 | +/**(前3位代表业务,后三位代表具体功能)**/ | ||
7 | + | ||
8 | +// 全局错误码 | ||
9 | +const SERVER_COMMON_ERROR uint32 = 100001 | ||
10 | +const REUQEST_PARAM_ERROR uint32 = 100002 | ||
11 | +const TOKEN_EXPIRE_ERROR uint32 = 100003 | ||
12 | +const TOKEN_GENERATE_ERROR uint32 = 100004 | ||
13 | +const DB_ERROR uint32 = 100005 | ||
14 | +const DB_UPDATE_AFFECTED_ZERO_ERROR uint32 = 100006 | ||
15 | + | ||
16 | +const REQUEST_ARGS_ERROR = 200001 | ||
17 | + | ||
18 | +// 微信模块 | ||
19 | +const ErrWxMiniAuthFailError uint32 = 500001 | ||
20 | +const ErrUserNoAuth uint32 = 500002 |
pkg/xerr/errMsg.go
0 → 100644
1 | +package xerr | ||
2 | + | ||
3 | +var message map[uint32]string | ||
4 | + | ||
5 | +func init() { | ||
6 | + message = make(map[uint32]string) | ||
7 | + message[OK] = "SUCCESS" | ||
8 | + message[SERVER_COMMON_ERROR] = "服务器开小差啦,稍后再来试一试" | ||
9 | + message[REUQEST_PARAM_ERROR] = "参数错误" | ||
10 | + message[TOKEN_EXPIRE_ERROR] = "token失效,请重新登陆" | ||
11 | + message[TOKEN_GENERATE_ERROR] = "生成token失败" | ||
12 | + message[DB_ERROR] = "数据库繁忙,请稍后再试" | ||
13 | + message[DB_UPDATE_AFFECTED_ZERO_ERROR] = "更新数据影响行数为0" | ||
14 | + message[ErrUserNoAuth] = "无权限" | ||
15 | + message[ErrWxMiniAuthFailError] = "微信授权失败" | ||
16 | +} | ||
17 | + | ||
18 | +func MapErrMsg(errcode uint32) string { | ||
19 | + if msg, ok := message[errcode]; ok { | ||
20 | + return msg | ||
21 | + } else { | ||
22 | + return "服务器开小差啦,稍后再来试一试" | ||
23 | + } | ||
24 | +} | ||
25 | + | ||
26 | +func IsCodeErr(errcode uint32) bool { | ||
27 | + if _, ok := message[errcode]; ok { | ||
28 | + return true | ||
29 | + } else { | ||
30 | + return false | ||
31 | + } | ||
32 | +} |
pkg/xerr/errors.go
0 → 100644
1 | +package xerr | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | +) | ||
6 | + | ||
7 | +/** | ||
8 | +常用通用固定错误 | ||
9 | +*/ | ||
10 | + | ||
11 | +type CodeError struct { | ||
12 | + errCode uint32 | ||
13 | + errMsg string | ||
14 | + InternalError error | ||
15 | +} | ||
16 | + | ||
17 | +// GetErrCode 返回给前端的错误码 | ||
18 | +func (e *CodeError) GetErrCode() uint32 { | ||
19 | + return e.errCode | ||
20 | +} | ||
21 | + | ||
22 | +// GetErrMsg 返回给前端显示端错误信息 | ||
23 | +func (e *CodeError) GetErrMsg() string { | ||
24 | + if e.errMsg == "" { | ||
25 | + return MapErrMsg(e.errCode) | ||
26 | + } | ||
27 | + return e.errMsg | ||
28 | +} | ||
29 | + | ||
30 | +func (e *CodeError) Error() string { | ||
31 | + if e.InternalError != nil { | ||
32 | + return fmt.Sprintf("ErrCode:%d,ErrMsg:%s InternalError:%s", e.errCode, e.errMsg, e.InternalError.Error()) | ||
33 | + } | ||
34 | + return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg) | ||
35 | +} | ||
36 | + | ||
37 | +/* | ||
38 | + 指定错误码的错误 | ||
39 | +*/ | ||
40 | + | ||
41 | +func NewCodeErr(errCode uint32, err error) *CodeError { | ||
42 | + return &CodeError{errCode: errCode, errMsg: MapErrMsg(errCode), InternalError: err} | ||
43 | +} | ||
44 | + | ||
45 | +func NewCodeErrMsg(errCode uint32, err error, msg string) *CodeError { | ||
46 | + return &CodeError{errCode: errCode, errMsg: msg, InternalError: err} | ||
47 | +} | ||
48 | + | ||
49 | +/* | ||
50 | + 默认的服务错误 | ||
51 | +*/ | ||
52 | + | ||
53 | +func NewErr(err error) *CodeError { | ||
54 | + return &CodeError{errCode: SERVER_COMMON_ERROR, InternalError: err} | ||
55 | +} | ||
56 | + | ||
57 | +func NewErrMsg(errMsg string) *CodeError { | ||
58 | + return &CodeError{errCode: SERVER_COMMON_ERROR, errMsg: errMsg} | ||
59 | +} | ||
60 | + | ||
61 | +func NewErrMsgErr(errMsg string, err error) *CodeError { | ||
62 | + return &CodeError{errCode: SERVER_COMMON_ERROR, errMsg: errMsg, InternalError: err} | ||
63 | +} |
-
请 注册 或 登录 后发表评论