From 9cb45992cbc19370c6ee40c14f428ca3c933bddb Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 13 Jun 2025 15:18:08 +0800 Subject: [PATCH 01/83] support rssapp --- src/apps/adminapp/i18n/en.go | 23 + src/apps/adminapp/i18n/zh.go | 23 + src/apps/rssapp/api_server.go | 118 +++++ src/apps/rssapp/app.go | 110 ++++ src/apps/rssapp/app_test.go | 54 ++ src/apps/rssapp/config.go | 14 + src/apps/rssapp/controllers/http/ctr_rss.go | 56 +++ .../rssapp/controllers/http/ctr_rss_mag.go | 74 +++ src/apps/rssapp/docs/rssservice_docs.go | 472 ++++++++++++++++++ src/apps/rssapp/docs/rssservice_swagger.json | 443 ++++++++++++++++ src/apps/rssapp/docs/rssservice_swagger.yaml | 293 +++++++++++ src/apps/rssapp/gen_docs.sh | 12 + src/domain/configstc/app.go | 48 ++ src/domain/dto/rss.go | 5 + src/domain/interfaces/controller_gin.go | 9 + src/domain/services/rss.go | 8 + src/domain/vo/rss.go | 10 +- src/pkg/{rss => rsshelp}/rss.go | 15 +- src/pkg/{rss => rsshelp}/rss_test.go | 11 +- 19 files changed, 1775 insertions(+), 23 deletions(-) create mode 100644 src/apps/adminapp/i18n/en.go create mode 100644 src/apps/adminapp/i18n/zh.go create mode 100644 src/apps/rssapp/api_server.go create mode 100644 src/apps/rssapp/app.go create mode 100644 src/apps/rssapp/app_test.go create mode 100644 src/apps/rssapp/config.go create mode 100644 src/apps/rssapp/controllers/http/ctr_rss.go create mode 100644 src/apps/rssapp/controllers/http/ctr_rss_mag.go create mode 100644 src/apps/rssapp/docs/rssservice_docs.go create mode 100644 src/apps/rssapp/docs/rssservice_swagger.json create mode 100644 src/apps/rssapp/docs/rssservice_swagger.yaml create mode 100644 src/apps/rssapp/gen_docs.sh create mode 100644 src/domain/dto/rss.go create mode 100644 src/domain/services/rss.go rename src/pkg/{rss => rsshelp}/rss.go (86%) rename src/pkg/{rss => rsshelp}/rss_test.go (75%) diff --git a/src/apps/adminapp/i18n/en.go b/src/apps/adminapp/i18n/en.go new file mode 100644 index 0000000..c32b716 --- /dev/null +++ b/src/apps/adminapp/i18n/en.go @@ -0,0 +1,23 @@ +package i18n + +// En 英文 +const En = ` +SUCCESS="success" +ERROR="error" +PERMISSION_DENY="deny" +TRY_AGAIN="please try again" +SYSTEM_BUSY="system busy" +PARAMS_INVALID="invalid parameter" +OPERATE_FAIL="operate fail" + +Hello="Halo" +OperateFail="operate fail" +AccountExist="account has exist" +AccountDisable="account has disable" +PasswordWrong="password wrong" + +[user] +Exists="has exists" + +AccountWrong="account {{.Name}} wrong" +` diff --git a/src/apps/adminapp/i18n/zh.go b/src/apps/adminapp/i18n/zh.go new file mode 100644 index 0000000..8195b0d --- /dev/null +++ b/src/apps/adminapp/i18n/zh.go @@ -0,0 +1,23 @@ +package i18n + +// Zh 中文 +const Zh = ` +SUCCESS="成功" +ERROR="错误" +PERMISSION_DENY="无权限" +TRY_AGAIN="请重试" +SYSTEM_BUSY="系统繁忙" +PARAMS_INVALID="无效参数" +OPERATE_FAIL="操作失败" + +Hello="你好" +OperateFail="操作失败" +AccountExist = "账户已存在" +AccountDisable="账户已禁用" +PasswordWrong="密码错误" + +[user] +Exists = "已存在" + +AccountWrong = "账户{{.Name}}错误" +` diff --git a/src/apps/rssapp/api_server.go b/src/apps/rssapp/api_server.go new file mode 100644 index 0000000..8f2bf2c --- /dev/null +++ b/src/apps/rssapp/api_server.go @@ -0,0 +1,118 @@ +package rssapp + +import ( + "gitee.com/captials-team/ubdframe/src/apps" + "gitee.com/captials-team/ubdframe/src/apps/rssapp/docs" + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/common/utils" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/domain/interfaces" + "gitee.com/captials-team/ubdframe/src/pkg/gin_http" + "gitee.com/captials-team/ubdframe/src/pkg/jwtauth" + v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "github.com/gin-gonic/gin" + "go.uber.org/dig" +) + +type ApiServer struct { + *apps.ApiServer + + di *dig.Scope + conf *configstc.RssAppConfig + + gin_http.AuthOption //认证相关选项配置 + gin_http.SwaggerOption //swagger相关选项配置 + gin_http.PProfOption //pprof选项配置 + gin_http.OperateLogOption //操作日志 + gin_http.AuthExtendInfoOption //认证扩展信息选项配置 + gin_http.AccreditOption //授权选项 + + AdminAuth gin_http.AuthOption //管理站认证相关选项配置 +} + +func (s *ApiServer) Name() string { + return "rss_api" +} + +func (s *ApiServer) InitRouter() { + s.Engine().GET("ping", gin_http.PingHandler) + s.InitRouterForGin(s.Engine()) +} + +func (s *ApiServer) router(g gin.IRouter) { + g.Use( + gin_http.PanicHandler, + gin_http.QPSLimiterHandler(10, 10), + ) + + //用于客户端 + common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfRssController) { + g.POST("/rss/items", ctr.RssCacheItems) + })) + + authGroup := g.Group("", s.OptAuthHandler(), s.OptAccreditHandler()) + //管理站 + common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfRssManageController) { + //rss原数据 + authGroup.POST("/mag/rss/sources", ctr.RssSourceList) + authGroup.POST("/mag/rss/source/items", ctr.RssSourceItems) + })) +} + +func (s *ApiServer) InitRouterForGin(engine *gin.Engine) { + var g = engine.Group("") + if len(s.conf.RoutePrefix) > 0 { + g = engine.Group(s.conf.RoutePrefix) + } + + //注册swagger + s.SwaggerRouter(g) + + //注册pprof + s.PProfRouter(engine) + + s.router(g) + + return +} + +func (s *ApiServer) Start() error { + if !s.Module() { + s.InitRouter() + } + return s.ApiServer.Start() +} + +func (s *ApiServer) Stop() error { + return s.ApiServer.Stop() +} + +func NewApiServer(di *dig.Scope, conf *configstc.RssAppConfig, logger v1log.ILog) *ApiServer { + + //swagger配置,取值后取指针是为了实现复用(多App) + swaggerDocs := *docs.SwaggerInforssservice + swaggerDocs.Host = conf.ApiServer.HostAddr() + utils.KeepHasPrefix(conf.RoutePrefix, "/") + + s := &ApiServer{ + di: di, + conf: conf, + SwaggerOption: gin_http.SwaggerOption{ + Enable: conf.DocsEnable, + Name: swaggerDocs.InstanceName(), + Swagger: &swaggerDocs, + }, + PProfOption: gin_http.PProfOption{ + Enable: conf.PProfEnable, + }, + ApiServer: apps.NewApiServer(gin.Default(), conf.ApiServer), + } + + //开启跨域设置 + s.ApiServer.WithCors() + + if conf.AuthConfig.Enable { + s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) + } + + return s +} diff --git a/src/apps/rssapp/app.go b/src/apps/rssapp/app.go new file mode 100644 index 0000000..3f14c2e --- /dev/null +++ b/src/apps/rssapp/app.go @@ -0,0 +1,110 @@ +package rssapp + +import ( + "gitee.com/captials-team/ubdframe/src/apps" + httpController "gitee.com/captials-team/ubdframe/src/apps/rssapp/controllers/http" + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/domain/interfaces" + "gitee.com/captials-team/ubdframe/src/domain/services" + "gitee.com/captials-team/ubdframe/src/domain/vo" + "gitee.com/captials-team/ubdframe/src/infrastructure/caches" + v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "gitee.com/captials-team/ubdframe/src/pkg/rsshelp" + "gitee.com/captials-team/ubdframe/src/pkg/uber_help" + "github.com/robfig/cron/v3" + "go.uber.org/dig" + "time" +) + +type App struct { + *apps.App + + l v1log.ILog + di *dig.Scope + ApiServer *ApiServer + RssFetcher *rsshelp.RssFetcher +} + +func (app *App) initCaches(conf *configstc.RssAppConfig) { + common.ErrPanic(app.di.Provide(func() caches.ItfCache { + return caches.NewMemoryStore(time.Minute * 10) + }), uber_help.ErrAlreadyProvided) + + common.ErrPanic(app.di.Provide(func(cache caches.ItfCache) services.RssCache { + return caches.NewCacheFacade[string, vo.RssBody](cache) + }), uber_help.ErrAlreadyProvided) + + app.l.Info("provide cache success") +} + +func (app *App) startCron(f *rsshelp.RssFetcher, conf *configstc.RssAppConfig) { + //异步初始化 + if conf.AsyncInit { + go f.Fetch() + } else { + f.Fetch() + } + + //每30分钟获取一次 + c := cron.New() + c.AddFunc("@every 30m", f.Fetch) + c.Start() +} + +func (app *App) initRss(f *rsshelp.RssFetcher, ca services.RssCache, logger v1log.ILog, conf *configstc.RssAppConfig) { + app.RssFetcher = f + + for _, v := range conf.RssConfig { + if v.Enable { + f.AddRss(v.Name, v.Rss) + } + } + + f.AddHandler(func(meta *vo.RssMeta, body *vo.RssBody) { + err := ca.SetDefault(meta.Name, body) + logger.Ctl(err != nil).Error("%s", err) + }) +} + +func (app *App) initController() { + common.ErrPanic(app.di.Provide(httpController.NewRssController, dig.As(new(interfaces.ItfRssController))), uber_help.ErrAlreadyProvided) + common.ErrPanic(app.di.Provide(httpController.NewRssManagerController, dig.As(new(interfaces.ItfRssManageController))), uber_help.ErrAlreadyProvided) +} + +func NewApp(di *dig.Container, conf *configstc.RssAppConfig, logger v1log.ILog) *App { + scope := di.Scope("admin") + + common.ErrPanic(scope.Provide(func() *dig.Scope { + return scope + }), uber_help.ErrAlreadyProvided) + common.ErrPanic(scope.Provide(func() *configstc.RssAppConfig { + return conf + }), uber_help.ErrAlreadyProvided) + + app := &App{ + di: scope, + l: logger, + App: apps.NewApp(di, "rss_app"), + } + + app.WithModule(conf.ModuleMode) + + //db初始化 + common.ErrPanic(scope.Provide(rsshelp.NewRssFetcher)) + + common.ErrPanic(scope.Invoke(app.initCaches)) + common.ErrPanic(scope.Invoke(app.initRss)) + common.ErrPanic(scope.Invoke(app.initController)) + common.ErrPanic(scope.Invoke(app.startCron)) + + if conf.ApiServer.Enable { + common.ErrPanic(scope.Provide(NewApiServer)) + common.ErrPanic(scope.Invoke(func(api *ApiServer) { + app.WithApiServer(api) + app.ApiServer = api + })) + } + + return app +} diff --git a/src/apps/rssapp/app_test.go b/src/apps/rssapp/app_test.go new file mode 100644 index 0000000..a21fcf3 --- /dev/null +++ b/src/apps/rssapp/app_test.go @@ -0,0 +1,54 @@ +package rssapp + +import ( + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/tests" + "go.uber.org/dig" + "testing" + "time" +) + +func TestAppStart(t *testing.T) { + di := testDi(t) + common.ErrPanic(di.Provide(NewApp)) + + common.ErrPanic(di.Invoke(func(app *App) error { + return app.Start() + })) + + time.Sleep(time.Hour) +} + +func testDi(t *testing.T) *dig.Container { + di := tests.NewDi(t) + + common.ErrPanic(di.Provide(func() *configstc.RssAppConfig { + return &configstc.RssAppConfig{ + ApiServer: configstc.ServerConfig{ + Enable: true, + ServerBindAddr: "", + Port: 10000, + }, + DBConfig: configstc.DBConfig{ + DbHost: "192.168.149.128", + DbPort: "3306", + DbUser: "root", + DbPassword: "root", + DbName: "db_ubd_frame", + TablePrefix: "test_", + }, + DocsEnable: true, + RssConfig: []configstc.RssMetaConfig{ + { + Enable: true, + Name: "百度焦点", + Rss: "https://news.baidu.com/n?cmd=1&class=civilnews&tn=rss&sub=0", + }, + }, + AsyncInit: false, + } + })) + + return di +} diff --git a/src/apps/rssapp/config.go b/src/apps/rssapp/config.go new file mode 100644 index 0000000..a677e9a --- /dev/null +++ b/src/apps/rssapp/config.go @@ -0,0 +1,14 @@ +package rssapp + +import "gitee.com/captials-team/ubdframe/src/domain/configstc" + +// NewConfig godoc +// @Summary CONFIG-配置 +// @Description CONFIG-配置内容 +// @Tags admin +// @Produce json +// @Success 200 {array} configstc.AdminAppConfig +// @Router /-admin-config [post] +func NewConfig() *configstc.RssAppConfig { + return configstc.LoadConfig(new(configstc.RssAppConfig)) +} diff --git a/src/apps/rssapp/controllers/http/ctr_rss.go b/src/apps/rssapp/controllers/http/ctr_rss.go new file mode 100644 index 0000000..c337c85 --- /dev/null +++ b/src/apps/rssapp/controllers/http/ctr_rss.go @@ -0,0 +1,56 @@ +package http + +import ( + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" + "gitee.com/captials-team/ubdframe/src/domain/services" + "gitee.com/captials-team/ubdframe/src/domain/vo" + "gitee.com/captials-team/ubdframe/src/pkg/gin_http" + v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "github.com/gin-gonic/gin" +) + +type RssController struct { + l v1log.ILog + cache services.RssCache + conf *configstc.RssAppConfig + + gin_http.ResponseController +} + +func NewRssController(l v1log.ILog, conf *configstc.RssAppConfig, cache services.RssCache) *RssController { + ctr := &RssController{ + l: l, + cache: cache, + conf: conf, + } + + return ctr +} + +// RssCacheItems godoc +// @Summary RSS文章列表(缓存) +// @Description rss内容列表(缓存) +// @Tags rss +// @Produce json +// @success 200 {object} respdata.ResponseData{data=[]vo.RssItems} "授权成功" +// @success 500 {object} respdata.ResponseData{} "授权失败" +// @Router /rss/items [post] +func (ctr *RssController) RssCacheItems(ctx *gin.Context) { + var list []*vo.RssItems + + for _, v := range ctr.conf.RssConfig { + body, err := ctr.cache.Get(v.Name) + if err != nil { + ctr.l.Error("cache get err %s,%s", err, v.Name) + ctr.Response(ctx, respdata.CError) + return + } + for _, item := range body.Channel.Items { + item.Author = v.Name //修改作者 + list = append(list, item) + } + } + + ctr.Response(ctx, respdata.CSuccess.MData(list)) +} diff --git a/src/apps/rssapp/controllers/http/ctr_rss_mag.go b/src/apps/rssapp/controllers/http/ctr_rss_mag.go new file mode 100644 index 0000000..4385d56 --- /dev/null +++ b/src/apps/rssapp/controllers/http/ctr_rss_mag.go @@ -0,0 +1,74 @@ +package http + +import ( + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/domain/dto" + "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" + "gitee.com/captials-team/ubdframe/src/domain/vo" + "gitee.com/captials-team/ubdframe/src/pkg/gin_http" + v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "gitee.com/captials-team/ubdframe/src/pkg/rsshelp" + "github.com/gin-gonic/gin" +) + +type RssManagerController struct { + l v1log.ILog + fetcher *rsshelp.RssFetcher + conf *configstc.RssAppConfig + + gin_http.ResponseController +} + +func NewRssManagerController(l v1log.ILog, conf *configstc.RssAppConfig, fetcher *rsshelp.RssFetcher) *RssManagerController { + ctr := &RssManagerController{ + l: l, + fetcher: fetcher, + conf: conf, + } + + return ctr +} + +// RssSourceList godoc +// @Summary RSS源列表 +// @Description rss源列表 +// @Tags rss-manager +// @Produce json +// @Security AdminKeyAuth +// @success 200 {object} respdata.ResponseData{data=configstc.RssMetaConfig} "授权成功" +// @success 500 {object} respdata.ResponseData{} "授权失败" +// @Router /mag/rss/sources [post] +func (ctr *RssManagerController) RssSourceList(ctx *gin.Context) { + ctr.Response(ctx, respdata.CSuccess.MData(ctr.conf.RssConfig)) +} + +// RssSourceItems godoc +// @Summary RSS源文章列表 +// @Description rss源内容列表 +// @Tags rss-manager +// @Produce json +// @Security AdminKeyAuth +// @Param param body dto.SearchRssContentReq true "验证信息" +// @success 200 {object} respdata.ResponseData{data=vo.RssItems} "授权成功" +// @success 500 {object} respdata.ResponseData{} "授权失败" +// @Router /mag/rss/source/items [post] +func (ctr *RssManagerController) RssSourceItems(ctx *gin.Context) { + var req dto.SearchRssContentReq + ctx.ShouldBindJSON(&req) + + var fetcher = ctr.fetcher + if req.Rss != "" { + //指定rss + fetcher = rsshelp.NewRssFetcher(ctr.l) + fetcher.AddRss("tmp", req.Rss) + } + + var list []*vo.RssItems + fetcher.FetchHandler(func(meta *vo.RssMeta, body *vo.RssBody) { + for _, item := range body.Channel.Items { + item.Author = meta.Name //修改作者 + list = append(list, item) + } + }) + ctr.Response(ctx, respdata.CSuccess.MData(list)) +} diff --git a/src/apps/rssapp/docs/rssservice_docs.go b/src/apps/rssapp/docs/rssservice_docs.go new file mode 100644 index 0000000..567d774 --- /dev/null +++ b/src/apps/rssapp/docs/rssservice_docs.go @@ -0,0 +1,472 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplaterssservice = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/-admin-config": { + "post": { + "description": "CONFIG-配置内容", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "CONFIG-配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/configstc.AdminAppConfig" + } + } + } + } + } + }, + "/mag/rss/source/items": { + "post": { + "security": [ + { + "AdminKeyAuth": [] + } + ], + "description": "rss源内容列表", + "produces": [ + "application/json" + ], + "tags": [ + "rss-manager" + ], + "summary": "RSS源文章列表", + "parameters": [ + { + "description": "验证信息", + "name": "param", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchRssContentReq" + } + } + ], + "responses": { + "200": { + "description": "授权成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/respdata.ResponseData" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/vo.RssItems" + } + } + } + ] + } + }, + "500": { + "description": "授权失败", + "schema": { + "$ref": "#/definitions/respdata.ResponseData" + } + } + } + } + }, + "/mag/rss/sources": { + "post": { + "security": [ + { + "AdminKeyAuth": [] + } + ], + "description": "rss源列表", + "produces": [ + "application/json" + ], + "tags": [ + "rss-manager" + ], + "summary": "RSS源列表", + "responses": { + "200": { + "description": "授权成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/respdata.ResponseData" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/configstc.RssMetaConfig" + } + } + } + ] + } + }, + "500": { + "description": "授权失败", + "schema": { + "$ref": "#/definitions/respdata.ResponseData" + } + } + } + } + }, + "/rss/items": { + "post": { + "description": "rss内容列表(缓存)", + "produces": [ + "application/json" + ], + "tags": [ + "rss" + ], + "summary": "RSS文章列表(缓存)", + "responses": { + "200": { + "description": "授权成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/respdata.ResponseData" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/vo.RssItems" + } + } + } + } + ] + } + }, + "500": { + "description": "授权失败", + "schema": { + "$ref": "#/definitions/respdata.ResponseData" + } + } + } + } + } + }, + "definitions": { + "configstc.AdminAppConfig": { + "type": "object", + "properties": { + "ApiServer": { + "$ref": "#/definitions/configstc.ServerConfig" + }, + "AuthConfig": { + "$ref": "#/definitions/configstc.AuthConfig" + }, + "AutoCreateAdmin": { + "description": "是否自动创建admin账户", + "type": "boolean" + }, + "DBConfig": { + "$ref": "#/definitions/configstc.DBConfig" + }, + "Debug": { + "description": "debug开关", + "type": "boolean" + }, + "DocsEnable": { + "description": "是否启用文档", + "type": "boolean" + }, + "I18nFiles": { + "description": "多语言配置:key=language,value=path", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "LimitingApi": { + "description": "api限流", + "allOf": [ + { + "$ref": "#/definitions/configstc.LimitingConfig" + } + ] + }, + "LogLevel": { + "type": "string" + }, + "LogPath": { + "description": "日志路径", + "type": "string" + }, + "ModuleMode": { + "description": "模块模式,作为module使用", + "type": "boolean" + }, + "Name": { + "type": "string" + }, + "PProfEnable": { + "description": "是否启用pprof", + "type": "boolean" + }, + "PasswordSalt": { + "description": "密码salt", + "type": "string" + }, + "PermissionFile": { + "description": "权限文件json", + "type": "string" + }, + "RoutePrefix": { + "description": "路由前缀", + "type": "string" + }, + "UserPasswordSalt": { + "description": "用户-密码salt", + "type": "string" + } + } + }, + "configstc.AuthConfig": { + "type": "object", + "properties": { + "AuthExpired": { + "description": "授权失效时间,单位:秒", + "type": "integer" + }, + "Enable": { + "description": "是否启用", + "type": "boolean" + }, + "SecretKey": { + "description": "加密秘钥,如JWT", + "type": "string" + } + } + }, + "configstc.DBConfig": { + "type": "object", + "properties": { + "ConnMaxIdleTime": { + "description": "每个链接最大空闲时间", + "type": "integer", + "default": 0 + }, + "ConnMaxLifeTime": { + "description": "每个链接最大生存时间", + "type": "integer", + "default": 0 + }, + "DbConn": { + "description": "数据库类型,如mysql", + "type": "string" + }, + "DbDsn": { + "description": "数据库dsn", + "type": "string" + }, + "DbHost": { + "description": "数据库地址", + "type": "string" + }, + "DbName": { + "description": "数据库", + "type": "string" + }, + "DbPassword": { + "description": "密码", + "type": "string" + }, + "DbPort": { + "description": "端口", + "type": "string" + }, + "DbUser": { + "description": "用户名", + "type": "string" + }, + "MaxConcatLen": { + "type": "string" + }, + "MaxIdleConn": { + "description": "预留并发链接数", + "type": "integer", + "default": 0 + }, + "MaxOpenConn": { + "description": "最大支持链接", + "type": "integer", + "default": 0 + }, + "TablePrefix": { + "description": "表前缀", + "type": "string" + }, + "TimeZone": { + "description": "时区设置", + "type": "string" + } + } + }, + "configstc.LimitingConfig": { + "type": "object", + "properties": { + "MaxRate": { + "description": "允许的最大速率", + "type": "integer" + }, + "PerRate": { + "description": "每次(秒)速率", + "type": "integer" + } + } + }, + "configstc.RssMetaConfig": { + "type": "object", + "properties": { + "Enable": { + "type": "boolean" + }, + "Name": { + "type": "string" + }, + "Rss": { + "type": "string" + } + } + }, + "configstc.ServerConfig": { + "type": "object", + "properties": { + "AllowOrigins": { + "description": "指定跨域允许访问源,空则为不限制访问", + "type": "array", + "items": { + "type": "string" + } + }, + "Enable": { + "description": "是否启用", + "type": "boolean" + }, + "EndpointAddr": { + "description": "对外访问地址", + "type": "string" + }, + "EndpointPort": { + "description": "对外访问端口", + "type": "integer" + }, + "Host": { + "description": "Host is the hostname or IP address of the service.", + "type": "string" + }, + "Port": { + "description": "端口", + "type": "integer" + }, + "ServerBindAddr": { + "description": "ListenAndServe to bind to, such as 0.0.0.0", + "type": "string" + }, + "Timeout": { + "description": "Timeout specifies a timeout (in milliseconds)", + "type": "integer" + } + } + }, + "dto.SearchRssContentReq": { + "type": "object", + "properties": { + "rss": { + "description": "指定rss地址", + "type": "string", + "x-nullable": true, + "example": "" + } + } + }, + "respdata.ResponseData": { + "type": "object", + "properties": { + "code": { + "description": "返回码,0:成功,\u003e0为对应错误码", + "type": "integer" + }, + "data": {}, + "msg": { + "type": "string" + } + } + }, + "vo.RssItems": { + "type": "object", + "properties": { + "author": { + "description": "作者", + "type": "string" + }, + "description": { + "description": "描述信息", + "type": "string" + }, + "link": { + "description": "连接", + "type": "string" + }, + "pubDate": { + "description": "发布日期/时间", + "type": "string" + }, + "title": { + "description": "新闻标题", + "type": "string" + } + } + } + } +}` + +// SwaggerInforssservice holds exported Swagger Info so clients can modify it +var SwaggerInforssservice = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "rssservice", + SwaggerTemplate: docTemplaterssservice, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInforssservice.InstanceName(), SwaggerInforssservice) +} diff --git a/src/apps/rssapp/docs/rssservice_swagger.json b/src/apps/rssapp/docs/rssservice_swagger.json new file mode 100644 index 0000000..525809b --- /dev/null +++ b/src/apps/rssapp/docs/rssservice_swagger.json @@ -0,0 +1,443 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/-admin-config": { + "post": { + "description": "CONFIG-配置内容", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "CONFIG-配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/configstc.AdminAppConfig" + } + } + } + } + } + }, + "/mag/rss/source/items": { + "post": { + "security": [ + { + "AdminKeyAuth": [] + } + ], + "description": "rss源内容列表", + "produces": [ + "application/json" + ], + "tags": [ + "rss-manager" + ], + "summary": "RSS源文章列表", + "parameters": [ + { + "description": "验证信息", + "name": "param", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchRssContentReq" + } + } + ], + "responses": { + "200": { + "description": "授权成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/respdata.ResponseData" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/vo.RssItems" + } + } + } + ] + } + }, + "500": { + "description": "授权失败", + "schema": { + "$ref": "#/definitions/respdata.ResponseData" + } + } + } + } + }, + "/mag/rss/sources": { + "post": { + "security": [ + { + "AdminKeyAuth": [] + } + ], + "description": "rss源列表", + "produces": [ + "application/json" + ], + "tags": [ + "rss-manager" + ], + "summary": "RSS源列表", + "responses": { + "200": { + "description": "授权成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/respdata.ResponseData" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/configstc.RssMetaConfig" + } + } + } + ] + } + }, + "500": { + "description": "授权失败", + "schema": { + "$ref": "#/definitions/respdata.ResponseData" + } + } + } + } + }, + "/rss/items": { + "post": { + "description": "rss内容列表(缓存)", + "produces": [ + "application/json" + ], + "tags": [ + "rss" + ], + "summary": "RSS文章列表(缓存)", + "responses": { + "200": { + "description": "授权成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/respdata.ResponseData" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/vo.RssItems" + } + } + } + } + ] + } + }, + "500": { + "description": "授权失败", + "schema": { + "$ref": "#/definitions/respdata.ResponseData" + } + } + } + } + } + }, + "definitions": { + "configstc.AdminAppConfig": { + "type": "object", + "properties": { + "ApiServer": { + "$ref": "#/definitions/configstc.ServerConfig" + }, + "AuthConfig": { + "$ref": "#/definitions/configstc.AuthConfig" + }, + "AutoCreateAdmin": { + "description": "是否自动创建admin账户", + "type": "boolean" + }, + "DBConfig": { + "$ref": "#/definitions/configstc.DBConfig" + }, + "Debug": { + "description": "debug开关", + "type": "boolean" + }, + "DocsEnable": { + "description": "是否启用文档", + "type": "boolean" + }, + "I18nFiles": { + "description": "多语言配置:key=language,value=path", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "LimitingApi": { + "description": "api限流", + "allOf": [ + { + "$ref": "#/definitions/configstc.LimitingConfig" + } + ] + }, + "LogLevel": { + "type": "string" + }, + "LogPath": { + "description": "日志路径", + "type": "string" + }, + "ModuleMode": { + "description": "模块模式,作为module使用", + "type": "boolean" + }, + "Name": { + "type": "string" + }, + "PProfEnable": { + "description": "是否启用pprof", + "type": "boolean" + }, + "PasswordSalt": { + "description": "密码salt", + "type": "string" + }, + "PermissionFile": { + "description": "权限文件json", + "type": "string" + }, + "RoutePrefix": { + "description": "路由前缀", + "type": "string" + }, + "UserPasswordSalt": { + "description": "用户-密码salt", + "type": "string" + } + } + }, + "configstc.AuthConfig": { + "type": "object", + "properties": { + "AuthExpired": { + "description": "授权失效时间,单位:秒", + "type": "integer" + }, + "Enable": { + "description": "是否启用", + "type": "boolean" + }, + "SecretKey": { + "description": "加密秘钥,如JWT", + "type": "string" + } + } + }, + "configstc.DBConfig": { + "type": "object", + "properties": { + "ConnMaxIdleTime": { + "description": "每个链接最大空闲时间", + "type": "integer", + "default": 0 + }, + "ConnMaxLifeTime": { + "description": "每个链接最大生存时间", + "type": "integer", + "default": 0 + }, + "DbConn": { + "description": "数据库类型,如mysql", + "type": "string" + }, + "DbDsn": { + "description": "数据库dsn", + "type": "string" + }, + "DbHost": { + "description": "数据库地址", + "type": "string" + }, + "DbName": { + "description": "数据库", + "type": "string" + }, + "DbPassword": { + "description": "密码", + "type": "string" + }, + "DbPort": { + "description": "端口", + "type": "string" + }, + "DbUser": { + "description": "用户名", + "type": "string" + }, + "MaxConcatLen": { + "type": "string" + }, + "MaxIdleConn": { + "description": "预留并发链接数", + "type": "integer", + "default": 0 + }, + "MaxOpenConn": { + "description": "最大支持链接", + "type": "integer", + "default": 0 + }, + "TablePrefix": { + "description": "表前缀", + "type": "string" + }, + "TimeZone": { + "description": "时区设置", + "type": "string" + } + } + }, + "configstc.LimitingConfig": { + "type": "object", + "properties": { + "MaxRate": { + "description": "允许的最大速率", + "type": "integer" + }, + "PerRate": { + "description": "每次(秒)速率", + "type": "integer" + } + } + }, + "configstc.RssMetaConfig": { + "type": "object", + "properties": { + "Enable": { + "type": "boolean" + }, + "Name": { + "type": "string" + }, + "Rss": { + "type": "string" + } + } + }, + "configstc.ServerConfig": { + "type": "object", + "properties": { + "AllowOrigins": { + "description": "指定跨域允许访问源,空则为不限制访问", + "type": "array", + "items": { + "type": "string" + } + }, + "Enable": { + "description": "是否启用", + "type": "boolean" + }, + "EndpointAddr": { + "description": "对外访问地址", + "type": "string" + }, + "EndpointPort": { + "description": "对外访问端口", + "type": "integer" + }, + "Host": { + "description": "Host is the hostname or IP address of the service.", + "type": "string" + }, + "Port": { + "description": "端口", + "type": "integer" + }, + "ServerBindAddr": { + "description": "ListenAndServe to bind to, such as 0.0.0.0", + "type": "string" + }, + "Timeout": { + "description": "Timeout specifies a timeout (in milliseconds)", + "type": "integer" + } + } + }, + "dto.SearchRssContentReq": { + "type": "object", + "properties": { + "rss": { + "description": "指定rss地址", + "type": "string", + "x-nullable": true, + "example": "" + } + } + }, + "respdata.ResponseData": { + "type": "object", + "properties": { + "code": { + "description": "返回码,0:成功,\u003e0为对应错误码", + "type": "integer" + }, + "data": {}, + "msg": { + "type": "string" + } + } + }, + "vo.RssItems": { + "type": "object", + "properties": { + "author": { + "description": "作者", + "type": "string" + }, + "description": { + "description": "描述信息", + "type": "string" + }, + "link": { + "description": "连接", + "type": "string" + }, + "pubDate": { + "description": "发布日期/时间", + "type": "string" + }, + "title": { + "description": "新闻标题", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/src/apps/rssapp/docs/rssservice_swagger.yaml b/src/apps/rssapp/docs/rssservice_swagger.yaml new file mode 100644 index 0000000..0f68e0e --- /dev/null +++ b/src/apps/rssapp/docs/rssservice_swagger.yaml @@ -0,0 +1,293 @@ +definitions: + configstc.AdminAppConfig: + properties: + ApiServer: + $ref: '#/definitions/configstc.ServerConfig' + AuthConfig: + $ref: '#/definitions/configstc.AuthConfig' + AutoCreateAdmin: + description: 是否自动创建admin账户 + type: boolean + DBConfig: + $ref: '#/definitions/configstc.DBConfig' + Debug: + description: debug开关 + type: boolean + DocsEnable: + description: 是否启用文档 + type: boolean + I18nFiles: + additionalProperties: + type: string + description: 多语言配置:key=language,value=path + type: object + LimitingApi: + allOf: + - $ref: '#/definitions/configstc.LimitingConfig' + description: api限流 + LogLevel: + type: string + LogPath: + description: 日志路径 + type: string + ModuleMode: + description: 模块模式,作为module使用 + type: boolean + Name: + type: string + PProfEnable: + description: 是否启用pprof + type: boolean + PasswordSalt: + description: 密码salt + type: string + PermissionFile: + description: 权限文件json + type: string + RoutePrefix: + description: 路由前缀 + type: string + UserPasswordSalt: + description: 用户-密码salt + type: string + type: object + configstc.AuthConfig: + properties: + AuthExpired: + description: 授权失效时间,单位:秒 + type: integer + Enable: + description: 是否启用 + type: boolean + SecretKey: + description: 加密秘钥,如JWT + type: string + type: object + configstc.DBConfig: + properties: + ConnMaxIdleTime: + default: 0 + description: 每个链接最大空闲时间 + type: integer + ConnMaxLifeTime: + default: 0 + description: 每个链接最大生存时间 + type: integer + DbConn: + description: 数据库类型,如mysql + type: string + DbDsn: + description: 数据库dsn + type: string + DbHost: + description: 数据库地址 + type: string + DbName: + description: 数据库 + type: string + DbPassword: + description: 密码 + type: string + DbPort: + description: 端口 + type: string + DbUser: + description: 用户名 + type: string + MaxConcatLen: + type: string + MaxIdleConn: + default: 0 + description: 预留并发链接数 + type: integer + MaxOpenConn: + default: 0 + description: 最大支持链接 + type: integer + TablePrefix: + description: 表前缀 + type: string + TimeZone: + description: 时区设置 + type: string + type: object + configstc.LimitingConfig: + properties: + MaxRate: + description: 允许的最大速率 + type: integer + PerRate: + description: 每次(秒)速率 + type: integer + type: object + configstc.RssMetaConfig: + properties: + Enable: + type: boolean + Name: + type: string + Rss: + type: string + type: object + configstc.ServerConfig: + properties: + AllowOrigins: + description: 指定跨域允许访问源,空则为不限制访问 + items: + type: string + type: array + Enable: + description: 是否启用 + type: boolean + EndpointAddr: + description: 对外访问地址 + type: string + EndpointPort: + description: 对外访问端口 + type: integer + Host: + description: Host is the hostname or IP address of the service. + type: string + Port: + description: 端口 + type: integer + ServerBindAddr: + description: ListenAndServe to bind to, such as 0.0.0.0 + type: string + Timeout: + description: Timeout specifies a timeout (in milliseconds) + type: integer + type: object + dto.SearchRssContentReq: + properties: + rss: + description: 指定rss地址 + example: "" + type: string + x-nullable: true + type: object + respdata.ResponseData: + properties: + code: + description: 返回码,0:成功,>0为对应错误码 + type: integer + data: {} + msg: + type: string + type: object + vo.RssItems: + properties: + author: + description: 作者 + type: string + description: + description: 描述信息 + type: string + link: + description: 连接 + type: string + pubDate: + description: 发布日期/时间 + type: string + title: + description: 新闻标题 + type: string + type: object +info: + contact: {} +paths: + /-admin-config: + post: + description: CONFIG-配置内容 + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/configstc.AdminAppConfig' + type: array + summary: CONFIG-配置 + tags: + - admin + /mag/rss/source/items: + post: + description: rss源内容列表 + parameters: + - description: 验证信息 + in: body + name: param + required: true + schema: + $ref: '#/definitions/dto.SearchRssContentReq' + produces: + - application/json + responses: + "200": + description: 授权成功 + schema: + allOf: + - $ref: '#/definitions/respdata.ResponseData' + - properties: + data: + $ref: '#/definitions/vo.RssItems' + type: object + "500": + description: 授权失败 + schema: + $ref: '#/definitions/respdata.ResponseData' + security: + - AdminKeyAuth: [] + summary: RSS源文章列表 + tags: + - rss-manager + /mag/rss/sources: + post: + description: rss源列表 + produces: + - application/json + responses: + "200": + description: 授权成功 + schema: + allOf: + - $ref: '#/definitions/respdata.ResponseData' + - properties: + data: + $ref: '#/definitions/configstc.RssMetaConfig' + type: object + "500": + description: 授权失败 + schema: + $ref: '#/definitions/respdata.ResponseData' + security: + - AdminKeyAuth: [] + summary: RSS源列表 + tags: + - rss-manager + /rss/items: + post: + description: rss内容列表(缓存) + produces: + - application/json + responses: + "200": + description: 授权成功 + schema: + allOf: + - $ref: '#/definitions/respdata.ResponseData' + - properties: + data: + items: + $ref: '#/definitions/vo.RssItems' + type: array + type: object + "500": + description: 授权失败 + schema: + $ref: '#/definitions/respdata.ResponseData' + summary: RSS文章列表(缓存) + tags: + - rss +swagger: "2.0" diff --git a/src/apps/rssapp/gen_docs.sh b/src/apps/rssapp/gen_docs.sh new file mode 100644 index 0000000..86921a9 --- /dev/null +++ b/src/apps/rssapp/gen_docs.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +swag init -g app.go --parseDependency --parseInternal --instanceName rssservice + + + + + + + + diff --git a/src/domain/configstc/app.go b/src/domain/configstc/app.go index 4a1e06b..e6df2db 100644 --- a/src/domain/configstc/app.go +++ b/src/domain/configstc/app.go @@ -145,6 +145,7 @@ type ProxyRule struct { Replace string `json:"Replace" yaml:"Replace"` //替换前缀 } +// WebsocketAppConfig ws应用app type WebsocketAppConfig struct { Name string `json:"Name" yaml:"Name"` ApiServer ServerConfig `json:"ApiServer" yaml:"ApiServer"` //web服务器配置 @@ -157,6 +158,7 @@ type WebsocketAppConfig struct { RoutePrefix string `json:"RoutePrefix" yaml:"RoutePrefix"` //路由前缀 } +// OssAppConfig oss存储app type OssAppConfig struct { Name string `json:"Name" yaml:"Name"` ApiServer ServerConfig `json:"ApiServer" yaml:"ApiServer"` //web服务器配置 @@ -281,6 +283,52 @@ type JobAppConfig struct { JobConfig } +type RssAppConfig struct { + Name string `json:"Name" yaml:"Name"` + ApiServer ServerConfig `json:"ApiServer" yaml:"ApiServer"` + + LogConfig LogConfig `json:"LogConfig" yaml:"LogConfig"` //日志配置 + + //debug开关 + Debug bool `json:"Debug" yaml:"Debug"` + + AuthConfig AuthConfig `json:"AuthConfig" yaml:"AuthConfig"` //授权配置 + + DBConfig DBConfig `json:"DBConfig" yaml:"DBConfig"` //数据库配置 + + LimitingApi LimitingConfig `json:"LimitingApi" yaml:"LimitingApi"` //api限流 + + //路由前缀 + RoutePrefix string `json:"RoutePrefix" yaml:"RoutePrefix"` //路由前缀 + + //是否启用文档 + DocsEnable bool `json:"DocsEnable" yaml:"DocsEnable"` + + //是否启用pprof + PProfEnable bool `json:"PProfEnable" yaml:"PProfEnable"` + + //rss配置,示例: + //RssConfig: + // - Name: "人民网" + // Rss: "http://www.people.com.cn/rss/politics.xml" + // Enable: true + // - Name: "搜狐中国" + // Rss: "http://rss.news.sohu.com/rss/pfocus.xml" + // Enable: false + + RssConfig []RssMetaConfig `json:"RssConfig" yaml:"RssConfig"` + + AsyncInit bool `json:"AsyncInit" yaml:"AsyncInit"` //是否异步初始化运行,包含定时任务等 + + ModuleMode bool `json:"ModuleMode" yaml:"ModuleMode"` //模块模式,作为module使用 +} + +type RssMetaConfig struct { + Enable bool `json:"Enable" yaml:"Enable"` + Name string `json:"Name" yaml:"Name"` + Rss string `json:"Rss" yaml:"Rss"` +} + const ( ProxyModePrefix = "prefix" //前缀替换,匹配并替换指定前缀 ProxyModeFix = "fixed" //固定地址,完全匹配路由进行转发 diff --git a/src/domain/dto/rss.go b/src/domain/dto/rss.go new file mode 100644 index 0000000..c667d88 --- /dev/null +++ b/src/domain/dto/rss.go @@ -0,0 +1,5 @@ +package dto + +type SearchRssContentReq struct { + Rss string `json:"rss" extensions:"x-nullable" example:""` //指定rss地址 +} diff --git a/src/domain/interfaces/controller_gin.go b/src/domain/interfaces/controller_gin.go index 47a612f..9fb0448 100644 --- a/src/domain/interfaces/controller_gin.go +++ b/src/domain/interfaces/controller_gin.go @@ -168,3 +168,12 @@ type ItfOrganizeManageController interface { DisableOrganize(ctx *gin.Context) //启用/禁用组织 DeleteOrganize(ctx *gin.Context) //删除组织 } + +type ItfRssController interface { + RssCacheItems(ctx *gin.Context) +} + +type ItfRssManageController interface { + RssSourceList(ctx *gin.Context) //rss源列表 + RssSourceItems(ctx *gin.Context) //rss内容列表 +} diff --git a/src/domain/services/rss.go b/src/domain/services/rss.go new file mode 100644 index 0000000..b04162b --- /dev/null +++ b/src/domain/services/rss.go @@ -0,0 +1,8 @@ +package services + +import ( + "gitee.com/captials-team/ubdframe/src/domain/vo" + "gitee.com/captials-team/ubdframe/src/infrastructure/caches" +) + +type RssCache = *caches.CacheFacade[string, vo.RssBody] diff --git a/src/domain/vo/rss.go b/src/domain/vo/rss.go index c130e55..b254f7e 100644 --- a/src/domain/vo/rss.go +++ b/src/domain/vo/rss.go @@ -33,11 +33,11 @@ type RssChannel struct { } type RssItems struct { - Title string `json:"title" xml:"title"` - Author string `json:"author" xml:"author"` - Link string `json:"link" xml:"link"` - PubDate string `json:"pubDate" xml:"pubDate"` - Description string `json:"description" xml:"description"` + Title string `json:"title" xml:"title"` //新闻标题 + Author string `json:"author" xml:"author"` //作者 + Link string `json:"link" xml:"link"` //连接 + PubDate string `json:"pubDate" xml:"pubDate"` //发布日期/时间 + Description string `json:"description" xml:"description"` //描述信息 } func (rss *RssBody) Parse(r io.Reader) error { diff --git a/src/pkg/rss/rss.go b/src/pkg/rsshelp/rss.go similarity index 86% rename from src/pkg/rss/rss.go rename to src/pkg/rsshelp/rss.go index 189a66d..dbb3257 100644 --- a/src/pkg/rss/rss.go +++ b/src/pkg/rsshelp/rss.go @@ -1,11 +1,9 @@ -package rss +package rsshelp import ( "fmt" - "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/domain/vo" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" - "go.uber.org/dig" "io" "net/http" "os" @@ -21,17 +19,14 @@ type RssFetcher struct { type RssHandler func(meta *vo.RssMeta, body *vo.RssBody) -func NewRssFetcher(di *dig.Container) *RssFetcher { - var logger v1log.ILog = v1log.NewWriterLog(os.Stdout, v1log.DebugLog) - if di != nil { - common.ErrPanic(di.Invoke(func(l v1log.ILog) { - logger = l - })) +func NewRssFetcher(l v1log.ILog) *RssFetcher { + if l == nil { + l = v1log.NewWriterLog(os.Stdout, v1log.DebugLog) } return &RssFetcher{ client: &http.Client{Timeout: time.Second * 5}, rssMap: map[string]*vo.RssMeta{}, - l: logger, + l: l, } } diff --git a/src/pkg/rss/rss_test.go b/src/pkg/rsshelp/rss_test.go similarity index 75% rename from src/pkg/rss/rss_test.go rename to src/pkg/rsshelp/rss_test.go index 338a762..d7cbf5e 100644 --- a/src/pkg/rss/rss_test.go +++ b/src/pkg/rsshelp/rss_test.go @@ -1,20 +1,15 @@ -package rss +package rsshelp import ( - "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/domain/vo" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" - "go.uber.org/dig" "testing" ) func TestRssFetchWorker_Run(t *testing.T) { - di := dig.New() - common.ErrPanic(di.Provide(func() v1log.ILog { - return v1log.NewTestingLog(t, v1log.DebugLog) - }, dig.As(new(v1log.ILog)))) + l := v1log.NewTestingLog(t, v1log.DebugLog) - rss := NewRssFetcher(di) + rss := NewRssFetcher(l) rss.AddRssMeta(&vo.RssMeta{ Name: "people_com_china", RssUrl: "http://www.people.com.cn/rss/politics.xml", -- Gitee From 93a79589c0864e8582d87f2237bf249df72bdff8 Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 16 Jun 2025 10:32:26 +0800 Subject: [PATCH 02/83] modify log --- src/apps/app.go | 2 +- src/apps/wssapp/app.go | 3 +- src/pkg/logs/consts.go | 2 +- src/pkg/logs/invokelog.go | 131 +++++++++++++++++++++++++++++++++ src/pkg/logs/invokelog_test.go | 21 ++++++ src/pkg/logs/log.go | 128 -------------------------------- 6 files changed, 156 insertions(+), 131 deletions(-) create mode 100644 src/pkg/logs/invokelog.go create mode 100644 src/pkg/logs/invokelog_test.go diff --git a/src/apps/app.go b/src/apps/app.go index 0af61be..9660a64 100644 --- a/src/apps/app.go +++ b/src/apps/app.go @@ -41,7 +41,7 @@ func (app *App) Start() error { err := server.Start() if err != nil { - logs.Out.Error("====== App[%s] Start FAIL %s", app.Name(), err) + logs.Out.Error("====== App[%s] START FAIL %s", app.Name(), err) return } }() diff --git a/src/apps/wssapp/app.go b/src/apps/wssapp/app.go index c435a4b..eb78e70 100644 --- a/src/apps/wssapp/app.go +++ b/src/apps/wssapp/app.go @@ -33,6 +33,8 @@ func NewApp(di *dig.Container, conf *configstc.WebsocketAppConfig) *App { common.ErrPanic(di.Provide(NewApiServer)) wsserver.SetMessageSize(2048 * 1000) + wsserver.SetLogger(logger.CallerSkip(-1)) + common.ErrPanic(di.Provide(wsserver.NewHub)) common.ErrPanic(di.Provide(workers.NewWorkerMgr)) @@ -41,7 +43,6 @@ func NewApp(di *dig.Container, conf *configstc.WebsocketAppConfig) *App { mgr.AddWorker("log", workers.NewLogWorker(di, logger)) hub.SetWorker(mgr) - wsserver.SetLogger(logger) })) common.ErrPanic(di.Invoke(func(api *ApiServer, hub *wsserver.Hub, mgr *workers.WorkerMgr) { diff --git a/src/pkg/logs/consts.go b/src/pkg/logs/consts.go index 8f8e26a..7fc51e3 100644 --- a/src/pkg/logs/consts.go +++ b/src/pkg/logs/consts.go @@ -7,7 +7,7 @@ import ( // 全局logger var ( - Out ILog = NewWriterLog(os.Stdout, DebugLog) + Out ILog = NewWriterLog(os.Stdout, DebugLog).CallerSkip(01) ) func SetLogger(log ILog) { diff --git a/src/pkg/logs/invokelog.go b/src/pkg/logs/invokelog.go new file mode 100644 index 0000000..a58e654 --- /dev/null +++ b/src/pkg/logs/invokelog.go @@ -0,0 +1,131 @@ +package logs + +import "fmt" + +// InvokeLog fast use log in struct +type InvokeLog struct { + logger ILog +} + +func (ink *InvokeLog) AddLogger(logger ILog) { + ink.logger = logger +} + +func (ink *InvokeLog) GetLogger() ILog { + return ink.logger +} + +func (ink *InvokeLog) Log(level LogLevel, msg string) { + if ink.logger == nil { + return + } + switch level { + case DebugLog: + ink.logger.Debug(msg) + case WarnLog: + ink.logger.Warn(msg) + case InfoLog: + ink.logger.Info(msg) + case ErrorLog: + ink.logger.Error(msg) + case FatalLog: + ink.logger.Fatal(msg) + case PanicLog: + ink.logger.Panic(msg) + } +} + +func (ink *InvokeLog) Debug(log string, params ...interface{}) { + if ink.logger == nil { + return + } + if len(params) <= 0 { + ink.logger.Debug(log) + return + } + + ink.logger.Debug(fmt.Sprintf(log, params...)) + return +} + +func (ink *InvokeLog) Info(log string, params ...interface{}) { + if ink.logger == nil { + return + } + if len(params) <= 0 { + ink.logger.Info(log) + return + } + + ink.logger.Info(fmt.Sprintf(log, params...)) + return +} + +func (ink *InvokeLog) Warn(log string, params ...interface{}) { + if ink.logger == nil { + return + } + if len(params) <= 0 { + ink.logger.Warn(log) + return + } + + ink.logger.Warn(fmt.Sprintf(log, params...)) + return +} +func (ink *InvokeLog) Error(log string, params ...interface{}) { + if ink.logger == nil { + return + } + if len(params) <= 0 { + ink.logger.Error(log) + return + } + + ink.logger.Error(fmt.Sprintf(log, params...)) + return +} +func (ink *InvokeLog) Panic(log string, params ...interface{}) { + + if ink.logger == nil { + return + } + if len(params) <= 0 { + ink.logger.Panic(log) + return + } + + ink.logger.Panic(fmt.Sprintf(log, params...)) + return +} +func (ink *InvokeLog) Fatal(log string, params ...interface{}) { + if ink.logger == nil { + return + } + if len(params) <= 0 { + ink.logger.Fatal(log) + return + } + + ink.logger.Fatal(fmt.Sprintf(log, params...)) + return +} +func (ink *InvokeLog) Write(p []byte) (n int, err error) { + return ink.logger.Write(p) +} +func (ink *InvokeLog) Ctl(t bool) ILog { + if !t { + return _nullLog + } + return ink +} + +func (ink *InvokeLog) Caller(skip int) ILog { + return &InvokeLog{ + logger: ink.logger.CallerSkip(skip), + } +} + +func (ink *InvokeLog) CallerSkip(skip int) ILog { + return ink.Caller(skip) +} diff --git a/src/pkg/logs/invokelog_test.go b/src/pkg/logs/invokelog_test.go new file mode 100644 index 0000000..a8316ac --- /dev/null +++ b/src/pkg/logs/invokelog_test.go @@ -0,0 +1,21 @@ +package logs + +import "testing" + +func TestInvokeLog_AddLogger(t *testing.T) { + + l := NewZapLog("xx", DefaultLogPath) + + a := &A{} + a.AddLogger(l) + + a.Do() +} + +type A struct { + InvokeLog +} + +func (a *A) Do() { + a.Debug("THIS DEBUG LOG") +} diff --git a/src/pkg/logs/log.go b/src/pkg/logs/log.go index 36f3009..eaa5576 100644 --- a/src/pkg/logs/log.go +++ b/src/pkg/logs/log.go @@ -32,134 +32,6 @@ type ILogCallerSkip interface { CallerSkip(offset int) ILog } -// InvokeLog fast use log in struct -type InvokeLog struct { - logger ILog -} - -func (ink *InvokeLog) AddLogger(logger ILog) { - ink.logger = logger -} - -func (ink *InvokeLog) GetLogger() ILog { - return ink.logger -} - -func (ink *InvokeLog) Log(level LogLevel, msg string) { - if ink.logger == nil { - return - } - switch level { - case DebugLog: - ink.logger.Debug(msg) - case WarnLog: - ink.logger.Warn(msg) - case InfoLog: - ink.logger.Info(msg) - case ErrorLog: - ink.logger.Error(msg) - case FatalLog: - ink.logger.Fatal(msg) - case PanicLog: - ink.logger.Panic(msg) - } -} - -func (ink *InvokeLog) Debug(log string, params ...interface{}) { - if ink.logger == nil { - return - } - if len(params) <= 0 { - ink.logger.Debug(log) - return - } - - ink.logger.Debug(fmt.Sprintf(log, params...)) - return -} - -func (ink *InvokeLog) Info(log string, params ...interface{}) { - if ink.logger == nil { - return - } - if len(params) <= 0 { - ink.logger.Info(log) - return - } - - ink.logger.Info(fmt.Sprintf(log, params...)) - return -} - -func (ink *InvokeLog) Warn(log string, params ...interface{}) { - if ink.logger == nil { - return - } - if len(params) <= 0 { - ink.logger.Warn(log) - return - } - - ink.logger.Warn(fmt.Sprintf(log, params...)) - return -} -func (ink *InvokeLog) Error(log string, params ...interface{}) { - if ink.logger == nil { - return - } - if len(params) <= 0 { - ink.logger.Error(log) - return - } - - ink.logger.Error(fmt.Sprintf(log, params...)) - return -} -func (ink *InvokeLog) Panic(log string, params ...interface{}) { - - if ink.logger == nil { - return - } - if len(params) <= 0 { - ink.logger.Panic(log) - return - } - - ink.logger.Panic(fmt.Sprintf(log, params...)) - return -} -func (ink *InvokeLog) Fatal(log string, params ...interface{}) { - if ink.logger == nil { - return - } - if len(params) <= 0 { - ink.logger.Fatal(log) - return - } - - ink.logger.Fatal(fmt.Sprintf(log, params...)) - return -} -func (ink *InvokeLog) Write(p []byte) (n int, err error) { - return ink.logger.Write(p) -} -func (ink *InvokeLog) Ctl(t bool) ILog { - if !t { - return _nullLog - } - return ink -} - -func (ink *InvokeLog) Caller(skip int) ILog { - return &InvokeLog{ - logger: ink.logger.CallerSkip(skip), - } -} - -func (ink *InvokeLog) CallerSkip(skip int) ILog { - return ink.Caller(skip) -} - type logHelper struct { } -- Gitee From 38e0e12caa5ecebcfb604847eb8cc582dff39bcc Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 16 Jun 2025 14:41:14 +0800 Subject: [PATCH 03/83] fix log message --- src/apps/wssapp/workers/mgr.go | 7 +++---- src/common/err.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/apps/wssapp/workers/mgr.go b/src/apps/wssapp/workers/mgr.go index 8e99683..d86d0f9 100644 --- a/src/apps/wssapp/workers/mgr.go +++ b/src/apps/wssapp/workers/mgr.go @@ -7,7 +7,6 @@ import ( v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/wsserver" "go.uber.org/dig" - "log" "runtime/debug" ) @@ -36,7 +35,7 @@ func (mgr *WorkerMgr) SetHub(h *wsserver.Hub) { } func (mgr *WorkerMgr) Receive(msg *wsserver.WebSocketMessage) { - mgr.l.Debug("receive msg %+v", msg) + mgr.l.Debug("receive msg %d,%s", msg.MessageType, string(msg.Message)) cmd := mgr.convertCmd(msg.Message) if cmd == nil { @@ -84,8 +83,8 @@ func (mgr *WorkerMgr) convertCmd(message []byte) *cmddata.ClientCmd { func (mgr *WorkerMgr) handleCmd(cmd *cmddata.ClientCmd) { defer func() { if err := recover(); err != nil { - log.Fatalf("recover %s", err) - log.Fatalf("recover stack %s", debug.Stack()) + v1log.Out.Error("recover err: %s", err) + v1log.Out.Error("recover stack %s", debug.Stack()) cmd.Client.SendMessage(cmddata.CmdErrFrame) } }() diff --git a/src/common/err.go b/src/common/err.go index 5aa8d08..ff2254f 100644 --- a/src/common/err.go +++ b/src/common/err.go @@ -27,7 +27,7 @@ func ErrPanic(err error, ignoreErrs ...error) { func CatchPanic(fs ...func(interface{})) { if err := recover(); err != nil { fmt.Printf("recover panic %s\n", err) - fmt.Printf("recover Stack: %s\n", debug.Stack()) + fmt.Printf("recover stack: %s\n", debug.Stack()) for _, f := range fs { f(err) } -- Gitee From b1a817a69880ccc50c631d082e249fa01faff68a Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 16 Jun 2025 15:09:05 +0800 Subject: [PATCH 04/83] modify facade --- src/infrastructure/caches/facade.go | 14 ++++++++++++- src/infrastructure/caches/facade_test.go | 25 +++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/infrastructure/caches/facade.go b/src/infrastructure/caches/facade.go index 280427e..19f198e 100644 --- a/src/infrastructure/caches/facade.go +++ b/src/infrastructure/caches/facade.go @@ -6,7 +6,19 @@ import ( "time" ) -func NewCacheFacade[KeyT int | int64 | string | interface{}, ValueT interface{}](cache ItfCache) *CacheFacade[KeyT, ValueT] { +type cacheConfig struct { + expiration time.Duration +} + +type CacheOpt func(*cacheConfig) + +func OptionCacheExpireTime(t time.Duration) CacheOpt { + return func(config *cacheConfig) { + config.expiration = t + } +} + +func NewCacheFacade[KeyT int | int64 | string | []string | interface{}, ValueT interface{}](cache ItfCache) *CacheFacade[KeyT, ValueT] { return &CacheFacade[KeyT, ValueT]{ cache: cache, cdc: new(codec.JsonCodec), diff --git a/src/infrastructure/caches/facade_test.go b/src/infrastructure/caches/facade_test.go index 218b193..a9fe25e 100644 --- a/src/infrastructure/caches/facade_test.go +++ b/src/infrastructure/caches/facade_test.go @@ -1,6 +1,7 @@ package caches import ( + "fmt" "gitee.com/captials-team/ubdframe/src/common/utils" "github.com/stretchr/testify/assert" "testing" @@ -9,7 +10,8 @@ import ( func TestNewCacheFacade(t *testing.T) { - fa := NewCacheFacade(NewMemoryStore(time.Second*3), nil, func(id int64) *A { + fa := NewCacheFacade[int64, A](NewMemoryStore(time.Second * 3)) + fa.SetValueFunc(func(id int64) *A { return &A{Id: id} }) @@ -22,7 +24,8 @@ func TestNewCacheFacade(t *testing.T) { func TestNewCacheFacade_Get(t *testing.T) { - fa := NewCacheFacade(NewMemoryStore(time.Second*3), nil, func(id int64) *A { + fa := NewCacheFacade[int64, A](NewMemoryStore(time.Second * 3)) + fa.SetValueFunc(func(id int64) *A { return &A{Id: id} }) @@ -38,7 +41,8 @@ func TestNewCacheFacade_Set(t *testing.T) { Id int64 `json:"id"` } - fa := NewCacheFacade(NewMemoryStore(time.Second*3), nil, func(id int64) *A { + fa := NewCacheFacade[int64, A](NewMemoryStore(time.Second * 3)) + fa.SetValueFunc(func(id int64) *A { return &A{Id: id} }) @@ -62,6 +66,21 @@ func TestNewCacheFacade_Set(t *testing.T) { assert.Equal(t, utils.IsNil(d), true) } +func TestNewCacheFacade_map(t *testing.T) { + fa := NewCacheFacade[int64, map[string]string](NewMemoryStore(time.Second * 3)) + fa.SetValueFunc(func(id int64) *map[string]string { + s := fmt.Sprint(id) + return &map[string]string{ + s: s, + } + }) + + d, err := fa.Get(12) + assert.Equal(t, err, nil) + + t.Logf("ID= %v", d) +} + type A struct { Id int64 `json:"id"` } -- Gitee From e9483f3beacaae63dd21a43b39e9d99de1ecd269 Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 16 Jun 2025 15:10:06 +0800 Subject: [PATCH 05/83] modify --- src/infrastructure/caches/facade.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/infrastructure/caches/facade.go b/src/infrastructure/caches/facade.go index 19f198e..752ffce 100644 --- a/src/infrastructure/caches/facade.go +++ b/src/infrastructure/caches/facade.go @@ -25,6 +25,8 @@ func NewCacheFacade[KeyT int | int64 | string | []string | interface{}, ValueT i } } +// CacheFacade cache门面 +// valueT返回使用指针的原因:若缓存值不存在则会返回nil type CacheFacade[KeyT int | int64 | string | interface{}, ValueT interface{}] struct { cache ItfCache cdc interfaces.Codec -- Gitee From ee0839931d398f27290995742afc27a99e8dbc29 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 17 Jun 2025 13:59:56 +0800 Subject: [PATCH 06/83] modify cache key file --- src/infrastructure/caches/cache.go | 47 --------------------------- src/infrastructure/caches/key.go | 51 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 src/infrastructure/caches/key.go diff --git a/src/infrastructure/caches/cache.go b/src/infrastructure/caches/cache.go index 184d451..fd23f0f 100644 --- a/src/infrastructure/caches/cache.go +++ b/src/infrastructure/caches/cache.go @@ -1,9 +1,6 @@ package caches import ( - "encoding/json" - "fmt" - "strings" "time" ) @@ -38,47 +35,3 @@ type ItemKey struct { Key string `json:"key"` //key值 Expire time.Duration `json:"expire"` //有效时间 } - -const CombineSep = "::" - -func GenericKey[T int | int64 | string | []string | interface{}](k T) string { - switch any(k).(type) { - case int: - d := any(k).(int) - return fmt.Sprintf("id_%d", d) - case int64: - d := any(k).(int64) - return fmt.Sprintf("id_%d", d) - case string: - d := any(k).(string) - return fmt.Sprintf("key_%s", d) - case []string: - d := any(k).([]string) - return fmt.Sprintf("key_%s", strings.Join(d, ".")) - } - //默认进行json处理后生成可以不同的key - s, _ := json.Marshal(k) - return fmt.Sprintf("key_%s", string(s)) -} - -// CombineKeys 多个key合并为一个key -func CombineKeys(keys ...string) string { - return strings.Join(keys, CombineSep) -} - -// SeparateKeys 分隔一个key为多个key -func SeparateKeys(key string) []string { - return strings.Split(key, CombineSep) -} - -// SeparateKeysFix 分隔一个key为多个key(保证返回的固定size的数组) -func SeparateKeysFix(key string, l int) []string { - arr := make([]string, l, l) - arr = strings.Split(key, CombineSep) - if len(arr) < l { - for i := len(arr); i < l; i++ { - arr = append(arr, "") - } - } - return arr -} diff --git a/src/infrastructure/caches/key.go b/src/infrastructure/caches/key.go new file mode 100644 index 0000000..29bf71f --- /dev/null +++ b/src/infrastructure/caches/key.go @@ -0,0 +1,51 @@ +package caches + +import ( + "encoding/json" + "fmt" + "strings" +) + +const CombineSep = "::" + +func GenericKey[T int | int64 | string | []string | interface{}](k T) string { + switch any(k).(type) { + case int: + d := any(k).(int) + return fmt.Sprintf("id_%d", d) + case int64: + d := any(k).(int64) + return fmt.Sprintf("id_%d", d) + case string: + d := any(k).(string) + return fmt.Sprintf("key_%s", d) + case []string: + d := any(k).([]string) + return fmt.Sprintf("key_%s", strings.Join(d, ".")) + } + //默认进行json处理后生成可以不同的key + s, _ := json.Marshal(k) + return fmt.Sprintf("key_%s", string(s)) +} + +// CombineKeys 多个key合并为一个key +func CombineKeys(keys ...string) string { + return strings.Join(keys, CombineSep) +} + +// SeparateKeys 分隔一个key为多个key +func SeparateKeys(key string) []string { + return strings.Split(key, CombineSep) +} + +// SeparateKeysFix 分隔一个key为多个key(保证返回的固定size的数组) +func SeparateKeysFix(key string, l int) []string { + arr := make([]string, l, l) + arr = strings.Split(key, CombineSep) + if len(arr) < l { + for i := len(arr); i < l; i++ { + arr = append(arr, "") + } + } + return arr +} -- Gitee From f6f123af25b6b7a202aaf15668081ae01d254b34 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 17 Jun 2025 18:06:24 +0800 Subject: [PATCH 07/83] add caches support --- src/apps/rssapp/app.go | 2 +- src/domain/services/block.go | 48 ++++---- src/domain/services/organize.go | 31 ++--- src/domain/services/rss.go | 2 +- src/domain/services/setting.go | 15 +-- src/infrastructure/caches/cache.go | 20 ++-- src/infrastructure/caches/cache_mag.go | 1 + src/infrastructure/caches/facade.go | 113 +++++++++++------- src/infrastructure/caches/facade_opt.go | 17 +++ src/infrastructure/caches/facade_test.go | 80 +++++++++---- src/infrastructure/caches/key.go | 48 +++++++- src/infrastructure/caches/store_memory.go | 14 +++ .../caches/store_memory_test.go | 24 ++++ src/infrastructure/caches/store_redis.go | 55 +++++++-- 14 files changed, 338 insertions(+), 132 deletions(-) create mode 100644 src/infrastructure/caches/facade_opt.go diff --git a/src/apps/rssapp/app.go b/src/apps/rssapp/app.go index 3f14c2e..3cec70a 100644 --- a/src/apps/rssapp/app.go +++ b/src/apps/rssapp/app.go @@ -32,7 +32,7 @@ func (app *App) initCaches(conf *configstc.RssAppConfig) { }), uber_help.ErrAlreadyProvided) common.ErrPanic(app.di.Provide(func(cache caches.ItfCache) services.RssCache { - return caches.NewCacheFacade[string, vo.RssBody](cache) + return caches.NewCacheFacade[string, *vo.RssBody](cache) }), uber_help.ErrAlreadyProvided) app.l.Info("provide cache success") diff --git a/src/domain/services/block.go b/src/domain/services/block.go index a9330ea..3a3d6a8 100644 --- a/src/domain/services/block.go +++ b/src/domain/services/block.go @@ -14,35 +14,39 @@ import ( type BlockService struct { l v1log.ILog dao interfaces.ItfBlockContentDao - cacheById *caches.CacheFacade[int64, models.BlockContent] - cacheByCode *caches.CacheFacade[string, models.BlockContent] + cacheById *caches.CacheFacade[int64, *models.BlockContent] + cacheByCode *caches.CacheFacade[string, *models.BlockContent] } func NewBlockService(di *dig.Container, l v1log.ILog, blockDao interfaces.ItfBlockContentDao, cache caches.ItfCache) *BlockService { - ctr := &BlockService{ + svc := &BlockService{ l: l, dao: blockDao, - cacheById: caches.NewCacheFacade[int64, models.BlockContent](cache), - cacheByCode: caches.NewCacheFacade[string, models.BlockContent](cache), + cacheById: caches.NewCacheFacade[int64, *models.BlockContent](cache), + cacheByCode: caches.NewCacheFacade[string, *models.BlockContent](cache), } - ctr.cacheById.SetFunc(ctr.CacheKeyForId, func(k int64) *models.BlockContent { - s, err := blockDao.Query(k) - if err != nil { - return nil - } - return s - }) - - ctr.cacheByCode.SetFunc(ctr.CacheKeyForCode, func(k string) *models.BlockContent { - s, err := blockDao.QueryByCode(k) - if err != nil { - return nil - } - return s - }) - - return ctr + svc.cacheById.WithValueFunc(svc.blockCache).WithKeyPrefix("block_") + + svc.cacheByCode.WithValueFunc(svc.blockCacheByCode).WithKeyPrefix("block_code_") + + return svc +} + +func (s *BlockService) blockCache(k int64) *models.BlockContent { + find, err := s.dao.Query(k) + if err != nil { + return nil + } + return find +} + +func (s *BlockService) blockCacheByCode(k string) *models.BlockContent { + find, err := s.dao.QueryByCode(k) + if err != nil { + return nil + } + return find } func (s *BlockService) Query(id int64, code string) (*models.BlockContent, error) { diff --git a/src/domain/services/organize.go b/src/domain/services/organize.go index 56c88f8..4501159 100644 --- a/src/domain/services/organize.go +++ b/src/domain/services/organize.go @@ -20,30 +20,31 @@ type OrganizeService struct { organizeDao interfaces.ItfOrganize relateDao interfaces.ItfOrganizeRelate - cacheById *caches.CacheFacade[int64, models.Organize] + cacheById *caches.CacheFacade[int64, *models.Organize] } func NewOrganizeService(di *dig.Container, l v1log.ILog, organizeDao interfaces.ItfOrganize, relateDao interfaces.ItfOrganizeRelate, cache caches.ItfCache) *OrganizeService { - ctr := &OrganizeService{ + svc := &OrganizeService{ l: l, organizeDao: organizeDao, relateDao: relateDao, - cacheById: caches.NewCacheFacade[int64, models.Organize](cache), + cacheById: caches.NewCacheFacade[int64, *models.Organize](cache), } - ctr.cacheById.SetFunc(func(id int64) string { - return fmt.Sprintf("org_%d", id) - }, func(id int64) *models.Organize { - s, err := organizeDao.Query(id) - if err != nil { - l.Error("query organize-%d fail %s", id, err) - return nil - } - l.Info("org=%d %+v", id, s) - return s - }) + svc.cacheById.WithValueFunc(svc.orgCacheValue).WithKeyPrefix("org_") - return ctr + return svc +} + +// orgCacheValue 缓存org值获取方法 +func (s *OrganizeService) orgCacheValue(id int64) *models.Organize { + find, err := s.organizeDao.Query(id) + if err != nil { + s.l.Error("query organize-%d fail %s", id, err) + return nil + } + s.l.Info("org=%d %+v", id, s) + return find } func (s *OrganizeService) Search(search dto.SearchOrganizeParams, pa *paginate.Pager) ([]*respdata.OrganizeListItem, *paginate.Pager, error) { diff --git a/src/domain/services/rss.go b/src/domain/services/rss.go index b04162b..632fe6d 100644 --- a/src/domain/services/rss.go +++ b/src/domain/services/rss.go @@ -5,4 +5,4 @@ import ( "gitee.com/captials-team/ubdframe/src/infrastructure/caches" ) -type RssCache = *caches.CacheFacade[string, vo.RssBody] +type RssCache = *caches.CacheFacade[string, *vo.RssBody] diff --git a/src/domain/services/setting.go b/src/domain/services/setting.go index c0beb55..675a1f4 100644 --- a/src/domain/services/setting.go +++ b/src/domain/services/setting.go @@ -20,15 +20,13 @@ func NewSettingService(di *dig.Container, l v1log.ILog, settingDao interfaces.It cacheByName: caches.NewCacheFacade[string, string](cache), } - ctr.cacheByName.SetFunc(func(k string) string { - return "settings_" + k - }, func(k string) *string { + ctr.cacheByName.WithValueFunc(func(k string) string { s, err := settingDao.Get(k) if err != nil { - return nil + return "" } - return &s - }) + return s + }).WithKeyPrefix("settings_") return ctr } @@ -55,10 +53,7 @@ func (s *SettingService) GetFromCache(name string) (string, error) { if err != nil { return "", err } - if v == nil { - return "", nil - } - return *v, err + return v, err } func (s *SettingService) GetMultiFromCache(names ...string) (map[string]string, error) { diff --git a/src/infrastructure/caches/cache.go b/src/infrastructure/caches/cache.go index fd23f0f..4c8804d 100644 --- a/src/infrastructure/caches/cache.go +++ b/src/infrastructure/caches/cache.go @@ -18,17 +18,21 @@ import ( //} type ItfCache interface { - Get(key string) (string, bool, error) - Set(key string, value string) error - SetExpire(key string, value string, expired time.Duration) error - TTL(key string) (time.Duration, error) //指定key的剩余过期时间 + Get(key string) (string, bool, error) //获取缓存值 + Set(key string, value string) error //设置缓存值 + SetExpire(key string, value string, expired time.Duration) error //设置缓存值且指定缓存时间 + TTL(key string) (time.Duration, error) //指定key的剩余过期时间 - Inc(k string, n int64) error - Dec(k string, n int64) error - Exist(k string) (bool, error) - Delete(key string) error + Inc(k string, n int64) error //对指定key的缓存值加 + Dec(k string, n int64) error //对指定key的缓存值减 + Exist(k string) (bool, error) //判断缓存key是否存在 + Delete(key string) error //删除指定缓存key GetSet(key string, value string, expired time.Duration) (string, error) GetSetFunc(key string, f func() string, expired time.Duration) (string, error) + + Keys() ([]string, error) //返回所有缓存key + + SetKeyPrefix(prefix string) } type ItemKey struct { diff --git a/src/infrastructure/caches/cache_mag.go b/src/infrastructure/caches/cache_mag.go index 84fe256..12c6177 100644 --- a/src/infrastructure/caches/cache_mag.go +++ b/src/infrastructure/caches/cache_mag.go @@ -1,6 +1,7 @@ package caches // CacheMag 缓存管理器 +// 已废弃,请使用 CacheFacade type CacheMag struct { m map[string]ItfCache } diff --git a/src/infrastructure/caches/facade.go b/src/infrastructure/caches/facade.go index 752ffce..87b0fd6 100644 --- a/src/infrastructure/caches/facade.go +++ b/src/infrastructure/caches/facade.go @@ -1,23 +1,14 @@ package caches import ( + "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/domain/interfaces" "gitee.com/captials-team/ubdframe/src/pkg/codec" + "gitee.com/captials-team/ubdframe/src/pkg/logs" + "reflect" "time" ) -type cacheConfig struct { - expiration time.Duration -} - -type CacheOpt func(*cacheConfig) - -func OptionCacheExpireTime(t time.Duration) CacheOpt { - return func(config *cacheConfig) { - config.expiration = t - } -} - func NewCacheFacade[KeyT int | int64 | string | []string | interface{}, ValueT interface{}](cache ItfCache) *CacheFacade[KeyT, ValueT] { return &CacheFacade[KeyT, ValueT]{ cache: cache, @@ -28,63 +19,82 @@ func NewCacheFacade[KeyT int | int64 | string | []string | interface{}, ValueT i // CacheFacade cache门面 // valueT返回使用指针的原因:若缓存值不存在则会返回nil type CacheFacade[KeyT int | int64 | string | interface{}, ValueT interface{}] struct { - cache ItfCache - cdc interfaces.Codec - keyFunc func(KeyT) string - valueFunc func(KeyT) *ValueT + cache ItfCache + cdc interfaces.Codec //编码器 + + //目前暂不支持,支持需要parseKeyFunc的 + keyFunc func(KeyT) string //自定义key转换的方法 + + valueFunc func(KeyT) ValueT //value获取的方法 + keyPrefix string //Key前缀 } // Get 直接获取缓存 -func (ca *CacheFacade[KeyT, T]) Get(k KeyT) (*T, error) { +func (ca *CacheFacade[KeyT, T]) Get(k KeyT) (T, error) { + var v T relKey := ca.key(k) s, exist, err := ca.cache.Get(relKey) if err != nil { - return nil, err + return v, err } if !exist { - return nil, nil + return v, nil } if string(s) == "" { - return nil, nil + return v, nil } - var d T - err = ca.cdc.Unmarshal([]byte(s), &d) - return &d, err + return ca.parseValue(s) } // GetOrLoad 获取缓存,当不存在时进行加载 -func (ca *CacheFacade[KeyT, T]) GetOrLoad(k KeyT) (*T, error) { +func (ca *CacheFacade[KeyT, T]) GetOrLoad(k KeyT) (T, error) { relKey := ca.key(k) + var v T s, exist, err := ca.cache.Get(relKey) if err != nil { - return nil, err + return v, err } if !exist { return ca.Load(k) } if string(s) == "" { - return nil, nil + return v, nil } + return ca.parseValue(s) +} + +// Load 重新加载入缓存 +func (ca *CacheFacade[KeyT, T]) parseValue(s string) (T, error) { var d T - err = ca.cdc.Unmarshal([]byte(s), &d) - return &d, err + var err error + + tyRef := reflect.TypeOf(d) + //针对指针进行特殊处理 + if tyRef.Kind() == reflect.Pointer { + d = reflect.New(tyRef.Elem()).Interface().(T) + err = ca.cdc.Unmarshal([]byte(s), d) + } else { + err = ca.cdc.Unmarshal([]byte(s), &d) + } + + return d, err } // Load 重新加载入缓存 -func (ca *CacheFacade[KeyT, T]) Load(k KeyT) (*T, error) { +func (ca *CacheFacade[KeyT, T]) Load(k KeyT) (T, error) { value := ca.value(k) err := ca.SetDefault(k, value) return value, err } // Set 手动设置值 -func (ca *CacheFacade[KeyT, T]) Set(k KeyT, v *T, expiration time.Duration) error { +func (ca *CacheFacade[KeyT, T]) Set(k KeyT, v T, expiration time.Duration) error { relKey := ca.key(k) - if v == nil { + if utils.IsNil(v) { return ca.cache.SetExpire(relKey, "", expiration) } value, err := ca.cdc.Marshal(v) @@ -94,10 +104,12 @@ func (ca *CacheFacade[KeyT, T]) Set(k KeyT, v *T, expiration time.Duration) erro return ca.cache.SetExpire(relKey, string(value), expiration) } -func (ca *CacheFacade[KeyT, T]) SetDefault(k KeyT, v *T) error { +func (ca *CacheFacade[KeyT, T]) SetDefault(k KeyT, v T) error { relKey := ca.key(k) - if v == nil { + logs.Out.Info("RelKey= %s", relKey) + + if utils.IsNil(v) { return ca.cache.Set(relKey, "") } value, err := ca.cdc.Marshal(v) @@ -109,10 +121,23 @@ func (ca *CacheFacade[KeyT, T]) SetDefault(k KeyT, v *T) error { func (ca *CacheFacade[KeyT, T]) Delete(k KeyT) error { relKey := ca.key(k) - return ca.cache.Delete(relKey) } +func (ca *CacheFacade[KeyT, T]) Keys() ([]KeyT, error) { + keys, err := ca.cache.Keys() + if err != nil { + return nil, err + } + + var ts []KeyT + for _, key := range keys { + ts = append(ts, ParseGenericKey[KeyT](key)) + } + + return ts, nil +} + func (ca *CacheFacade[KeyT, T]) key(key KeyT) string { if ca.keyFunc == nil { return GenericKey(key) @@ -120,22 +145,30 @@ func (ca *CacheFacade[KeyT, T]) key(key KeyT) string { return ca.keyFunc(key) } -func (ca *CacheFacade[KeyT, T]) value(key KeyT) *T { +func (ca *CacheFacade[KeyT, T]) value(key KeyT) T { + var v T if ca.valueFunc == nil { - return nil + return v } return ca.valueFunc(key) } -func (ca *CacheFacade[KeyT, T]) SetFunc(keyFunc func(KeyT) string, valueFunc func(KeyT) *T) { +func (ca *CacheFacade[KeyT, T]) WithFunc(keyFunc func(KeyT) string, valueFunc func(KeyT) T) { ca.keyFunc = keyFunc ca.valueFunc = valueFunc } -func (ca *CacheFacade[KeyT, T]) SetKeyFunc(keyFunc func(KeyT) string) { - ca.keyFunc = keyFunc +func (ca *CacheFacade[KeyT, T]) WithKeyPrefix(pre string) *CacheFacade[KeyT, T] { + ca.cache.SetKeyPrefix(pre) + return ca } -func (ca *CacheFacade[KeyT, T]) SetValueFunc(valueFunc func(KeyT) *T) { +func (ca *CacheFacade[KeyT, T]) WithValueFunc(valueFunc func(KeyT) T) *CacheFacade[KeyT, T] { ca.valueFunc = valueFunc + return ca +} + +func (ca *CacheFacade[KeyT, T]) WithCodec(cdc interfaces.Codec) *CacheFacade[KeyT, T] { + ca.cdc = cdc + return ca } diff --git a/src/infrastructure/caches/facade_opt.go b/src/infrastructure/caches/facade_opt.go new file mode 100644 index 0000000..2c7012f --- /dev/null +++ b/src/infrastructure/caches/facade_opt.go @@ -0,0 +1,17 @@ +package caches + +import "time" + +type facadeConfig struct { + expiration time.Duration + + keyPrefix string //key前缀 +} + +type CacheOpt func(*facadeConfig) + +func OptionCacheExpireTime(t time.Duration) CacheOpt { + return func(config *facadeConfig) { + config.expiration = t + } +} diff --git a/src/infrastructure/caches/facade_test.go b/src/infrastructure/caches/facade_test.go index a9fe25e..d60cac6 100644 --- a/src/infrastructure/caches/facade_test.go +++ b/src/infrastructure/caches/facade_test.go @@ -2,30 +2,29 @@ package caches import ( "fmt" + "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/common/utils" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + redisClients "gitee.com/captials-team/ubdframe/src/infrastructure/clients/redis" "github.com/stretchr/testify/assert" "testing" "time" ) func TestNewCacheFacade(t *testing.T) { - - fa := NewCacheFacade[int64, A](NewMemoryStore(time.Second * 3)) - fa.SetValueFunc(func(id int64) *A { + fa := NewCacheFacade[int64, *A](getStore()).WithValueFunc(func(id int64) *A { return &A{Id: id} }) - + var a *A d, err := fa.Get(12) assert.Equal(t, err, nil) - assert.Equal(t, d, nil) + assert.Equal(t, d, a) - t.Logf("ID= %d", d) + t.Logf("ID= %+v", d) } func TestNewCacheFacade_Get(t *testing.T) { - - fa := NewCacheFacade[int64, A](NewMemoryStore(time.Second * 3)) - fa.SetValueFunc(func(id int64) *A { + fa := NewCacheFacade[int64, *A](getStore()).WithValueFunc(func(id int64) *A { return &A{Id: id} }) @@ -41,15 +40,14 @@ func TestNewCacheFacade_Set(t *testing.T) { Id int64 `json:"id"` } - fa := NewCacheFacade[int64, A](NewMemoryStore(time.Second * 3)) - fa.SetValueFunc(func(id int64) *A { + fa := NewCacheFacade[int64, *A](getStore()).WithValueFunc(func(id int64) *A { return &A{Id: id} - }) + }).WithKeyPrefix("testing_set_") var id = int64(1) var value = int64(12) - err := fa.Set(1, &A{Id: value}, time.Second*1) + err := fa.Set(1, &A{Id: value}, time.Second*5) assert.Equal(t, err, nil) d, err := fa.Get(id) @@ -59,7 +57,7 @@ func TestNewCacheFacade_Set(t *testing.T) { t.Logf("ID= %d", d.Id) - time.Sleep(time.Second * 2) + time.Sleep(time.Second * 8) d, err = fa.Get(id) assert.Equal(t, err, nil) @@ -67,20 +65,56 @@ func TestNewCacheFacade_Set(t *testing.T) { } func TestNewCacheFacade_map(t *testing.T) { - fa := NewCacheFacade[int64, map[string]string](NewMemoryStore(time.Second * 3)) - fa.SetValueFunc(func(id int64) *map[string]string { - s := fmt.Sprint(id) - return &map[string]string{ - s: s, - } - }) - - d, err := fa.Get(12) + fa := NewCacheFacade[int64, map[string]string](getStore()). + WithValueFunc(func(id int64) map[string]string { + s := fmt.Sprint(id) + return map[string]string{ + s: fmt.Sprint(id + 1), + } + }) + + d, err := fa.GetOrLoad(12) assert.Equal(t, err, nil) + assert.Equal(t, d["12"], "13") t.Logf("ID= %v", d) } +func TestNewCacheFacade_Keys(t *testing.T) { + fa := NewCacheFacade[int64, map[string]string](getStore()).WithKeyPrefix("test_keys_") + + common.ErrPanic(fa.SetDefault(1, map[string]string{ + "A": "111", + "V": "222", + })) + common.ErrPanic(fa.SetDefault(2, map[string]string{ + "A": "ccc", + "V": "bbb", + })) + + keys, err := fa.Keys() + common.ErrPanic(err) + + t.Logf("Len= %d", len(keys)) + for _, k := range keys { + v, err := fa.Get(k) + common.ErrPanic(err) + t.Logf("Key= %+v,Value= %+v", k, v) + } + +} + type A struct { Id int64 `json:"id"` } + +func getStore() ItfCache { + rd := redisClients.NewRedisClient(configstc.DBConfig{ + DbHost: "192.168.149.128", + DbPort: "6379", + }) + rs := NewRedisStore(rd, time.Minute*10) + return rs + + return NewMemoryStore(time.Second * 3) +} diff --git a/src/infrastructure/caches/key.go b/src/infrastructure/caches/key.go index 29bf71f..b9720f7 100644 --- a/src/infrastructure/caches/key.go +++ b/src/infrastructure/caches/key.go @@ -3,29 +3,67 @@ package caches import ( "encoding/json" "fmt" + "github.com/spf13/cast" + "reflect" "strings" ) const CombineSep = "::" +const CacheKeyPrefix = "" +// GenericKey 通用key生成(泛型) func GenericKey[T int | int64 | string | []string | interface{}](k T) string { switch any(k).(type) { case int: d := any(k).(int) - return fmt.Sprintf("id_%d", d) + return fmt.Sprintf("%d", d) case int64: d := any(k).(int64) - return fmt.Sprintf("id_%d", d) + return fmt.Sprintf("%d", d) case string: d := any(k).(string) - return fmt.Sprintf("key_%s", d) + return fmt.Sprintf("%s", d) case []string: d := any(k).([]string) - return fmt.Sprintf("key_%s", strings.Join(d, ".")) + return fmt.Sprintf("%s", strings.Join(d, ".")) } //默认进行json处理后生成可以不同的key s, _ := json.Marshal(k) - return fmt.Sprintf("key_%s", string(s)) + return fmt.Sprintf("%s", string(s)) +} + +// ParseGenericKey 解析缓存key(泛型) +func ParseGenericKey[T int | int64 | string | []string | interface{}](k string) T { + var v T + d := k + + var res interface{} + + switch any(v).(type) { + case int: + res = cast.ToInt(d) + return res.(T) + case int64: + res = cast.ToInt64(d) + return res.(T) + case string: + res = d + return res.(T) + case []string: + res = strings.Split(d, ".") + return res.(T) + } + //默认进行json处理后生成可以不同的key + + tyRef := reflect.TypeOf(v) + //针对指针进行特殊处理 + if tyRef.Kind() == reflect.Pointer { + v = reflect.New(tyRef.Elem()).Interface().(T) + json.Unmarshal([]byte(d), v) + } else { + json.Unmarshal([]byte(d), &v) + } + return v } // CombineKeys 多个key合并为一个key diff --git a/src/infrastructure/caches/store_memory.go b/src/infrastructure/caches/store_memory.go index efd1ef2..3fe2b01 100644 --- a/src/infrastructure/caches/store_memory.go +++ b/src/infrastructure/caches/store_memory.go @@ -80,3 +80,17 @@ func (store *MemoryStore) SetExpire(key string, value string, expired time.Durat store.cache.Set(key, value, expired) return nil } + +// Keys 返回所有缓存key +func (store *MemoryStore) Keys() ([]string, error) { + d := store.cache.Items() + var keys []string + for k, _ := range d { + keys = append(keys, k) + } + return keys, nil +} + +func (store *MemoryStore) SetKeyPrefix(prefix string) { + return +} diff --git a/src/infrastructure/caches/store_memory_test.go b/src/infrastructure/caches/store_memory_test.go index 1116c25..b0867a3 100644 --- a/src/infrastructure/caches/store_memory_test.go +++ b/src/infrastructure/caches/store_memory_test.go @@ -1,6 +1,7 @@ package caches import ( + "gitee.com/captials-team/ubdframe/src/common" "github.com/stretchr/testify/assert" "testing" "time" @@ -38,3 +39,26 @@ func TestMemoryStore_Set(t *testing.T) { assert.Equal(t, exist, true) assert.Equal(t, d, value) } + +func TestMemoryStore_Keys(t *testing.T) { + var c ItfCache + c = NewMemoryStore(time.Second * 5) + + mockData(t, c) + + keys, err := c.Keys() + common.ErrPanic(err) + + assert.Greater(t, len(keys), 0) + t.Logf("keys len= %d", len(keys)) + t.Logf("keys= %+v", keys) +} + +func mockData(t *testing.T, c ItfCache) { + common.ErrPanic(c.Set("xxx_1", time.Now().String())) + common.ErrPanic(c.Set("xxx_2", "hello")) + common.ErrPanic(c.Set("xxx_3", "world")) + common.ErrPanic(c.Set("aaa_1", time.Now().String())) + common.ErrPanic(c.Set("bbb_1", time.Now().String())) + common.ErrPanic(c.Set("vvv_1", time.Now().String())) +} diff --git a/src/infrastructure/caches/store_redis.go b/src/infrastructure/caches/store_redis.go index 281fd01..a4fd928 100644 --- a/src/infrastructure/caches/store_redis.go +++ b/src/infrastructure/caches/store_redis.go @@ -5,22 +5,27 @@ import ( "errors" "gitee.com/captials-team/ubdframe/src/domain/interfaces" "github.com/go-redis/redis/v8" + "strings" "time" ) type RedisStore struct { - rd *redis.Client - - expired time.Duration + rd *redis.Client + expired time.Duration + keyPrefix string Codec interfaces.Codec } func (store *RedisStore) Get(key string) (string, bool, error) { + key = store.key(key) tmp, err := store.rd.Get(context.Background(), key).Result() if errors.Is(err, redis.Nil) { return tmp, false, nil } + if err != nil { + return tmp, false, err + } return tmp, true, nil } @@ -29,11 +34,13 @@ func (store *RedisStore) Set(key string, value string) error { } func (store *RedisStore) SetExpire(key string, value string, expired time.Duration) error { + key = store.key(key) _, err := store.rd.Set(context.Background(), key, value, expired).Result() return err } func (store *RedisStore) TTL(key string) (time.Duration, error) { + key = store.key(key) t, err := store.rd.TTL(context.Background(), key).Result() if errors.Is(err, redis.Nil) { return -1, nil @@ -42,10 +49,12 @@ func (store *RedisStore) TTL(key string) (time.Duration, error) { } func (store *RedisStore) Inc(key string, n int64) error { + key = store.key(key) return store.rd.IncrBy(context.Background(), key, n).Err() } func (store *RedisStore) Dec(key string, n int64) error { + key = store.key(key) return store.rd.DecrBy(context.Background(), key, n).Err() } @@ -56,25 +65,57 @@ func (store *RedisStore) GetSet(key string, value string, expired time.Duration) } func (store *RedisStore) GetSetFunc(key string, f func() string, expired time.Duration) (string, error) { - tmp, err := store.rd.Get(context.Background(), key).Result() - if errors.Is(err, redis.Nil) { + tmp, exists, err := store.Get(key) + if err != nil { + return "", err + } + if !exists { d := f() - err := store.SetExpire(key, d, expired) - return d, err + err1 := store.SetExpire(key, d, expired) + return d, err1 } return tmp, nil } func (store *RedisStore) Delete(key string) error { + key = store.key(key) _, err := store.rd.Del(context.Background(), key).Result() return err } func (store *RedisStore) Exist(key string) (bool, error) { + key = store.key(key) d, err := store.rd.Exists(context.Background(), key).Result() return d > 0, err } +func (store *RedisStore) Keys() ([]string, error) { + key := store.key("*") + keys, err := store.rd.Keys(context.Background(), key).Result() + if err != nil { + return nil, err + } + + if store.keyPrefix == "" { + return keys, nil + } + + var retKeys []string + for _, k := range keys { + oriK, _ := strings.CutPrefix(k, store.keyPrefix) + retKeys = append(retKeys, oriK) + } + return retKeys, nil +} + +func (store *RedisStore) key(key string) string { + return store.keyPrefix + key +} + +func (store *RedisStore) SetKeyPrefix(prefix string) { + store.keyPrefix = prefix +} + func NewRedisStore(rd *redis.Client, expired time.Duration) *RedisStore { return &RedisStore{ rd: rd, -- Gitee From d040b6a4fab7e354452759c7ef02d198936e0e42 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 17 Jun 2025 18:08:53 +0800 Subject: [PATCH 08/83] modify --- src/domain/services/block.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/domain/services/block.go b/src/domain/services/block.go index 3a3d6a8..5e4bb31 100644 --- a/src/domain/services/block.go +++ b/src/domain/services/block.go @@ -27,7 +27,6 @@ func NewBlockService(di *dig.Container, l v1log.ILog, blockDao interfaces.ItfBlo } svc.cacheById.WithValueFunc(svc.blockCache).WithKeyPrefix("block_") - svc.cacheByCode.WithValueFunc(svc.blockCacheByCode).WithKeyPrefix("block_code_") return svc -- Gitee From 3781565f99d53fd38fbc2a37f73bb4c3563cc7eb Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 19 Jun 2025 10:51:40 +0800 Subject: [PATCH 09/83] add auth check --- src/apps/userapp/api_server.go | 3 ++- src/apps/userapp/controllers/http/ctr_auth.go | 12 ++++++++++++ src/domain/interfaces/controller_gin.go | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/apps/userapp/api_server.go b/src/apps/userapp/api_server.go index 8839296..9517ba1 100644 --- a/src/apps/userapp/api_server.go +++ b/src/apps/userapp/api_server.go @@ -77,7 +77,8 @@ func (s *ApiServer) router(g gin.IRouter) { g.POST("/auth/login", s.captchaCtr.VerifyHandler(), s.authCtr.AuthLogin) g.POST("/auth/wechat_code", s.authCtr.AuthByWxCode) //微信code第三方登录 - authGroup.POST("/auth/info", s.authCtr.AuthInfo) //获取授权信息(通用授权) + authGroup.POST("/auth/check", s.authCtr.AuthCheck) //校验登录状态 + authGroup.POST("/auth/info", s.authCtr.AuthInfo) //获取授权信息(通用授权) authGroup.POST("/auth/logout", s.authCtr.AuthLogout) authGroup.POST("/auth/fresh_token", s.authCtr.AuthFreshToken) //授权刷新token } diff --git a/src/apps/userapp/controllers/http/ctr_auth.go b/src/apps/userapp/controllers/http/ctr_auth.go index acbe3de..b97c867 100644 --- a/src/apps/userapp/controllers/http/ctr_auth.go +++ b/src/apps/userapp/controllers/http/ctr_auth.go @@ -301,6 +301,18 @@ func (ctr *AuthController) AuthInfo(ctx *gin.Context) { ctr.Response(ctx, respdata.CSuccess.MData(utils.CopyTo(&auth, new(respdata.AuthResp)))) } +// AuthCheck godoc +// @Summary Auth-登录校验 +// @Description 登录校验ok正常返回,不通过返回99 +// @Tags user.auth +// @Security ApiKeyAuth +// @Produce json +// @success 200 {object} respdata.ResponseData{} "返回结果" +// @Router /auth/check [post] +func (ctr *AuthController) AuthCheck(ctx *gin.Context) { + ctr.Response(ctx, respdata.CSuccess) +} + // DebugAuthByThird godoc // @Summary [DEBUG]第三方授权 // @Description 第三方登录-微信 diff --git a/src/domain/interfaces/controller_gin.go b/src/domain/interfaces/controller_gin.go index 9fb0448..3ccd7b2 100644 --- a/src/domain/interfaces/controller_gin.go +++ b/src/domain/interfaces/controller_gin.go @@ -66,6 +66,7 @@ type ItfAuthController interface { AuthLogin(ctx *gin.Context) //登录 AuthLogout(ctx *gin.Context) //登出 + AuthCheck(ctx *gin.Context) //校验登录状态 AuthInfo(ctx *gin.Context) //获取认证信息 AuthFreshToken(ctx *gin.Context) //token刷新 } -- Gitee From acb5fcfb720120546c45bd1551dc1be365b4e0db Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 19 Jun 2025 10:57:49 +0800 Subject: [PATCH 10/83] modify auth --- src/apps/adminapp/api_server.go | 1 + src/apps/adminapp/controllers/http/ctr_auth.go | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/apps/adminapp/api_server.go b/src/apps/adminapp/api_server.go index 72f402b..7de1ae3 100644 --- a/src/apps/adminapp/api_server.go +++ b/src/apps/adminapp/api_server.go @@ -57,6 +57,7 @@ func (s *ApiServer) router(g gin.IRouter) { //登陆相关 g.POST("/mag/auth/login", captcha.VerifyHandler(), ctr.AuthLogin, s.RecordLog("登录")) //不需要授权 + authGroup.POST("/mag/auth/check", ctr.AuthCheck) authGroup.POST("/mag/auth/info", ctr.AuthInfo) authGroup.POST("/mag/auth/logout", ctr.AuthLogout, s.RecordLog("登出")) authGroup.POST("/mag/auth/fresh_token", ctr.AuthFreshToken) diff --git a/src/apps/adminapp/controllers/http/ctr_auth.go b/src/apps/adminapp/controllers/http/ctr_auth.go index 603927c..6944629 100644 --- a/src/apps/adminapp/controllers/http/ctr_auth.go +++ b/src/apps/adminapp/controllers/http/ctr_auth.go @@ -178,6 +178,18 @@ func (ctr *AuthController) AuthInfo(ctx *gin.Context) { ctr.Response(ctx, respdata.CSuccess.MData(utils.CopyTo(&auth, new(respdata.AuthResp)))) } +// AuthCheck godoc +// @Summary Auth-登录校验 +// @Description 登录校验ok正常返回,不通过返回99 +// @Tags user.auth +// @Security ApiKeyAuth +// @Produce json +// @success 200 {object} respdata.ResponseData{} "返回结果" +// @Router /mag/auth/check [post] +func (ctr *AuthController) AuthCheck(ctx *gin.Context) { + ctr.Response(ctx, respdata.CSuccess) +} + // AuthFreshToken godoc // @Summary Auth-刷新授权 // @Description 刷新授权token -- Gitee From 2b810825171a58dbea1f7a3e0ad067aae7acca5d Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 24 Jun 2025 15:50:05 +0800 Subject: [PATCH 11/83] add oss test --- src/pkg/ossadaptor/minio_oss.go | 15 ++++++++----- src/pkg/ossadaptor/minio_oss_test.go | 33 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/pkg/ossadaptor/minio_oss_test.go diff --git a/src/pkg/ossadaptor/minio_oss.go b/src/pkg/ossadaptor/minio_oss.go index cabf551..db5672b 100644 --- a/src/pkg/ossadaptor/minio_oss.go +++ b/src/pkg/ossadaptor/minio_oss.go @@ -3,6 +3,7 @@ package ossadaptor import ( "encoding/json" "fmt" + "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "golang.org/x/net/context" @@ -18,11 +19,11 @@ type MinioOss struct { } type MinioOssOps struct { - AccessKey string - SecretKey string - Endpoint string - Bucket string - SubPath string + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` + Endpoint string `json:"endpoint"` // minio服务器地址端口,如:192.168.1.129:9000 + Bucket string `json:"bucket"` //bucket名称 + SubPath string `json:"subPath"` //子路径,如: download/images } func NewMinioOss(bindDomain string, ops *MinioOssOps) (*MinioOss, error) { @@ -65,9 +66,11 @@ func (oss *MinioOss) Parse(s string) error { } func (oss *MinioOss) url(info minio.UploadInfo) string { + logs.Out.Info("Upload= %+v", info) return fmt.Sprintf("%s/%s/%s", oss.bindDomain, info.Bucket, info.Key) } +// Put 上传文件, file: 本地文件路径,objName: minio存储的对象名 func (oss *MinioOss) Put(file string, objName string) (*UploadRet, error) { ret := &UploadRet{} bucketName := oss.ops.Bucket @@ -80,7 +83,7 @@ func (oss *MinioOss) Put(file string, objName string) (*UploadRet, error) { ret.Url = oss.url(info) - println(oss.client.EndpointURL()) + //println(oss.client.EndpointURL()) return ret, nil } diff --git a/src/pkg/ossadaptor/minio_oss_test.go b/src/pkg/ossadaptor/minio_oss_test.go new file mode 100644 index 0000000..4dd222b --- /dev/null +++ b/src/pkg/ossadaptor/minio_oss_test.go @@ -0,0 +1,33 @@ +package ossadaptor + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestMinioOss_Put(t *testing.T) { + //export OSS_BIND_DOMAIN="http://192.168.1.128:9000" + //export OSS_ACCESS_KEY="xx" + //export OSS_SECRET_KEY="xx" + //export OSS_ENDPOINT="192.168.1.128:9000" + //export OSS_BUCKET="demo" + //export OSS_SUB_PATH="t" + + //最终生成的访问url: http://192.168.1.128:9000/demo/t/ossgotest + oss, err := NewMinioOss(os.Getenv("OSS_BIND_DOMAIN"), &MinioOssOps{ + AccessKey: os.Getenv("OSS_ACCESS_KEY"), + SecretKey: os.Getenv("OSS_SECRET_KEY"), + Endpoint: os.Getenv("OSS_ENDPOINT"), + Bucket: os.Getenv("OSS_BUCKET"), + SubPath: os.Getenv("OSS_SUB_PATH"), + }) + assert.Equal(t, err, nil) + + t.Logf("inited") + + ret, err := oss.Put("./oss.go", "ossgotest") + assert.Equal(t, err, nil) + + t.Logf("%s", ret.Url) +} -- Gitee From 4de7976b5a4bf3b6823054cd67f2aad65f8dd528 Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 27 Jun 2025 16:43:22 +0800 Subject: [PATCH 12/83] modify third --- src/domain/models/user_third.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/domain/models/user_third.go b/src/domain/models/user_third.go index 77cfd9c..8911e6c 100644 --- a/src/domain/models/user_third.go +++ b/src/domain/models/user_third.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "gitee.com/captials-team/ubdframe/src/common/utils" "gorm.io/gorm/schema" ) @@ -44,6 +45,9 @@ func (r *UserThird) TableName(namer schema.Namer) string { // ConvertUser 第三方转换为User func (r *UserThird) ConvertUser() *User { + if r.Nickname == "" { + r.Nickname = fmt.Sprintf("用户-%s", utils.RandLetterFigureCode(8)) + } return &User{ Id: r.UId, SqlTimeFields: r.SqlTimeFields, -- Gitee From 40d861e7cc637df495e5c1a77fb0ac8d0df8e870 Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 3 Jul 2025 13:59:11 +0800 Subject: [PATCH 13/83] modify apiserver --- src/apps/adminapp/api_server.go | 6 +++--- src/apps/userapp/api_server.go | 2 +- src/apps/webapp/api_server.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/apps/adminapp/api_server.go b/src/apps/adminapp/api_server.go index 7de1ae3..fc74f21 100644 --- a/src/apps/adminapp/api_server.go +++ b/src/apps/adminapp/api_server.go @@ -65,7 +65,7 @@ func (s *ApiServer) router(g gin.IRouter) { //个人中心 common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfAdminCenterController) { - authGroup.POST("/mag/u_center/info", ctr.UserInfo) //个人信息 + authGroup.POST("/mag/u_center/info", ctr.UserInfo) //个人信息 authGroup.POST("/mag/u_center/modify_pwd", ctr.ModifyPassword, s.RecordLog("修改密码")) //修改密码(登录情况下) authGroup.POST("/mag/u_center/modify_info", ctr.ModifyInfo, s.RecordLog("修改个人信息")) })) @@ -170,8 +170,8 @@ func NewApiServer(di *dig.Scope, conf *configstc.AdminAppConfig, logger v1log.IL ApiServer: apps.NewApiServer(gin.Default(), conf.ApiServer), } - //开启跨域设置 - s.ApiServer.WithCors() + s.WithName(conf.Name) + s.WithCors() //开启跨域设置 s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) diff --git a/src/apps/userapp/api_server.go b/src/apps/userapp/api_server.go index 9517ba1..1ac38f8 100644 --- a/src/apps/userapp/api_server.go +++ b/src/apps/userapp/api_server.go @@ -160,7 +160,7 @@ func NewApiServer(conf *configstc.UserAppConfig, logger v1log.ILog, } s.WithName(conf.Name) - s.WithCors() + s.WithCors() //开启跨域设置 //用户端auth s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) diff --git a/src/apps/webapp/api_server.go b/src/apps/webapp/api_server.go index 4612d62..e893dcf 100644 --- a/src/apps/webapp/api_server.go +++ b/src/apps/webapp/api_server.go @@ -169,7 +169,8 @@ func NewApiServer(di *dig.Container, conf *configstc.WebAppConfig, logger v1log. ApiServer: apps.NewApiServer(gin.Default(), conf.WebServer), } - s.ApiServer.WithCors() + s.WithName(conf.Name) + s.WithCors() //开启跨域设置 return s } -- Gitee From e87e5272479c115f34a14c6aec0979e5fd1b7745 Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 3 Jul 2025 14:04:37 +0800 Subject: [PATCH 14/83] modify api --- src/apps/adminapp/api_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/adminapp/api_server.go b/src/apps/adminapp/api_server.go index fc74f21..e1a2da9 100644 --- a/src/apps/adminapp/api_server.go +++ b/src/apps/adminapp/api_server.go @@ -65,7 +65,7 @@ func (s *ApiServer) router(g gin.IRouter) { //个人中心 common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfAdminCenterController) { - authGroup.POST("/mag/u_center/info", ctr.UserInfo) //个人信息 + authGroup.POST("/mag/u_center/info", ctr.UserInfo) //个人信息 authGroup.POST("/mag/u_center/modify_pwd", ctr.ModifyPassword, s.RecordLog("修改密码")) //修改密码(登录情况下) authGroup.POST("/mag/u_center/modify_info", ctr.ModifyInfo, s.RecordLog("修改个人信息")) })) -- Gitee From bc395c41a5da22701a86aafb9f500bd028b1e16e Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 31 Jul 2025 17:56:51 +0800 Subject: [PATCH 15/83] modify rss vo --- src/apps/autoapiapp/api_server.go | 1 + src/apps/autoapiapp/app.go | 1 + src/apps/autoapiapp/config.go | 1 + src/apps/rssapp/app.go | 5 +- .../rssapp/controllers/http/ctr_rss_mag.go | 5 +- src/domain/configstc/app.go | 38 ++++++++++++++ src/domain/services/rss.go | 4 +- src/domain/vo/rss.go | 49 ------------------ src/pkg/rsshelp/rss.go | 17 +++---- src/pkg/rsshelp/rss_test.go | 5 +- src/pkg/rsshelp/vo.go | 51 +++++++++++++++++++ 11 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 src/apps/autoapiapp/api_server.go create mode 100644 src/apps/autoapiapp/app.go create mode 100644 src/apps/autoapiapp/config.go create mode 100644 src/pkg/rsshelp/vo.go diff --git a/src/apps/autoapiapp/api_server.go b/src/apps/autoapiapp/api_server.go new file mode 100644 index 0000000..bfb8c09 --- /dev/null +++ b/src/apps/autoapiapp/api_server.go @@ -0,0 +1 @@ +package autoapiapp diff --git a/src/apps/autoapiapp/app.go b/src/apps/autoapiapp/app.go new file mode 100644 index 0000000..bfb8c09 --- /dev/null +++ b/src/apps/autoapiapp/app.go @@ -0,0 +1 @@ +package autoapiapp diff --git a/src/apps/autoapiapp/config.go b/src/apps/autoapiapp/config.go new file mode 100644 index 0000000..bfb8c09 --- /dev/null +++ b/src/apps/autoapiapp/config.go @@ -0,0 +1 @@ +package autoapiapp diff --git a/src/apps/rssapp/app.go b/src/apps/rssapp/app.go index 3cec70a..e545b26 100644 --- a/src/apps/rssapp/app.go +++ b/src/apps/rssapp/app.go @@ -7,7 +7,6 @@ import ( "gitee.com/captials-team/ubdframe/src/domain/configstc" "gitee.com/captials-team/ubdframe/src/domain/interfaces" "gitee.com/captials-team/ubdframe/src/domain/services" - "gitee.com/captials-team/ubdframe/src/domain/vo" "gitee.com/captials-team/ubdframe/src/infrastructure/caches" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/rsshelp" @@ -32,7 +31,7 @@ func (app *App) initCaches(conf *configstc.RssAppConfig) { }), uber_help.ErrAlreadyProvided) common.ErrPanic(app.di.Provide(func(cache caches.ItfCache) services.RssCache { - return caches.NewCacheFacade[string, *vo.RssBody](cache) + return caches.NewCacheFacade[string, *rsshelp.RssBody](cache) }), uber_help.ErrAlreadyProvided) app.l.Info("provide cache success") @@ -61,7 +60,7 @@ func (app *App) initRss(f *rsshelp.RssFetcher, ca services.RssCache, logger v1lo } } - f.AddHandler(func(meta *vo.RssMeta, body *vo.RssBody) { + f.AddHandler(func(meta *rsshelp.RssMeta, body *rsshelp.RssBody) { err := ca.SetDefault(meta.Name, body) logger.Ctl(err != nil).Error("%s", err) }) diff --git a/src/apps/rssapp/controllers/http/ctr_rss_mag.go b/src/apps/rssapp/controllers/http/ctr_rss_mag.go index 4385d56..d59b8e2 100644 --- a/src/apps/rssapp/controllers/http/ctr_rss_mag.go +++ b/src/apps/rssapp/controllers/http/ctr_rss_mag.go @@ -4,7 +4,6 @@ import ( "gitee.com/captials-team/ubdframe/src/domain/configstc" "gitee.com/captials-team/ubdframe/src/domain/dto" "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" - "gitee.com/captials-team/ubdframe/src/domain/vo" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/rsshelp" @@ -63,8 +62,8 @@ func (ctr *RssManagerController) RssSourceItems(ctx *gin.Context) { fetcher.AddRss("tmp", req.Rss) } - var list []*vo.RssItems - fetcher.FetchHandler(func(meta *vo.RssMeta, body *vo.RssBody) { + var list []*rsshelp.RssItems + fetcher.FetchHandler(func(meta *rsshelp.RssMeta, body *rsshelp.RssBody) { for _, item := range body.Channel.Items { item.Author = meta.Name //修改作者 list = append(list, item) diff --git a/src/domain/configstc/app.go b/src/domain/configstc/app.go index e6df2db..0cb7140 100644 --- a/src/domain/configstc/app.go +++ b/src/domain/configstc/app.go @@ -333,3 +333,41 @@ const ( ProxyModePrefix = "prefix" //前缀替换,匹配并替换指定前缀 ProxyModeFix = "fixed" //固定地址,完全匹配路由进行转发 ) + +type AutoApiAppConfig struct { + Name string `json:"Name" yaml:"Name"` + ApiServer ServerConfig `json:"ApiServer" yaml:"ApiServer"` + + LogConfig LogConfig `json:"LogConfig" yaml:"LogConfig"` //日志配置 + + //debug开关 + Debug bool `json:"Debug" yaml:"Debug"` + + AuthConfig AuthConfig `json:"AuthConfig" yaml:"AuthConfig"` //授权配置 + + DBConfig DBConfig `json:"DBConfig" yaml:"DBConfig"` //数据库配置 + + LimitingApi LimitingConfig `json:"LimitingApi" yaml:"LimitingApi"` //api限流 + + //路由前缀 + RoutePrefix string `json:"RoutePrefix" yaml:"RoutePrefix"` //路由前缀 + + //是否启用文档 + DocsEnable bool `json:"DocsEnable" yaml:"DocsEnable"` + + //是否启用pprof + PProfEnable bool `json:"PProfEnable" yaml:"PProfEnable"` + + ModuleMode bool `json:"ModuleMode" yaml:"ModuleMode"` //模块模式,作为module使用 + + Apis []AutoApiDefined `json:"Apis" yaml:"Apis"` //自定义API列表 +} + +type AutoApiDefined struct { + Name string `json:"Name" yaml:"Name"` //api总称 + ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) + Fields []string `json:"Fields" yaml:"Fields"` +} + +type AutoApiFieldDefined struct { +} diff --git a/src/domain/services/rss.go b/src/domain/services/rss.go index 632fe6d..6030dba 100644 --- a/src/domain/services/rss.go +++ b/src/domain/services/rss.go @@ -1,8 +1,8 @@ package services import ( - "gitee.com/captials-team/ubdframe/src/domain/vo" "gitee.com/captials-team/ubdframe/src/infrastructure/caches" + "gitee.com/captials-team/ubdframe/src/pkg/rsshelp" ) -type RssCache = *caches.CacheFacade[string, *vo.RssBody] +type RssCache = *caches.CacheFacade[string, *rsshelp.RssBody] diff --git a/src/domain/vo/rss.go b/src/domain/vo/rss.go index b254f7e..1e0f122 100644 --- a/src/domain/vo/rss.go +++ b/src/domain/vo/rss.go @@ -1,51 +1,2 @@ package vo -import ( - "encoding/xml" - "io" -) - -// RssMeta 源信息 -type RssMeta struct { - Name string - RssUrl string -} - -// RssBody rss内容body -type RssBody struct { - Channel RssChannel `xml:"channel"` -} - -type RssChannel struct { - Title string `xml:"title"` - Description string `xml:"description"` - Link string `xml:"link"` - PubDate string `xml:"pubDate"` - Language string `xml:"language"` - Copyright string `xml:"copyright"` - Generator string `xml:"generator"` - Image struct { - Title string `xml:"title"` - Url string `xml:"url"` - Link string `xml:"link"` - } `xml:"image"` - Items []*RssItems `xml:"item"` -} - -type RssItems struct { - Title string `json:"title" xml:"title"` //新闻标题 - Author string `json:"author" xml:"author"` //作者 - Link string `json:"link" xml:"link"` //连接 - PubDate string `json:"pubDate" xml:"pubDate"` //发布日期/时间 - Description string `json:"description" xml:"description"` //描述信息 -} - -func (rss *RssBody) Parse(r io.Reader) error { - decoder := xml.NewDecoder(r) - err := decoder.Decode(rss) - if err != nil { - return err - } - - return nil -} diff --git a/src/pkg/rsshelp/rss.go b/src/pkg/rsshelp/rss.go index dbb3257..79df60f 100644 --- a/src/pkg/rsshelp/rss.go +++ b/src/pkg/rsshelp/rss.go @@ -2,7 +2,6 @@ package rsshelp import ( "fmt" - "gitee.com/captials-team/ubdframe/src/domain/vo" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "io" "net/http" @@ -12,12 +11,12 @@ import ( type RssFetcher struct { l v1log.ILog - rssMap map[string]*vo.RssMeta + rssMap map[string]*RssMeta client *http.Client handlers []RssHandler } -type RssHandler func(meta *vo.RssMeta, body *vo.RssBody) +type RssHandler func(meta *RssMeta, body *RssBody) func NewRssFetcher(l v1log.ILog) *RssFetcher { if l == nil { @@ -25,19 +24,19 @@ func NewRssFetcher(l v1log.ILog) *RssFetcher { } return &RssFetcher{ client: &http.Client{Timeout: time.Second * 5}, - rssMap: map[string]*vo.RssMeta{}, + rssMap: map[string]*RssMeta{}, l: l, } } -func (w *RssFetcher) AddRssMeta(meta ...*vo.RssMeta) { +func (w *RssFetcher) AddRssMeta(meta ...*RssMeta) { for _, v := range meta { w.rssMap[v.Name] = v } } func (w *RssFetcher) AddRss(name, url string) { - w.rssMap[name] = &vo.RssMeta{ + w.rssMap[name] = &RssMeta{ Name: name, RssUrl: url, } @@ -48,7 +47,7 @@ func (w *RssFetcher) AddHandler(h RssHandler) { } // runPullRss 拉取rss -func (w *RssFetcher) runPullRss(meta *vo.RssMeta, handlers ...RssHandler) error { +func (w *RssFetcher) runPullRss(meta *RssMeta, handlers ...RssHandler) error { w.l.Info("rss pull running: %+v", meta) var err error @@ -94,8 +93,8 @@ func (w *RssFetcher) FetchHandler(handlers ...RssHandler) { } } -func (w *RssFetcher) parseRss(r io.Reader) (*vo.RssBody, error) { - rss := &vo.RssBody{} +func (w *RssFetcher) parseRss(r io.Reader) (*RssBody, error) { + rss := &RssBody{} err := rss.Parse(r) if err != nil { return rss, err diff --git a/src/pkg/rsshelp/rss_test.go b/src/pkg/rsshelp/rss_test.go index d7cbf5e..358bcd4 100644 --- a/src/pkg/rsshelp/rss_test.go +++ b/src/pkg/rsshelp/rss_test.go @@ -1,7 +1,6 @@ package rsshelp import ( - "gitee.com/captials-team/ubdframe/src/domain/vo" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "testing" ) @@ -10,14 +9,14 @@ func TestRssFetchWorker_Run(t *testing.T) { l := v1log.NewTestingLog(t, v1log.DebugLog) rss := NewRssFetcher(l) - rss.AddRssMeta(&vo.RssMeta{ + rss.AddRssMeta(&RssMeta{ Name: "people_com_china", RssUrl: "http://www.people.com.cn/rss/politics.xml", }) rss.AddRss("sohu_com_china", "http://rss.news.sohu.com/rss/pfocus.xml") rss.AddRss("baidu_news", "https://news.baidu.com/n?cmd=1&class=civilnews&tn=rss&sub=0") - rss.AddHandler(func(meta *vo.RssMeta, body *vo.RssBody) { + rss.AddHandler(func(meta *RssMeta, body *RssBody) { t.Logf("META %+v", meta) //t.Logf("Content %+v", body.Channel) diff --git a/src/pkg/rsshelp/vo.go b/src/pkg/rsshelp/vo.go new file mode 100644 index 0000000..7cbb10e --- /dev/null +++ b/src/pkg/rsshelp/vo.go @@ -0,0 +1,51 @@ +package rsshelp + +import ( + "encoding/xml" + "io" +) + +// RssMeta 源信息 +type RssMeta struct { + Name string + RssUrl string +} + +// RssBody rss内容body +type RssBody struct { + Channel RssChannel `xml:"channel"` +} + +type RssChannel struct { + Title string `xml:"title"` + Description string `xml:"description"` + Link string `xml:"link"` + PubDate string `xml:"pubDate"` + Language string `xml:"language"` + Copyright string `xml:"copyright"` + Generator string `xml:"generator"` + Image struct { + Title string `xml:"title"` + Url string `xml:"url"` + Link string `xml:"link"` + } `xml:"image"` + Items []*RssItems `xml:"item"` +} + +type RssItems struct { + Title string `json:"title" xml:"title"` //新闻标题 + Author string `json:"author" xml:"author"` //作者 + Link string `json:"link" xml:"link"` //连接 + PubDate string `json:"pubDate" xml:"pubDate"` //发布日期/时间 + Description string `json:"description" xml:"description"` //描述信息 +} + +func (rss *RssBody) Parse(r io.Reader) error { + decoder := xml.NewDecoder(r) + err := decoder.Decode(rss) + if err != nil { + return err + } + + return nil +} -- Gitee From 43c84e4c6db83fe5bad6ec6ee0192570f7443322 Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 31 Jul 2025 19:48:55 +0800 Subject: [PATCH 16/83] modify extends param --- src/domain/configstc/app.go | 13 +--- src/domain/configstc/config_params.go | 80 +----------------------- src/pkg/atapi/config.go | 33 ++++++++++ src/pkg/atapi/facade.go | 1 + src/pkg/extendparams/params.go | 88 +++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 88 deletions(-) create mode 100644 src/pkg/atapi/config.go create mode 100644 src/pkg/atapi/facade.go create mode 100644 src/pkg/extendparams/params.go diff --git a/src/domain/configstc/app.go b/src/domain/configstc/app.go index 0cb7140..497e51a 100644 --- a/src/domain/configstc/app.go +++ b/src/domain/configstc/app.go @@ -1,5 +1,7 @@ package configstc +import "gitee.com/captials-team/ubdframe/src/pkg/atapi" + type UserAppConfig struct { Name string `json:"Name" yaml:"Name"` @@ -360,14 +362,5 @@ type AutoApiAppConfig struct { ModuleMode bool `json:"ModuleMode" yaml:"ModuleMode"` //模块模式,作为module使用 - Apis []AutoApiDefined `json:"Apis" yaml:"Apis"` //自定义API列表 -} - -type AutoApiDefined struct { - Name string `json:"Name" yaml:"Name"` //api总称 - ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) - Fields []string `json:"Fields" yaml:"Fields"` -} - -type AutoApiFieldDefined struct { + Apis []atapi.AutoApiDefined `json:"Apis" yaml:"Apis"` //自定义API列表 } diff --git a/src/domain/configstc/config_params.go b/src/domain/configstc/config_params.go index 2ec15a8..c5a25f1 100644 --- a/src/domain/configstc/config_params.go +++ b/src/domain/configstc/config_params.go @@ -1,83 +1,7 @@ package configstc import ( - "strconv" - "strings" + "gitee.com/captials-team/ubdframe/src/pkg/extendparams" ) -type CommonExtendParams map[string]string - -// ExtendParamByInt 返回extendParam指定k的int类型值 -func (op CommonExtendParams) ExtendParamByInt(k string, def ...int) int { - v := op.ExtendParamValue(k) - ret, err := strconv.Atoi(v) - if err != nil { - if len(def) > 0 { - return def[0] - } - return 0 - } - return ret -} - -// ExtendParamByInt64 返回extendParam指定k的int类型值 -func (op CommonExtendParams) ExtendParamByInt64(k string, def ...int64) int64 { - v := op.ExtendParamValue(k) - ret, err := strconv.ParseInt(v, 10, 64) - if err != nil { - if len(def) > 0 { - return def[0] - } - return 0 - } - return ret -} - -// ExtendParamByFloat 返回extendParam指定k的float类型值 -func (op CommonExtendParams) ExtendParamByFloat(k string, def ...float64) float64 { - v := op.ExtendParamValue(k) - ret, err := strconv.ParseFloat(v, 64) - if err != nil { - if len(def) > 0 { - return def[0] - } - return 0 - } - return ret -} - -// ExtendParamByBool 返回extendParam指定k的bool类型值 -func (op CommonExtendParams) ExtendParamByBool(k string, def ...bool) bool { - v := op.ExtendParamValue(k) - if v == "" { - if len(def) > 0 { - return def[0] - } - return false - } - - if v == "true" { - return true - } - - if v == "false" { - return true - } - - return v != "0" -} - -func (op CommonExtendParams) ExtendParamValue(key string, def ...string) string { - if op == nil { - return "" - } - v := strings.TrimSpace(op[key]) - if v == "" { - if len(def) > 0 { - return def[0] - } - return "" - } - - return v -} +type CommonExtendParams extendparams.CommonExtendParams diff --git a/src/pkg/atapi/config.go b/src/pkg/atapi/config.go new file mode 100644 index 0000000..4b51327 --- /dev/null +++ b/src/pkg/atapi/config.go @@ -0,0 +1,33 @@ +package atapi + +// AutoApiDefined 单个autoApi的定义 +type AutoApiDefined struct { + Name string `json:"Name" yaml:"Name"` //api总称 + ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) + + LoadFile string `json:"LoadFile" yaml:"LoadFile"` //加载文件路径,有路径值则从该路径加载覆盖当前AutoApi + + Fields []AutoApiFieldDefined `json:"Fields" yaml:"Fields"` //字段,定义该api会用到的字段,也会用于数据存储的表字段 + Plugins []AutoApiPluginDefined `json:"Plugins" yaml:"Plugins"` //加载使用的插件列表 + + Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 +} + +// AutoApiFieldDefined 单个autoApi字段的定义 +type AutoApiFieldDefined struct { + Name string `json:"Name" yaml:"Name"` //字段名,如:name + ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) + + //类型, 可为: int,int64,string, + Type string `json:"Type" yaml:"Type"` + Size int `json:"Size" yaml:"Size"` //大小,配合Type,可定义类型的长度 + + Default string `json:"Default" yaml:"Default"` //默认值 + Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 +} + +// AutoApiPluginDefined 单个autoApi使用插件的定义 +type AutoApiPluginDefined struct { + Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 + Enable bool `json:"Enable" yaml:"Enable"` //是否启用 +} diff --git a/src/pkg/atapi/facade.go b/src/pkg/atapi/facade.go new file mode 100644 index 0000000..468db82 --- /dev/null +++ b/src/pkg/atapi/facade.go @@ -0,0 +1 @@ +package atapi diff --git a/src/pkg/extendparams/params.go b/src/pkg/extendparams/params.go new file mode 100644 index 0000000..0ad2582 --- /dev/null +++ b/src/pkg/extendparams/params.go @@ -0,0 +1,88 @@ +package extendparams + +import ( + "strconv" + "strings" +) + +type CommonExtendParams map[string]string + +// ExtendParamByInt 返回extendParam指定k的int类型值 +func (op CommonExtendParams) ExtendParamByInt(k string, def ...int) int { + v := op.ExtendParamValue(k) + ret, err := strconv.Atoi(v) + if err != nil { + if len(def) > 0 { + return def[0] + } + return 0 + } + return ret +} + +// ExtendParamByInt64 返回extendParam指定k的int类型值 +func (op CommonExtendParams) ExtendParamByInt64(k string, def ...int64) int64 { + v := op.ExtendParamValue(k) + ret, err := strconv.ParseInt(v, 10, 64) + if err != nil { + if len(def) > 0 { + return def[0] + } + return 0 + } + return ret +} + +// ExtendParamByFloat 返回extendParam指定k的float类型值 +func (op CommonExtendParams) ExtendParamByFloat(k string, def ...float64) float64 { + v := op.ExtendParamValue(k) + ret, err := strconv.ParseFloat(v, 64) + if err != nil { + if len(def) > 0 { + return def[0] + } + return 0 + } + return ret +} + +// ExtendParamByBool 返回extendParam指定k的bool类型值 +func (op CommonExtendParams) ExtendParamByBool(k string, def ...bool) bool { + v := op.ExtendParamValue(k) + if v == "" { + if len(def) > 0 { + return def[0] + } + return false + } + + if v == "true" { + return true + } + + if v == "false" { + return true + } + + return v != "0" +} + +// ExtendParamByString 返回extendParam指定k的string类型值 +func (op CommonExtendParams) ExtendParamByString(k string, def ...string) string { + return op.ExtendParamValue(k, def...) +} + +func (op CommonExtendParams) ExtendParamValue(key string, def ...string) string { + if op == nil { + return "" + } + v := strings.TrimSpace(op[key]) + if v == "" { + if len(def) > 0 { + return def[0] + } + return "" + } + + return v +} -- Gitee From f8ecc5f3c0fb56edaf37a2e9580e17aec8edebbf Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 1 Aug 2025 18:01:56 +0800 Subject: [PATCH 17/83] add atapi --- src/pkg/atapi/config.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pkg/atapi/config.go b/src/pkg/atapi/config.go index 4b51327..e03c927 100644 --- a/src/pkg/atapi/config.go +++ b/src/pkg/atapi/config.go @@ -1,5 +1,7 @@ package atapi +import "gitee.com/captials-team/ubdframe/src/pkg/extendparams" + // AutoApiDefined 单个autoApi的定义 type AutoApiDefined struct { Name string `json:"Name" yaml:"Name"` //api总称 @@ -24,10 +26,13 @@ type AutoApiFieldDefined struct { Default string `json:"Default" yaml:"Default"` //默认值 Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 + + ExtendParams extendparams.CommonExtendParams `json:"ExtendParams" yaml:"ExtendParams"` //扩展参数 } // AutoApiPluginDefined 单个autoApi使用插件的定义 type AutoApiPluginDefined struct { - Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 - Enable bool `json:"Enable" yaml:"Enable"` //是否启用 + Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 + Enable bool `json:"Enable" yaml:"Enable"` //是否启用 + ExtendParams extendparams.CommonExtendParams `json:"ExtendParams" yaml:"ExtendParams"` //扩展参数 } -- Gitee From b701947b5be0647cd0d04fac73fab25843b2ada3 Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 7 Aug 2025 19:22:36 +0800 Subject: [PATCH 18/83] add autoapi --- src/apps/autoapiapp/api_server.go | 253 ++++++++++++++++++++++++++++++ src/apps/autoapiapp/app.go | 46 ++++++ src/apps/autoapiapp/app_test.go | 46 ++++++ 3 files changed, 345 insertions(+) create mode 100644 src/apps/autoapiapp/app_test.go diff --git a/src/apps/autoapiapp/api_server.go b/src/apps/autoapiapp/api_server.go index bfb8c09..b6e88c6 100644 --- a/src/apps/autoapiapp/api_server.go +++ b/src/apps/autoapiapp/api_server.go @@ -1 +1,254 @@ package autoapiapp + +import ( + "encoding/json" + "gitee.com/captials-team/ubdframe/src/apps" + "gitee.com/captials-team/ubdframe/src/common/utils" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/pkg/gin_http" + v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "github.com/gin-gonic/gin" + "github.com/go-openapi/spec" + "github.com/swaggo/swag" + "go.uber.org/dig" +) + +type ApiServer struct { + *apps.ApiServer + + di *dig.Scope + conf *configstc.AutoApiAppConfig + + gin_http.SwaggerOption //swagger相关选项配置 +} + +func (s *ApiServer) Name() string { + return "auto_api" +} + +func (s *ApiServer) InitRouter() { + s.Engine().GET("ping", gin_http.PingHandler) + s.InitRouterForGin(s.Engine()) +} + +func (s *ApiServer) InitRouterForGin(engine *gin.Engine) { + var g = engine.Group("") + if len(s.conf.RoutePrefix) > 0 { + g = engine.Group(s.conf.RoutePrefix) + } + + //注册swagger + s.SwaggerRouter(g) + + s.router(g) + + return +} + +func (s *ApiServer) router(g gin.IRouter) { + g.Use( + gin_http.PanicHandler, + gin_http.QPSLimiterHandler(10, 10), + ) +} + +func (s *ApiServer) Start() error { + if !s.Module() { + s.InitRouter() + } + return s.ApiServer.Start() +} + +func (s *ApiServer) Stop() error { + return s.ApiServer.Stop() +} + +func (s *ApiServer) genSwaggerDocs() string { + ps := spec.SwaggerProps{ + Schemes: []string{"http", "https"}, + Swagger: "2.0", + Produces: []string{ + "application/json", + "text/html", + }, + Host: "{{.Host}}", + BasePath: "{{.BasePath}}", + } + ps.Info.Title = "{{.Title}}" + ps.Info.Version = "{{.Version}}" + ps.Info.Description = "{{escape .Description}}" + ps.Info.TermsOfService = "http://swagger.io/terms/" + + // + paths := map[string]spec.PathItem{} + + //组装path + paths["/mag/at/org/search"] = spec.PathItem{ + Refable: spec.Refable{}, + VendorExtensible: spec.VendorExtensible{}, + PathItemProps: spec.PathItemProps{ + Get: &spec.Operation{ + OperationProps: spec.OperationProps{ + Description: "组织-搜索列表", + Summary: "组织搜索列表,根据搜索参数返回列表和分页信息", + Consumes: nil, + Produces: []string{"application/json"}, + Schemes: nil, + Tags: []string{"organize"}, + ExternalDocs: nil, + ID: "", + Deprecated: false, + Security: nil, + Parameters: []spec.Parameter{ + { + ParamProps: spec.ParamProps{ + Description: "搜索参数", + Name: "param", + In: "body", + Required: true, + Schema: &spec.Schema{ + ExtraProps: map[string]interface{}{ + "$ref": "#/definitions/reqdata.organize.SearchReqParam", + }, + }, + AllowEmptyValue: false, + }, + }, + }, + Responses: &spec.Responses{ + ResponsesProps: spec.ResponsesProps{ + Default: nil, + StatusCodeResponses: map[int]spec.Response{ + 0: spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "", + Schema: &spec.Schema{ + VendorExtensible: spec.VendorExtensible{}, + SchemaProps: spec.SchemaProps{}, + SwaggerSchemaProps: spec.SwaggerSchemaProps{}, + ExtraProps: map[string]interface{}{ + "$ref": "#/definitions/reqdata.organize.SearchResponse", + }, + }, + Headers: nil, + Examples: nil, + }, + }, + }, + }, + }, + }, + }, + }, + } + defines := spec.Definitions{ + "reqdata.organize.SearchReqParam": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "keywords": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "关键字搜索", + }, + }, + "page_no": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"int"}, + Description: "指定页码", + }, + }, + "page_size": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"int"}, + Description: "单页数量", + }, + }, + }, + }, + }, + "reqdata.organize.SearchResponse": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "code": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"int"}, + Description: "状态码", + }, + }, + "msg": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "信息内容", + }, + ExtraProps: map[string]interface{}{ + "example": "查询成功", + }, + }, + "data": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Description: "数据", + Properties: map[string]spec.Schema{ + "list": { + SchemaProps: spec.SchemaProps{ + Description: "列表数据", + Type: []string{"array"}, + }, + }, + "paginate": { + SchemaProps: spec.SchemaProps{ + Description: "分页数据", + Type: []string{"object"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + + ps.Paths = &spec.Paths{ + VendorExtensible: spec.VendorExtensible{}, + Paths: paths, + } + ps.Definitions = defines + + bts, _ := json.Marshal(ps) + return string(bts) +} + +func NewApiServer(di *dig.Scope, conf *configstc.AutoApiAppConfig, logger v1log.ILog) *ApiServer { + s := &ApiServer{ + di: di, + conf: conf, + ApiServer: apps.NewApiServer(gin.Default(), conf.ApiServer), + } + + swaggerDocs := swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/", + Schemes: []string{"http", "https"}, + Title: "AutoApi模块API", + Description: "This is a sample server celler server.", + InfoInstanceName: "autoservice", + SwaggerTemplate: s.genSwaggerDocs(), + LeftDelim: "{{", + RightDelim: "}}", + } + swaggerDocs.Host = conf.ApiServer.HostAddr() + utils.KeepHasPrefix(conf.RoutePrefix, "/") + s.SwaggerOption = gin_http.SwaggerOption{ + Enable: conf.DocsEnable, + Name: swaggerDocs.InstanceName(), + Swagger: &swaggerDocs, + } + + s.WithName(conf.Name) + s.WithCors() //开启跨域设置 + + return s +} diff --git a/src/apps/autoapiapp/app.go b/src/apps/autoapiapp/app.go index bfb8c09..dc18dc5 100644 --- a/src/apps/autoapiapp/app.go +++ b/src/apps/autoapiapp/app.go @@ -1 +1,47 @@ package autoapiapp + +import ( + "gitee.com/captials-team/ubdframe/src/apps" + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "gitee.com/captials-team/ubdframe/src/pkg/uber_help" + "go.uber.org/dig" +) + +type App struct { + *apps.App + + l v1log.ILog + di *dig.Scope + ApiServer *ApiServer +} + +func NewApp(di *dig.Container, conf *configstc.AutoApiAppConfig, logger v1log.ILog) *App { + scope := di.Scope("admin") + + common.ErrPanic(scope.Provide(func() *dig.Scope { + return scope + }), uber_help.ErrAlreadyProvided) + common.ErrPanic(scope.Provide(func() *configstc.AutoApiAppConfig { + return conf + }), uber_help.ErrAlreadyProvided) + + app := &App{ + di: scope, + l: logger, + App: apps.NewApp(di, "admin_app"), + } + + app.WithModule(conf.ModuleMode) + + if conf.ApiServer.Enable { + common.ErrPanic(scope.Provide(NewApiServer)) + common.ErrPanic(scope.Invoke(func(api *ApiServer) { + app.WithApiServer(api) + app.ApiServer = api + })) + } + + return app +} diff --git a/src/apps/autoapiapp/app_test.go b/src/apps/autoapiapp/app_test.go new file mode 100644 index 0000000..04d0492 --- /dev/null +++ b/src/apps/autoapiapp/app_test.go @@ -0,0 +1,46 @@ +package autoapiapp + +import ( + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/tests" + "go.uber.org/dig" + "testing" + "time" +) + +func TestAppStart(t *testing.T) { + di := testDi(t) + common.ErrPanic(di.Provide(NewApp)) + + common.ErrPanic(di.Invoke(func(app *App) error { + return app.Start() + })) + + time.Sleep(time.Hour) +} + +func testDi(t *testing.T) *dig.Container { + di := tests.NewDi(t) + + common.ErrPanic(di.Provide(func() *configstc.AutoApiAppConfig { + return &configstc.AutoApiAppConfig{ + ApiServer: configstc.ServerConfig{ + Enable: true, + ServerBindAddr: "", + Port: 10000, + }, + DBConfig: configstc.DBConfig{ + DbHost: "192.168.149.128", + DbPort: "3306", + DbUser: "root", + DbPassword: "root", + DbName: "db_ubd_frame", + TablePrefix: "test_", + }, + DocsEnable: true, + } + })) + + return di +} -- Gitee From 44ba23ca8ff21c4df61955b49847cfefcd12a450 Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 7 Aug 2025 19:57:21 +0800 Subject: [PATCH 19/83] add swagger gen --- src/apps/autoapiapp/api_server.go | 155 +++++++++++++++++++++++++----- 1 file changed, 130 insertions(+), 25 deletions(-) diff --git a/src/apps/autoapiapp/api_server.go b/src/apps/autoapiapp/api_server.go index b6e88c6..7b2e05f 100644 --- a/src/apps/autoapiapp/api_server.go +++ b/src/apps/autoapiapp/api_server.go @@ -71,6 +71,7 @@ func (s *ApiServer) genSwaggerDocs() string { "application/json", "text/html", }, + Info: &spec.Info{}, Host: "{{.Host}}", BasePath: "{{.BasePath}}", } @@ -82,23 +83,30 @@ func (s *ApiServer) genSwaggerDocs() string { // paths := map[string]spec.PathItem{} + newSchema := func(ty string, desc string, example interface{}) spec.Schema { + return spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: desc, + Type: []string{ty}, + }, + ExtraProps: map[string]interface{}{ + "example": example, + }, + } + } + //组装path - paths["/mag/at/org/search"] = spec.PathItem{ + paths[s.conf.RoutePrefix+"/org/search"] = spec.PathItem{ Refable: spec.Refable{}, VendorExtensible: spec.VendorExtensible{}, PathItemProps: spec.PathItemProps{ Get: &spec.Operation{ OperationProps: spec.OperationProps{ - Description: "组织-搜索列表", - Summary: "组织搜索列表,根据搜索参数返回列表和分页信息", - Consumes: nil, - Produces: []string{"application/json"}, - Schemes: nil, - Tags: []string{"organize"}, - ExternalDocs: nil, - ID: "", - Deprecated: false, - Security: nil, + Summary: "组织-搜索列表", + Description: "组织搜索列表,根据搜索参数返回列表和分页信息", + Produces: []string{"application/json"}, + Schemes: nil, + Tags: []string{"organize"}, Parameters: []spec.Parameter{ { ParamProps: spec.ParamProps{ @@ -110,6 +118,11 @@ func (s *ApiServer) genSwaggerDocs() string { ExtraProps: map[string]interface{}{ "$ref": "#/definitions/reqdata.organize.SearchReqParam", }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: map[string]interface{}{ + "keyword": "", + }, + }, }, AllowEmptyValue: false, }, @@ -120,6 +133,21 @@ func (s *ApiServer) genSwaggerDocs() string { Default: nil, StatusCodeResponses: map[int]spec.Response{ 0: spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "", + Schema: &spec.Schema{ + VendorExtensible: spec.VendorExtensible{}, + SchemaProps: spec.SchemaProps{}, + SwaggerSchemaProps: spec.SwaggerSchemaProps{}, + ExtraProps: map[string]interface{}{ + "$ref": "#/definitions/reqdata.common.Response", + }, + }, + Headers: nil, + Examples: nil, + }, + }, + 200: spec.Response{ ResponseProps: spec.ResponseProps{ Description: "", Schema: &spec.Schema{ @@ -151,23 +179,111 @@ func (s *ApiServer) genSwaggerDocs() string { Type: []string{"string"}, Description: "关键字搜索", }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: "搜索关键字", + }, }, "page_no": spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"int"}, Description: "指定页码", }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: 1, + }, }, "page_size": spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"int"}, Description: "单页数量", }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: 20, + }, }, }, }, }, "reqdata.organize.SearchResponse": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "code": newSchema("int", "状态码", 1), + "msg": newSchema("string", "信息内容", "查询成功"), + "data": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Description: "数据", + Properties: map[string]spec.Schema{ + "list": { + SchemaProps: spec.SchemaProps{ + Description: "列表数据", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "数组子项内容", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": newSchema("int", "唯一id", 1), + "name": newSchema("string", "名称", 1), + }, + }, + }, + }, + }, + }, + "paginate": { + SchemaProps: spec.SchemaProps{ + Description: "分页数据", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "page_no": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "当前页码", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 1, + }, + }, + "page_size": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "单页数量", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 10, + }, + }, + "total": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "页码总数", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 7, + }, + }, + "count": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "数据总数", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 64, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "reqdata.common.Response": spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ @@ -176,6 +292,9 @@ func (s *ApiServer) genSwaggerDocs() string { Type: []string{"int"}, Description: "状态码", }, + ExtraProps: map[string]interface{}{ + "example": 1, + }, }, "msg": spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -190,20 +309,6 @@ func (s *ApiServer) genSwaggerDocs() string { SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Description: "数据", - Properties: map[string]spec.Schema{ - "list": { - SchemaProps: spec.SchemaProps{ - Description: "列表数据", - Type: []string{"array"}, - }, - }, - "paginate": { - SchemaProps: spec.SchemaProps{ - Description: "分页数据", - Type: []string{"object"}, - }, - }, - }, }, }, }, -- Gitee From 7b4f2a6f212628abf310cdd5a7fdb276c0cb7e58 Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 8 Aug 2025 17:50:03 +0800 Subject: [PATCH 20/83] add atapi --- src/domain/configstc/app.go | 6 +- src/pkg/atapi/{ => config}/config.go | 72 ++++- src/pkg/atapi/config/param.go | 9 + src/pkg/atapi/config/parse.go | 5 + src/pkg/atapi/dataexec/dataexec_mysql.go | 294 ++++++++++++++++++ src/pkg/atapi/dataexec/dataexec_test.go | 1 + src/pkg/atapi/dataexec_test.go | 68 ++++ src/pkg/atapi/facade.go | 1 - src/pkg/atapi/httpapi/v1/api.go | 65 ++++ src/pkg/atapi/httpapi/v1/const.go | 3 + .../atapi/httpapi/v1/plugins/api_search.go | 57 ++++ src/pkg/atapi/httpapi/v1/plugins/base.go | 18 ++ src/pkg/atapi/ifs/data_exec.go | 23 ++ src/pkg/atapi/ifs/http_api.go | 42 +++ src/pkg/atapi/ifs/plugin.go | 5 + src/pkg/atapi/manager.go | 148 +++++++++ src/pkg/atapi/manager_test.go | 40 +++ src/pkg/atapi/tests/v1/demo.yaml | 42 +++ src/pkg/atapi/tests/v1/org.yaml | 40 +++ 19 files changed, 927 insertions(+), 12 deletions(-) rename src/pkg/atapi/{ => config}/config.go (35%) create mode 100644 src/pkg/atapi/config/param.go create mode 100644 src/pkg/atapi/config/parse.go create mode 100644 src/pkg/atapi/dataexec/dataexec_mysql.go create mode 100644 src/pkg/atapi/dataexec/dataexec_test.go create mode 100644 src/pkg/atapi/dataexec_test.go delete mode 100644 src/pkg/atapi/facade.go create mode 100644 src/pkg/atapi/httpapi/v1/api.go create mode 100644 src/pkg/atapi/httpapi/v1/const.go create mode 100644 src/pkg/atapi/httpapi/v1/plugins/api_search.go create mode 100644 src/pkg/atapi/httpapi/v1/plugins/base.go create mode 100644 src/pkg/atapi/ifs/data_exec.go create mode 100644 src/pkg/atapi/ifs/http_api.go create mode 100644 src/pkg/atapi/ifs/plugin.go create mode 100644 src/pkg/atapi/manager.go create mode 100644 src/pkg/atapi/manager_test.go create mode 100644 src/pkg/atapi/tests/v1/demo.yaml create mode 100644 src/pkg/atapi/tests/v1/org.yaml diff --git a/src/domain/configstc/app.go b/src/domain/configstc/app.go index 497e51a..a08f777 100644 --- a/src/domain/configstc/app.go +++ b/src/domain/configstc/app.go @@ -1,6 +1,8 @@ package configstc -import "gitee.com/captials-team/ubdframe/src/pkg/atapi" +import ( + atapiConfig "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" +) type UserAppConfig struct { Name string `json:"Name" yaml:"Name"` @@ -362,5 +364,5 @@ type AutoApiAppConfig struct { ModuleMode bool `json:"ModuleMode" yaml:"ModuleMode"` //模块模式,作为module使用 - Apis []atapi.AutoApiDefined `json:"Apis" yaml:"Apis"` //自定义API列表 + Apis []atapiConfig.AutoApiConfig `json:"Apis" yaml:"Apis"` //自定义API列表 } diff --git a/src/pkg/atapi/config.go b/src/pkg/atapi/config/config.go similarity index 35% rename from src/pkg/atapi/config.go rename to src/pkg/atapi/config/config.go index e03c927..bb428fd 100644 --- a/src/pkg/atapi/config.go +++ b/src/pkg/atapi/config/config.go @@ -1,28 +1,47 @@ -package atapi +package config -import "gitee.com/captials-team/ubdframe/src/pkg/extendparams" +import ( + "fmt" + "gitee.com/captials-team/ubdframe/src/pkg/extendparams" + "go/types" + "strings" +) + +type AutoApiConfig struct { + Apis []AutoApiDefined `json:"Apis" yaml:"Apis"` + ExtendParams extendparams.CommonExtendParams `json:"ExtendParams" yaml:"ExtendParams"` //扩展参数 +} // AutoApiDefined 单个autoApi的定义 type AutoApiDefined struct { Name string `json:"Name" yaml:"Name"` //api总称 ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) + Version string `json:"Version" yaml:"Version"` //版本,v1/v2 这样,为空则默认使用最新版本 LoadFile string `json:"LoadFile" yaml:"LoadFile"` //加载文件路径,有路径值则从该路径加载覆盖当前AutoApi - Fields []AutoApiFieldDefined `json:"Fields" yaml:"Fields"` //字段,定义该api会用到的字段,也会用于数据存储的表字段 - Plugins []AutoApiPluginDefined `json:"Plugins" yaml:"Plugins"` //加载使用的插件列表 + Actions []AutoApiActionDefined `json:"Actions" yaml:"Actions"` //使用的插件列表 + + AutoApiDataDefined `yaml:",inline"` Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 } +type AutoApiDataDefined struct { + //结构 + AutoTable bool `yaml:"AutoTable"` //是否自动建表,true:自动检查并创建表,false:不检查 + DataName string `yaml:"DataName"` //数据名称,用于给数据区分存储来源,如表名 + Fields []AutoApiFieldDefined `yaml:"Fields"` +} + // AutoApiFieldDefined 单个autoApi字段的定义 type AutoApiFieldDefined struct { Name string `json:"Name" yaml:"Name"` //字段名,如:name ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) //类型, 可为: int,int64,string, - Type string `json:"Type" yaml:"Type"` - Size int `json:"Size" yaml:"Size"` //大小,配合Type,可定义类型的长度 + SqlType string `json:"SqlType" yaml:"SqlType"` + //Size int `json:"Size" yaml:"Size"` //大小,配合SqlType,可定义类型的长度 Default string `json:"Default" yaml:"Default"` //默认值 Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 @@ -30,9 +49,44 @@ type AutoApiFieldDefined struct { ExtendParams extendparams.CommonExtendParams `json:"ExtendParams" yaml:"ExtendParams"` //扩展参数 } -// AutoApiPluginDefined 单个autoApi使用插件的定义 -type AutoApiPluginDefined struct { - Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 +func (f *AutoApiFieldDefined) CreateSql() string { + def := "" + if f.Default != "" { + switch f.Type() { + case types.String: + def = fmt.Sprintf("default '%s'", f.Default) + default: + def = "default " + f.Default + } + } + return fmt.Sprintf("`%s` %s %s comment '%s'", f.Name, f.SqlType, def, f.Comment) +} + +func (f *AutoApiFieldDefined) Type() types.BasicKind { + a := f.SqlType + if strings.HasPrefix(a, "varchar") { + return types.String + } else if strings.HasPrefix(a, "int") { + return types.Int64 + } else if strings.HasPrefix(a, "tinyint") { + return types.Int8 + } + + return types.String +} + +// AutoApiActionDefined 单个autoApi使用插件的定义 +type AutoApiActionDefined struct { + Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 + Describe string `json:"Describe" yaml:"Describe"` //插件名称,如:API_SEARCH,必须为注册的插件 + Enable bool `json:"Enable" yaml:"Enable"` //是否启用 + Plugins []AutoApiActionPluginDefined `json:"Plugins" yaml:"Plugins"` //扩展参数 +} + +// AutoApiActionPluginDefined 单个autoApi使用插件的定义 +type AutoApiActionPluginDefined struct { Enable bool `json:"Enable" yaml:"Enable"` //是否启用 + Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 + Describe string `json:"Describe" yaml:"Describe"` //插件名称,如:API_SEARCH,必须为注册的插件 ExtendParams extendparams.CommonExtendParams `json:"ExtendParams" yaml:"ExtendParams"` //扩展参数 } diff --git a/src/pkg/atapi/config/param.go b/src/pkg/atapi/config/param.go new file mode 100644 index 0000000..edf9b4b --- /dev/null +++ b/src/pkg/atapi/config/param.go @@ -0,0 +1,9 @@ +package config + +import "github.com/gin-gonic/gin" + +type GinHandleParam struct { + Method string + Path string + HandlerFunc []gin.HandlerFunc +} diff --git a/src/pkg/atapi/config/parse.go b/src/pkg/atapi/config/parse.go new file mode 100644 index 0000000..0f34038 --- /dev/null +++ b/src/pkg/atapi/config/parse.go @@ -0,0 +1,5 @@ +package config + +func ParseConfig() { + +} diff --git a/src/pkg/atapi/dataexec/dataexec_mysql.go b/src/pkg/atapi/dataexec/dataexec_mysql.go new file mode 100644 index 0000000..a5aa0b5 --- /dev/null +++ b/src/pkg/atapi/dataexec/dataexec_mysql.go @@ -0,0 +1,294 @@ +package dataexec + +import ( + "fmt" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/infrastructure/clients/mysql" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "gorm.io/gorm" + "strings" + "time" +) + +type MysqlDbExec struct { + db *gorm.DB + name string + tbName string +} + +func (exec *MysqlDbExec) DoSearch(conditions map[string]interface{}, fields []string, page, size int64) ([]map[string]interface{}, int64, error) { + //fmt.Printf("search params: %+v ,%+v ,%d ,%d\n", params, fields, page, size) + //fields = append(fields, "id", "created_at", "updated_at") + if conditions == nil { + conditions = map[string]interface{}{} + } + fields = append(fields, "id") + where := []string{ + "deleted_uid=0", + } + whereArgs := []interface{}{} + for k := range conditions { + v := conditions[k] + where = append(where, k) + whereArgs = append(whereArgs, v) + } + orderBy := []string{ + "id desc", + } + var list []map[string]interface{} + + newScan := func() ([]interface{}, []interface{}) { + arr := make([]interface{}, len(fields)) + arr1 := make([]interface{}, len(fields)) + for i := range fields { + arr1[i] = &arr[i] + } + return arr, arr1 + } + + toRetRow := func(arr []interface{}) map[string]interface{} { + m := map[string]interface{}{} + + for i := range arr { + v := arr[i] + var value interface{} + switch v.(type) { + case string: + value = v.(string) + case []byte: + value = string(v.([]byte)) + case int64: + value = v.(int64) + default: + value = v + } + m[fields[i]] = value + } + return m + } + + whereStr := "" + if len(where) > 0 { + whereStr = "WHERE " + strings.Join(where, " AND ") + } + var total int64 + countSql := fmt.Sprintf("SELECT COUNT(*) FROM %s %s LIMIT 1", exec.tbName, whereStr) + + ret := exec.db.Raw(countSql, whereArgs...).Scan(&total) + if ret.Error != nil { + return nil, 0, ret.Error + } + + if len(fields) <= 0 { + fields = append(fields, "*") + } + orderByStr := strings.Join(orderBy, ",") + if orderByStr != "" { + orderByStr = "ORDER BY " + orderByStr + } + whereArgs = append(whereArgs, size) + whereArgs = append(whereArgs, size*(page-1)) + dataSql := fmt.Sprintf("SELECT %s FROM %s %s %s LIMIT ? OFFSET ?", strings.Join(fields, ","), exec.tbName, whereStr, orderByStr) + + rows, err := exec.db.Raw(dataSql, whereArgs...).Rows() + if err != nil { + return nil, 0, err + } + + for rows.Next() { + arr, scans := newScan() + err := rows.Scan(scans...) + if err != nil { + return list, 0, err + } + + list = append(list, toRetRow(arr)) + } + + return list, total, err +} + +func (exec *MysqlDbExec) DoQuery(conditions map[string]interface{}, fields []string) (map[string]interface{}, error) { + //fmt.Printf("query params: %s, %+v, %+v\n", exec.tbName, conditions, fields) + fields = append(fields, "id") + where := []string{ + "deleted_uid=0", + } + whereArgs := []interface{}{} + for k, v := range conditions { + where = append(where, fmt.Sprintf("%s=?", k)) + whereArgs = append(whereArgs, v) + } + var fieldArr = []string{} + var scanArr = []interface{}{} + + m := map[string]interface{}{} + arr := make([]interface{}, len(fields)) + for i, v := range fields { + m[v] = &arr[i] + + fieldArr = append(fieldArr, fmt.Sprintf("`%s`", v)) + scanArr = append(scanArr, &arr[i]) + } + + whereStr := "" + orderByStr := "" + if len(where) > 0 { + whereStr = "WHERE " + strings.Join(where, " AND ") + } + fieldStr := strings.Join(fieldArr, ",") + + selectSql := fmt.Sprintf("SELECT %s FROM %s %s %s LIMIT 1", + fieldStr, exec.tbName, whereStr, orderByStr) + + ret := exec.db.Raw(selectSql, whereArgs...).Scan(&m) + + if ret.Error != nil { + return nil, ret.Error + } + + return m, nil +} + +func (exec *MysqlDbExec) DoCreate(params map[string]interface{}) (int64, error) { + var fieldArr = []string{} + var valueArr = []interface{}{} + for k, v := range params { + fieldArr = append(fieldArr, fmt.Sprintf("`%s`=?", k)) + valueArr = append(valueArr, v) + } + + fieldStr := strings.Join(fieldArr, ",") + + insertSql := fmt.Sprintf("INSERT INTO %s SET %s", exec.tbName, fieldStr) + ret := exec.db.Exec(insertSql, valueArr...) + + if ret.Error != nil { + return 0, ret.Error + } + return ret.RowsAffected, nil +} + +func (exec *MysqlDbExec) DoUpdate(id int64, params map[string]interface{}) (int64, error) { + //fmt.Printf("update params: %d, %+v\n", id, params) + where := []string{ + "id=?", + "deleted_uid=0", + } + whereArgs := []interface{}{ + id, + } + params["updated_at"] = time.Now().UnixMilli() + + var fieldArr = []string{} + var valueArr = []interface{}{} + for k, v := range params { + fieldArr = append(fieldArr, fmt.Sprintf("`%s`=?", k)) + valueArr = append(valueArr, v) + } + + fieldStr := strings.Join(fieldArr, ",") + + whereStr := "" + if len(where) > 0 { + whereStr = "WHERE " + strings.Join(where, " AND ") + } + + if len(whereArgs) > 0 { + for _, v := range whereArgs { + valueArr = append(valueArr, v) + } + } + + updateSql := fmt.Sprintf("UPDATE %s SET %s %s", exec.tbName, fieldStr, whereStr) + ret := exec.db.Exec(updateSql, valueArr...) + + if ret.Error != nil { + return 0, ret.Error + } + + return ret.RowsAffected, nil +} + +func (exec *MysqlDbExec) DoDelete(id int64) (int64, error) { + return exec.DoUpdate(id, map[string]interface{}{ + "deleted_uid": 1, + "deleted_at": time.Now().UnixMilli(), + }) +} + +func (exec *MysqlDbExec) Copy() ifs.IAutoApiDataExec { + return &MysqlDbExec{ + db: exec.db, + } +} + +func (exec *MysqlDbExec) InitDB(name string, c config.AutoApiDataDefined) error { + return exec.initV1(name, c) +} + +func (exec *MysqlDbExec) initV1(name string, config config.AutoApiDataDefined) error { + exec.name = name + exec.tbName = exec.genTableName(config) + //log.Printf("data init: %s, %+v",exec.name,exec.tbName) + + //自动初始化表 + if config.AutoTable { + ret := exec.db.Exec(exec.tableSql(config)) + return ret.Error + } + + return nil +} + +func (exec *MysqlDbExec) TableName() string { + return exec.tbName +} + +func (exec *MysqlDbExec) genTableName(config config.AutoApiDataDefined) string { + name := config.DataName + if name == "" { + name = exec.name + } + return "atapi_" + name +} + +func (exec *MysqlDbExec) tableSql(config config.AutoApiDataDefined) string { + tableName := exec.tbName + tableComment := "" + + fields := []string{} + + for _, f := range config.Fields { + fields = append(fields, + f.CreateSql(), + ) + } + + sql := ` +CREATE TABLE IF NOT EXISTS %s +( + id int unsigned auto_increment, + created_at datetime default current_timestamp null comment 'create time', + updated_at datetime default null comment 'update time', + deleted_at datetime default null comment 'delete time', + deleted_uid int unsigned default 0, + + %s, + + PRIMARY KEY (id) +) +COMMENT '%s'; +` + return fmt.Sprintf(sql, tableName, + strings.Join(fields, ",\n"), + tableComment) +} + +func NewMysqlDbExec(c configstc.DBConfig) (*MysqlDbExec, error) { + db := mysql.NewGormDB(c) + return &MysqlDbExec{ + db: db, + }, nil +} diff --git a/src/pkg/atapi/dataexec/dataexec_test.go b/src/pkg/atapi/dataexec/dataexec_test.go new file mode 100644 index 0000000..202a22d --- /dev/null +++ b/src/pkg/atapi/dataexec/dataexec_test.go @@ -0,0 +1 @@ +package dataexec diff --git a/src/pkg/atapi/dataexec_test.go b/src/pkg/atapi/dataexec_test.go new file mode 100644 index 0000000..b11d47b --- /dev/null +++ b/src/pkg/atapi/dataexec_test.go @@ -0,0 +1,68 @@ +package atapi + +import ( + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/dataexec" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMysqlDbExec(t *testing.T) { + exec, err := dataexec.NewMysqlDbExec(configstc.DBConfig{ + DbHost: "192.168.149.128", + DbPort: "3306", + DbUser: "root", + DbPassword: "root", + DbName: "db_ubd_frame", + TablePrefix: "test_", + }) + assert.Equal(t, err, nil) + + err = exec.InitDB("tttt", testDataExecConfig()) + assert.Equal(t, nil, err) + + doTestDBExec(t, exec) +} + +func doTestDBExec(t *testing.T, exec ifs.IAutoApiDataExec) { + + list, total, err := exec.DoSearch(nil, nil, 1, 10) + assert.Equal(t, nil, err) + t.Logf("Count=%d", total) + t.Logf("List=%+v", list) + + lastId, err := exec.DoCreate(map[string]interface{}{ + "name": "测试", + "status": 1, + }) + assert.Equal(t, nil, err) + + t.Logf("Id=%d", lastId) +} + +func testDataExecConfig() config.AutoApiDataDefined { + return config.AutoApiDataDefined{ + AutoTable: true, + DataName: "", + Fields: []config.AutoApiFieldDefined{ + { + Name: "name", + ShowName: "名称", + SqlType: "varchar(32)", + Default: "", + Comment: "", + ExtendParams: nil, + }, + { + Name: "status", + ShowName: "状态", + SqlType: "int", + Default: "", + Comment: "", + ExtendParams: nil, + }, + }, + } +} diff --git a/src/pkg/atapi/facade.go b/src/pkg/atapi/facade.go deleted file mode 100644 index 468db82..0000000 --- a/src/pkg/atapi/facade.go +++ /dev/null @@ -1 +0,0 @@ -package atapi diff --git a/src/pkg/atapi/httpapi/v1/api.go b/src/pkg/atapi/httpapi/v1/api.go new file mode 100644 index 0000000..4e77bd7 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/api.go @@ -0,0 +1,65 @@ +package v1 + +import ( + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "gitee.com/captials-team/ubdframe/src/pkg/extendparams" +) + +func NewAutoApi(c config.AutoApiDefined, extends map[string]string) (*AutoApi, error) { + return &AutoApi{ + c: c, + extends: extends, + }, nil +} + +type AutoApi struct { + c config.AutoApiDefined + extends extendparams.CommonExtendParams + + dataExec ifs.IAutoApiDataExec //数据处理器 + supportPlugins []ifs.IApiPlugin //支持的插件列表 +} + +func (a *AutoApi) SetDataExec(exec ifs.IAutoApiDataExec) { + a.dataExec = exec +} + +func (a *AutoApi) GetDataExec() ifs.IAutoApiDataExec { + return a.dataExec +} + +func (a *AutoApi) SetVars(m map[string]string) { + //TODO implement me + panic("implement me") +} + +func (a *AutoApi) GetVars() map[string]string { + //TODO implement me + panic("implement me") +} + +func (a *AutoApi) AddPlugin(arr ...ifs.IApiPlugin) { + a.supportPlugins = append(a.supportPlugins, arr...) +} + +func (a *AutoApi) Config() config.AutoApiDefined { + return a.c +} + +func (a *AutoApi) Code() string { + return a.c.Name +} + +func (a *AutoApi) ShowName() string { + return a.c.ShowName +} + +func (a *AutoApi) Init() error { + a.dataExec.InitDB(a.c.Name, a.c.AutoApiDataDefined) + return nil +} + +func (a *AutoApi) Handlers() []config.GinHandleParam { + return []config.GinHandleParam{} +} diff --git a/src/pkg/atapi/httpapi/v1/const.go b/src/pkg/atapi/httpapi/v1/const.go new file mode 100644 index 0000000..4b31b82 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/const.go @@ -0,0 +1,3 @@ +package v1 + +const Version = "1" diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go new file mode 100644 index 0000000..4e8e318 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -0,0 +1,57 @@ +package plugins + +import ( + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "strings" +) + +type ApiSearchAction struct { + //请求参数(可搜索条件字段) + matchSearch []string //匹配查询参数 + fuzzySearch []string // + orderRules []string + + //返回字段 + respFields []string + + //分页用参数 + pageParam string + sizeParam string +} + +func NewApiSearchAction(m map[string]string) (*ApiSearchAction, error) { + //初始化 + p := &ApiSearchAction{ + respFields: strings.Split(m[ResponseFieldsParamsKey], ","), + matchSearch: strings.Split(m[SearchFieldsParamsKey], ","), + fuzzySearch: strings.Split(m[SearchFuzzyFieldsParamsKey], ","), + orderRules: strings.Split(m[OrderRulesParamKey], ","), + //默认值 + pageParam: "_PageNum", + sizeParam: "_PageSize", + } + + if m[PageNumParamsKey] != "" { + p.pageParam = m[PageNumParamsKey] + } + + if m[PageSizeParamsKey] != "" { + p.sizeParam = m[PageSizeParamsKey] + } + + return p, nil +} + +func (p *ApiSearchAction) New(m map[string]string) (ifs.IApiPlugin, error) { + return NewApiSearchAction(m) +} + +func (p *ApiSearchAction) Name() string { + return "API_SEARCH" +} + +func (p *ApiSearchAction) Describe() string { + return ` +提供查询查询数据列表的能力,可以自定义查询过滤条件,包括完全匹配查询字段和模糊匹配查询字段 +` +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/base.go b/src/pkg/atapi/httpapi/v1/plugins/base.go new file mode 100644 index 0000000..41e3fe6 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/plugins/base.go @@ -0,0 +1,18 @@ +package plugins + +const ( + SecretKey = "SECRET" //秘钥配置字段 + + SearchFieldsParamsKey = "SEARCH_FIELDS" //完全匹配搜索字段列表配置 + SearchFuzzyFieldsParamsKey = "SEARCH_FUZZY_FIELDS" //模糊搜索搜索字段列表 + OrderRulesParamKey = "ORDER_RULES" //排序规则参数配置 + + PageNumParamsKey = "PAGE_NUM_PARAM" //分页-页码参数名 + PageSizeParamsKey = "PAGE_SIZE_PARAM" //分页-单页数量参数名 + ResponseFieldsParamsKey = "RESPONSE_FIELDS" //响应返回字段列表 + SubmitFieldsParamsKey = "SUBMIT_FIELDS" //提交参数字段列表 + IdFieldKey = "ID_FIELD" //id字段(用于指定数据id) + SaasIdKey = "SAAS_ID_FIELD" //saas_id字段 + + RateLimitKey = "LIMIT_RATE" //每秒限制速率 +) diff --git a/src/pkg/atapi/ifs/data_exec.go b/src/pkg/atapi/ifs/data_exec.go new file mode 100644 index 0000000..a646b02 --- /dev/null +++ b/src/pkg/atapi/ifs/data_exec.go @@ -0,0 +1,23 @@ +package ifs + +import "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + +// IAutoApiDataExec 自定义的数据执行器 +type IAutoApiDataExec interface { + IDataExecBase + IDataExecCURD +} + +type IDataExecBase interface { + TableName() string + InitDB(name string, c config.AutoApiDataDefined) error + Copy() IAutoApiDataExec +} + +type IDataExecCURD interface { + DoSearch(params map[string]interface{}, fields []string, page, size int64) ([]map[string]interface{}, int64, error) + DoQuery(params map[string]interface{}, fields []string) (map[string]interface{}, error) + DoCreate(params map[string]interface{}) (int64, error) + DoUpdate(id int64, params map[string]interface{}) (int64, error) + DoDelete(id int64) (int64, error) +} diff --git a/src/pkg/atapi/ifs/http_api.go b/src/pkg/atapi/ifs/http_api.go new file mode 100644 index 0000000..88c369e --- /dev/null +++ b/src/pkg/atapi/ifs/http_api.go @@ -0,0 +1,42 @@ +package ifs + +import ( + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" +) + +type IAutoApi interface { + IDataExec + IVars + + Config() config.AutoApiDefined + Code() string //唯一code + ShowName() string //展示名称 + Init() error //初始化 + + //InitHttp() error //初始化http api + // + //ApiEntry(request *http.Request, action string, params map[string]interface{}) (interface{}, error) //api-入口 + // + //Docs(req *http.Request) string //api文档 + // + //Configuration() string //序列化配置 + // + //LoadedProcesses() []IProcess //返回加载后的process处理器数组 + //LoadedActions() []IApiAction //返回加载后的process处理器数组 +} + +type IAutoHttpApi interface { + Handlers() []config.GinHandleParam +} + +// IDataExec dataExec操作 +type IDataExec interface { + SetDataExec(IAutoApiDataExec) //设置数据处理器-负责初始化数据库,操作数据CURD的相关操作 + GetDataExec() IAutoApiDataExec +} + +// IVars 共享变量操作 +type IVars interface { + SetVars(map[string]string) //设置可替换变量值 + GetVars() map[string]string +} diff --git a/src/pkg/atapi/ifs/plugin.go b/src/pkg/atapi/ifs/plugin.go new file mode 100644 index 0000000..c6f0d60 --- /dev/null +++ b/src/pkg/atapi/ifs/plugin.go @@ -0,0 +1,5 @@ +package ifs + +type IApiPlugin interface { + Name() string //plugin名称 +} diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go new file mode 100644 index 0000000..7c13e5b --- /dev/null +++ b/src/pkg/atapi/manager.go @@ -0,0 +1,148 @@ +package atapi + +import ( + "fmt" + "gitee.com/captials-team/ubdframe/src/common/utils" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + v1 "gitee.com/captials-team/ubdframe/src/pkg/atapi/httpapi/v1" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/httpapi/v1/plugins" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "gitee.com/captials-team/ubdframe/src/pkg/codec" + logv1 "gitee.com/captials-team/ubdframe/src/pkg/logs" + "github.com/gin-gonic/gin" + "io/ioutil" + "strings" +) + +// AtApisManager at apis管理器 +type AtApisManager struct { + //配置 + config *config.AutoApiConfig + + //数据存储器 + store ifs.IAutoApiDataExec + + //格式化器 + format *codec.JsonCodec + + //所有atapis + httpApis map[string]ifs.IAutoApi + + logv1.InvokeLog +} + +func NewAtApisManager(config *config.AutoApiConfig, opts ...interface{}) *AtApisManager { + mag := &AtApisManager{ + httpApis: map[string]ifs.IAutoApi{}, + config: config, + } + + for _, v := range opts { + if tmp, ok := v.(ifs.IAutoApiDataExec); ok { + mag.store = tmp + } + if tmp, ok := v.(logv1.ILog); ok { + mag.AddLogger(tmp) + } + } + mag.format = &codec.JsonCodec{} + return mag +} + +func (mag *AtApisManager) SetDataExec(store ifs.IAutoApiDataExec) { + mag.store = store +} + +func (mag *AtApisManager) Initial() error { + for _, def := range mag.config.Apis { + var api config.AutoApiDefined = def + + if def.LoadFile != "" { + tmp, err := mag.readApiPath(def.LoadFile) + if err != nil { + return err + } + api = tmp + } + + //基本信息 + mag.Info("=====> API: %s-%s, Version: %s", api.Name, api.ShowName, api.Version) + apiHandler, err := mag.newAutoApi(api) + if err != nil { + mag.Error("at_api init fail: %s,%s", apiHandler.Code(), err) + continue + } + mag.httpApis[apiHandler.Code()] = apiHandler + mag.Info("%s loaded", api.Name) + } + return nil +} + +func (mag *AtApisManager) StartByGin(g gin.IRouter) error { + for _, v := range mag.httpApis { + a, ok := v.(ifs.IAutoHttpApi) + if ok { + handlers := a.Handlers() + for _, h := range handlers { + g.Handle(h.Method, h.Path, h.HandlerFunc...) + mag.Info("register router: %s,%s", h.Method, h.Path) + } + } + } + return nil +} + +func (mag *AtApisManager) newAutoApi(api config.AutoApiDefined) (ifs.IAutoApi, error) { + switch strings.ToLower(api.Version) { + case v1.Version: + return mag.newAutoApiV1(api) + default: + return mag.newAutoApiV1(api) + } +} + +func (mag *AtApisManager) newAutoApiV1(apiDef config.AutoApiDefined) (*v1.AutoApi, error) { + api, err := v1.NewAutoApi(apiDef, mag.config.ExtendParams) + if err != nil { + return nil, fmt.Errorf("at_api new fail: %s", err) + } + api.SetDataExec(mag.store.Copy()) + //api.SetVars(vars) + api.AddPlugin() + api.AddPlugin( + &plugins.ApiSearchAction{}, + ) + + if err := api.Init(); err != nil { + return nil, err + } + mag.Info("[%s] table: %s ", api.Code(), api.GetDataExec().TableName()) + + return api, nil +} + +func (mag *AtApisManager) readApiPath(path string) (config.AutoApiDefined, error) { + vars := mag.config.ExtendParams + var c config.AutoApiDefined + + content, err := ioutil.ReadFile(path) + if err != nil { + return c, err + } + + //替换变量 + if vars != nil { + s := string(content) + for k, v := range vars { + s = strings.Replace(s, fmt.Sprintf("{{%s}}", k), v, -1) + } + content = []byte(s) + } + + err = utils.ReadYamlFile(&c, path) + if err != nil { + return c, err + } + + return c, nil +} diff --git a/src/pkg/atapi/manager_test.go b/src/pkg/atapi/manager_test.go new file mode 100644 index 0000000..2719e48 --- /dev/null +++ b/src/pkg/atapi/manager_test.go @@ -0,0 +1,40 @@ +package atapi + +import ( + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/common/utils" + "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/dataexec" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestNewManager(t *testing.T) { + c := config.AutoApiConfig{ + Apis: []config.AutoApiDefined{ + {LoadFile: utils.AbsPath(1) + "./tests/v1/org.yaml"}, + {LoadFile: utils.AbsPath(1) + "./tests/v1/demo.yaml"}, + }, + } + g := gin.New() + dataEx, err := dataexec.NewMysqlDbExec(configstc.DBConfig{ + DbHost: "192.168.149.128", + DbPort: "3306", + DbUser: "root", + DbPassword: "root", + DbName: "db_ubd_frame", + TablePrefix: "test_", + }) + assert.Equal(t, err, nil) + + mag := NewAtApisManager(&c) + mag.SetDataExec(dataEx) + common.ErrPanic(mag.Initial()) + common.ErrPanic(mag.StartByGin(g)) + + common.ErrPanic(g.Run(":10000")) + time.Sleep(time.Hour) +} diff --git a/src/pkg/atapi/tests/v1/demo.yaml b/src/pkg/atapi/tests/v1/demo.yaml new file mode 100644 index 0000000..d617e5d --- /dev/null +++ b/src/pkg/atapi/tests/v1/demo.yaml @@ -0,0 +1,42 @@ +#这里是有全部功能配置的配置文件,供参考配置 +Version: "1" +Name: demo +ShowName: "DEMO" + +# 定义处理器(可以理解为API),有以下配置: +# Name,名称(将会使用在api path构成中),每一项为一个action +# Plugins,定义使用的插件 +Actions: + - Name: "搜索接口" + Describe: "搜索接口" + Plugins: + - Name: "API_SEARCH" + ExtendParam: + fields: "id,name,pid,status" + - Name: "RATE_LIMIT" + ExtendParam: + limit_rate: 2 + +#是否自动创建表数据等信息 +AutoTable: true +DataName: "" #自定义数据存储的标识名称 +Fields: + - Name: title + SqlType: varchar(64) + Default: "" + Comment: 标题 + - Name: type + SqlType: tinyint unsigned + Default: "1" + Comment: "类型,0:无,1:文本,2:图片" + - Name: content + SqlType: varchar(1000) + Default: "1" + Comment: 块内容 + - Name: status + SqlType: tinyint unsigned + Default: "1" + Comment: 状态,0:无,1:有效,2:禁用 +Comment: DEMO管理 + + diff --git a/src/pkg/atapi/tests/v1/org.yaml b/src/pkg/atapi/tests/v1/org.yaml new file mode 100644 index 0000000..efa27f9 --- /dev/null +++ b/src/pkg/atapi/tests/v1/org.yaml @@ -0,0 +1,40 @@ +#这里是有全部功能配置的配置文件,供参考配置 +Version: "1" +Name: org +ShowName: "组织" + +# 定义处理器(可以理解为API),有以下配置: +# Name,名称(将会使用在api path构成中),每一项为一个action +# Plugins,定义使用的插件 +Actions: + - Name: "搜索接口" + Describe: "搜索接口" + Enable: true + Plugins: + - Name: "API_SEARCH" + ExtendParam: + fields: "id,name,pid,status" + - Name: "RATE_LIMIT" + ExtendParam: + limit_rate: 2 + +AutoTable: true +DataName: "" +Fields: + - Name: saas_id + SqlType: varchar(20) + Default: "" + Comment: saas model + - Name: name + SqlType: varchar(64) + Default: "未命名" + Comment: 组织名 + - Name: pid + SqlType: int unsigned + Default: "0" + Comment: pid + - Name: status + SqlType: tinyint unsigned + Default: "1" + Comment: 状态,0:无,1:有效,2:禁用 +Comment: 组织管理 \ No newline at end of file -- Gitee From 9d7f346c909d2af4ead2c9c601252412eed2b31a Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 11 Aug 2025 18:06:16 +0800 Subject: [PATCH 21/83] add plugin name --- src/pkg/atapi/config/config.go | 10 +-- src/pkg/atapi/config/param.go | 11 +++- src/pkg/atapi/httpapi/v1/api.go | 51 ++++++++++++++- .../atapi/httpapi/v1/plugins/api_response.go | 63 +++++++++++++++++++ .../atapi/httpapi/v1/plugins/api_search.go | 31 +++++++-- src/pkg/atapi/httpapi/v1/plugins/base.go | 6 ++ .../atapi/httpapi/v1/plugins/rate_limit.go | 34 ++++++++++ src/pkg/atapi/ifs/http_api.go | 2 - src/pkg/atapi/ifs/plugin.go | 32 +++++++++- src/pkg/atapi/manager.go | 11 ++-- src/pkg/atapi/manager_test.go | 1 + src/pkg/atapi/tests/v1/demo.yaml | 10 ++- 12 files changed, 238 insertions(+), 24 deletions(-) create mode 100644 src/pkg/atapi/httpapi/v1/plugins/api_response.go create mode 100644 src/pkg/atapi/httpapi/v1/plugins/rate_limit.go diff --git a/src/pkg/atapi/config/config.go b/src/pkg/atapi/config/config.go index bb428fd..e67f76e 100644 --- a/src/pkg/atapi/config/config.go +++ b/src/pkg/atapi/config/config.go @@ -22,7 +22,7 @@ type AutoApiDefined struct { Actions []AutoApiActionDefined `json:"Actions" yaml:"Actions"` //使用的插件列表 - AutoApiDataDefined `yaml:",inline"` + Data AutoApiDataDefined `json:"Data" yaml:"Data"` //数据定义 Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 } @@ -77,16 +77,16 @@ func (f *AutoApiFieldDefined) Type() types.BasicKind { // AutoApiActionDefined 单个autoApi使用插件的定义 type AutoApiActionDefined struct { - Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 - Describe string `json:"Describe" yaml:"Describe"` //插件名称,如:API_SEARCH,必须为注册的插件 + Name string `json:"Name" yaml:"Name"` //api操作名称 + Describe string `json:"Describe" yaml:"Describe"` //api操作具体描述内容 Enable bool `json:"Enable" yaml:"Enable"` //是否启用 - Plugins []AutoApiActionPluginDefined `json:"Plugins" yaml:"Plugins"` //扩展参数 + Plugins []AutoApiActionPluginDefined `json:"Plugins" yaml:"Plugins"` //该api操作的插件列表 } // AutoApiActionPluginDefined 单个autoApi使用插件的定义 type AutoApiActionPluginDefined struct { Enable bool `json:"Enable" yaml:"Enable"` //是否启用 Name string `json:"Name" yaml:"Name"` //插件名称,如:API_SEARCH,必须为注册的插件 - Describe string `json:"Describe" yaml:"Describe"` //插件名称,如:API_SEARCH,必须为注册的插件 ExtendParams extendparams.CommonExtendParams `json:"ExtendParams" yaml:"ExtendParams"` //扩展参数 + Remark string `json:"Remark" yaml:"Remark"` //备注,如使用的插件用途的具体描述 } diff --git a/src/pkg/atapi/config/param.go b/src/pkg/atapi/config/param.go index edf9b4b..9acc85c 100644 --- a/src/pkg/atapi/config/param.go +++ b/src/pkg/atapi/config/param.go @@ -1,9 +1,18 @@ package config -import "github.com/gin-gonic/gin" +import ( + "context" + "github.com/gin-gonic/gin" +) type GinHandleParam struct { Method string Path string HandlerFunc []gin.HandlerFunc } + +// PluginParam 插件使用的参数合集 +type PluginParam struct { + Ctx context.Context + Api AutoApiDefined +} diff --git a/src/pkg/atapi/httpapi/v1/api.go b/src/pkg/atapi/httpapi/v1/api.go index 4e77bd7..afce8e7 100644 --- a/src/pkg/atapi/httpapi/v1/api.go +++ b/src/pkg/atapi/httpapi/v1/api.go @@ -4,6 +4,7 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" "gitee.com/captials-team/ubdframe/src/pkg/extendparams" + "gitee.com/captials-team/ubdframe/src/pkg/logs" ) func NewAutoApi(c config.AutoApiDefined, extends map[string]string) (*AutoApi, error) { @@ -19,6 +20,8 @@ type AutoApi struct { dataExec ifs.IAutoApiDataExec //数据处理器 supportPlugins []ifs.IApiPlugin //支持的插件列表 + + actions []actionData } func (a *AutoApi) SetDataExec(exec ifs.IAutoApiDataExec) { @@ -56,10 +59,56 @@ func (a *AutoApi) ShowName() string { } func (a *AutoApi) Init() error { - a.dataExec.InitDB(a.c.Name, a.c.AutoApiDataDefined) + //初始化数据库 + err := a.dataExec.InitDB(a.c.Name, a.c.Data) + if err != nil { + return err + } + //初始化操作 + a._initActions() + return nil } +func (a *AutoApi) _initActions() { + //初始化数据库 + var actions []actionData + + supportPluginMap := map[string]ifs.IApiPlugin{} + for _, v := range a.supportPlugins { + supportPluginMap[v.Name()] = v + } + + for _, action := range a.c.Actions { + item := actionData{ + action: action, + } + logs.Out.Info("action[%s] init", action.Name) + for _, plugin := range action.Plugins { + if tmp, exist := supportPluginMap[plugin.Name]; exist { + item.usePlugins = append(item.usePlugins, tmp) + logs.Out.Info("plugin[%s] load", plugin.Name) + } else { + logs.Out.Warn("plugin[%s] not support", plugin.Name) + } + } + + actions = append(actions, item) + } +} + func (a *AutoApi) Handlers() []config.GinHandleParam { + for _, v := range a.actions { + for _, plugin := range v.usePlugins { + //todo 添加gin handler + logs.Out.Info("%s", plugin.Name()) + } + } return []config.GinHandleParam{} } + +// actionData 单个操作Data +type actionData struct { + action config.AutoApiActionDefined + usePlugins []ifs.IApiPlugin +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_response.go b/src/pkg/atapi/httpapi/v1/plugins/api_response.go new file mode 100644 index 0000000..244ae67 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/plugins/api_response.go @@ -0,0 +1,63 @@ +package plugins + +import ( + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "gitee.com/captials-team/ubdframe/src/pkg/extendparams" + "github.com/gin-gonic/gin" + "net/http" +) + +type ApiResponseAction struct { + extends extendparams.CommonExtendParams +} + +func NewApiResponseAction(m map[string]string) (*ApiResponseAction, error) { + //初始化 + p := &ApiResponseAction{ + extends: m, + } + + return p, nil +} + +func (p *ApiResponseAction) Name() string { + return "API_RESPONSE" +} + +func (p *ApiResponseAction) Describe() string { + return ` +提供返回响应数据,http路由定义的能力 +` +} + +func (p *ApiResponseAction) Do(param *config.PluginParam) { + +} + +func (p *ApiResponseAction) HttpHandler(param *config.PluginParam) ifs.IApiPluginHttpHandler { + return &ApiResponseHttpHandler{ + action: p, + running: param, + } +} + +type ApiResponseHttpHandler struct { + action *ApiResponseAction + //运行时参数变量 + running *config.PluginParam +} + +func (p *ApiResponseHttpHandler) Router() []config.GinHandleParam { + return []config.GinHandleParam{ + { + Method: http.MethodPost, + Path: "/ping", + HandlerFunc: []gin.HandlerFunc{ + func(ctx *gin.Context) { + ctx.JSON(http.StatusOK, "ok") + }, + }, + }, + } +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go index 4e8e318..c7b16a1 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_search.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -1,6 +1,8 @@ package plugins import ( + "context" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" "strings" ) @@ -42,10 +44,6 @@ func NewApiSearchAction(m map[string]string) (*ApiSearchAction, error) { return p, nil } -func (p *ApiSearchAction) New(m map[string]string) (ifs.IApiPlugin, error) { - return NewApiSearchAction(m) -} - func (p *ApiSearchAction) Name() string { return "API_SEARCH" } @@ -55,3 +53,28 @@ func (p *ApiSearchAction) Describe() string { 提供查询查询数据列表的能力,可以自定义查询过滤条件,包括完全匹配查询字段和模糊匹配查询字段 ` } + +func (p *ApiSearchHttpHandler) Do(param *config.PluginParam) { + param.Ctx = context.WithValue(param.Ctx, "data", []int64{ + 1, + 2, + 3, + }) +} + +func (p *ApiSearchAction) HttpHandler(param *config.PluginParam) ifs.IApiPluginHttpHandler { + return &ApiSearchHttpHandler{ + action: p, + running: param, + } +} + +type ApiSearchHttpHandler struct { + action *ApiSearchAction + //运行时参数变量 + running *config.PluginParam +} + +func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { + return nil +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/base.go b/src/pkg/atapi/httpapi/v1/plugins/base.go index 41e3fe6..96e1426 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/base.go +++ b/src/pkg/atapi/httpapi/v1/plugins/base.go @@ -1,5 +1,7 @@ package plugins +import "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + const ( SecretKey = "SECRET" //秘钥配置字段 @@ -16,3 +18,7 @@ const ( RateLimitKey = "LIMIT_RATE" //每秒限制速率 ) + +type PluginParam struct { + Api config.AutoApiDefined +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go new file mode 100644 index 0000000..d46f490 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go @@ -0,0 +1,34 @@ +package plugins + +import ( + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" +) + +type RateLimitAction struct { +} + +func NewRateLimitAction(m map[string]string) (*RateLimitAction, error) { + //初始化 + p := &RateLimitAction{} + + return p, nil +} + +func (p *RateLimitAction) Name() string { + return "RATE_LIMIT" +} + +func (p *RateLimitAction) Describe() string { + return ` +限流处理 +` +} + +func (p *RateLimitAction) Do(param *config.PluginParam) { + +} + +func (p *RateLimitAction) HttpHandler(param *config.PluginParam) ifs.IApiPluginHttpHandler { + return nil +} diff --git a/src/pkg/atapi/ifs/http_api.go b/src/pkg/atapi/ifs/http_api.go index 88c369e..04f4b14 100644 --- a/src/pkg/atapi/ifs/http_api.go +++ b/src/pkg/atapi/ifs/http_api.go @@ -23,9 +23,7 @@ type IAutoApi interface { // //LoadedProcesses() []IProcess //返回加载后的process处理器数组 //LoadedActions() []IApiAction //返回加载后的process处理器数组 -} -type IAutoHttpApi interface { Handlers() []config.GinHandleParam } diff --git a/src/pkg/atapi/ifs/plugin.go b/src/pkg/atapi/ifs/plugin.go index c6f0d60..6a488f0 100644 --- a/src/pkg/atapi/ifs/plugin.go +++ b/src/pkg/atapi/ifs/plugin.go @@ -1,5 +1,35 @@ package ifs +import ( + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" +) + type IApiPlugin interface { - Name() string //plugin名称 + Name() string //plugin名称 + Describe() string //插件具体描述内容 + + Do(param *config.PluginParam) + + HttpHandler(param *config.PluginParam) IApiPluginHttpHandler //该插件的具体handler类 + CmdHandler(param *config.PluginParam) IApiPluginCmdHandler //该插件的具体handler类 +} + +// IApiPluginHandler 插件http场景下需支持的处理 +type IApiPluginHandler interface { + Do(param *config.PluginParam) +} + +// IApiPluginHttpHandler 插件http场景下需支持的处理 +type IApiPluginHttpHandler interface { + Router() []config.GinHandleParam +} + +// IApiPluginCmdHandler 插件命令行场景下需支持的处理 +type IApiPluginCmdHandler interface { + //todo 后续增加 +} + +// IApiPluginRPCHandler 插件rpc场景下需支持的处理 +type IApiPluginRPCHandler interface { + //todo 后续增加 } diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go index 7c13e5b..06c6c0e 100644 --- a/src/pkg/atapi/manager.go +++ b/src/pkg/atapi/manager.go @@ -80,13 +80,10 @@ func (mag *AtApisManager) Initial() error { func (mag *AtApisManager) StartByGin(g gin.IRouter) error { for _, v := range mag.httpApis { - a, ok := v.(ifs.IAutoHttpApi) - if ok { - handlers := a.Handlers() - for _, h := range handlers { - g.Handle(h.Method, h.Path, h.HandlerFunc...) - mag.Info("register router: %s,%s", h.Method, h.Path) - } + handlers := v.Handlers() + for _, h := range handlers { + g.Handle(h.Method, h.Path, h.HandlerFunc...) + mag.Info("register router: %s,%s", h.Method, h.Path) } } return nil diff --git a/src/pkg/atapi/manager_test.go b/src/pkg/atapi/manager_test.go index 2719e48..bca303f 100644 --- a/src/pkg/atapi/manager_test.go +++ b/src/pkg/atapi/manager_test.go @@ -32,6 +32,7 @@ func TestNewManager(t *testing.T) { mag := NewAtApisManager(&c) mag.SetDataExec(dataEx) + common.ErrPanic(mag.Initial()) common.ErrPanic(mag.StartByGin(g)) diff --git a/src/pkg/atapi/tests/v1/demo.yaml b/src/pkg/atapi/tests/v1/demo.yaml index d617e5d..210a609 100644 --- a/src/pkg/atapi/tests/v1/demo.yaml +++ b/src/pkg/atapi/tests/v1/demo.yaml @@ -9,13 +9,17 @@ ShowName: "DEMO" Actions: - Name: "搜索接口" Describe: "搜索接口" + Enable: true Plugins: - - Name: "API_SEARCH" - ExtendParam: - fields: "id,name,pid,status" - Name: "RATE_LIMIT" ExtendParam: limit_rate: 2 + - Name: "API_SEARCH" + ExtendParam: + fields: "id,title,type,status" + - Name: "API_RESPONSE" + ExtendParam: + path: "/demo/search" #是否自动创建表数据等信息 AutoTable: true -- Gitee From 333b763f9d49181c74b7f40eaf22fb1bbecfba34 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 12 Aug 2025 18:04:41 +0800 Subject: [PATCH 22/83] modify plugin --- src/apps/autoapiapp/api_server.go | 5 +- src/apps/autoapiapp/app.go | 12 ++ src/domain/configstc/app.go | 2 +- src/pkg/atapi/config/param.go | 21 +- src/pkg/atapi/httpapi/v1/api.go | 72 ++++++- .../atapi/httpapi/v1/plugins/api_response.go | 65 +++--- .../atapi/httpapi/v1/plugins/api_search.go | 192 ++++++++++++++++-- src/pkg/atapi/httpapi/v1/plugins/base.go | 6 - .../atapi/httpapi/v1/plugins/rate_limit.go | 24 ++- src/pkg/atapi/ifs/plugin.go | 8 +- src/pkg/atapi/manager.go | 22 +- src/pkg/atapi/manager_test.go | 2 + src/pkg/logs/consts.go | 2 +- 13 files changed, 354 insertions(+), 79 deletions(-) diff --git a/src/apps/autoapiapp/api_server.go b/src/apps/autoapiapp/api_server.go index 7b2e05f..2f7e2ba 100644 --- a/src/apps/autoapiapp/api_server.go +++ b/src/apps/autoapiapp/api_server.go @@ -97,8 +97,6 @@ func (s *ApiServer) genSwaggerDocs() string { //组装path paths[s.conf.RoutePrefix+"/org/search"] = spec.PathItem{ - Refable: spec.Refable{}, - VendorExtensible: spec.VendorExtensible{}, PathItemProps: spec.PathItemProps{ Get: &spec.Operation{ OperationProps: spec.OperationProps{ @@ -317,8 +315,7 @@ func (s *ApiServer) genSwaggerDocs() string { } ps.Paths = &spec.Paths{ - VendorExtensible: spec.VendorExtensible{}, - Paths: paths, + Paths: paths, } ps.Definitions = defines diff --git a/src/apps/autoapiapp/app.go b/src/apps/autoapiapp/app.go index dc18dc5..c47e89c 100644 --- a/src/apps/autoapiapp/app.go +++ b/src/apps/autoapiapp/app.go @@ -4,6 +4,8 @@ import ( "gitee.com/captials-team/ubdframe/src/apps" "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/pkg/atapi" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/dataexec" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/uber_help" "go.uber.org/dig" @@ -15,6 +17,8 @@ type App struct { l v1log.ILog di *dig.Scope ApiServer *ApiServer + + AtManager *atapi.AtApisManager } func NewApp(di *dig.Container, conf *configstc.AutoApiAppConfig, logger v1log.ILog) *App { @@ -43,5 +47,13 @@ func NewApp(di *dig.Container, conf *configstc.AutoApiAppConfig, logger v1log.IL })) } + //autoApi管理器 + dataEx, err := dataexec.NewMysqlDbExec(conf.DBConfig) + common.ErrPanic(err) + + mag := atapi.NewAtApisManager(&conf.AtApi) + mag.SetDataExec(dataEx) + mag.AddLogger(logger) + return app } diff --git a/src/domain/configstc/app.go b/src/domain/configstc/app.go index a08f777..fcd22ae 100644 --- a/src/domain/configstc/app.go +++ b/src/domain/configstc/app.go @@ -364,5 +364,5 @@ type AutoApiAppConfig struct { ModuleMode bool `json:"ModuleMode" yaml:"ModuleMode"` //模块模式,作为module使用 - Apis []atapiConfig.AutoApiConfig `json:"Apis" yaml:"Apis"` //自定义API列表 + AtApi atapiConfig.AutoApiConfig `json:"AtApi" yaml:"AtApi"` //自定义API列表 } diff --git a/src/pkg/atapi/config/param.go b/src/pkg/atapi/config/param.go index 9acc85c..0360b23 100644 --- a/src/pkg/atapi/config/param.go +++ b/src/pkg/atapi/config/param.go @@ -3,16 +3,29 @@ package config import ( "context" "github.com/gin-gonic/gin" + "github.com/go-openapi/spec" ) type GinHandleParam struct { Method string Path string HandlerFunc []gin.HandlerFunc + SwaggerPath spec.PathItem } -// PluginParam 插件使用的参数合集 -type PluginParam struct { - Ctx context.Context - Api AutoApiDefined +// RunningParam 运行时使用的参数合集 +type RunningParam struct { + Ctx context.Context + Api AutoApiDefined + Action AutoApiActionDefined //当前action信息 + + Abort func() + GinCtx func() *gin.Context +} + +// PluginData 插件使用的参数合集 +type PluginData struct { + Api AutoApiDefined //当前api信息 + Action AutoApiActionDefined //当前action信息 + Plugin AutoApiActionPluginDefined //当前plugin信息 } diff --git a/src/pkg/atapi/httpapi/v1/api.go b/src/pkg/atapi/httpapi/v1/api.go index afce8e7..3dbc135 100644 --- a/src/pkg/atapi/httpapi/v1/api.go +++ b/src/pkg/atapi/httpapi/v1/api.go @@ -1,10 +1,12 @@ package v1 import ( + "context" "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" "gitee.com/captials-team/ubdframe/src/pkg/extendparams" "gitee.com/captials-team/ubdframe/src/pkg/logs" + "github.com/gin-gonic/gin" ) func NewAutoApi(c config.AutoApiDefined, extends map[string]string) (*AutoApi, error) { @@ -64,14 +66,16 @@ func (a *AutoApi) Init() error { if err != nil { return err } + logs.Out.Info("[%s] table: %s init", a.c.Name, a.dataExec.TableName()) + //初始化操作 a._initActions() return nil } +// _initActions 初始化操作集合 func (a *AutoApi) _initActions() { - //初始化数据库 var actions []actionData supportPluginMap := map[string]ifs.IApiPlugin{} @@ -83,32 +87,78 @@ func (a *AutoApi) _initActions() { item := actionData{ action: action, } - logs.Out.Info("action[%s] init", action.Name) + logs.Out.Info("[%s] action: %s init", a.c.Name, action.Name) for _, plugin := range action.Plugins { if tmp, exist := supportPluginMap[plugin.Name]; exist { item.usePlugins = append(item.usePlugins, tmp) - logs.Out.Info("plugin[%s] load", plugin.Name) + item.usePluginsConfig = append(item.usePluginsConfig, plugin) + + logs.Out.Info("\tplugin[%s] load", plugin.Name) } else { - logs.Out.Warn("plugin[%s] not support", plugin.Name) + logs.Out.Warn("\tplugin[%s] not support", plugin.Name) } } actions = append(actions, item) } + + a.actions = actions } func (a *AutoApi) Handlers() []config.GinHandleParam { - for _, v := range a.actions { - for _, plugin := range v.usePlugins { - //todo 添加gin handler - logs.Out.Info("%s", plugin.Name()) + var routers []config.GinHandleParam + for _, act := range a.actions { + for i, plugin := range act.usePlugins { + h := plugin.HttpHandler(config.PluginData{ + Api: a.c, + Action: act.action, + Plugin: act.usePluginsConfig[i], + }) + if h == nil { + continue + } + + r := h.Router() + if len(r) > 0 { + for _, router := range r { + if router.HandlerFunc == nil { + router.HandlerFunc = []gin.HandlerFunc{a.ginHandler(act)} + } + routers = append(routers, router) + } + } + } + } + return routers +} + +func (a *AutoApi) ginHandler(action actionData) gin.HandlerFunc { + return func(ctx *gin.Context) { + abort := false + running := config.RunningParam{ + Ctx: context.Background(), + Api: a.c, + Abort: func() { + abort = true + }, + GinCtx: func() *gin.Context { + return ctx + }, + } + + for _, v := range action.usePlugins { + v.Do(&running) + if abort { + break + } } } - return []config.GinHandleParam{} } // actionData 单个操作Data type actionData struct { - action config.AutoApiActionDefined - usePlugins []ifs.IApiPlugin + action config.AutoApiActionDefined + + usePlugins []ifs.IApiPlugin + usePluginsConfig []config.AutoApiActionPluginDefined } diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_response.go b/src/pkg/atapi/httpapi/v1/plugins/api_response.go index 244ae67..0b3665a 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_response.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_response.go @@ -4,60 +4,77 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" "gitee.com/captials-team/ubdframe/src/pkg/extendparams" - "github.com/gin-gonic/gin" "net/http" ) -type ApiResponseAction struct { +type ApiResponsePlugin struct { extends extendparams.CommonExtendParams } -func NewApiResponseAction(m map[string]string) (*ApiResponseAction, error) { +func NewApiResponsePlugin(m map[string]string) (*ApiResponsePlugin, error) { //初始化 - p := &ApiResponseAction{ + p := &ApiResponsePlugin{ extends: m, } return p, nil } -func (p *ApiResponseAction) Name() string { +func (p *ApiResponsePlugin) Name() string { return "API_RESPONSE" } -func (p *ApiResponseAction) Describe() string { +func (p *ApiResponsePlugin) Describe() string { return ` 提供返回响应数据,http路由定义的能力 ` } -func (p *ApiResponseAction) Do(param *config.PluginParam) { +func (p *ApiResponsePlugin) Do(running *config.RunningParam) { + ctx := running.GinCtx() + resp := struct { + Code int `json:"code"` //返回码,0:成功,>0为对应错误码 + Msg string `json:"msg"` + Data interface{} `json:"data"` + }{ + Code: 0, + Msg: "SUCCESS", + Data: nil, + } + + code, ok := running.Ctx.Value("code").(int) + if ok { + resp.Code = code + } + msg, ok := running.Ctx.Value("msg").(string) + if ok { + resp.Msg = msg + } + data := running.Ctx.Value("data") + if data != nil { + resp.Data = data + } + + ctx.JSON(http.StatusOK, resp) } -func (p *ApiResponseAction) HttpHandler(param *config.PluginParam) ifs.IApiPluginHttpHandler { +func (p *ApiResponsePlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { return &ApiResponseHttpHandler{ - action: p, - running: param, + action: p, + data: data, } } +func (p *ApiResponsePlugin) CmdHandler(api config.PluginData) ifs.IApiPluginCmdHandler { + return nil +} + type ApiResponseHttpHandler struct { - action *ApiResponseAction - //运行时参数变量 - running *config.PluginParam + action *ApiResponsePlugin + data config.PluginData } func (p *ApiResponseHttpHandler) Router() []config.GinHandleParam { - return []config.GinHandleParam{ - { - Method: http.MethodPost, - Path: "/ping", - HandlerFunc: []gin.HandlerFunc{ - func(ctx *gin.Context) { - ctx.JSON(http.StatusOK, "ok") - }, - }, - }, - } + return []config.GinHandleParam{} } diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go index c7b16a1..aec879f 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_search.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -2,12 +2,15 @@ package plugins import ( "context" + "fmt" "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "github.com/go-openapi/spec" + "net/http" "strings" ) -type ApiSearchAction struct { +type ApiSearchPlugin struct { //请求参数(可搜索条件字段) matchSearch []string //匹配查询参数 fuzzySearch []string // @@ -21,9 +24,9 @@ type ApiSearchAction struct { sizeParam string } -func NewApiSearchAction(m map[string]string) (*ApiSearchAction, error) { +func NewApiSearchPlugin(m map[string]string) (*ApiSearchPlugin, error) { //初始化 - p := &ApiSearchAction{ + p := &ApiSearchPlugin{ respFields: strings.Split(m[ResponseFieldsParamsKey], ","), matchSearch: strings.Split(m[SearchFieldsParamsKey], ","), fuzzySearch: strings.Split(m[SearchFuzzyFieldsParamsKey], ","), @@ -44,37 +47,200 @@ func NewApiSearchAction(m map[string]string) (*ApiSearchAction, error) { return p, nil } -func (p *ApiSearchAction) Name() string { +func (p *ApiSearchPlugin) Name() string { return "API_SEARCH" } -func (p *ApiSearchAction) Describe() string { +func (p *ApiSearchPlugin) Describe() string { return ` 提供查询查询数据列表的能力,可以自定义查询过滤条件,包括完全匹配查询字段和模糊匹配查询字段 ` } -func (p *ApiSearchHttpHandler) Do(param *config.PluginParam) { - param.Ctx = context.WithValue(param.Ctx, "data", []int64{ +func (p *ApiSearchPlugin) Do(running *config.RunningParam) { + running.Ctx = context.WithValue(running.Ctx, "data", []int64{ 1, 2, 3, }) } -func (p *ApiSearchAction) HttpHandler(param *config.PluginParam) ifs.IApiPluginHttpHandler { +func (p *ApiSearchPlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { return &ApiSearchHttpHandler{ - action: p, - running: param, + action: p, + data: data, } } +func (p *ApiSearchPlugin) CmdHandler(param config.PluginData) ifs.IApiPluginCmdHandler { + return nil +} + type ApiSearchHttpHandler struct { - action *ApiSearchAction + action *ApiSearchPlugin //运行时参数变量 - running *config.PluginParam + data config.PluginData } func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { - return nil + path := fmt.Sprintf("/%s/search", p.data.Api.Name) + + newSchema := func(ty string, desc string, example interface{}) spec.Schema { + return spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: desc, + Type: []string{ty}, + }, + ExtraProps: map[string]interface{}{ + "example": example, + }, + } + } + searchParams := map[string]spec.Schema{} + fields := map[string]spec.Schema{} + for _, v := range p.data.Api.Data.Fields { + searchParams[v.Name] = spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: v.ShowName, + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: "", + }, + } + fields[v.Name] = newSchema("string", v.ShowName, "example value") + } + resp1 := spec.Response{ + ResponseProps: spec.ResponseProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Description: "响应Data", + Properties: map[string]spec.Schema{ + "list": { + SchemaProps: spec.SchemaProps{ + Description: "列表数据", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "数组子项内容", + Type: []string{"object"}, + Properties: fields, + }, + }, + }, + }, + }, + "paginate": { + SchemaProps: spec.SchemaProps{ + Description: "分页数据", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "page_no": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "当前页码", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 1, + }, + }, + "page_size": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "单页数量", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 10, + }, + }, + "total": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "页码总数", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 7, + }, + }, + "count": spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "数据总数", + Type: []string{"int"}, + }, + ExtraProps: map[string]interface{}{ + "example": 64, + }, + }, + }, + }, + }, + }, + }, + }, + Headers: nil, + Examples: nil, + }, + } + + return []config.GinHandleParam{ + { + Method: http.MethodPost, + Path: path, + SwaggerPath: spec.PathItem{ + PathItemProps: spec.PathItemProps{ + Post: &spec.Operation{ + OperationProps: spec.OperationProps{ + Summary: "搜索列表", + Description: "搜索列表,根据搜索参数返回列表和分页信息", + Produces: []string{"application/json"}, + Schemes: nil, + Tags: []string{p.data.Api.Name}, + Parameters: []spec.Parameter{ + { + ParamProps: spec.ParamProps{ + Description: "搜索参数", + Name: "param", + In: "body", + Required: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: searchParams, + }, + }, + AllowEmptyValue: false, + }, + }, + }, + Responses: &spec.Responses{ + ResponsesProps: spec.ResponsesProps{ + Default: nil, + StatusCodeResponses: map[int]spec.Response{ + 0: spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "", + Schema: &spec.Schema{ + VendorExtensible: spec.VendorExtensible{}, + SchemaProps: spec.SchemaProps{}, + SwaggerSchemaProps: spec.SwaggerSchemaProps{}, + ExtraProps: map[string]interface{}{ + "$ref": "#/definitions/reqdata.common.Response", + }, + }, + Headers: nil, + Examples: nil, + }, + }, + 200: resp1, + }, + }, + }, + }, + }, + }, + }, + }, + } } diff --git a/src/pkg/atapi/httpapi/v1/plugins/base.go b/src/pkg/atapi/httpapi/v1/plugins/base.go index 96e1426..41e3fe6 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/base.go +++ b/src/pkg/atapi/httpapi/v1/plugins/base.go @@ -1,7 +1,5 @@ package plugins -import "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" - const ( SecretKey = "SECRET" //秘钥配置字段 @@ -18,7 +16,3 @@ const ( RateLimitKey = "LIMIT_RATE" //每秒限制速率 ) - -type PluginParam struct { - Api config.AutoApiDefined -} diff --git a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go index d46f490..7ac3ad2 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go +++ b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go @@ -1,34 +1,44 @@ package plugins import ( + "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "net/http" ) -type RateLimitAction struct { +type RateLimitPlugin struct { } -func NewRateLimitAction(m map[string]string) (*RateLimitAction, error) { +func NewRateLimitPlugin(m map[string]string) (*RateLimitPlugin, error) { //初始化 - p := &RateLimitAction{} + p := &RateLimitPlugin{} return p, nil } -func (p *RateLimitAction) Name() string { +func (p *RateLimitPlugin) Name() string { return "RATE_LIMIT" } -func (p *RateLimitAction) Describe() string { +func (p *RateLimitPlugin) Describe() string { return ` 限流处理 ` } -func (p *RateLimitAction) Do(param *config.PluginParam) { +func (p *RateLimitPlugin) Do(running *config.RunningParam) { + ip := utils.GetRequestIp(running.GinCtx().Request) + if utils.TrueScopeRand(0, 100) < 50 { + running.GinCtx().String(http.StatusOK, ip+",rate limit") + running.Abort() + } +} +func (p *RateLimitPlugin) HttpHandler(api config.PluginData) ifs.IApiPluginHttpHandler { + return nil } -func (p *RateLimitAction) HttpHandler(param *config.PluginParam) ifs.IApiPluginHttpHandler { +func (p *RateLimitPlugin) CmdHandler(api config.PluginData) ifs.IApiPluginCmdHandler { return nil } diff --git a/src/pkg/atapi/ifs/plugin.go b/src/pkg/atapi/ifs/plugin.go index 6a488f0..6d639c8 100644 --- a/src/pkg/atapi/ifs/plugin.go +++ b/src/pkg/atapi/ifs/plugin.go @@ -8,15 +8,15 @@ type IApiPlugin interface { Name() string //plugin名称 Describe() string //插件具体描述内容 - Do(param *config.PluginParam) + Do(param *config.RunningParam) - HttpHandler(param *config.PluginParam) IApiPluginHttpHandler //该插件的具体handler类 - CmdHandler(param *config.PluginParam) IApiPluginCmdHandler //该插件的具体handler类 + HttpHandler(api config.PluginData) IApiPluginHttpHandler //该插件的具体handler类 + CmdHandler(api config.PluginData) IApiPluginCmdHandler //该插件的具体handler类 } // IApiPluginHandler 插件http场景下需支持的处理 type IApiPluginHandler interface { - Do(param *config.PluginParam) + Do(param *config.RunningParam) } // IApiPluginHttpHandler 插件http场景下需支持的处理 diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go index 06c6c0e..fc4cc47 100644 --- a/src/pkg/atapi/manager.go +++ b/src/pkg/atapi/manager.go @@ -10,6 +10,7 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/codec" logv1 "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/gin-gonic/gin" + "github.com/go-openapi/spec" "io/ioutil" "strings" ) @@ -73,13 +74,14 @@ func (mag *AtApisManager) Initial() error { continue } mag.httpApis[apiHandler.Code()] = apiHandler - mag.Info("%s loaded", api.Name) + mag.Info("%s loaded, handler: %s", api.Name, apiHandler.Code()) } return nil } func (mag *AtApisManager) StartByGin(g gin.IRouter) error { for _, v := range mag.httpApis { + mag.Info("%s start", v.Code()) handlers := v.Handlers() for _, h := range handlers { g.Handle(h.Method, h.Path, h.HandlerFunc...) @@ -89,6 +91,17 @@ func (mag *AtApisManager) StartByGin(g gin.IRouter) error { return nil } +func (mag *AtApisManager) StartBySwagger(w *spec.Swagger) error { + for _, v := range mag.httpApis { + mag.Info("%s start", v.Code()) + handlers := v.Handlers() + for _, h := range handlers { + w.Paths.Paths[h.Path] = h.SwaggerPath + } + } + return nil +} + func (mag *AtApisManager) newAutoApi(api config.AutoApiDefined) (ifs.IAutoApi, error) { switch strings.ToLower(api.Version) { case v1.Version: @@ -105,15 +118,16 @@ func (mag *AtApisManager) newAutoApiV1(apiDef config.AutoApiDefined) (*v1.AutoAp } api.SetDataExec(mag.store.Copy()) //api.SetVars(vars) - api.AddPlugin() api.AddPlugin( - &plugins.ApiSearchAction{}, + &plugins.RateLimitPlugin{}) + api.AddPlugin( + &plugins.ApiSearchPlugin{}, + &plugins.ApiResponsePlugin{}, ) if err := api.Init(); err != nil { return nil, err } - mag.Info("[%s] table: %s ", api.Code(), api.GetDataExec().TableName()) return api, nil } diff --git a/src/pkg/atapi/manager_test.go b/src/pkg/atapi/manager_test.go index bca303f..e1e2cd9 100644 --- a/src/pkg/atapi/manager_test.go +++ b/src/pkg/atapi/manager_test.go @@ -6,6 +6,7 @@ import ( "gitee.com/captials-team/ubdframe/src/domain/configstc" "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/dataexec" + "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "testing" @@ -32,6 +33,7 @@ func TestNewManager(t *testing.T) { mag := NewAtApisManager(&c) mag.SetDataExec(dataEx) + mag.AddLogger(logs.Out.CallerSkip(1)) common.ErrPanic(mag.Initial()) common.ErrPanic(mag.StartByGin(g)) diff --git a/src/pkg/logs/consts.go b/src/pkg/logs/consts.go index 7fc51e3..fcc52c2 100644 --- a/src/pkg/logs/consts.go +++ b/src/pkg/logs/consts.go @@ -7,7 +7,7 @@ import ( // 全局logger var ( - Out ILog = NewWriterLog(os.Stdout, DebugLog).CallerSkip(01) + Out ILog = NewWriterLog(os.Stdout, DebugLog).CallerSkip(0) ) func SetLogger(log ILog) { -- Gitee From d07fe49c1688e9c9054ca5f96f48768c4af65c4c Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 12 Aug 2025 18:05:15 +0800 Subject: [PATCH 23/83] modify --- src/apps/autoapiapp/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/autoapiapp/app.go b/src/apps/autoapiapp/app.go index c47e89c..b9942db 100644 --- a/src/apps/autoapiapp/app.go +++ b/src/apps/autoapiapp/app.go @@ -34,7 +34,7 @@ func NewApp(di *dig.Container, conf *configstc.AutoApiAppConfig, logger v1log.IL app := &App{ di: scope, l: logger, - App: apps.NewApp(di, "admin_app"), + App: apps.NewApp(di, "auto_api_app"), } app.WithModule(conf.ModuleMode) -- Gitee From a509fbdcc69ab3acc8072e0405225c297a2b88fd Mon Sep 17 00:00:00 2001 From: sage Date: Wed, 13 Aug 2025 17:59:11 +0800 Subject: [PATCH 24/83] modify --- src/apps/autoapiapp/api_server.go | 20 +++++++------- src/apps/autoapiapp/app.go | 26 +++++++++++++++++++ src/apps/autoapiapp/app_test.go | 8 ++++++ .../atapi/httpapi/v1/plugins/api_search.go | 22 +++------------- src/pkg/atapi/manager.go | 10 ++++++- 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/apps/autoapiapp/api_server.go b/src/apps/autoapiapp/api_server.go index 2f7e2ba..389a697 100644 --- a/src/apps/autoapiapp/api_server.go +++ b/src/apps/autoapiapp/api_server.go @@ -64,16 +64,18 @@ func (s *ApiServer) Stop() error { } func (s *ApiServer) genSwaggerDocs() string { - ps := spec.SwaggerProps{ - Schemes: []string{"http", "https"}, - Swagger: "2.0", - Produces: []string{ - "application/json", - "text/html", + ps := spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Schemes: []string{"http", "https"}, + Swagger: "2.0", + Produces: []string{ + "application/json", + "text/html", + }, + Info: &spec.Info{}, + Host: "{{.Host}}", + BasePath: "{{.BasePath}}", }, - Info: &spec.Info{}, - Host: "{{.Host}}", - BasePath: "{{.BasePath}}", } ps.Info.Title = "{{.Title}}" ps.Info.Version = "{{.Version}}" diff --git a/src/apps/autoapiapp/app.go b/src/apps/autoapiapp/app.go index b9942db..dff2588 100644 --- a/src/apps/autoapiapp/app.go +++ b/src/apps/autoapiapp/app.go @@ -8,6 +8,7 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/atapi/dataexec" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/uber_help" + "github.com/go-openapi/spec" "go.uber.org/dig" ) @@ -54,6 +55,31 @@ func NewApp(di *dig.Container, conf *configstc.AutoApiAppConfig, logger v1log.IL mag := atapi.NewAtApisManager(&conf.AtApi) mag.SetDataExec(dataEx) mag.AddLogger(logger) + common.ErrPanic(mag.Initial()) + + ps := &spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Schemes: []string{"http", "https"}, + Swagger: "2.0", + Produces: []string{ + "application/json", + "text/html", + }, + Info: &spec.Info{}, + Host: "{{.Host}}", + BasePath: "{{.BasePath}}", + }, + } + ps.Info.Title = "{{.Title}}" + ps.Info.Version = "{{.Version}}" + ps.Info.Description = "{{escape .Description}}" + ps.Info.TermsOfService = "http://swagger.io/terms/" + + common.ErrPanic(mag.StartByGin(app.ApiServer.Engine())) + common.ErrPanic(mag.StartBySwagger(ps)) + + s, _ := ps.MarshalJSON() + app.ApiServer.Swagger.SwaggerTemplate = string(s) return app } diff --git a/src/apps/autoapiapp/app_test.go b/src/apps/autoapiapp/app_test.go index 04d0492..d2c4546 100644 --- a/src/apps/autoapiapp/app_test.go +++ b/src/apps/autoapiapp/app_test.go @@ -2,7 +2,9 @@ package autoapiapp import ( "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/tests" "go.uber.org/dig" "testing" @@ -39,6 +41,12 @@ func testDi(t *testing.T) *dig.Container { TablePrefix: "test_", }, DocsEnable: true, + AtApi: config.AutoApiConfig{ + Apis: []config.AutoApiDefined{ + {LoadFile: utils.CurrentPath() + "/../../pkg/atapi/tests/v1/demo.yaml"}, + {LoadFile: utils.CurrentPath() + "/../../pkg/atapi/tests/v1/org.yaml"}, + }, + }, } })) diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go index aec879f..1907720 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_search.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -206,11 +206,12 @@ func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { Required: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: searchParams, + Description: "参数项内容", + Type: []string{"object"}, + Properties: searchParams, }, }, - AllowEmptyValue: false, + AllowEmptyValue: true, }, }, }, @@ -218,21 +219,6 @@ func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { ResponsesProps: spec.ResponsesProps{ Default: nil, StatusCodeResponses: map[int]spec.Response{ - 0: spec.Response{ - ResponseProps: spec.ResponseProps{ - Description: "", - Schema: &spec.Schema{ - VendorExtensible: spec.VendorExtensible{}, - SchemaProps: spec.SchemaProps{}, - SwaggerSchemaProps: spec.SwaggerSchemaProps{}, - ExtraProps: map[string]interface{}{ - "$ref": "#/definitions/reqdata.common.Response", - }, - }, - Headers: nil, - Examples: nil, - }, - }, 200: resp1, }, }, diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go index fc4cc47..87e1642 100644 --- a/src/pkg/atapi/manager.go +++ b/src/pkg/atapi/manager.go @@ -92,11 +92,19 @@ func (mag *AtApisManager) StartByGin(g gin.IRouter) error { } func (mag *AtApisManager) StartBySwagger(w *spec.Swagger) error { + if w.Paths == nil { + w.Paths = &spec.Paths{} + } + + if w.Paths.Paths == nil { + w.Paths.Paths = make(map[string]spec.PathItem) + } for _, v := range mag.httpApis { - mag.Info("%s start", v.Code()) handlers := v.Handlers() + mag.Info("%s swagger len=%d", v.Code(), len(handlers)) for _, h := range handlers { w.Paths.Paths[h.Path] = h.SwaggerPath + mag.Info("\tadd swagger path: %s", h.Path) } } return nil -- Gitee From 1812be227891c7950ebee95bd3e1288681ead1cd Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 14 Aug 2025 18:01:12 +0800 Subject: [PATCH 25/83] add plugins --- src/pkg/atapi/config/config.go | 7 +- src/pkg/atapi/config/param.go | 20 +- src/pkg/atapi/dataexec/dataexec_mysql.go | 2 +- src/pkg/atapi/httpapi/v1/api.go | 25 +-- .../atapi/httpapi/v1/plugins/api_response.go | 30 ++- .../atapi/httpapi/v1/plugins/api_search.go | 208 +++++++++--------- .../atapi/httpapi/v1/plugins/rate_limit.go | 6 +- src/pkg/atapi/httpapi/v1/running.go | 57 +++++ src/pkg/atapi/ifs/data_exec.go | 2 +- src/pkg/atapi/ifs/http_api.go | 6 + src/pkg/atapi/ifs/plugin.go | 4 +- src/pkg/atapi/ifs/running.go | 21 ++ src/pkg/atapi/manager.go | 51 ++++- src/pkg/atapi/manager_test.go | 29 ++- src/pkg/atapi/tests/v1/demo.yaml | 43 ++-- src/pkg/atapi/tests/v1/org.yaml | 43 ++-- 16 files changed, 348 insertions(+), 206 deletions(-) create mode 100644 src/pkg/atapi/httpapi/v1/running.go create mode 100644 src/pkg/atapi/ifs/running.go diff --git a/src/pkg/atapi/config/config.go b/src/pkg/atapi/config/config.go index e67f76e..c70313b 100644 --- a/src/pkg/atapi/config/config.go +++ b/src/pkg/atapi/config/config.go @@ -29,9 +29,10 @@ type AutoApiDefined struct { type AutoApiDataDefined struct { //结构 - AutoTable bool `yaml:"AutoTable"` //是否自动建表,true:自动检查并创建表,false:不检查 - DataName string `yaml:"DataName"` //数据名称,用于给数据区分存储来源,如表名 - Fields []AutoApiFieldDefined `yaml:"Fields"` + DataName string `json:"DataName" yaml:"DataName"` //数据名称,用于给数据区分存储来源,如表名 + AutoTable bool `json:"AutoTable" yaml:"AutoTable"` //是否自动建表,true:自动检查并创建表,false:不检查 + Fields []AutoApiFieldDefined `json:"Fields" yaml:"Fields"` + Comment string `json:"Comment" yaml:"Comment"` //数据名称,用于给数据区分存储来源,如表名 } // AutoApiFieldDefined 单个autoApi字段的定义 diff --git a/src/pkg/atapi/config/param.go b/src/pkg/atapi/config/param.go index 0360b23..c03503e 100644 --- a/src/pkg/atapi/config/param.go +++ b/src/pkg/atapi/config/param.go @@ -1,26 +1,16 @@ package config import ( - "context" "github.com/gin-gonic/gin" "github.com/go-openapi/spec" ) type GinHandleParam struct { - Method string - Path string - HandlerFunc []gin.HandlerFunc - SwaggerPath spec.PathItem -} - -// RunningParam 运行时使用的参数合集 -type RunningParam struct { - Ctx context.Context - Api AutoApiDefined - Action AutoApiActionDefined //当前action信息 - - Abort func() - GinCtx func() *gin.Context + Method string + Path string + HandlerFunc []gin.HandlerFunc + SwaggerPaths map[string]spec.PathItem + SwaggerDefinitions map[string]spec.Schema } // PluginData 插件使用的参数合集 diff --git a/src/pkg/atapi/dataexec/dataexec_mysql.go b/src/pkg/atapi/dataexec/dataexec_mysql.go index a5aa0b5..6ca26cd 100644 --- a/src/pkg/atapi/dataexec/dataexec_mysql.go +++ b/src/pkg/atapi/dataexec/dataexec_mysql.go @@ -17,7 +17,7 @@ type MysqlDbExec struct { tbName string } -func (exec *MysqlDbExec) DoSearch(conditions map[string]interface{}, fields []string, page, size int64) ([]map[string]interface{}, int64, error) { +func (exec *MysqlDbExec) DoSearch(conditions map[string]interface{}, fields []string, page, size int) ([]map[string]interface{}, int64, error) { //fmt.Printf("search params: %+v ,%+v ,%d ,%d\n", params, fields, page, size) //fields = append(fields, "id", "created_at", "updated_at") if conditions == nil { diff --git a/src/pkg/atapi/httpapi/v1/api.go b/src/pkg/atapi/httpapi/v1/api.go index 3dbc135..8312f51 100644 --- a/src/pkg/atapi/httpapi/v1/api.go +++ b/src/pkg/atapi/httpapi/v1/api.go @@ -35,13 +35,11 @@ func (a *AutoApi) GetDataExec() ifs.IAutoApiDataExec { } func (a *AutoApi) SetVars(m map[string]string) { - //TODO implement me - panic("implement me") + a.extends = m } func (a *AutoApi) GetVars() map[string]string { - //TODO implement me - panic("implement me") + return a.extends } func (a *AutoApi) AddPlugin(arr ...ifs.IApiPlugin) { @@ -134,21 +132,18 @@ func (a *AutoApi) Handlers() []config.GinHandleParam { func (a *AutoApi) ginHandler(action actionData) gin.HandlerFunc { return func(ctx *gin.Context) { - abort := false - running := config.RunningParam{ - Ctx: context.Background(), - Api: a.c, - Abort: func() { - abort = true - }, - GinCtx: func() *gin.Context { - return ctx - }, + running := runningData{ + ctx: context.Background(), + api: a.c, + action: action.action, + dataExec: a.dataExec, + ginCtx: ctx, + data: map[string]interface{}{}, } for _, v := range action.usePlugins { v.Do(&running) - if abort { + if running.abort { break } } diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_response.go b/src/pkg/atapi/httpapi/v1/plugins/api_response.go index 0b3665a..b99194f 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_response.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_response.go @@ -4,6 +4,7 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" "gitee.com/captials-team/ubdframe/src/pkg/extendparams" + "github.com/go-openapi/spec" "net/http" ) @@ -11,13 +12,13 @@ type ApiResponsePlugin struct { extends extendparams.CommonExtendParams } -func NewApiResponsePlugin(m map[string]string) (*ApiResponsePlugin, error) { +func NewApiResponsePlugin(m map[string]string) *ApiResponsePlugin { //初始化 p := &ApiResponsePlugin{ extends: m, } - return p, nil + return p } func (p *ApiResponsePlugin) Name() string { @@ -30,7 +31,7 @@ func (p *ApiResponsePlugin) Describe() string { ` } -func (p *ApiResponsePlugin) Do(running *config.RunningParam) { +func (p *ApiResponsePlugin) Do(running ifs.RunningParam) { ctx := running.GinCtx() resp := struct { @@ -43,15 +44,24 @@ func (p *ApiResponsePlugin) Do(running *config.RunningParam) { Data: nil, } - code, ok := running.Ctx.Value("code").(int) + //支持error直接处理 + err, ok := running.GetVar("error").(error) + if ok { + resp.Code = 1 + resp.Msg = err.Error() + ctx.JSON(http.StatusOK, resp) + return + } + + code, ok := running.GetVar("code").(int) if ok { resp.Code = code } - msg, ok := running.Ctx.Value("msg").(string) + msg, ok := running.GetVar("msg").(string) if ok { resp.Msg = msg } - data := running.Ctx.Value("data") + data := running.GetVar("data") if data != nil { resp.Data = data } @@ -76,5 +86,11 @@ type ApiResponseHttpHandler struct { } func (p *ApiResponseHttpHandler) Router() []config.GinHandleParam { - return []config.GinHandleParam{} + return []config.GinHandleParam{ + { + SwaggerDefinitions: map[string]spec.Schema{ + "common.": newSwaggerSpecSchema("object", "错误", ""), + }, + }, + } } diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go index 1907720..cbfd476 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_search.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -1,11 +1,13 @@ package plugins import ( - "context" "fmt" + "gitee.com/captials-team/ubdframe/src/domain/dto/paginate" "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/go-openapi/spec" + "github.com/spf13/cast" "net/http" "strings" ) @@ -24,7 +26,7 @@ type ApiSearchPlugin struct { sizeParam string } -func NewApiSearchPlugin(m map[string]string) (*ApiSearchPlugin, error) { +func NewApiSearchPlugin(m map[string]string) *ApiSearchPlugin { //初始化 p := &ApiSearchPlugin{ respFields: strings.Split(m[ResponseFieldsParamsKey], ","), @@ -32,8 +34,8 @@ func NewApiSearchPlugin(m map[string]string) (*ApiSearchPlugin, error) { fuzzySearch: strings.Split(m[SearchFuzzyFieldsParamsKey], ","), orderRules: strings.Split(m[OrderRulesParamKey], ","), //默认值 - pageParam: "_PageNum", - sizeParam: "_PageSize", + pageParam: "_page_no", + sizeParam: "_page_size", } if m[PageNumParamsKey] != "" { @@ -44,7 +46,7 @@ func NewApiSearchPlugin(m map[string]string) (*ApiSearchPlugin, error) { p.sizeParam = m[PageSizeParamsKey] } - return p, nil + return p } func (p *ApiSearchPlugin) Name() string { @@ -57,12 +59,41 @@ func (p *ApiSearchPlugin) Describe() string { ` } -func (p *ApiSearchPlugin) Do(running *config.RunningParam) { - running.Ctx = context.WithValue(running.Ctx, "data", []int64{ +func (p *ApiSearchPlugin) Do(running ifs.RunningParam) { + running.SetVar("data", []int64{ 1, 2, 3, }) + + var m = make(map[string]interface{}) + running.GinCtx().ShouldBindJSON(&m) + + fields := []string{} + pageNoInt := cast.ToInt(m[p.pageParam]) + pageSizeInt := cast.ToInt(m[p.sizeParam]) + + //todo 增加搜索参数的处理 + + list, count, err := running.DataExec().DoSearch(nil, fields, pageNoInt, pageSizeInt) + if err != nil { + running.SetVar("error", err) + return + } + pager := paginate.Pager{ + Page: pageNoInt, + Size: pageSizeInt, + Total: count, + } + pager.Correct() + + if list == nil { + list = []map[string]interface{}{} + } + running.SetVar("data", map[string]interface{}{ + "list": list, + "paginate": pager, + }) } func (p *ApiSearchPlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { @@ -82,34 +113,20 @@ type ApiSearchHttpHandler struct { data config.PluginData } -func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { - path := fmt.Sprintf("/%s/search", p.data.Api.Name) - - newSchema := func(ty string, desc string, example interface{}) spec.Schema { - return spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: desc, - Type: []string{ty}, - }, - ExtraProps: map[string]interface{}{ - "example": example, - }, - } - } +func (p *ApiSearchHttpHandler) swaggerPathItem() spec.PathItem { searchParams := map[string]spec.Schema{} fields := map[string]spec.Schema{} + + logs.Out.Info("Fields= %+v", p.data.Api.Data.Fields) + logs.Out.Info("Api= %+v", p.data.Api) + for _, v := range p.data.Api.Data.Fields { - searchParams[v.Name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Description: v.ShowName, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: "", - }, - } - fields[v.Name] = newSchema("string", v.ShowName, "example value") + searchParams[v.Name] = newSwaggerSpecSchema("string", v.ShowName, "example") + fields[v.Name] = newSwaggerSpecSchema("string", v.ShowName, "example") } + + searchParams[p.action.pageParam] = newSwaggerSpecSchema("int", "指定页码", 1) + searchParams[p.action.sizeParam] = newSwaggerSpecSchema("int", "指定条目数量", 10) resp1 := spec.Response{ ResponseProps: spec.ResponseProps{ Schema: &spec.Schema{ @@ -137,42 +154,10 @@ func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { Description: "分页数据", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "page_no": spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "当前页码", - Type: []string{"int"}, - }, - ExtraProps: map[string]interface{}{ - "example": 1, - }, - }, - "page_size": spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "单页数量", - Type: []string{"int"}, - }, - ExtraProps: map[string]interface{}{ - "example": 10, - }, - }, - "total": spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "页码总数", - Type: []string{"int"}, - }, - ExtraProps: map[string]interface{}{ - "example": 7, - }, - }, - "count": spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "数据总数", - Type: []string{"int"}, - }, - ExtraProps: map[string]interface{}{ - "example": 64, - }, - }, + "page_no": newSwaggerSpecSchema("int", "当前页码", 1), + "page_size": newSwaggerSpecSchema("int", "单页数量", 10), + "total": newSwaggerSpecSchema("int", "页码总数", 7), + "count": newSwaggerSpecSchema("int", "数据总条数", 68), }, }, }, @@ -184,44 +169,37 @@ func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { }, } - return []config.GinHandleParam{ - { - Method: http.MethodPost, - Path: path, - SwaggerPath: spec.PathItem{ - PathItemProps: spec.PathItemProps{ - Post: &spec.Operation{ - OperationProps: spec.OperationProps{ - Summary: "搜索列表", - Description: "搜索列表,根据搜索参数返回列表和分页信息", - Produces: []string{"application/json"}, - Schemes: nil, - Tags: []string{p.data.Api.Name}, - Parameters: []spec.Parameter{ - { - ParamProps: spec.ParamProps{ - Description: "搜索参数", - Name: "param", - In: "body", - Required: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "参数项内容", - Type: []string{"object"}, - Properties: searchParams, - }, - }, - AllowEmptyValue: true, + return spec.PathItem{ + PathItemProps: spec.PathItemProps{ + Post: &spec.Operation{ + OperationProps: spec.OperationProps{ + Summary: "搜索列表", + Description: "搜索列表,根据搜索参数返回列表和分页信息", + Produces: []string{"application/json"}, + Schemes: nil, + Tags: []string{p.data.Api.Name}, + Parameters: []spec.Parameter{ + { + ParamProps: spec.ParamProps{ + Description: "搜索参数", + Name: "param", + In: "body", + Required: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: searchParams, }, }, + AllowEmptyValue: true, }, - Responses: &spec.Responses{ - ResponsesProps: spec.ResponsesProps{ - Default: nil, - StatusCodeResponses: map[int]spec.Response{ - 200: resp1, - }, - }, + }, + }, + Responses: &spec.Responses{ + ResponsesProps: spec.ResponsesProps{ + Default: nil, + StatusCodeResponses: map[int]spec.Response{ + 200: resp1, }, }, }, @@ -230,3 +208,29 @@ func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { }, } } + +func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { + path := fmt.Sprintf("/%s/search", p.data.Api.Name) + + return []config.GinHandleParam{ + { + Method: http.MethodPost, + Path: path, + SwaggerPaths: map[string]spec.PathItem{ + path: p.swaggerPathItem(), + }, + }, + } +} + +func newSwaggerSpecSchema(ty string, desc string, example interface{}) spec.Schema { + return spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: desc, + Type: []string{ty}, + }, + ExtraProps: map[string]interface{}{ + "example": example, + }, + } +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go index 7ac3ad2..4e05682 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go +++ b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go @@ -10,11 +10,11 @@ import ( type RateLimitPlugin struct { } -func NewRateLimitPlugin(m map[string]string) (*RateLimitPlugin, error) { +func NewRateLimitPlugin(m map[string]string) *RateLimitPlugin { //初始化 p := &RateLimitPlugin{} - return p, nil + return p } func (p *RateLimitPlugin) Name() string { @@ -27,7 +27,7 @@ func (p *RateLimitPlugin) Describe() string { ` } -func (p *RateLimitPlugin) Do(running *config.RunningParam) { +func (p *RateLimitPlugin) Do(running ifs.RunningParam) { ip := utils.GetRequestIp(running.GinCtx().Request) if utils.TrueScopeRand(0, 100) < 50 { running.GinCtx().String(http.StatusOK, ip+",rate limit") diff --git a/src/pkg/atapi/httpapi/v1/running.go b/src/pkg/atapi/httpapi/v1/running.go new file mode 100644 index 0000000..8a38d60 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/running.go @@ -0,0 +1,57 @@ +package v1 + +import ( + "context" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "github.com/gin-gonic/gin" +) + +type runningData struct { + ctx context.Context + api config.AutoApiDefined + action config.AutoApiActionDefined + dataExec ifs.IDataExecCURD + abort bool + ginCtx *gin.Context + + data map[string]interface{} +} + +func (r *runningData) Ctx() context.Context { + return r.ctx +} + +func (r *runningData) ActionConfig() config.AutoApiActionDefined { + return r.action +} + +func (r *runningData) Abort() { + r.abort = true +} + +func (r *runningData) GinCtx() *gin.Context { + return r.ginCtx +} + +func (r *runningData) DataExec() ifs.IDataExecCURD { + return r.dataExec +} + +func (r *runningData) Config() config.AutoApiDefined { + return r.api +} + +func (r *runningData) GetVar(k string) interface{} { + if r.data == nil { + return nil + } + return r.data[k] +} + +func (r *runningData) SetVar(k string, v interface{}) { + if r.data == nil { + r.data = map[string]interface{}{} + } + r.data[k] = v +} diff --git a/src/pkg/atapi/ifs/data_exec.go b/src/pkg/atapi/ifs/data_exec.go index a646b02..4ce6185 100644 --- a/src/pkg/atapi/ifs/data_exec.go +++ b/src/pkg/atapi/ifs/data_exec.go @@ -15,7 +15,7 @@ type IDataExecBase interface { } type IDataExecCURD interface { - DoSearch(params map[string]interface{}, fields []string, page, size int64) ([]map[string]interface{}, int64, error) + DoSearch(params map[string]interface{}, fields []string, page, size int) ([]map[string]interface{}, int64, error) DoQuery(params map[string]interface{}, fields []string) (map[string]interface{}, error) DoCreate(params map[string]interface{}) (int64, error) DoUpdate(id int64, params map[string]interface{}) (int64, error) diff --git a/src/pkg/atapi/ifs/http_api.go b/src/pkg/atapi/ifs/http_api.go index 04f4b14..169b4ac 100644 --- a/src/pkg/atapi/ifs/http_api.go +++ b/src/pkg/atapi/ifs/http_api.go @@ -38,3 +38,9 @@ type IVars interface { SetVars(map[string]string) //设置可替换变量值 GetVars() map[string]string } + +type IAutoApiHelp interface { + Config() config.AutoApiDefined + GetDataExec() IAutoApiDataExec + GetVars() map[string]string //全局参数 +} diff --git a/src/pkg/atapi/ifs/plugin.go b/src/pkg/atapi/ifs/plugin.go index 6d639c8..6e4ff5f 100644 --- a/src/pkg/atapi/ifs/plugin.go +++ b/src/pkg/atapi/ifs/plugin.go @@ -8,7 +8,7 @@ type IApiPlugin interface { Name() string //plugin名称 Describe() string //插件具体描述内容 - Do(param *config.RunningParam) + Do(param RunningParam) HttpHandler(api config.PluginData) IApiPluginHttpHandler //该插件的具体handler类 CmdHandler(api config.PluginData) IApiPluginCmdHandler //该插件的具体handler类 @@ -16,7 +16,7 @@ type IApiPlugin interface { // IApiPluginHandler 插件http场景下需支持的处理 type IApiPluginHandler interface { - Do(param *config.RunningParam) + Do(param RunningParam) } // IApiPluginHttpHandler 插件http场景下需支持的处理 diff --git a/src/pkg/atapi/ifs/running.go b/src/pkg/atapi/ifs/running.go new file mode 100644 index 0000000..e27147d --- /dev/null +++ b/src/pkg/atapi/ifs/running.go @@ -0,0 +1,21 @@ +package ifs + +import ( + "context" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "github.com/gin-gonic/gin" +) + +// RunningParam 运行时使用的参数合集 +type RunningParam interface { + Ctx() context.Context + Config() config.AutoApiDefined + ActionConfig() config.AutoApiActionDefined //当前action信息 + GinCtx() *gin.Context + DataExec() IDataExecCURD + + Abort() //终止处理 + + GetVar(string) interface{} + SetVar(string, interface{}) +} diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go index 87e1642..b4ea42c 100644 --- a/src/pkg/atapi/manager.go +++ b/src/pkg/atapi/manager.go @@ -15,6 +15,8 @@ import ( "strings" ) +type pluginFunc func(map[string]string) ifs.IApiPlugin + // AtApisManager at apis管理器 type AtApisManager struct { //配置 @@ -29,6 +31,8 @@ type AtApisManager struct { //所有atapis httpApis map[string]ifs.IAutoApi + extraPlugins []pluginFunc + logv1.InvokeLog } @@ -54,6 +58,10 @@ func (mag *AtApisManager) SetDataExec(store ifs.IAutoApiDataExec) { mag.store = store } +func (mag *AtApisManager) AddPlugin(f pluginFunc) { + mag.extraPlugins = append(mag.extraPlugins, f) +} + func (mag *AtApisManager) Initial() error { for _, def := range mag.config.Apis { var api config.AutoApiDefined = def @@ -84,8 +92,10 @@ func (mag *AtApisManager) StartByGin(g gin.IRouter) error { mag.Info("%s start", v.Code()) handlers := v.Handlers() for _, h := range handlers { - g.Handle(h.Method, h.Path, h.HandlerFunc...) - mag.Info("register router: %s,%s", h.Method, h.Path) + if h.Method != "" && len(h.HandlerFunc) > 0 { + g.Handle(h.Method, h.Path, h.HandlerFunc...) + mag.Info("register router: %s,%s", h.Method, h.Path) + } } } return nil @@ -95,16 +105,29 @@ func (mag *AtApisManager) StartBySwagger(w *spec.Swagger) error { if w.Paths == nil { w.Paths = &spec.Paths{} } - + if w.Definitions == nil { + w.Definitions = spec.Definitions{} + } if w.Paths.Paths == nil { w.Paths.Paths = make(map[string]spec.PathItem) } - for _, v := range mag.httpApis { - handlers := v.Handlers() - mag.Info("%s swagger len=%d", v.Code(), len(handlers)) + for _, api := range mag.httpApis { + handlers := api.Handlers() + mag.Info("%s swagger len=%d", api.Code(), len(handlers)) for _, h := range handlers { - w.Paths.Paths[h.Path] = h.SwaggerPath - mag.Info("\tadd swagger path: %s", h.Path) + //合并swagger + if h.SwaggerPaths != nil { + for k, v := range h.SwaggerPaths { + w.Paths.Paths[k] = v + mag.Info("\tadd swagger path: %s", k) + } + } + if h.SwaggerDefinitions != nil { + for k, v := range h.SwaggerDefinitions { + w.Definitions[k] = v + mag.Info("\tadd swagger definition: %s", k) + } + } } } return nil @@ -126,12 +149,18 @@ func (mag *AtApisManager) newAutoApiV1(apiDef config.AutoApiDefined) (*v1.AutoAp } api.SetDataExec(mag.store.Copy()) //api.SetVars(vars) + //内置plugin api.AddPlugin( - &plugins.RateLimitPlugin{}) + plugins.NewRateLimitPlugin(mag.config.ExtendParams), + ) api.AddPlugin( - &plugins.ApiSearchPlugin{}, - &plugins.ApiResponsePlugin{}, + plugins.NewApiResponsePlugin(mag.config.ExtendParams), + plugins.NewApiSearchPlugin(mag.config.ExtendParams), ) + //自定义插件 + for _, tmp := range mag.extraPlugins { + api.AddPlugin(tmp(mag.config.ExtendParams)) + } if err := api.Init(); err != nil { return nil, err diff --git a/src/pkg/atapi/manager_test.go b/src/pkg/atapi/manager_test.go index e1e2cd9..d1b04e6 100644 --- a/src/pkg/atapi/manager_test.go +++ b/src/pkg/atapi/manager_test.go @@ -1,6 +1,7 @@ package atapi import ( + "encoding/json" "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/domain/configstc" @@ -8,19 +9,41 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/atapi/dataexec" "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/gin-gonic/gin" + "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" "testing" "time" ) func TestNewManager(t *testing.T) { + g := gin.New() + mag := getTestAtApiManager(t) + + common.ErrPanic(mag.StartByGin(g)) + + common.ErrPanic(g.Run(":10000")) + time.Sleep(time.Hour) +} + +func TestAtManager_swagger(t *testing.T) { + mag := getTestAtApiManager(t) + + swg := &spec.Swagger{} + common.ErrPanic(mag.StartBySwagger(swg)) + + s, err := json.MarshalIndent(swg, "", "\t") + common.ErrPanic(err) + + t.Logf("\t%s", s) +} + +func getTestAtApiManager(t *testing.T) *AtApisManager { c := config.AutoApiConfig{ Apis: []config.AutoApiDefined{ {LoadFile: utils.AbsPath(1) + "./tests/v1/org.yaml"}, {LoadFile: utils.AbsPath(1) + "./tests/v1/demo.yaml"}, }, } - g := gin.New() dataEx, err := dataexec.NewMysqlDbExec(configstc.DBConfig{ DbHost: "192.168.149.128", DbPort: "3306", @@ -36,8 +59,6 @@ func TestNewManager(t *testing.T) { mag.AddLogger(logs.Out.CallerSkip(1)) common.ErrPanic(mag.Initial()) - common.ErrPanic(mag.StartByGin(g)) - common.ErrPanic(g.Run(":10000")) - time.Sleep(time.Hour) + return mag } diff --git a/src/pkg/atapi/tests/v1/demo.yaml b/src/pkg/atapi/tests/v1/demo.yaml index 210a609..f74ffe1 100644 --- a/src/pkg/atapi/tests/v1/demo.yaml +++ b/src/pkg/atapi/tests/v1/demo.yaml @@ -8,7 +8,7 @@ ShowName: "DEMO" # Plugins,定义使用的插件 Actions: - Name: "搜索接口" - Describe: "搜索接口" + Describe: "搜索传参并返回列表、分页信息" Enable: true Plugins: - Name: "RATE_LIMIT" @@ -22,25 +22,26 @@ Actions: path: "/demo/search" #是否自动创建表数据等信息 -AutoTable: true -DataName: "" #自定义数据存储的标识名称 -Fields: - - Name: title - SqlType: varchar(64) - Default: "" - Comment: 标题 - - Name: type - SqlType: tinyint unsigned - Default: "1" - Comment: "类型,0:无,1:文本,2:图片" - - Name: content - SqlType: varchar(1000) - Default: "1" - Comment: 块内容 - - Name: status - SqlType: tinyint unsigned - Default: "1" - Comment: 状态,0:无,1:有效,2:禁用 -Comment: DEMO管理 +Data: + DataName: "" #自定义数据存储的标识名称,为空则会自动根据api名生成名称 + AutoTable: true + Fields: + - Name: title + SqlType: varchar(64) + Default: "" + Comment: 标题 + - Name: type + SqlType: tinyint unsigned + Default: "1" + Comment: "类型,0:无,1:文本,2:图片" + - Name: content + SqlType: varchar(1000) + Default: "1" + Comment: 块内容 + - Name: status + SqlType: tinyint unsigned + Default: "1" + Comment: 状态,0:无,1:有效,2:禁用 + Comment: DEMO管理 diff --git a/src/pkg/atapi/tests/v1/org.yaml b/src/pkg/atapi/tests/v1/org.yaml index efa27f9..b1a25df 100644 --- a/src/pkg/atapi/tests/v1/org.yaml +++ b/src/pkg/atapi/tests/v1/org.yaml @@ -8,7 +8,7 @@ ShowName: "组织" # Plugins,定义使用的插件 Actions: - Name: "搜索接口" - Describe: "搜索接口" + Describe: "搜索传参并返回列表、分页信息" Enable: true Plugins: - Name: "API_SEARCH" @@ -18,23 +18,24 @@ Actions: ExtendParam: limit_rate: 2 -AutoTable: true -DataName: "" -Fields: - - Name: saas_id - SqlType: varchar(20) - Default: "" - Comment: saas model - - Name: name - SqlType: varchar(64) - Default: "未命名" - Comment: 组织名 - - Name: pid - SqlType: int unsigned - Default: "0" - Comment: pid - - Name: status - SqlType: tinyint unsigned - Default: "1" - Comment: 状态,0:无,1:有效,2:禁用 -Comment: 组织管理 \ No newline at end of file +Data: + DataName: "" + AutoTable: true + Fields: + - Name: saas_id + SqlType: varchar(20) + Default: "" + Comment: saas model + - Name: name + SqlType: varchar(64) + Default: "未命名" + Comment: 组织名 + - Name: pid + SqlType: int unsigned + Default: "0" + Comment: pid + - Name: status + SqlType: tinyint unsigned + Default: "1" + Comment: 状态,0:无,1:有效,2:禁用 + Comment: 组织管理 \ No newline at end of file -- Gitee From 8efb6ede20a442299a748824b8dcd55877f919e8 Mon Sep 17 00:00:00 2001 From: sage Date: Thu, 14 Aug 2025 19:54:21 +0800 Subject: [PATCH 26/83] add api create --- .../atapi/httpapi/v1/plugins/api_create.go | 175 ++++++++++++++++++ src/pkg/atapi/manager.go | 3 +- src/pkg/atapi/tests/v1/demo.yaml | 13 +- 3 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/pkg/atapi/httpapi/v1/plugins/api_create.go diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_create.go b/src/pkg/atapi/httpapi/v1/plugins/api_create.go new file mode 100644 index 0000000..ecdbdf3 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/plugins/api_create.go @@ -0,0 +1,175 @@ +package plugins + +import ( + "fmt" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" + "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" + "gitee.com/captials-team/ubdframe/src/pkg/logs" + "github.com/go-openapi/spec" + "net/http" + "strings" +) + +type ApiCreatePlugin struct { + //请求参数(可搜索条件字段) + matchSearch []string //匹配查询参数 + fuzzySearch []string // + orderRules []string + + //返回字段 + respFields []string + + //分页用参数 + pageParam string + sizeParam string +} + +func NewApiCreatePlugin(m map[string]string) *ApiCreatePlugin { + //初始化 + p := &ApiCreatePlugin{} + + return p +} + +func (p *ApiCreatePlugin) Name() string { + return "API_CREATE" +} + +func (p *ApiCreatePlugin) Describe() string { + return ` +提供创建数据的能力,可以自定义创建数据 +` +} + +func (p *ApiCreatePlugin) Do(running ifs.RunningParam) { + var m = make(map[string]interface{}) + running.GinCtx().ShouldBindJSON(&m) + + create := map[string]interface{}{} + for _, v := range running.Config().Data.Fields { + val, exist := m[v.Name] + if !exist { + running.SetVar("error", fmt.Errorf("%s must post", v.Name)) + return + } + create[v.Name] = val + } + + //todo 增加搜索参数的处理 + + id, err := running.DataExec().DoCreate(create) + if err != nil { + running.SetVar("error", err) + return + } + running.SetVar("data", map[string]interface{}{ + "id": id, + }) +} + +func (p *ApiCreatePlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { + return &ApiCreateHttpHandler{ + action: p, + data: data, + } +} + +func (p *ApiCreatePlugin) CmdHandler(param config.PluginData) ifs.IApiPluginCmdHandler { + return nil +} + +type ApiCreateHttpHandler struct { + action *ApiCreatePlugin + //运行时参数变量 + data config.PluginData +} + +func (p *ApiCreateHttpHandler) swaggerPathItem() spec.PathItem { + postParams := map[string]spec.Schema{} + fields := map[string]spec.Schema{} + + logs.Out.Info("Fields= %+v", p.data.Api.Data.Fields) + logs.Out.Info("Api= %+v", p.data.Api) + + for _, v := range p.data.Api.Data.Fields { + + ty := "string" + var example interface{} = "example" + if strings.Index(v.SqlType, "int") >= 0 { + ty = "int" + example = 1 + } + + postParams[v.Name] = newSwaggerSpecSchema(ty, v.ShowName, example) + fields[v.Name] = newSwaggerSpecSchema(ty, v.ShowName, example) + } + + resp1 := spec.Response{ + ResponseProps: spec.ResponseProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Description: "响应Data", + Properties: map[string]spec.Schema{ + "id": newSwaggerSpecSchema("int", "条目id", 1), + }, + }, + }, + Headers: nil, + Examples: nil, + }, + } + + return spec.PathItem{ + PathItemProps: spec.PathItemProps{ + Post: &spec.Operation{ + OperationProps: spec.OperationProps{ + Summary: "新建", + Description: "新建操作,根据提交参数创建数据并返回条目id", + Produces: []string{"application/json"}, + Schemes: nil, + Tags: []string{p.data.Api.Name}, + Parameters: []spec.Parameter{ + { + ParamProps: spec.ParamProps{ + Description: "传参", + Name: "param", + In: "body", + Required: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: postParams, + }, + }, + AllowEmptyValue: true, + }, + }, + }, + Responses: &spec.Responses{ + ResponsesProps: spec.ResponsesProps{ + Default: nil, + StatusCodeResponses: map[int]spec.Response{ + 200: resp1, + }, + }, + }, + }, + }, + }, + } +} + +func (p *ApiCreateHttpHandler) Router() []config.GinHandleParam { + path := fmt.Sprintf("/%s/create", p.data.Api.Name) + + return []config.GinHandleParam{ + { + Method: http.MethodPost, + Path: path, + SwaggerPaths: map[string]spec.PathItem{ + path: p.swaggerPathItem(), + }, + }, + } +} diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go index b4ea42c..7d3676f 100644 --- a/src/pkg/atapi/manager.go +++ b/src/pkg/atapi/manager.go @@ -154,8 +154,9 @@ func (mag *AtApisManager) newAutoApiV1(apiDef config.AutoApiDefined) (*v1.AutoAp plugins.NewRateLimitPlugin(mag.config.ExtendParams), ) api.AddPlugin( - plugins.NewApiResponsePlugin(mag.config.ExtendParams), plugins.NewApiSearchPlugin(mag.config.ExtendParams), + plugins.NewApiCreatePlugin(mag.config.ExtendParams), + plugins.NewApiResponsePlugin(mag.config.ExtendParams), ) //自定义插件 for _, tmp := range mag.extraPlugins { diff --git a/src/pkg/atapi/tests/v1/demo.yaml b/src/pkg/atapi/tests/v1/demo.yaml index f74ffe1..1234ea2 100644 --- a/src/pkg/atapi/tests/v1/demo.yaml +++ b/src/pkg/atapi/tests/v1/demo.yaml @@ -8,7 +8,6 @@ ShowName: "DEMO" # Plugins,定义使用的插件 Actions: - Name: "搜索接口" - Describe: "搜索传参并返回列表、分页信息" Enable: true Plugins: - Name: "RATE_LIMIT" @@ -18,8 +17,18 @@ Actions: ExtendParam: fields: "id,title,type,status" - Name: "API_RESPONSE" + + - Name: "创建接口" + Enable: true + Plugins: + - Name: "RATE_LIMIT" ExtendParam: - path: "/demo/search" + limit_rate: 2 + - Name: "API_CREATE" + ExtendParam: + fields: "id,title,type,status" + - Name: "API_RESPONSE" + #是否自动创建表数据等信息 Data: -- Gitee From 678cab60d5cfb553da30c68dd59090e096624687 Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 15 Aug 2025 18:02:28 +0800 Subject: [PATCH 27/83] modify api handler --- src/common/utils/array.go | 90 +++++++++++++++++ src/common/utils/array_test.go | 76 ++++++++++++++ src/pkg/atapi/config/config.go | 16 ++- src/pkg/atapi/dataexec/dataexec_mysql.go | 19 ++-- src/pkg/atapi/httpapi/v1/api.go | 27 +++-- .../atapi/httpapi/v1/plugins/api_create.go | 29 +++--- .../atapi/httpapi/v1/plugins/api_response.go | 22 +++-- .../atapi/httpapi/v1/plugins/api_search.go | 99 ++++++++++--------- src/pkg/atapi/httpapi/v1/plugins/help.go | 15 +++ .../atapi/httpapi/v1/plugins/rate_limit.go | 13 ++- src/pkg/atapi/httpapi/v1/running.go | 11 --- src/pkg/atapi/ifs/plugin.go | 8 +- src/pkg/atapi/ifs/running.go | 3 - src/pkg/atapi/manager.go | 8 +- src/pkg/atapi/manager_test.go | 1 + src/pkg/atapi/tests/v1/demo.yaml | 3 +- src/pkg/atapi/tests/v1/org.yaml | 13 +-- 17 files changed, 325 insertions(+), 128 deletions(-) create mode 100644 src/common/utils/array.go create mode 100644 src/common/utils/array_test.go create mode 100644 src/pkg/atapi/httpapi/v1/plugins/help.go diff --git a/src/common/utils/array.go b/src/common/utils/array.go new file mode 100644 index 0000000..cf462d0 --- /dev/null +++ b/src/common/utils/array.go @@ -0,0 +1,90 @@ +package utils + +import ( + "fmt" +) + +// ArrayIntersection 取数组交集 +func ArrayIntersection[T interface{}](slices ...[]T) []T { + elemCount := make(map[string]int) + elements := make(map[string]T) + for _, item := range slices { + temp := make(map[string]bool) + for _, v := range item { + k := fmt.Sprintf("%+v", v) + elements[k] = v + if _, exist := temp[k]; exist { + // 避免当前切片内重复元素干扰 + continue + } + temp[k] = true + if _, exists := elemCount[k]; !exists { + elemCount[k] = 0 + } + //计数 + elemCount[k]++ + } + } + + var intersection []T + for k, v := range elemCount { + //计数值=总数组数则是所有数都存在的 + if v == len(slices) { + intersection = append(intersection, elements[k]) + } + } + + return intersection +} + +// ArrayDifference 取数组差集 +func ArrayDifference[T interface{}](slices ...[]T) []T { + elemCount := make(map[string]int) + elements := make(map[string]T) + for _, item := range slices { + temp := make(map[string]bool) + for _, v := range item { + k := fmt.Sprintf("%+v", v) + fmt.Printf("%s\n", k) + elements[k] = v + if _, exist := temp[k]; exist { + // 避免当前切片内重复元素干扰 + continue + } + temp[k] = true + if _, exists := elemCount[k]; !exists { + elemCount[k] = 0 + } + //计数 + elemCount[k]++ + } + } + + var intersection []T + for k, v := range elemCount { + //计数值=1表示只出现过一次的,即为差值 + if v == 1 { + intersection = append(intersection, elements[k]) + } + } + + return intersection +} + +// ArrayUnion 取数组并集 +func ArrayUnion[T interface{}](slices ...[]T) []T { + var union []T + m := make(map[string]bool) + for _, item := range slices { + for _, v := range item { + k := fmt.Sprintf("%+v", v) + if _, exist := m[k]; exist { + continue + } + + m[k] = true + union = append(union, v) + } + } + return union +} diff --git a/src/common/utils/array_test.go b/src/common/utils/array_test.go new file mode 100644 index 0000000..0df2e80 --- /dev/null +++ b/src/common/utils/array_test.go @@ -0,0 +1,76 @@ +package utils + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestArrayIntersection(t *testing.T) { + + array := ArrayIntersection([]int{1, 2, 3}, []int{1, 2, 3, 4}) + t.Logf("Intersection= %+v", array) + assert.Equal(t, len(array), 3) + assert.Equal(t, array[0], 1) + assert.Equal(t, array[1], 2) + assert.Equal(t, array[2], 3) + + array = ArrayIntersection([]int{1, 2, 3}, []int{4, 5, 6}) + t.Logf("Intersection= %+v", array) + assert.Equal(t, len(array), 0) + + array = ArrayIntersection([]int{1, 2, 4}, []int{4, 5, 6}) + t.Logf("Intersection= %+v", array) + assert.Equal(t, len(array), 1) + assert.Equal(t, array[0], 4) +} + +func TestArrayDifference(t *testing.T) { + + array := ArrayDifference([]int{1, 2, 3}, []int{1, 2, 3, 4}) + t.Logf("Difference= %+v", array) + assert.Equal(t, len(array), 1) + assert.Equal(t, array[0], 4) + + array = ArrayDifference([]int{1, 2, 3}, []int{4, 5, 6}) + t.Logf("Difference= %+v", array) + assert.Equal(t, len(array), 6) + assert.Equal(t, array[0], 1) + assert.Equal(t, array[1], 2) + assert.Equal(t, array[2], 3) + assert.Equal(t, array[5], 6) + + array = ArrayDifference([]int{1, 2, 4}, []int{4, 5, 6}) + t.Logf("Difference= %+v", array) + assert.Equal(t, len(array), 4) + assert.Equal(t, array[0], 1) + assert.Equal(t, array[1], 2) + assert.Equal(t, array[2], 5) +} + +func TestArrayUnion(t *testing.T) { + + array := ArrayUnion([]int{1, 2, 3}, []int{1, 2, 3, 4}) + t.Logf("Union= %+v", array) + assert.Equal(t, len(array), 4) + assert.Equal(t, array[0], 1) + assert.Equal(t, array[1], 2) + assert.Equal(t, array[2], 3) + assert.Equal(t, array[3], 4) + + array = ArrayUnion([]int{1, 2, 3}, []int{4, 5, 6}) + t.Logf("Union= %+v", array) + assert.Equal(t, len(array), 6) + assert.Equal(t, array[0], 1) + assert.Equal(t, array[1], 2) + assert.Equal(t, array[2], 3) + assert.Equal(t, array[5], 6) + + array = ArrayUnion([]int{1, 2, 4}, []int{4, 5, 6}) + t.Logf("Union= %+v", array) + assert.Equal(t, len(array), 5) + assert.Equal(t, array[0], 1) + assert.Equal(t, array[1], 2) + assert.Equal(t, array[2], 4) + assert.Equal(t, array[3], 5) + assert.Equal(t, array[4], 6) +} diff --git a/src/pkg/atapi/config/config.go b/src/pkg/atapi/config/config.go index c70313b..eb36212 100644 --- a/src/pkg/atapi/config/config.go +++ b/src/pkg/atapi/config/config.go @@ -35,14 +35,26 @@ type AutoApiDataDefined struct { Comment string `json:"Comment" yaml:"Comment"` //数据名称,用于给数据区分存储来源,如表名 } +func (a AutoApiDataDefined) FieldNames() []string { + var fs []string + for _, v := range a.Fields { + fs = append(fs, v.Name) + } + return fs +} + // AutoApiFieldDefined 单个autoApi字段的定义 type AutoApiFieldDefined struct { Name string `json:"Name" yaml:"Name"` //字段名,如:name ShowName string `json:"ShowName" yaml:"ShowName"` //展示名称(一般为中文) - //类型, 可为: int,int64,string, + //类型, 可为: + // varchar(32) + // bigint unsigned + // int unsigned + // tinyint unsigned + // datetime(3) SqlType string `json:"SqlType" yaml:"SqlType"` - //Size int `json:"Size" yaml:"Size"` //大小,配合SqlType,可定义类型的长度 Default string `json:"Default" yaml:"Default"` //默认值 Comment string `json:"Comment" yaml:"Comment"` //备注/注释说明 diff --git a/src/pkg/atapi/dataexec/dataexec_mysql.go b/src/pkg/atapi/dataexec/dataexec_mysql.go index 6ca26cd..89aa7c1 100644 --- a/src/pkg/atapi/dataexec/dataexec_mysql.go +++ b/src/pkg/atapi/dataexec/dataexec_mysql.go @@ -152,21 +152,22 @@ func (exec *MysqlDbExec) DoQuery(conditions map[string]interface{}, fields []str } func (exec *MysqlDbExec) DoCreate(params map[string]interface{}) (int64, error) { - var fieldArr = []string{} - var valueArr = []interface{}{} + var values = map[string]interface{}{} + for k, v := range params { - fieldArr = append(fieldArr, fmt.Sprintf("`%s`=?", k)) - valueArr = append(valueArr, v) + values[k] = v } - fieldStr := strings.Join(fieldArr, ",") - - insertSql := fmt.Sprintf("INSERT INTO %s SET %s", exec.tbName, fieldStr) - ret := exec.db.Exec(insertSql, valueArr...) - + ret := exec.db.Table(exec.tbName).Create(values) if ret.Error != nil { return 0, ret.Error } + //logs.Out.Info("Values= %+v", values["@id"]) + //主键id值 + id, exist := values["@id"] + if exist { + return id.(int64), nil + } return ret.RowsAffected, nil } diff --git a/src/pkg/atapi/httpapi/v1/api.go b/src/pkg/atapi/httpapi/v1/api.go index 8312f51..213eb72 100644 --- a/src/pkg/atapi/httpapi/v1/api.go +++ b/src/pkg/atapi/httpapi/v1/api.go @@ -88,7 +88,11 @@ func (a *AutoApi) _initActions() { logs.Out.Info("[%s] action: %s init", a.c.Name, action.Name) for _, plugin := range action.Plugins { if tmp, exist := supportPluginMap[plugin.Name]; exist { - item.usePlugins = append(item.usePlugins, tmp) + item.usePlugins = append(item.usePlugins, tmp.Factory(&config.PluginData{ + Api: a.c, + Action: action, + Plugin: plugin, + })) item.usePluginsConfig = append(item.usePluginsConfig, plugin) logs.Out.Info("\tplugin[%s] load", plugin.Name) @@ -106,12 +110,8 @@ func (a *AutoApi) _initActions() { func (a *AutoApi) Handlers() []config.GinHandleParam { var routers []config.GinHandleParam for _, act := range a.actions { - for i, plugin := range act.usePlugins { - h := plugin.HttpHandler(config.PluginData{ - Api: a.c, - Action: act.action, - Plugin: act.usePluginsConfig[i], - }) + for _, plugin := range act.usePlugins { + h := plugin.HttpHandler() if h == nil { continue } @@ -130,18 +130,17 @@ func (a *AutoApi) Handlers() []config.GinHandleParam { return routers } -func (a *AutoApi) ginHandler(action actionData) gin.HandlerFunc { +func (a *AutoApi) ginHandler(act actionData) gin.HandlerFunc { + return func(ctx *gin.Context) { + running := runningData{ ctx: context.Background(), - api: a.c, - action: action.action, - dataExec: a.dataExec, ginCtx: ctx, + dataExec: a.dataExec, data: map[string]interface{}{}, } - - for _, v := range action.usePlugins { + for _, v := range act.usePlugins { v.Do(&running) if running.abort { break @@ -154,6 +153,6 @@ func (a *AutoApi) ginHandler(action actionData) gin.HandlerFunc { type actionData struct { action config.AutoApiActionDefined - usePlugins []ifs.IApiPlugin + usePlugins []ifs.IApiPluginFactory usePluginsConfig []config.AutoApiActionPluginDefined } diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_create.go b/src/pkg/atapi/httpapi/v1/plugins/api_create.go index ecdbdf3..2122344 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_create.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_create.go @@ -11,20 +11,10 @@ import ( ) type ApiCreatePlugin struct { - //请求参数(可搜索条件字段) - matchSearch []string //匹配查询参数 - fuzzySearch []string // - orderRules []string - - //返回字段 - respFields []string - - //分页用参数 - pageParam string - sizeParam string + conf *config.PluginData } -func NewApiCreatePlugin(m map[string]string) *ApiCreatePlugin { +func NewApiCreatePlugin() *ApiCreatePlugin { //初始化 p := &ApiCreatePlugin{} @@ -41,12 +31,17 @@ func (p *ApiCreatePlugin) Describe() string { ` } +func (p *ApiCreatePlugin) Factory(conf *config.PluginData) ifs.IApiPluginFactory { + fa := &ApiCreatePlugin{conf: conf} + return fa +} + func (p *ApiCreatePlugin) Do(running ifs.RunningParam) { var m = make(map[string]interface{}) running.GinCtx().ShouldBindJSON(&m) create := map[string]interface{}{} - for _, v := range running.Config().Data.Fields { + for _, v := range p.conf.Api.Data.Fields { val, exist := m[v.Name] if !exist { running.SetVar("error", fmt.Errorf("%s must post", v.Name)) @@ -67,21 +62,21 @@ func (p *ApiCreatePlugin) Do(running ifs.RunningParam) { }) } -func (p *ApiCreatePlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { +func (p *ApiCreatePlugin) HttpHandler() ifs.IApiPluginHttpHandler { return &ApiCreateHttpHandler{ action: p, - data: data, + data: p.conf, } } -func (p *ApiCreatePlugin) CmdHandler(param config.PluginData) ifs.IApiPluginCmdHandler { +func (p *ApiCreatePlugin) CmdHandler() ifs.IApiPluginCmdHandler { return nil } type ApiCreateHttpHandler struct { action *ApiCreatePlugin //运行时参数变量 - data config.PluginData + data *config.PluginData } func (p *ApiCreateHttpHandler) swaggerPathItem() spec.PathItem { diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_response.go b/src/pkg/atapi/httpapi/v1/plugins/api_response.go index b99194f..d074d0a 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_response.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_response.go @@ -3,20 +3,17 @@ package plugins import ( "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" - "gitee.com/captials-team/ubdframe/src/pkg/extendparams" "github.com/go-openapi/spec" "net/http" ) type ApiResponsePlugin struct { - extends extendparams.CommonExtendParams + conf *config.PluginData } -func NewApiResponsePlugin(m map[string]string) *ApiResponsePlugin { +func NewApiResponsePlugin() *ApiResponsePlugin { //初始化 - p := &ApiResponsePlugin{ - extends: m, - } + p := &ApiResponsePlugin{} return p } @@ -31,6 +28,11 @@ func (p *ApiResponsePlugin) Describe() string { ` } +func (p *ApiResponsePlugin) Factory(conf *config.PluginData) ifs.IApiPluginFactory { + fa := &ApiResponsePlugin{conf: conf} + return fa +} + func (p *ApiResponsePlugin) Do(running ifs.RunningParam) { ctx := running.GinCtx() @@ -69,20 +71,20 @@ func (p *ApiResponsePlugin) Do(running ifs.RunningParam) { ctx.JSON(http.StatusOK, resp) } -func (p *ApiResponsePlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { +func (p *ApiResponsePlugin) HttpHandler() ifs.IApiPluginHttpHandler { return &ApiResponseHttpHandler{ action: p, - data: data, + data: p.conf, } } -func (p *ApiResponsePlugin) CmdHandler(api config.PluginData) ifs.IApiPluginCmdHandler { +func (p *ApiResponsePlugin) CmdHandler() ifs.IApiPluginCmdHandler { return nil } type ApiResponseHttpHandler struct { action *ApiResponsePlugin - data config.PluginData + data *config.PluginData } func (p *ApiResponseHttpHandler) Router() []config.GinHandleParam { diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go index cbfd476..4a27a62 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_search.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -2,6 +2,7 @@ package plugins import ( "fmt" + "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/domain/dto/paginate" "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" @@ -24,27 +25,13 @@ type ApiSearchPlugin struct { //分页用参数 pageParam string sizeParam string + + conf *config.PluginData } -func NewApiSearchPlugin(m map[string]string) *ApiSearchPlugin { +func NewApiSearchPlugin() *ApiSearchPlugin { //初始化 - p := &ApiSearchPlugin{ - respFields: strings.Split(m[ResponseFieldsParamsKey], ","), - matchSearch: strings.Split(m[SearchFieldsParamsKey], ","), - fuzzySearch: strings.Split(m[SearchFuzzyFieldsParamsKey], ","), - orderRules: strings.Split(m[OrderRulesParamKey], ","), - //默认值 - pageParam: "_page_no", - sizeParam: "_page_size", - } - - if m[PageNumParamsKey] != "" { - p.pageParam = m[PageNumParamsKey] - } - - if m[PageSizeParamsKey] != "" { - p.sizeParam = m[PageSizeParamsKey] - } + p := &ApiSearchPlugin{} return p } @@ -53,12 +40,50 @@ func (p *ApiSearchPlugin) Name() string { return "API_SEARCH" } +func (p *ApiSearchPlugin) init() { + m := p.conf.Plugin.ExtendParams + if m == nil { + m = map[string]string{} + } + + //响应字段集合 + p.respFields = p.conf.Api.Data.FieldNames() + responseFields := strings.Split(m[ResponseFieldsParamsKey], ",") + if len(responseFields) > 0 { + p.respFields = utils.ArrayIntersection(p.respFields, responseFields) + } + p.respFields = utils.ArrayUnion([]string{ + "id", + "created_at", + "updated_at", + }, p.respFields) + + p.matchSearch = strings.Split(m[SearchFieldsParamsKey], ",") + p.fuzzySearch = strings.Split(m[SearchFuzzyFieldsParamsKey], ",") + p.orderRules = strings.Split(m[OrderRulesParamKey], ",") + //默认值 + p.pageParam = m.ExtendParamByString(PageNumParamsKey, "_page_no") + p.sizeParam = m.ExtendParamByString(PageSizeParamsKey, "_page_size") +} + +func (p *ApiSearchPlugin) Factory(conf *config.PluginData) ifs.IApiPluginFactory { + fa := &ApiSearchPlugin{conf: conf} + fa.init() + return fa +} + func (p *ApiSearchPlugin) Describe() string { return ` 提供查询查询数据列表的能力,可以自定义查询过滤条件,包括完全匹配查询字段和模糊匹配查询字段 ` } +func (p *ApiSearchPlugin) Extends() map[string]string { + return map[string]string{ + "fields": "f1,f2,f3", //指定返回字段名称,多个逗号分隔 + } +} + func (p *ApiSearchPlugin) Do(running ifs.RunningParam) { running.SetVar("data", []int64{ 1, @@ -69,13 +94,11 @@ func (p *ApiSearchPlugin) Do(running ifs.RunningParam) { var m = make(map[string]interface{}) running.GinCtx().ShouldBindJSON(&m) - fields := []string{} pageNoInt := cast.ToInt(m[p.pageParam]) pageSizeInt := cast.ToInt(m[p.sizeParam]) //todo 增加搜索参数的处理 - - list, count, err := running.DataExec().DoSearch(nil, fields, pageNoInt, pageSizeInt) + list, count, err := running.DataExec().DoSearch(nil, p.respFields, pageNoInt, pageSizeInt) if err != nil { running.SetVar("error", err) return @@ -96,21 +119,21 @@ func (p *ApiSearchPlugin) Do(running ifs.RunningParam) { }) } -func (p *ApiSearchPlugin) HttpHandler(data config.PluginData) ifs.IApiPluginHttpHandler { +func (p *ApiSearchPlugin) HttpHandler() ifs.IApiPluginHttpHandler { return &ApiSearchHttpHandler{ - action: p, - data: data, + plugin: p, + data: p.conf, } } -func (p *ApiSearchPlugin) CmdHandler(param config.PluginData) ifs.IApiPluginCmdHandler { +func (p *ApiSearchPlugin) CmdHandler() ifs.IApiPluginCmdHandler { return nil } type ApiSearchHttpHandler struct { - action *ApiSearchPlugin + plugin *ApiSearchPlugin //运行时参数变量 - data config.PluginData + data *config.PluginData } func (p *ApiSearchHttpHandler) swaggerPathItem() spec.PathItem { @@ -120,13 +143,13 @@ func (p *ApiSearchHttpHandler) swaggerPathItem() spec.PathItem { logs.Out.Info("Fields= %+v", p.data.Api.Data.Fields) logs.Out.Info("Api= %+v", p.data.Api) - for _, v := range p.data.Api.Data.Fields { - searchParams[v.Name] = newSwaggerSpecSchema("string", v.ShowName, "example") - fields[v.Name] = newSwaggerSpecSchema("string", v.ShowName, "example") + for _, v := range p.plugin.respFields { + searchParams[v] = newSwaggerSpecSchema("string", v, "example") + fields[v] = newSwaggerSpecSchema("string", v, "example") } - searchParams[p.action.pageParam] = newSwaggerSpecSchema("int", "指定页码", 1) - searchParams[p.action.sizeParam] = newSwaggerSpecSchema("int", "指定条目数量", 10) + searchParams[p.plugin.pageParam] = newSwaggerSpecSchema("int", "指定页码", 1) + searchParams[p.plugin.sizeParam] = newSwaggerSpecSchema("int", "指定条目数量", 10) resp1 := spec.Response{ ResponseProps: spec.ResponseProps{ Schema: &spec.Schema{ @@ -222,15 +245,3 @@ func (p *ApiSearchHttpHandler) Router() []config.GinHandleParam { }, } } - -func newSwaggerSpecSchema(ty string, desc string, example interface{}) spec.Schema { - return spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: desc, - Type: []string{ty}, - }, - ExtraProps: map[string]interface{}{ - "example": example, - }, - } -} diff --git a/src/pkg/atapi/httpapi/v1/plugins/help.go b/src/pkg/atapi/httpapi/v1/plugins/help.go new file mode 100644 index 0000000..5816f98 --- /dev/null +++ b/src/pkg/atapi/httpapi/v1/plugins/help.go @@ -0,0 +1,15 @@ +package plugins + +import "github.com/go-openapi/spec" + +func newSwaggerSpecSchema(ty string, desc string, example interface{}) spec.Schema { + return spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: desc, + Type: []string{ty}, + }, + ExtraProps: map[string]interface{}{ + "example": example, + }, + } +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go index 4e05682..1fbe26c 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go +++ b/src/pkg/atapi/httpapi/v1/plugins/rate_limit.go @@ -8,10 +8,10 @@ import ( ) type RateLimitPlugin struct { + conf *config.PluginData } -func NewRateLimitPlugin(m map[string]string) *RateLimitPlugin { - //初始化 +func NewRateLimitPlugin() *RateLimitPlugin { p := &RateLimitPlugin{} return p @@ -27,6 +27,11 @@ func (p *RateLimitPlugin) Describe() string { ` } +func (p *RateLimitPlugin) Factory(conf *config.PluginData) ifs.IApiPluginFactory { + fa := &RateLimitPlugin{conf: conf} + return fa +} + func (p *RateLimitPlugin) Do(running ifs.RunningParam) { ip := utils.GetRequestIp(running.GinCtx().Request) if utils.TrueScopeRand(0, 100) < 50 { @@ -35,10 +40,10 @@ func (p *RateLimitPlugin) Do(running ifs.RunningParam) { } } -func (p *RateLimitPlugin) HttpHandler(api config.PluginData) ifs.IApiPluginHttpHandler { +func (p *RateLimitPlugin) HttpHandler() ifs.IApiPluginHttpHandler { return nil } -func (p *RateLimitPlugin) CmdHandler(api config.PluginData) ifs.IApiPluginCmdHandler { +func (p *RateLimitPlugin) CmdHandler() ifs.IApiPluginCmdHandler { return nil } diff --git a/src/pkg/atapi/httpapi/v1/running.go b/src/pkg/atapi/httpapi/v1/running.go index 8a38d60..1ada25c 100644 --- a/src/pkg/atapi/httpapi/v1/running.go +++ b/src/pkg/atapi/httpapi/v1/running.go @@ -2,15 +2,12 @@ package v1 import ( "context" - "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "gitee.com/captials-team/ubdframe/src/pkg/atapi/ifs" "github.com/gin-gonic/gin" ) type runningData struct { ctx context.Context - api config.AutoApiDefined - action config.AutoApiActionDefined dataExec ifs.IDataExecCURD abort bool ginCtx *gin.Context @@ -22,10 +19,6 @@ func (r *runningData) Ctx() context.Context { return r.ctx } -func (r *runningData) ActionConfig() config.AutoApiActionDefined { - return r.action -} - func (r *runningData) Abort() { r.abort = true } @@ -38,10 +31,6 @@ func (r *runningData) DataExec() ifs.IDataExecCURD { return r.dataExec } -func (r *runningData) Config() config.AutoApiDefined { - return r.api -} - func (r *runningData) GetVar(k string) interface{} { if r.data == nil { return nil diff --git a/src/pkg/atapi/ifs/plugin.go b/src/pkg/atapi/ifs/plugin.go index 6e4ff5f..92ab5f7 100644 --- a/src/pkg/atapi/ifs/plugin.go +++ b/src/pkg/atapi/ifs/plugin.go @@ -8,10 +8,14 @@ type IApiPlugin interface { Name() string //plugin名称 Describe() string //插件具体描述内容 + Factory(*config.PluginData) IApiPluginFactory +} + +type IApiPluginFactory interface { Do(param RunningParam) - HttpHandler(api config.PluginData) IApiPluginHttpHandler //该插件的具体handler类 - CmdHandler(api config.PluginData) IApiPluginCmdHandler //该插件的具体handler类 + HttpHandler() IApiPluginHttpHandler //该插件的具体handler类 + CmdHandler() IApiPluginCmdHandler //该插件的具体handler类 } // IApiPluginHandler 插件http场景下需支持的处理 diff --git a/src/pkg/atapi/ifs/running.go b/src/pkg/atapi/ifs/running.go index e27147d..276c74c 100644 --- a/src/pkg/atapi/ifs/running.go +++ b/src/pkg/atapi/ifs/running.go @@ -2,15 +2,12 @@ package ifs import ( "context" - "gitee.com/captials-team/ubdframe/src/pkg/atapi/config" "github.com/gin-gonic/gin" ) // RunningParam 运行时使用的参数合集 type RunningParam interface { Ctx() context.Context - Config() config.AutoApiDefined - ActionConfig() config.AutoApiActionDefined //当前action信息 GinCtx() *gin.Context DataExec() IDataExecCURD diff --git a/src/pkg/atapi/manager.go b/src/pkg/atapi/manager.go index 7d3676f..6011736 100644 --- a/src/pkg/atapi/manager.go +++ b/src/pkg/atapi/manager.go @@ -151,12 +151,12 @@ func (mag *AtApisManager) newAutoApiV1(apiDef config.AutoApiDefined) (*v1.AutoAp //api.SetVars(vars) //内置plugin api.AddPlugin( - plugins.NewRateLimitPlugin(mag.config.ExtendParams), + plugins.NewRateLimitPlugin(), ) api.AddPlugin( - plugins.NewApiSearchPlugin(mag.config.ExtendParams), - plugins.NewApiCreatePlugin(mag.config.ExtendParams), - plugins.NewApiResponsePlugin(mag.config.ExtendParams), + plugins.NewApiSearchPlugin(), + plugins.NewApiCreatePlugin(), + plugins.NewApiResponsePlugin(), ) //自定义插件 for _, tmp := range mag.extraPlugins { diff --git a/src/pkg/atapi/manager_test.go b/src/pkg/atapi/manager_test.go index d1b04e6..6828515 100644 --- a/src/pkg/atapi/manager_test.go +++ b/src/pkg/atapi/manager_test.go @@ -25,6 +25,7 @@ func TestNewManager(t *testing.T) { time.Sleep(time.Hour) } +// TestAtManager_swagger 测试生成swagger func TestAtManager_swagger(t *testing.T) { mag := getTestAtApiManager(t) diff --git a/src/pkg/atapi/tests/v1/demo.yaml b/src/pkg/atapi/tests/v1/demo.yaml index 1234ea2..2d29245 100644 --- a/src/pkg/atapi/tests/v1/demo.yaml +++ b/src/pkg/atapi/tests/v1/demo.yaml @@ -15,7 +15,7 @@ Actions: limit_rate: 2 - Name: "API_SEARCH" ExtendParam: - fields: "id,title,type,status" + RESPONSE_FIELDS: "id,title,type,status" - Name: "API_RESPONSE" - Name: "创建接口" @@ -29,7 +29,6 @@ Actions: fields: "id,title,type,status" - Name: "API_RESPONSE" - #是否自动创建表数据等信息 Data: DataName: "" #自定义数据存储的标识名称,为空则会自动根据api名生成名称 diff --git a/src/pkg/atapi/tests/v1/org.yaml b/src/pkg/atapi/tests/v1/org.yaml index b1a25df..141e5b4 100644 --- a/src/pkg/atapi/tests/v1/org.yaml +++ b/src/pkg/atapi/tests/v1/org.yaml @@ -8,18 +8,19 @@ ShowName: "组织" # Plugins,定义使用的插件 Actions: - Name: "搜索接口" - Describe: "搜索传参并返回列表、分页信息" Enable: true Plugins: - - Name: "API_SEARCH" - ExtendParam: - fields: "id,name,pid,status" - Name: "RATE_LIMIT" ExtendParam: limit_rate: 2 + - Name: "API_SEARCH" + ExtendParam: + fields: "id,name,pid,status" + - Name: "API_RESPONSE" +#是否自动创建表数据等信息 Data: - DataName: "" + DataName: "" #自定义数据存储的标识名称,为空则会自动根据api名生成名称 AutoTable: true Fields: - Name: saas_id @@ -31,7 +32,7 @@ Data: Default: "未命名" Comment: 组织名 - Name: pid - SqlType: int unsigned + SqlType: bigint unsigned Default: "0" Comment: pid - Name: status -- Gitee From 7214153c5c364122bbd82bb077333be81a77c399 Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 18 Aug 2025 15:04:02 +0800 Subject: [PATCH 28/83] add dag --- src/common/utils/other.go | 12 +++ src/pkg/algorithm/dag.go | 101 ++++++++++++++++++ src/pkg/algorithm/dag_test.go | 80 ++++++++++++++ .../atapi/httpapi/v1/plugins/api_search.go | 4 +- 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/pkg/algorithm/dag.go create mode 100644 src/pkg/algorithm/dag_test.go diff --git a/src/common/utils/other.go b/src/common/utils/other.go index aafd9ef..5033b69 100644 --- a/src/common/utils/other.go +++ b/src/common/utils/other.go @@ -292,3 +292,15 @@ func StringIntId(s string) int { // v == MinInt return 0 } + +// CopyMap 复制map +func CopyMap[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 | string | struct{}, T1 interface{}](m map[T]T1) map[T]T1 { + if m == nil { + return nil + } + newM := make(map[T]T1, len(m)) + for k, v := range m { + newM[k] = v + } + return newM +} diff --git a/src/pkg/algorithm/dag.go b/src/pkg/algorithm/dag.go new file mode 100644 index 0000000..11850c1 --- /dev/null +++ b/src/pkg/algorithm/dag.go @@ -0,0 +1,101 @@ +package algorithm + +import ( + "fmt" + "gitee.com/captials-team/ubdframe/src/common/utils" +) + +// DAG 有序无向图 +// 提供方法:添加顶点,添加边,获取有向图 +type DAG struct { + nodes map[string]interface{} //存储所有节点 + edges map[string][]string //存储所有边,key=当前节点,value=当前节点相邻的节点数组 +} + +// AddVertex 添加顶点 +func (dag *DAG) AddVertex(id string, node interface{}) error { + dag.nodes[id] = node + return nil +} + +// AddEdge 添加边 +func (dag *DAG) AddEdge(from string, to string) error { + edges := utils.CopyMap(dag.edges) + if _, exist := edges[from]; !exist { + edges[from] = []string{} + } + edges[from] = append(edges[from], to) + + //检测环 + if dag.checkLoop(from, edges, nil) { + return fmt.Errorf("has circle loop") + } + //通过检测的才可加入边 + dag.edges = edges + return nil +} + +// checkLoop 检测是否存在环,true:存在环,false:不存在 +func (dag *DAG) checkLoop(start string, edges map[string][]string, has map[string]bool) bool { + if edges == nil { + return false + } + edge, exist := edges[start] + if !exist { + return false + } + if has == nil { + has = make(map[string]bool) + } + for _, v := range edge { + if _, exs := has[v]; exs { + return true + } + + newHas := utils.CopyMap(has) + newHas[start] = true + if dag.checkLoop(v, edges, newHas) { + return true + } + } + return false +} + +// TopologicalSort 有序拓扑图 +func (dag *DAG) TopologicalSort() [][]string { + nodes := utils.CopyMap(dag.nodes) + var topo [][]string + + for len(nodes) > 0 { + has := make(map[string]bool) + //查找目前剩余节点的所有边, + for node, _ := range nodes { + edge, exist := dag.edges[node] + if !exist || edge == nil { + continue + } + //将节点所有边的相邻节点记录下 + for _, v := range edge { + has[v] = true + } + } + + var deal []string + //若节点不在当前相邻节点中的则先加入 + for k, _ := range nodes { + if _, exist := has[k]; !exist { + deal = append(deal, k) + delete(nodes, k) + } + } + topo = append(topo, deal) + } + return topo +} + +func NewDAG() *DAG { + return &DAG{ + nodes: make(map[string]interface{}), + edges: make(map[string][]string), + } +} diff --git a/src/pkg/algorithm/dag_test.go b/src/pkg/algorithm/dag_test.go new file mode 100644 index 0000000..9789646 --- /dev/null +++ b/src/pkg/algorithm/dag_test.go @@ -0,0 +1,80 @@ +package algorithm + +import ( + "gitee.com/captials-team/ubdframe/src/common" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +// TestDAG_TopologicalSort 有序无环图(无环情况) +func TestDAG_TopologicalSort(t *testing.T) { + tests := [][]string{ + {"1-2", "3"}, + {"1-4", "2-3", "3-4"}, + {"1-2", "2-3", "3-4", "5-6", "6-7", "7-8"}, + } + + var empty struct{} + for index, list := range tests { + t.Logf("========== %d", index) + dag := NewDAG() + for _, v := range list { + arr := strings.Split(v, "-") + if len(arr) == 1 { + common.ErrPanic(dag.AddVertex(arr[0], empty)) + t.Logf("Add %s", v) + } + if len(arr) == 2 { + common.ErrPanic(dag.AddVertex(arr[0], empty)) + common.ErrPanic(dag.AddVertex(arr[1], empty)) + common.ErrPanic(dag.AddEdge(arr[0], arr[1])) + t.Logf("AddEdge %s", v) + } + } + + ret := dag.TopologicalSort() + for i, v := range ret { + t.Logf("[%d]%s", i, strings.Join(v, ",")) + } + } +} + +// TestDAG_TopologicalSort_circle 有序无环图(有环情况) +func TestDAG_TopologicalSort_circle(t *testing.T) { + tests := [][]string{ + {"1-2", "2-3", "3-4", "5-6", "6-7", "7-8", "3-5", "7-1"}, //有环 + {"1-2", "2-3", "3-4", "5-6", "6-7", "7-8", "3-5", "7-2"}, //有环 + {"1-1", "2-3"}, //有环 + {"1-2", "2-3", "3-1"}, //有环 + {"1-2", "2-3", "3-2"}, //有环 + } + + var empty struct{} + for index, list := range tests { + t.Logf("========== %d", index) + dag := NewDAG() + var errCount int + for _, v := range list { + arr := strings.Split(v, "-") + if len(arr) == 1 { + common.ErrPanic(dag.AddVertex(arr[0], empty)) + t.Logf("Add %s", v) + } + if len(arr) == 2 { + common.ErrPanic(dag.AddVertex(arr[0], empty)) + common.ErrPanic(dag.AddVertex(arr[1], empty)) + + err := dag.AddEdge(arr[0], arr[1]) + t.Logf("AddEdge %s", v) + + if err != nil { + errCount++ + } + } + } + + assert.Greater(t, errCount, 0) + t.Logf("CIRCLE LOOP") + } +} diff --git a/src/pkg/atapi/httpapi/v1/plugins/api_search.go b/src/pkg/atapi/httpapi/v1/plugins/api_search.go index 4a27a62..a3077be 100644 --- a/src/pkg/atapi/httpapi/v1/plugins/api_search.go +++ b/src/pkg/atapi/httpapi/v1/plugins/api_search.go @@ -80,7 +80,9 @@ func (p *ApiSearchPlugin) Describe() string { func (p *ApiSearchPlugin) Extends() map[string]string { return map[string]string{ - "fields": "f1,f2,f3", //指定返回字段名称,多个逗号分隔 + "response_fields": "f1,f2,f3", //指定返回字段名称,多个逗号分隔 + "page_num": "_page_no", //指定页码 + "page_size": "_page_size", //指定查询条目 } } -- Gitee From f3e7677344bc55b749c4909e22f485b5cbb28a2d Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 18 Aug 2025 18:09:01 +0800 Subject: [PATCH 29/83] add workflow --- go.mod | 3 +- go.sum | 2 + src/pkg/{algorithm => workflow}/dag.go | 2 +- src/pkg/{algorithm => workflow}/dag_test.go | 2 +- src/pkg/workflow/sts.go | 30 ++++ src/pkg/workflow/workflow.go | 163 +++++++++++++++++++ src/pkg/workflow/workflow_test.go | 168 ++++++++++++++++++++ 7 files changed, 367 insertions(+), 3 deletions(-) rename src/pkg/{algorithm => workflow}/dag.go (99%) rename src/pkg/{algorithm => workflow}/dag_test.go (99%) create mode 100644 src/pkg/workflow/sts.go create mode 100644 src/pkg/workflow/workflow.go create mode 100644 src/pkg/workflow/workflow_test.go diff --git a/go.mod b/go.mod index 045da7e..53295dc 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.22.0 require ( github.com/BurntSushi/toml v1.4.0 + github.com/Jeffail/tunny v0.1.4 github.com/Knetic/govaluate v3.0.0+incompatible github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.0 @@ -15,6 +16,7 @@ require ( github.com/gin-contrib/multitemplate v1.0.1 github.com/gin-contrib/static v1.1.2 github.com/gin-gonic/gin v1.10.0 + github.com/go-openapi/spec v0.20.4 github.com/go-playground/assert/v2 v2.2.0 github.com/go-redis/redis/v7 v7.4.1 github.com/go-redis/redis/v8 v8.11.5 @@ -88,7 +90,6 @@ require ( github.com/go-ini/ini v1.67.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect diff --git a/go.sum b/go.sum index 9bc2f52..bb91494 100644 --- a/go.sum +++ b/go.sum @@ -600,6 +600,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Jeffail/tunny v0.1.4 h1:chtpdz+nUtaYQeCKlNBg6GycFF/kGVHOr6A3cmzTJXs= +github.com/Jeffail/tunny v0.1.4/go.mod h1:P8xAx4XQl0xsuhjX1DtfaMDCSuavzdb2rwbd0lk+fvo= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= diff --git a/src/pkg/algorithm/dag.go b/src/pkg/workflow/dag.go similarity index 99% rename from src/pkg/algorithm/dag.go rename to src/pkg/workflow/dag.go index 11850c1..89c4a80 100644 --- a/src/pkg/algorithm/dag.go +++ b/src/pkg/workflow/dag.go @@ -1,4 +1,4 @@ -package algorithm +package workflow import ( "fmt" diff --git a/src/pkg/algorithm/dag_test.go b/src/pkg/workflow/dag_test.go similarity index 99% rename from src/pkg/algorithm/dag_test.go rename to src/pkg/workflow/dag_test.go index 9789646..97d76b2 100644 --- a/src/pkg/algorithm/dag_test.go +++ b/src/pkg/workflow/dag_test.go @@ -1,4 +1,4 @@ -package algorithm +package workflow import ( "gitee.com/captials-team/ubdframe/src/common" diff --git a/src/pkg/workflow/sts.go b/src/pkg/workflow/sts.go new file mode 100644 index 0000000..6cc11f1 --- /dev/null +++ b/src/pkg/workflow/sts.go @@ -0,0 +1,30 @@ +package workflow + +import "sync" + +// State 工作流状态类型 +type State string + +const ( + StatePending State = "pending" + StateRunning State = "running" + StateCompleted State = "completed" + StateFailed State = "failed" +) + +// Node 工作流节点定义 +type Node struct { + ID string `json:"ID"` // 节点唯一标识 + Action func(ctx Context) error `json:"-"` // 执行函数 + RetryPolicy int `json:"RetryPolicy"` // 重试次数 + DependsOn []string `json:"DependsOn"` // 依赖的节点ID +} + +// Context 工作流上下文 +type Context struct { + sync.Locker //锁 + + Data map[string]interface{} `json:"data"` // 共享数据 + NodeID string `json:"nodeID"` // 当前节点ID + Workflow *Workflow `json:"workflow"` // 所属工作流 +} diff --git a/src/pkg/workflow/workflow.go b/src/pkg/workflow/workflow.go new file mode 100644 index 0000000..7a6f921 --- /dev/null +++ b/src/pkg/workflow/workflow.go @@ -0,0 +1,163 @@ +package workflow + +import ( + "encoding/json" + "fmt" + "github.com/Jeffail/tunny" + "gorm.io/gorm" + "gorm.io/gorm/schema" + "math" + "runtime" + "sync" + "time" +) + +// Workflow 工作流定义 +type Workflow struct { + ID string `json:"id"` //工作流唯一ID + Nodes map[string]Node //节点集合 + State State `json:"state"` //当前工作流状态 + + context Context +} + +// Run 执行工作流(并发安全) +func (w *Workflow) Run() error { + w.State = StateRunning + w.context = Context{ + Data: make(map[string]interface{}), + NodeID: "", + Workflow: w, + Locker: &sync.Mutex{}, + } + + // 构建DAG依赖关系 + dag := w.buildDAG() + + // 使用协程池控制并发 + pool := tunny.NewFunc(runtime.NumCPU(), func(payload interface{}) interface{} { + nodeID := payload.(string) + return w.executeNode(nodeID) + }) + defer pool.Close() + + // 拓扑排序后顺序执行 + for _, nodeIDs := range dag.TopologicalSort() { + var wg sync.WaitGroup + for _, nodeID := range nodeIDs { + wg.Add(1) + go func(id string) { + defer wg.Done() + pool.Process(id) + }(nodeID) + } + wg.Wait() + + // 检查是否有节点失败 + if w.State == StateFailed { + return fmt.Errorf("workflow aborted") + } + } + + w.State = StateCompleted + return nil +} + +// buildDAG 构建DAG图 +func (w *Workflow) buildDAG() *DAG { + d := NewDAG() + for id, node := range w.Nodes { + _ = d.AddVertex(id, node) + for _, depID := range node.DependsOn { + _ = d.AddEdge(depID, id) + } + } + return d +} + +// 执行单个节点 +func (w *Workflow) executeNode(nodeID string) error { + node := w.Nodes[nodeID] + for i := 0; i <= node.RetryPolicy; i++ { + err := node.Action(Context{ + Data: w.context.Data, + NodeID: node.ID, + Workflow: w, + Locker: w.context.Locker, + }) + if err == nil { + return nil + } + if i == node.RetryPolicy { + w.State = StateFailed + return fmt.Errorf("node %s failed: %v", nodeID, err) + } + time.Sleep(time.Second * time.Duration(math.Pow(2, float64(i)))) // 指数退避 + } + return nil +} + +// Resume 从失败节点恢复 +func (w *Workflow) Resume() error { + if w.State != StateFailed { + return nil + } + + // 找出未完成的节点 + pendingNodes := w.getPendingNodes() + + // 创建子工作流继续执行 + subflow := &Workflow{ + ID: w.ID + "_recovery", + Nodes: pendingNodes, + } + return subflow.Run() +} + +func (w *Workflow) getPendingNodes() map[string]Node { + pending := map[string]Node{} + for _, v := range w.Nodes { + pending[v.ID] = v + } + return pending +} + +type saveModel struct { + Id int64 `json:"id" gorm:"primaryKey"` + CreatedAt *time.Time `json:"created_at,omitempty" gorm:"default:current_timestamp(3);<-:false"` + Workflow string `json:"workflow" gorm:"type:varchar(64);index"` + Data string `json:"data,omitempty" gorm:"type:varchar(512)"` //流数据 +} + +func (ad *saveModel) TableName(namer schema.Namer) string { + return namer.TableName("workflows") +} + +// SaveWorkflow 保存工作流状态到数据库(gorm版本,如需自行保存可做参考) +func SaveWorkflow(db *gorm.DB, w *Workflow) error { + data, err := json.Marshal(w) + if err != nil { + return err + } + query := db.Model(new(saveModel)) + + ret := query.Create(&saveModel{ + Workflow: w.ID, + Data: string(data), + }) + + return ret.Error +} + +// LoadWorkflow 从数据库恢复工作流(gorm版本,如需自行保存可做参考) +func LoadWorkflow(db *gorm.DB, id string) (*Workflow, error) { + query := db.Model(new(saveModel)) + + m := new(saveModel) + ret := query.Order("id DESC").Where("workflow", id).First(m) + if ret.Error != nil { + return nil, ret.Error + } + var w Workflow + return &w, json.Unmarshal([]byte(m.Data), &w) +} diff --git a/src/pkg/workflow/workflow_test.go b/src/pkg/workflow/workflow_test.go new file mode 100644 index 0000000..b9c1022 --- /dev/null +++ b/src/pkg/workflow/workflow_test.go @@ -0,0 +1,168 @@ +package workflow + +import ( + "encoding/json" + "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/common/utils" + "strings" + "testing" +) + +func TestWorkflow_Run(t *testing.T) { + w := &Workflow{ + ID: "order_process", + Nodes: map[string]Node{ + "order_create": { + Action: func(ctx Context) error { + orderId := utils.RandLetterFigureCode(10) + userId := utils.RandLetterFigureCode(10) + + ctx.Lock() + ctx.Data["orderId"] = orderId + ctx.Data["userId"] = userId + ctx.Unlock() + + t.Logf("CreateOrder= %s,User= %s", orderId, userId) + return nil + }, + RetryPolicy: 3, + DependsOn: nil, + }, + "order_pay": { + Action: func(ctx Context) error { + payId := utils.RandLetterFigureCode(10) + + ctx.Lock() + ctx.Data["payId"] = payId + ctx.Unlock() + t.Logf("PayOrder= %s", payId) + return nil + }, + RetryPolicy: 3, + DependsOn: []string{"order_create"}, + }, + "notify_user": { + Action: func(ctx Context) error { + ctx.Lock() + userId := ctx.Data["userId"] + ctx.Unlock() + + t.Logf("NotifyUser= %s", userId) + return nil + }, + RetryPolicy: 3, + DependsOn: []string{"order_create"}, + }, + "order_complete": { + Action: func(ctx Context) error { + ctx.Lock() + orderId := ctx.Data["orderId"] + payId := ctx.Data["payId"] + ctx.Unlock() + + t.Logf("CompleteOrder= %s,%s", orderId, payId) + return nil + }, + RetryPolicy: 3, + DependsOn: []string{"order_pay"}, + }, + }, + } + + s, _ := json.MarshalIndent(w, "", "\t") + t.Logf("Workflow= %s", string(s)) + + common.ErrPanic(w.Run()) +} + +// TestWorkflow_concurrence 测试并发 +func TestWorkflow_concurrence(t *testing.T) { + for i := 1; i <= 100000; i++ { + t.Logf("%s %d", strings.Repeat("=", 30), i) + TestWorkflow_Run(t) + } +} + +func TestWorkflow_context(t *testing.T) { + ctx := Context{Data: map[string]interface{}{}} + + c1 := Context{Data: ctx.Data} + c2 := Context{Data: ctx.Data} + + c1.Data["xxx"] = 123 + + t.Logf("%+v", c2.Data) +} + +// TestWorkflow_Persistence 持久化 +func TestWorkflow_Persistence(t *testing.T) { + w := &Workflow{ + ID: "order_process", + Nodes: map[string]Node{ + "order_create": { + Action: func(ctx Context) error { + orderId := utils.RandLetterFigureCode(10) + userId := utils.RandLetterFigureCode(10) + + ctx.Lock() + ctx.Data["orderId"] = orderId + ctx.Data["userId"] = userId + ctx.Unlock() + + t.Logf("CreateOrder= %s,User= %s", orderId, userId) + return nil + }, + RetryPolicy: 3, + DependsOn: nil, + }, + "order_pay": { + Action: func(ctx Context) error { + payId := utils.RandLetterFigureCode(10) + + ctx.Lock() + ctx.Data["payId"] = payId + ctx.Unlock() + t.Logf("PayOrder= %s", payId) + return nil + }, + RetryPolicy: 3, + DependsOn: []string{"order_create"}, + }, + "notify_user": { + Action: func(ctx Context) error { + ctx.Lock() + userId := ctx.Data["userId"] + ctx.Unlock() + + t.Logf("NotifyUser= %s", userId) + return nil + }, + RetryPolicy: 3, + DependsOn: []string{"order_create"}, + }, + "order_complete": { + Action: func(ctx Context) error { + ctx.Lock() + orderId := ctx.Data["orderId"] + payId := ctx.Data["payId"] + ctx.Unlock() + + t.Logf("CompleteOrder= %s,%s", orderId, payId) + return nil + }, + RetryPolicy: 3, + DependsOn: []string{"order_pay"}, + }, + }, + } + + s, err := json.MarshalIndent(w, "", "\t") + common.ErrPanic(err) + t.Logf("Workflow= %s", string(s)) + + common.ErrPanic(w.Run()) + + s1, err1 := json.MarshalIndent(w, "", "\t") + common.ErrPanic(err1) + t.Logf("Workflow= %s", string(s1)) +} -- Gitee From 0a56cd8b2cb03dc8907d67368d51fe144b6da988 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 19 Aug 2025 14:38:30 +0800 Subject: [PATCH 30/83] fix workflow --- src/pkg/workflow/workflow.go | 61 +++++++++---- src/pkg/workflow/workflow_test.go | 139 ++++++++++++++---------------- 2 files changed, 110 insertions(+), 90 deletions(-) diff --git a/src/pkg/workflow/workflow.go b/src/pkg/workflow/workflow.go index 7a6f921..c480771 100644 --- a/src/pkg/workflow/workflow.go +++ b/src/pkg/workflow/workflow.go @@ -3,6 +3,7 @@ package workflow import ( "encoding/json" "fmt" + "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/Jeffail/tunny" "gorm.io/gorm" "gorm.io/gorm/schema" @@ -18,17 +19,25 @@ type Workflow struct { Nodes map[string]Node //节点集合 State State `json:"state"` //当前工作流状态 + Success map[string]bool `json:"success"` context Context } // Run 执行工作流(并发安全) func (w *Workflow) Run() error { + //重置 w.State = StateRunning - w.context = Context{ - Data: make(map[string]interface{}), - NodeID: "", - Workflow: w, - Locker: &sync.Mutex{}, + w.context.Workflow = w + w.context.Locker = &sync.Mutex{} + if w.context.Data == nil { + w.context.Data = make(map[string]interface{}) + } + if w.Success == nil { + w.Success = make(map[string]bool) + } + for k, node := range w.Nodes { + node.ID = k + w.Nodes[k] = node } // 构建DAG依赖关系 @@ -55,7 +64,7 @@ func (w *Workflow) Run() error { // 检查是否有节点失败 if w.State == StateFailed { - return fmt.Errorf("workflow aborted") + return fmt.Errorf("workflow aborted %+v", w.Success) } } @@ -78,23 +87,25 @@ func (w *Workflow) buildDAG() *DAG { // 执行单个节点 func (w *Workflow) executeNode(nodeID string) error { node := w.Nodes[nodeID] + var err error for i := 0; i <= node.RetryPolicy; i++ { - err := node.Action(Context{ + err = node.Action(Context{ Data: w.context.Data, NodeID: node.ID, Workflow: w, Locker: w.context.Locker, }) if err == nil { + w.context.Lock() + w.Success[nodeID] = true + w.context.Unlock() return nil } - if i == node.RetryPolicy { - w.State = StateFailed - return fmt.Errorf("node %s failed: %v", nodeID, err) - } - time.Sleep(time.Second * time.Duration(math.Pow(2, float64(i)))) // 指数退避 + time.Sleep(time.Millisecond * 100 * time.Duration(math.Pow(2, float64(i)))) // 指数退避 } - return nil + + w.State = StateFailed + return fmt.Errorf("node %s failed: %v", nodeID, err) } // Resume 从失败节点恢复 @@ -106,10 +117,13 @@ func (w *Workflow) Resume() error { // 找出未完成的节点 pendingNodes := w.getPendingNodes() + //logs.Out.Info("Pending= %+v", pendingNodes) // 创建子工作流继续执行 subflow := &Workflow{ - ID: w.ID + "_recovery", - Nodes: pendingNodes, + ID: w.ID + "_recovery", + Nodes: pendingNodes, + Success: w.Success, + context: w.context, } return subflow.Run() } @@ -117,7 +131,22 @@ func (w *Workflow) Resume() error { func (w *Workflow) getPendingNodes() map[string]Node { pending := map[string]Node{} for _, v := range w.Nodes { - pending[v.ID] = v + if result, exist := w.Success[v.ID]; exist && result { + continue + } + + var newDepends []string + for _, vv := range v.DependsOn { + if result, exist := w.Success[vv]; exist && result { + continue + } + newDepends = append(newDepends, vv) + } + var newNode = v + newNode.DependsOn = newDepends + pending[v.ID] = newNode + + logs.Out.Info("AddPending= %+v", newNode) } return pending } diff --git a/src/pkg/workflow/workflow_test.go b/src/pkg/workflow/workflow_test.go index b9c1022..049d4dd 100644 --- a/src/pkg/workflow/workflow_test.go +++ b/src/pkg/workflow/workflow_test.go @@ -2,6 +2,7 @@ package workflow import ( "encoding/json" + "fmt" "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/common/utils" "strings" @@ -9,65 +10,7 @@ import ( ) func TestWorkflow_Run(t *testing.T) { - w := &Workflow{ - ID: "order_process", - Nodes: map[string]Node{ - "order_create": { - Action: func(ctx Context) error { - orderId := utils.RandLetterFigureCode(10) - userId := utils.RandLetterFigureCode(10) - - ctx.Lock() - ctx.Data["orderId"] = orderId - ctx.Data["userId"] = userId - ctx.Unlock() - - t.Logf("CreateOrder= %s,User= %s", orderId, userId) - return nil - }, - RetryPolicy: 3, - DependsOn: nil, - }, - "order_pay": { - Action: func(ctx Context) error { - payId := utils.RandLetterFigureCode(10) - - ctx.Lock() - ctx.Data["payId"] = payId - ctx.Unlock() - t.Logf("PayOrder= %s", payId) - return nil - }, - RetryPolicy: 3, - DependsOn: []string{"order_create"}, - }, - "notify_user": { - Action: func(ctx Context) error { - ctx.Lock() - userId := ctx.Data["userId"] - ctx.Unlock() - - t.Logf("NotifyUser= %s", userId) - return nil - }, - RetryPolicy: 3, - DependsOn: []string{"order_create"}, - }, - "order_complete": { - Action: func(ctx Context) error { - ctx.Lock() - orderId := ctx.Data["orderId"] - payId := ctx.Data["payId"] - ctx.Unlock() - - t.Logf("CompleteOrder= %s,%s", orderId, payId) - return nil - }, - RetryPolicy: 3, - DependsOn: []string{"order_pay"}, - }, - }, - } + w := testWorkflow(t, false) s, _ := json.MarshalIndent(w, "", "\t") t.Logf("Workflow= %s", string(s)) @@ -96,7 +39,42 @@ func TestWorkflow_context(t *testing.T) { // TestWorkflow_Persistence 持久化 func TestWorkflow_Persistence(t *testing.T) { - w := &Workflow{ + w := testWorkflow(t, false) + + s, err := json.MarshalIndent(w, "", "\t") + common.ErrPanic(err) + t.Logf("Workflow= %s", string(s)) + + common.ErrPanic(w.Run()) + + s1, err1 := json.MarshalIndent(w, "", "\t") + common.ErrPanic(err1) + t.Logf("Workflow= %s", string(s1)) +} + +// TestWorkflow_Resume 重新运行服务 +func TestWorkflow_Resume(t *testing.T) { + w := testWorkflow(t, true) + + err := w.Run() + t.Logf("RunFail %s", err) + if err != nil { + for i := 1; i <= 10; i++ { + t.Logf("Resume=> %d", i) + if err1 := w.Resume(); err1 != nil { + t.Logf("ResumeFail %s", err1) + continue + } + break + } + } + +} + +func testWorkflow(t *testing.T, needFail bool) *Workflow { + retry := 1 + + return &Workflow{ ID: "order_process", Nodes: map[string]Node{ "order_create": { @@ -104,6 +82,11 @@ func TestWorkflow_Persistence(t *testing.T) { orderId := utils.RandLetterFigureCode(10) userId := utils.RandLetterFigureCode(10) + //随机失败 + if needFail && utils.TrueScopeRand(1, 100) > 20 { + return fmt.Errorf("create order randome fail") + } + ctx.Lock() ctx.Data["orderId"] = orderId ctx.Data["userId"] = userId @@ -112,24 +95,35 @@ func TestWorkflow_Persistence(t *testing.T) { t.Logf("CreateOrder= %s,User= %s", orderId, userId) return nil }, - RetryPolicy: 3, + RetryPolicy: retry, DependsOn: nil, }, "order_pay": { Action: func(ctx Context) error { payId := utils.RandLetterFigureCode(10) + //随机失败 + if needFail && utils.TrueScopeRand(1, 100) > 20 { + return fmt.Errorf("pay order randome fail") + } + ctx.Lock() ctx.Data["payId"] = payId ctx.Unlock() t.Logf("PayOrder= %s", payId) return nil }, - RetryPolicy: 3, + RetryPolicy: retry, DependsOn: []string{"order_create"}, }, "notify_user": { Action: func(ctx Context) error { + + //随机失败 + if needFail && utils.TrueScopeRand(1, 100) > 20 { + return fmt.Errorf("notify randome fail") + } + ctx.Lock() userId := ctx.Data["userId"] ctx.Unlock() @@ -137,11 +131,17 @@ func TestWorkflow_Persistence(t *testing.T) { t.Logf("NotifyUser= %s", userId) return nil }, - RetryPolicy: 3, - DependsOn: []string{"order_create"}, + RetryPolicy: retry, + DependsOn: []string{"order_create", "order_pay"}, }, "order_complete": { Action: func(ctx Context) error { + + //随机失败 + if needFail && utils.TrueScopeRand(1, 100) > 20 { + return fmt.Errorf("complete order randome fail") + } + ctx.Lock() orderId := ctx.Data["orderId"] payId := ctx.Data["payId"] @@ -150,19 +150,10 @@ func TestWorkflow_Persistence(t *testing.T) { t.Logf("CompleteOrder= %s,%s", orderId, payId) return nil }, - RetryPolicy: 3, + RetryPolicy: retry, DependsOn: []string{"order_pay"}, }, }, } - s, err := json.MarshalIndent(w, "", "\t") - common.ErrPanic(err) - t.Logf("Workflow= %s", string(s)) - - common.ErrPanic(w.Run()) - - s1, err1 := json.MarshalIndent(w, "", "\t") - common.ErrPanic(err1) - t.Logf("Workflow= %s", string(s1)) } -- Gitee From ab66a00e948fcdef3f0c3cd1421a636f7fdab7f6 Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 12 Sep 2025 11:25:50 +0800 Subject: [PATCH 31/83] add randstr cmd --- cmd/microdocs/README.md | 20 ++++++++++++++++++++ cmd/randstr/README.md | 19 +++++++++++++++++++ cmd/randstr/main.go | 18 ++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 cmd/microdocs/README.md create mode 100644 cmd/randstr/README.md create mode 100644 cmd/randstr/main.go diff --git a/cmd/microdocs/README.md b/cmd/microdocs/README.md new file mode 100644 index 0000000..569f15b --- /dev/null +++ b/cmd/microdocs/README.md @@ -0,0 +1,20 @@ + +# microdocs (微swagger文档) + +## 1、安装 + +``` +go install gitee.com/captials-team/ubdframe/cmd/microdocs@latest +``` + + +## 2、运行 + +``` +./microdocs + +``` + + + +运行后浏览器访问查看: **http://127.0.0.1:8080/swagger/index.html** diff --git a/cmd/randstr/README.md b/cmd/randstr/README.md new file mode 100644 index 0000000..37ab4df --- /dev/null +++ b/cmd/randstr/README.md @@ -0,0 +1,19 @@ + +# randstr (生成随机字符串) + +## 1、安装 + +``` +go install gitee.com/captials-team/ubdframe/cmd/randstr@latest +``` + + +## 2、运行 + +生成50个长度为10的随机字符串 +``` +randstr -l 10 -n 50 +``` + + + diff --git a/cmd/randstr/main.go b/cmd/randstr/main.go new file mode 100644 index 0000000..5cfafad --- /dev/null +++ b/cmd/randstr/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "flag" + "fmt" + "gitee.com/captials-team/ubdframe/src/common/utils" +) + +var len = flag.Int("l", 10, "指定随机字符串长度") +var num = flag.Int("n", 1, "指定生成数量") + +func main() { + flag.Parse() + + for i := 1; i <= (*num); i++ { + fmt.Println(utils.RandLetterFigureCode(*len)) + } +} -- Gitee From bed59e780ed228df35fd74308c113ec9de9d344d Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 12 Sep 2025 11:39:50 +0800 Subject: [PATCH 32/83] modify rand str --- cmd/randstr/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/randstr/main.go b/cmd/randstr/main.go index 5cfafad..9f68709 100644 --- a/cmd/randstr/main.go +++ b/cmd/randstr/main.go @@ -8,11 +8,12 @@ import ( var len = flag.Int("l", 10, "指定随机字符串长度") var num = flag.Int("n", 1, "指定生成数量") +var prefix = flag.String("", "", "指定前缀") func main() { flag.Parse() for i := 1; i <= (*num); i++ { - fmt.Println(utils.RandLetterFigureCode(*len)) + fmt.Println(*prefix + utils.RandLetterFigureCode(*len)) } } -- Gitee From 603f4806812b20059fcb6a49f04afc0a3e14ad01 Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 12 Sep 2025 11:40:38 +0800 Subject: [PATCH 33/83] modify prefix --- cmd/randstr/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/randstr/main.go b/cmd/randstr/main.go index 9f68709..4e6081b 100644 --- a/cmd/randstr/main.go +++ b/cmd/randstr/main.go @@ -8,7 +8,7 @@ import ( var len = flag.Int("l", 10, "指定随机字符串长度") var num = flag.Int("n", 1, "指定生成数量") -var prefix = flag.String("", "", "指定前缀") +var prefix = flag.String("p", "", "指定前缀") func main() { flag.Parse() -- Gitee From 7e6b6e14a2c23f1205d972a18744c89d76dac64d Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 15 Sep 2025 16:54:03 +0800 Subject: [PATCH 34/83] add timeclock --- cmd/timeclock/README.md | 23 ++++++++++ cmd/timeclock/main.go | 80 +++++++++++++++++++++++++++++++++ go.mod | 41 ++++++++++++++--- go.sum | 99 +++++++++++++++++++++++++++++++++-------- 4 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 cmd/timeclock/README.md create mode 100644 cmd/timeclock/main.go diff --git a/cmd/timeclock/README.md b/cmd/timeclock/README.md new file mode 100644 index 0000000..71761c8 --- /dev/null +++ b/cmd/timeclock/README.md @@ -0,0 +1,23 @@ +# timeclock (时间时钟) + +## 1、安装 + +``` +go install gitee.com/captials-team/ubdframe/cmd/timeclock@latest +``` + +## 2、运行 + +控制台运行 + +``` +timeclock +``` + +控件UI运行 + +``` +timeclock -ui +``` + + diff --git a/cmd/timeclock/main.go b/cmd/timeclock/main.go new file mode 100644 index 0000000..abeef9c --- /dev/null +++ b/cmd/timeclock/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "flag" + "fmt" + "gitee.com/captials-team/ubdframe/src/common" + "github.com/gonutz/wui" + "os" + "strings" + "time" +) + +var ui = flag.Bool("ui", false, "以控件UI展示") + +func main() { + flag.Parse() + + format := "2006-01-02 15:04:05.000" + + ch := make(chan string, 64) + ctx := context.Background() + if *ui { + go runAsWindows(ctx, ch) + } else { + go runAsConsole(ctx, ch) + } + + for { + ch <- time.Now().Format(format) + time.Sleep(time.Millisecond * 20) + } +} + +func runAsConsole(ctx context.Context, ch chan string) { + fmt.Printf("%s\n\n", strings.Repeat("=", 50)) + for s := range ch { + fmt.Printf("\r%s%s", s, strings.Repeat(" ", 20)) + } +} + +func runAsWindows(ctx context.Context, ch chan string) { + w := wui.NewWindow() + w.SetTitle("TimeClock") + w.SetSize(300, 200) + + label := wui.NewLabel() + label.SetWidth(w.Width()) + label.SetHeight(w.Height()) + label.SetCenterAlign() + label.SetVisible(true) + label.SetText("-") + label.SetEnabled(true) + label.SetPos(0, 0) + + f, _ := wui.NewFont(wui.FontDesc{ + Name: "微软雅黑", + Height: 24, + }) + label.SetFont(f) + + w.Add(label) + w.SetOnResize(func() { + label.SetWidth(w.Width()) + label.SetHeight(w.Height()) + }) + + w.SetOnClose(func() { + os.Exit(1) + }) + w.SetOnShow(func() { + go func() { + for s := range ch { + label.SetText(s) + } + }() + }) + common.ErrPanic(w.Show()) + +} diff --git a/go.mod b/go.mod index 53295dc..80546e4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22 toolchain go1.22.0 require ( + fyne.io/fyne/v2 v2.6.3 github.com/BurntSushi/toml v1.4.0 github.com/Jeffail/tunny v0.1.4 github.com/Knetic/govaluate v3.0.0+incompatible @@ -21,15 +22,17 @@ require ( github.com/go-redis/redis/v7 v7.4.1 github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.7.0 + github.com/gonutz/wui v1.0.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/jinzhu/copier v0.4.0 github.com/jinzhu/gorm v1.9.16 + github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 github.com/minio/minio-go/v7 v7.0.84 github.com/modern-go/reflect2 v1.0.2 github.com/mojocn/base64Captcha v1.3.6 github.com/natefinch/lumberjack v2.0.0+incompatible - github.com/nicksnyder/go-i18n/v2 v2.4.1 + github.com/nicksnyder/go-i18n/v2 v2.5.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.19.0 github.com/rfyiamcool/backoff v1.1.0 @@ -47,8 +50,8 @@ require ( go.etcd.io/etcd/client/v3 v3.5.17 go.uber.org/dig v1.18.0 go.uber.org/zap v1.24.0 - golang.org/x/net v0.33.0 - golang.org/x/text v0.21.0 + golang.org/x/net v0.35.0 + golang.org/x/text v0.22.0 golang.org/x/time v0.8.0 google.golang.org/grpc v1.68.1 google.golang.org/protobuf v1.34.2 @@ -60,6 +63,7 @@ require ( ) require ( + fyne.io/systray v1.11.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -85,8 +89,16 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/structs v1.1.0 // indirect + github.com/fredbi/uri v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fyne-io/gl-js v0.2.0 // indirect + github.com/fyne-io/glfw-js v0.3.0 // indirect + github.com/fyne-io/image v0.1.1 // indirect + github.com/fyne-io/oksvg v0.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect @@ -94,28 +106,41 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-text/render v0.2.0 // indirect + github.com/go-text/typesetting v0.2.1 // indirect github.com/goccy/go-json v0.10.4 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/gonutz/w32 v1.0.0 // indirect + github.com/hack-pad/go-indexeddb v0.3.2 // indirect + github.com/hack-pad/safejs v0.1.0 // indirect + github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/rymdport/portal v0.4.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/tidwall/gjson v1.14.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect @@ -123,17 +148,19 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/winc-link/edge-driver-proto v0.0.0-20250202082005-d2ba4a4e3ef5 // indirect + github.com/yuin/goldmark v1.7.8 // indirect go.etcd.io/etcd/api/v3 v3.5.17 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.17 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/image v0.13.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/image v0.24.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/tools v0.24.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index bb91494..1e1f6a7 100644 --- a/go.sum +++ b/go.sum @@ -593,6 +593,10 @@ cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vf cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +fyne.io/fyne/v2 v2.6.3 h1:cvtM2KHeRuH+WhtHiA63z5wJVBkQ9+Ay0UMl9PxFHyA= +fyne.io/fyne/v2 v2.6.3/go.mod h1:NGSurpRElVoI1G3h+ab2df3O5KLGh1CGbsMMcX0bPIs= +fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= +fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -757,13 +761,26 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DP github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= +github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs= +github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= +github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= +github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= +github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= +github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= +github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw= +github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -784,9 +801,13 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= @@ -819,10 +840,18 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= +github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= +github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= +github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -866,6 +895,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gonutz/w32 v1.0.0 h1:3t1z6ZfkFvirjFYBx9pHeHBuKoN/VBVk9yHb/m2Ll/k= +github.com/gonutz/w32 v1.0.0/go.mod h1:Rc/YP5K9gv0FW4p6X9qL3E7Y56lfMflEol1fLElfMW4= +github.com/gonutz/wui v1.0.0 h1:kXv6iHawOtz8g6qK4K29rs8KClHo1yYrrYddHArxpcY= +github.com/gonutz/wui v1.0.0/go.mod h1:cpEPmIh19mpxkcho2qMHLX16gVteB1aee8g11887kyE= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -908,6 +941,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -941,12 +976,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= +github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= +github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8= +github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= @@ -965,6 +1006,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -994,6 +1037,10 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= @@ -1027,8 +1074,10 @@ github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DG github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g= -github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk= +github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -1058,6 +1107,8 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -1088,6 +1139,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA= +github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -1106,6 +1159,10 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -1159,6 +1216,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -1212,8 +1271,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1242,8 +1301,9 @@ golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeap golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1273,8 +1333,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1345,8 +1405,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1392,8 +1452,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1429,6 +1489,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1486,8 +1547,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1519,8 +1580,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1595,8 +1656,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.1 h1:vxuHLTNS3Np5zrYoPRpcheASHX/7KiGo+8Y4ZM1J2O8= +golang.org/x/tools v0.24.1/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1882,6 +1943,8 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= +gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -- Gitee From d17ca9102a74c6816757d599977637b68c50d1db Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 16 Sep 2025 08:55:33 +0800 Subject: [PATCH 35/83] fix rss --- src/apps/rssapp/controllers/http/ctr_rss.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/rssapp/controllers/http/ctr_rss.go b/src/apps/rssapp/controllers/http/ctr_rss.go index c337c85..672db60 100644 --- a/src/apps/rssapp/controllers/http/ctr_rss.go +++ b/src/apps/rssapp/controllers/http/ctr_rss.go @@ -4,9 +4,9 @@ import ( "gitee.com/captials-team/ubdframe/src/domain/configstc" "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" "gitee.com/captials-team/ubdframe/src/domain/services" - "gitee.com/captials-team/ubdframe/src/domain/vo" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" + "gitee.com/captials-team/ubdframe/src/pkg/rsshelp" "github.com/gin-gonic/gin" ) @@ -37,7 +37,7 @@ func NewRssController(l v1log.ILog, conf *configstc.RssAppConfig, cache services // @success 500 {object} respdata.ResponseData{} "授权失败" // @Router /rss/items [post] func (ctr *RssController) RssCacheItems(ctx *gin.Context) { - var list []*vo.RssItems + var list []*rsshelp.RssItems for _, v := range ctr.conf.RssConfig { body, err := ctr.cache.Get(v.Name) -- Gitee From 37758b61cb9c9ce3a29a0023e86c7448c08d7e72 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 16 Sep 2025 18:13:46 +0800 Subject: [PATCH 36/83] add event bus --- go.mod | 25 +------- go.sum | 59 +------------------ src/apps/userapp/app.go | 41 ++++++++++++- src/apps/userapp/app_test.go | 23 ++++++++ src/apps/userapp/controllers/http/ctr_auth.go | 44 +++++++------- src/domain/configstc/config_params.go | 2 +- src/domain/dto/other.go | 13 ++++ src/domain/events/user.go | 6 ++ src/domain/interfaces/eventbus.go | 1 + 9 files changed, 106 insertions(+), 108 deletions(-) create mode 100644 src/domain/events/user.go create mode 100644 src/domain/interfaces/eventbus.go diff --git a/go.mod b/go.mod index 80546e4..71602cc 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.22 toolchain go1.22.0 require ( - fyne.io/fyne/v2 v2.6.3 github.com/BurntSushi/toml v1.4.0 github.com/Jeffail/tunny v0.1.4 github.com/Knetic/govaluate v3.0.0+incompatible github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.0 github.com/alibabacloud-go/tea-utils/v2 v2.0.7 + github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/multitemplate v1.0.1 @@ -27,7 +27,6 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/jinzhu/copier v0.4.0 github.com/jinzhu/gorm v1.9.16 - github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 github.com/minio/minio-go/v7 v7.0.84 github.com/modern-go/reflect2 v1.0.2 github.com/mojocn/base64Captcha v1.3.6 @@ -63,7 +62,6 @@ require ( ) require ( - fyne.io/systray v1.11.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -89,16 +87,9 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/structs v1.1.0 // indirect - github.com/fredbi/uri v1.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fyne-io/gl-js v0.2.0 // indirect - github.com/fyne-io/glfw-js v0.3.0 // indirect - github.com/fyne-io/image v0.1.1 // indirect - github.com/fyne-io/oksvg v0.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect @@ -106,41 +97,29 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/go-text/render v0.2.0 // indirect - github.com/go-text/typesetting v0.2.1 // indirect github.com/goccy/go-json v0.10.4 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gonutz/w32 v1.0.0 // indirect - github.com/hack-pad/go-indexeddb v0.3.2 // indirect - github.com/hack-pad/safejs v0.1.0 // indirect - github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/rymdport/portal v0.4.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect - github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect - github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/tidwall/gjson v1.14.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect @@ -148,7 +127,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/winc-link/edge-driver-proto v0.0.0-20250202082005-d2ba4a4e3ef5 // indirect - github.com/yuin/goldmark v1.7.8 // indirect go.etcd.io/etcd/api/v3 v3.5.17 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.17 // indirect go.uber.org/atomic v1.7.0 // indirect @@ -160,7 +138,6 @@ require ( golang.org/x/tools v0.24.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 1e1f6a7..4f431b7 100644 --- a/go.sum +++ b/go.sum @@ -593,10 +593,6 @@ cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vf cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.6.3 h1:cvtM2KHeRuH+WhtHiA63z5wJVBkQ9+Ay0UMl9PxFHyA= -fyne.io/fyne/v2 v2.6.3/go.mod h1:NGSurpRElVoI1G3h+ab2df3O5KLGh1CGbsMMcX0bPIs= -fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= -fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -679,6 +675,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM= +github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -761,26 +759,14 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DP github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= -github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= -github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs= -github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= -github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= -github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= -github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= -github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= -github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw= -github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -801,13 +787,9 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= -github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= -github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= @@ -840,18 +822,10 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= -github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= -github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= -github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= -github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= -github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -941,8 +915,6 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -976,18 +948,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= -github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= -github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8= -github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= -github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= @@ -1006,8 +972,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= -github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1037,10 +1001,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw= -github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= @@ -1074,8 +1034,6 @@ github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DG github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk= github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -1107,8 +1065,6 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= -github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -1139,8 +1095,6 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= -github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA= -github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -1159,10 +1113,6 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -1216,8 +1166,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -1489,7 +1437,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1943,8 +1890,6 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= -gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/apps/userapp/app.go b/src/apps/userapp/app.go index 5386c8d..106a028 100644 --- a/src/apps/userapp/app.go +++ b/src/apps/userapp/app.go @@ -31,11 +31,15 @@ import ( httpController "gitee.com/captials-team/ubdframe/src/apps/userapp/controllers/http" "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/domain/dto" + "gitee.com/captials-team/ubdframe/src/domain/events" "gitee.com/captials-team/ubdframe/src/domain/interfaces" + "gitee.com/captials-team/ubdframe/src/domain/models" "gitee.com/captials-team/ubdframe/src/domain/services" mysqlDao "gitee.com/captials-team/ubdframe/src/infrastructure/dao/mysql" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" "gitee.com/captials-team/ubdframe/src/pkg/uber_help" + evbus "github.com/asaskevich/EventBus" "go.uber.org/dig" ) @@ -49,6 +53,11 @@ type App struct { RpcServer *RpcServer } +// DIC 依赖注入容器 +func (app *App) DIC() *dig.Scope { + return app.di +} + func (app *App) initDao() { common.ErrPanic(app.di.Provide(func(conf *configstc.UserAppConfig) (user interfaces.ItfUser, userThird interfaces.ItfUserThird, vtCode interfaces.ItfVerifyCode, loginRecord interfaces.ItfUserLoginRecord) { var err error @@ -93,6 +102,30 @@ func (app *App) initUser(conf *configstc.UserAppConfig, s interfaces.ItfUserServ } } +// initEvent 初始化事件处理 +func (app *App) initEvent(conf *configstc.UserAppConfig, evenBus evbus.Bus, userDao interfaces.ItfUser, loginRecordDao interfaces.ItfUserLoginRecord) { + //用户登录 + common.ErrPanic(evenBus.SubscribeAsync(events.UserLogin, func(uid int64, info *dto.RequestInfo) { + userDao.LoginNote(uid, &dto.LoginInfoParam{ + Time: info.Time, + Ip: info.Ip, + Device: info.Device, + }) + }, false)) + + common.ErrPanic(evenBus.SubscribeAsync(events.UserLogin, func(uid int64, info *dto.RequestInfo) { + loginRecordDao.Add(&models.UserLoginRecord{ + Uid: uid, + LastDevice: info.Device, + LastLogin: &info.Time, + LastLocate: "", + LastIp: info.Ip, + Remark: "", + }) + }, false)) + +} + // NewApp 初始化app func NewApp(di *dig.Container, conf *configstc.UserAppConfig) *App { scope := di.Scope("user") @@ -105,6 +138,9 @@ func NewApp(di *dig.Container, conf *configstc.UserAppConfig) *App { return conf }), uber_help.ErrAlreadyProvided) + //事件总线 + common.ErrPanic(scope.Provide(evbus.New)) + app := &App{ di: scope, App: apps.NewApp(di, "user_app"), @@ -115,14 +151,14 @@ func NewApp(di *dig.Container, conf *configstc.UserAppConfig) *App { common.ErrPanic(scope.Invoke(app.initDao)) common.ErrPanic(scope.Invoke(app.initService)) common.ErrPanic(scope.Invoke(app.initController)) - common.ErrPanic(scope.Invoke(app.initUser)) //初始化用户 + common.ErrPanic(scope.Invoke(app.initUser)) //初始化用户 + common.ErrPanic(scope.Invoke(app.initEvent)) //初始化事件 //http api if conf.ApiServer.Enable { common.ErrPanic(scope.Provide(NewApiServer)) common.ErrPanic(scope.Invoke(func(api *ApiServer) { api.WithModule(conf.ModuleMode) - app.WithApiServer(api) app.ApiServer = api })) @@ -133,7 +169,6 @@ func NewApp(di *dig.Container, conf *configstc.UserAppConfig) *App { common.ErrPanic(scope.Provide(NewRpcServer)) common.ErrPanic(scope.Invoke(func(server *RpcServer) { server.WithModule(conf.ModuleMode) - app.App.WithApiServer(server) app.RpcServer = server })) diff --git a/src/apps/userapp/app_test.go b/src/apps/userapp/app_test.go index 52ac9ab..0cfa33a 100644 --- a/src/apps/userapp/app_test.go +++ b/src/apps/userapp/app_test.go @@ -4,7 +4,10 @@ import ( "gitee.com/captials-team/ubdframe/src/apps" "gitee.com/captials-team/ubdframe/src/common" "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/domain/dto" + "gitee.com/captials-team/ubdframe/src/domain/events" "gitee.com/captials-team/ubdframe/src/tests" + evbus "github.com/asaskevich/EventBus" "github.com/gin-gonic/gin" "go.uber.org/dig" "testing" @@ -15,7 +18,20 @@ func TestAppStart(t *testing.T) { di := testDi(t) common.ErrPanic(di.Provide(NewApp)) + //事件订阅处理 common.ErrPanic(di.Invoke(func(app *App) error { + return app.DIC().Invoke(func(eventBus evbus.Bus) { + eventBus.SubscribeAsync(events.UserLogin, func(uid int64, info *dto.RequestInfo) { + t.Logf("UserLogin %d", uid) + }, false) + eventBus.SubscribeAsync(events.UserLogout, func(uid int64, info *dto.RequestInfo) { + t.Logf("UserLogout %d", uid) + }, false) + }) + })) + + common.ErrPanic(di.Invoke(func(app *App) error { + return app.Start() })) @@ -163,6 +179,13 @@ func testDi(t *testing.T) *dig.Container { DbName: "db_ubd_frame", TablePrefix: "test_", }, + AutoCreateUser: []configstc.CreateUserConfig{ + { + UserName: "示例用户", + Account: "demo", + Password: "123456", + }, + }, DocsEnable: true, } })) diff --git a/src/apps/userapp/controllers/http/ctr_auth.go b/src/apps/userapp/controllers/http/ctr_auth.go index b97c867..84ea923 100644 --- a/src/apps/userapp/controllers/http/ctr_auth.go +++ b/src/apps/userapp/controllers/http/ctr_auth.go @@ -8,6 +8,7 @@ import ( "gitee.com/captials-team/ubdframe/src/domain/dto" "gitee.com/captials-team/ubdframe/src/domain/dto/reqdata" "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" + "gitee.com/captials-team/ubdframe/src/domain/events" "gitee.com/captials-team/ubdframe/src/domain/interfaces" "gitee.com/captials-team/ubdframe/src/domain/models" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" @@ -15,6 +16,7 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/jwtauth" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/passwd" + evbus "github.com/asaskevich/EventBus" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/silenceper/wechat/v2" @@ -40,11 +42,13 @@ type AuthController struct { wxMiniProg *miniprogram.MiniProgram //微信miniProg支持 + eventBus evbus.Bus //事件总线 + *gin_http.I18nController passwd.SaltMd5Password } -func NewAuthController(conf *configstc.UserAppConfig, l v1log.ILog, userDao interfaces.ItfUser, userThirdDao interfaces.ItfUserThird, loginRecordDao interfaces.ItfUserLoginRecord, verifyCodeDao interfaces.ItfVerifyCode) *AuthController { +func NewAuthController(conf *configstc.UserAppConfig, l v1log.ILog, userDao interfaces.ItfUser, userThirdDao interfaces.ItfUserThird, loginRecordDao interfaces.ItfUserLoginRecord, verifyCodeDao interfaces.ItfVerifyCode, eventBus evbus.Bus) *AuthController { ctr := &AuthController{ l: l, conf: conf, @@ -83,6 +87,7 @@ func NewAuthController(conf *configstc.UserAppConfig, l v1log.ILog, userDao inte ctr.userThirdDao = userThirdDao ctr.loginRecordDao = loginRecordDao ctr.verifyCodeDao = verifyCodeDao + ctr.eventBus = eventBus return ctr } @@ -197,9 +202,6 @@ func (ctr *AuthController) AuthLogin(ctx *gin.Context) { return } - //登录记录 - ctr.loginRecord(ctx, ctr.user2Auth(user)) - ctr.authResponse(ctx, ctr.user2Auth(user)) } @@ -213,6 +215,10 @@ func (ctr *AuthController) AuthLogin(ctx *gin.Context) { // @success 500 {object} respdata.ResponseData{} "取消失败" // @Router /auth/logout [post] func (ctr *AuthController) AuthLogout(ctx *gin.Context) { + if ctr.eventBus != nil { + auth := gin_http.GetAuth(ctx) + ctr.eventBus.Publish(events.UserLogout, auth.Id, ctr.browserInfo(ctx)) + } ctx.JSON(http.StatusOK, respdata.CSuccess) } @@ -404,7 +410,9 @@ func (ctr *AuthController) authResponse(ctx *gin.Context, auth dto.AuthInfo) { return } - ctr.loginRecord(ctx, auth) + if ctr.eventBus != nil { + ctr.eventBus.Publish(events.UserLogin, auth.Id, ctr.browserInfo(ctx)) + } ctr.Response(ctx, respdata.CSuccess.MData(utils.CopyTo(&authData, new(respdata.AuthResp)))) } @@ -432,22 +440,12 @@ func (ctr *AuthController) user2Auth(data *models.User) dto.AuthInfo { } } -// loginRecord 登录记录 -func (ctr *AuthController) loginRecord(ctx *gin.Context, auth dto.AuthInfo) { - //登录记录 - go func(p *dto.LoginInfoParam) { - ctr.userDao.LoginNote(auth.Id, p) - ctr.loginRecordDao.Add(&models.UserLoginRecord{ - Uid: auth.Id, - LastDevice: p.Device, - LastLogin: &p.Time, - LastLocate: "", - LastIp: p.Ip, - Remark: "", - }) - }(&dto.LoginInfoParam{ - Time: time.Now(), - Ip: ctx.ClientIP(), - Device: ctx.Request.Header.Get("User-Agent"), - }) +// browserInfo 浏览器信息 +func (ctr *AuthController) browserInfo(ctx *gin.Context) *dto.RequestInfo { + return &dto.RequestInfo{ + Time: time.Now(), + Ip: ctx.ClientIP(), + Device: ctx.Request.Header.Get("User-Agent"), + Request: ctx.Request, + } } diff --git a/src/domain/configstc/config_params.go b/src/domain/configstc/config_params.go index c5a25f1..68e3851 100644 --- a/src/domain/configstc/config_params.go +++ b/src/domain/configstc/config_params.go @@ -4,4 +4,4 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/extendparams" ) -type CommonExtendParams extendparams.CommonExtendParams +type CommonExtendParams = extendparams.CommonExtendParams diff --git a/src/domain/dto/other.go b/src/domain/dto/other.go index 7bb942b..5e816b1 100644 --- a/src/domain/dto/other.go +++ b/src/domain/dto/other.go @@ -1,5 +1,10 @@ package dto +import ( + "net/http" + "time" +) + // SearchGenericRecordParams 用户通用记录搜索参数 type SearchGenericRecordParams struct { Uid int64 `json:"uid" ` @@ -25,3 +30,11 @@ type WeatherInfo struct { Temp string `json:"temp"` //温度 Icon string `json:"icon"` //图标 } + +// RequestInfo 浏览器访问信息 +type RequestInfo struct { + Time time.Time //登录时间 + Ip string //ip + Device string //登陆设备名 + Request *http.Request +} diff --git a/src/domain/events/user.go b/src/domain/events/user.go new file mode 100644 index 0000000..ef922a7 --- /dev/null +++ b/src/domain/events/user.go @@ -0,0 +1,6 @@ +package events + +const UserCreated = "UserCreated" //用户创建后触发,参数:userId + +const UserLogin = "UserLogin" //用户登录后触发,参数:userId int64,param dto.RequestInfo +const UserLogout = "UserLogout" //用户退出登录后触发,参数:userId int64,param dto.RequestInfo diff --git a/src/domain/interfaces/eventbus.go b/src/domain/interfaces/eventbus.go new file mode 100644 index 0000000..08badf2 --- /dev/null +++ b/src/domain/interfaces/eventbus.go @@ -0,0 +1 @@ +package interfaces -- Gitee From 85757be48eb2cd541ce1b24903bbda6fcc852c01 Mon Sep 17 00:00:00 2001 From: sage Date: Wed, 17 Sep 2025 14:28:37 +0800 Subject: [PATCH 37/83] add event bus --- .../userapp/controllers/http/ctr_user_manage.go | 14 ++++++++++---- src/domain/events/user.go | 4 ++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/apps/userapp/controllers/http/ctr_user_manage.go b/src/apps/userapp/controllers/http/ctr_user_manage.go index 658c2ae..6cd0838 100644 --- a/src/apps/userapp/controllers/http/ctr_user_manage.go +++ b/src/apps/userapp/controllers/http/ctr_user_manage.go @@ -8,12 +8,13 @@ import ( "gitee.com/captials-team/ubdframe/src/domain/dto/paginate" "gitee.com/captials-team/ubdframe/src/domain/dto/reqdata" "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" + "gitee.com/captials-team/ubdframe/src/domain/events" "gitee.com/captials-team/ubdframe/src/domain/interfaces" "gitee.com/captials-team/ubdframe/src/domain/models" - mysqlDao "gitee.com/captials-team/ubdframe/src/infrastructure/dao/mysql" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/passwd" + evbus "github.com/asaskevich/EventBus" "github.com/gin-gonic/gin" ) @@ -22,18 +23,20 @@ type UserManageController struct { conf *configstc.UserAppConfig userModel interfaces.ItfUser userThirdModel interfaces.ItfUserThird + eventBus evbus.Bus passwd.SaltMd5Password gin_http.ResponseController gin_http.OperateLogController } -func NewUserManageController(l v1log.ILog, conf *configstc.UserAppConfig) *UserManageController { +func NewUserManageController(l v1log.ILog, conf *configstc.UserAppConfig, userDao interfaces.ItfUser, userThirdDao interfaces.ItfUserThird, eventBus evbus.Bus) *UserManageController { ctr := &UserManageController{ l: l, - userModel: mysqlDao.NewUserDao(conf.DBConfig), - userThirdModel: mysqlDao.NewUserThirdDao(conf.DBConfig), conf: conf, + userModel: userDao, + userThirdModel: userThirdDao, + eventBus: eventBus, SaltMd5Password: passwd.SaltMd5Password{Salt: conf.PasswordSalt}, } @@ -133,6 +136,7 @@ func (ctr *UserManageController) createUser(ctx *gin.Context, req reqdata.AddEdi } ctr.CustomizeLog(ctx, "新增用户-"+user.Nickname) + ctr.eventBus.Publish(events.UserCreated, user.Id) ctr.Response(ctx, respdata.CSuccess.MData(respdata.DetailResp{Id: user.Id})) } @@ -178,6 +182,7 @@ func (ctr *UserManageController) updateUser(ctx *gin.Context, req reqdata.AddEdi } ctr.CustomizeLog(ctx, "更新用户-"+user.Nickname) + ctr.eventBus.Publish(events.UserUpdated, user.Id) ctr.Response(ctx, respdata.CSuccess.MData(respdata.DetailResp{Id: user.Id})) } @@ -311,6 +316,7 @@ func (ctr *UserManageController) DeleteUser(ctx *gin.Context) { } ctr.CustomizeLog(ctx, "删除用户-"+user.Nickname) + ctr.eventBus.Publish(events.UserDeleted, user.Id) ctr.Response(ctx, respdata.CSuccess) } diff --git a/src/domain/events/user.go b/src/domain/events/user.go index ef922a7..3bf550f 100644 --- a/src/domain/events/user.go +++ b/src/domain/events/user.go @@ -1,6 +1,10 @@ package events const UserCreated = "UserCreated" //用户创建后触发,参数:userId +const UserUpdated = "UserUpdated" //用户信息更新后触发,参数:userId +const UserDeleted = "UserDeleted" //用户信息更新后触发,参数:userId const UserLogin = "UserLogin" //用户登录后触发,参数:userId int64,param dto.RequestInfo const UserLogout = "UserLogout" //用户退出登录后触发,参数:userId int64,param dto.RequestInfo + +const UserLogRecord = "UserLogRecord" //用户操作记录,参数:string,*http.Request -- Gitee From 61a9319645e5e4b21d6c5c6f7fba16d2db9f18c5 Mon Sep 17 00:00:00 2001 From: sage Date: Mon, 29 Sep 2025 18:04:32 +0800 Subject: [PATCH 38/83] modify job --- src/apps/adminapp/api_server.go | 4 +- src/apps/jobapp/api_server.go | 27 ++++++- src/apps/jobapp/app_test.go | 2 + src/apps/jobapp/controllers/http/ctr_job.go | 53 +++++++++++--- src/apps/jobapp/view/components/layout.html | 38 ++++++++++ src/apps/jobapp/view/components/nav.html | 40 +++++++++++ src/apps/jobapp/view/index.html | 59 +++++++++++++++ src/apps/jobapp/view/redirect.html | 30 ++++++++ src/apps/jobapp/view/user.html | 12 ++++ src/apps/userapp/api_server.go | 8 ++- src/domain/dto/job.go | 6 ++ src/domain/interfaces/controller_gin.go | 4 +- src/pkg/jobworker/mgr.go | 80 +++++++++++++++++++-- src/pkg/jobworker/worker.go | 14 +++- src/pkg/viewhandler/view.go | 79 ++++++++++---------- 15 files changed, 393 insertions(+), 63 deletions(-) create mode 100644 src/apps/jobapp/view/components/layout.html create mode 100644 src/apps/jobapp/view/components/nav.html create mode 100644 src/apps/jobapp/view/index.html create mode 100644 src/apps/jobapp/view/redirect.html create mode 100644 src/apps/jobapp/view/user.html create mode 100644 src/domain/dto/job.go diff --git a/src/apps/adminapp/api_server.go b/src/apps/adminapp/api_server.go index e1a2da9..c31b2d0 100644 --- a/src/apps/adminapp/api_server.go +++ b/src/apps/adminapp/api_server.go @@ -173,7 +173,9 @@ func NewApiServer(di *dig.Scope, conf *configstc.AdminAppConfig, logger v1log.IL s.WithName(conf.Name) s.WithCors() //开启跨域设置 - s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) + if conf.AuthConfig.Enable { + s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) + } common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfAdminLogController) { s.OperateLogOption.OperateLogHandler = ctr.LogHandler() diff --git a/src/apps/jobapp/api_server.go b/src/apps/jobapp/api_server.go index dbe3b45..9d2621f 100644 --- a/src/apps/jobapp/api_server.go +++ b/src/apps/jobapp/api_server.go @@ -1,6 +1,7 @@ package jobapp import ( + "embed" "gitee.com/captials-team/ubdframe/src/apps" httpController "gitee.com/captials-team/ubdframe/src/apps/jobapp/controllers/http" "gitee.com/captials-team/ubdframe/src/apps/jobapp/docs" @@ -11,6 +12,7 @@ import ( "gitee.com/captials-team/ubdframe/src/pkg/jwtauth" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/uber_help" + httplibs "gitee.com/captials-team/ubdframe/src/pkg/viewhandler" "github.com/gin-gonic/gin" "go.uber.org/dig" ) @@ -18,6 +20,7 @@ import ( type ApiServer struct { di *dig.Container conf *configstc.JobAppConfig + l v1log.ILog *apps.ApiServer gin_http.SwaggerOption //swagger相关选项配置 @@ -52,8 +55,10 @@ func (s *ApiServer) InitRouterForGin(engine *gin.Engine) { return } -func (s *ApiServer) router(g gin.IRouter) { +//go:embed view/* +var viewFs embed.FS +func (s *ApiServer) router(g gin.IRouter) { g.Use( gin_http.PanicHandler, gin_http.QPSLimiterHandler(10, 10), @@ -62,8 +67,21 @@ func (s *ApiServer) router(g gin.IRouter) { //后台模块 common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfJobController) { - adminGroup.POST("/mag/jobs/search", ctr.SearchJobs) //job搜索 + adminGroup.POST("/mag/jobs/reg/search", ctr.RegJobs) //job搜索 + + adminGroup.POST("/mag/jobs/running/search", ctr.RunningJobs) //job搜索 })) + + //web页面渲染 + viewConfig := httplibs.DefaultViewHandlerConfig + viewConfig.ViewFs = viewFs + + viewHandler := httplibs.NewViewHandler(viewConfig) + viewHandler.OptionIncludeFlag = "view/components/" //用于区分前缀目录进行加载 + viewHandler.AddLogger(s.l) + viewHandler.InitiateRender() + + viewHandler.GinRouterJump(s.Engine(), "/view") } func (s *ApiServer) Start() error { @@ -85,6 +103,7 @@ func NewApiServer(di *dig.Container, conf *configstc.JobAppConfig, logger v1log. s := &ApiServer{ di: di, conf: conf, + l: logger, SwaggerOption: gin_http.SwaggerOption{ Enable: conf.DocsEnable, Name: docs.SwaggerInfojobserevice.InstanceName(), @@ -99,7 +118,9 @@ func NewApiServer(di *dig.Container, conf *configstc.JobAppConfig, logger v1log. s.ApiServer.WithCors() //管理端auth - s.AdminAuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) + if conf.AuthConfig.Enable { + s.AdminAuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) + } return s } diff --git a/src/apps/jobapp/app_test.go b/src/apps/jobapp/app_test.go index cdd9ab2..a962924 100644 --- a/src/apps/jobapp/app_test.go +++ b/src/apps/jobapp/app_test.go @@ -16,6 +16,8 @@ func TestAppStart(t *testing.T) { common.ErrPanic(di.Provide(NewApp)) common.ErrPanic(di.Invoke(func(app *App) error { + app.WorkerMgr.Reg(workers.NewDemoWorker) + go func() { time.Sleep(time.Hour) app.Stop() diff --git a/src/apps/jobapp/controllers/http/ctr_job.go b/src/apps/jobapp/controllers/http/ctr_job.go index ce92ea5..4b28d03 100644 --- a/src/apps/jobapp/controllers/http/ctr_job.go +++ b/src/apps/jobapp/controllers/http/ctr_job.go @@ -2,8 +2,10 @@ package http import ( "gitee.com/captials-team/ubdframe/src/domain/configstc" - "gitee.com/captials-team/ubdframe/src/domain/interfaces" + "gitee.com/captials-team/ubdframe/src/domain/dto" + "gitee.com/captials-team/ubdframe/src/domain/dto/respdata" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" + "gitee.com/captials-team/ubdframe/src/pkg/jobworker" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "github.com/gin-gonic/gin" "go.uber.org/dig" @@ -12,12 +14,12 @@ import ( type JobController struct { l v1log.ILog conf *configstc.JobAppConfig - mgr interfaces.ItfWorker + mgr *jobworker.WorkerMgr gin_http.ResponseController } -func NewJobController(di *dig.Container, l v1log.ILog, conf *configstc.JobAppConfig, mgr interfaces.ItfWorker) *JobController { +func NewJobController(di *dig.Container, l v1log.ILog, conf *configstc.JobAppConfig, mgr *jobworker.WorkerMgr) *JobController { ctr := &JobController{ l: l, conf: conf, @@ -27,15 +29,44 @@ func NewJobController(di *dig.Container, l v1log.ILog, conf *configstc.JobAppCon return ctr } -// SearchJobs godoc -// @Summary 当前job列表 -// @Description 当前job列表 +// RegJobs godoc +// @Summary 注册的job列表 +// @Description 注册的job列表,可供使用的job // @Tags admin // @Produce json -// @Param param body reqdata.MetricDataReq true "查询参数" -// @fail 1 {object} respdata.ResponseData{} "重置失败" -// @success 200 {object} respdata.ResponseData{data=models.MetricData} "重置成功" -// @Router /mag/jobs/search [post] -func (ctr *JobController) SearchJobs(ctx *gin.Context) { +// @fail 1 {object} respdata.ResponseData{} "返回失败" +// @success 200 {object} respdata.ResponseData{data=[]dto.JobWorkerItem} "返回数据" +// @Router /mag/jobs/reg/search [post] +func (ctr *JobController) RegJobs(ctx *gin.Context) { + workers := ctr.mgr.RegWorkers() + var list []dto.JobWorkerItem + for _, v := range workers { + list = append(list, dto.JobWorkerItem{ + Name: v.Name(), + Interval: v.Interval().String(), + }) + } + ctr.Response(ctx, respdata.CSuccess.MData(list)) + return +} + +// RunningJobs godoc +// @Summary 运行中job列表 +// @Description 运行中job列表 +// @Tags admin +// @Produce json +// @fail 1 {object} respdata.ResponseData{} "返回失败" +// @success 200 {object} respdata.ResponseData{data=[]dto.JobWorkerItem} "返回数据" +// @Router /mag/jobs/running/search [post] +func (ctr *JobController) RunningJobs(ctx *gin.Context) { + workers := ctr.mgr.Workers() + var list []dto.JobWorkerItem + for _, v := range workers { + list = append(list, dto.JobWorkerItem{ + Name: v.Name(), + Interval: v.Interval().String(), + }) + } + ctr.Response(ctx, respdata.CSuccess.MData(list)) return } diff --git a/src/apps/jobapp/view/components/layout.html b/src/apps/jobapp/view/components/layout.html new file mode 100644 index 0000000..0ad54d0 --- /dev/null +++ b/src/apps/jobapp/view/components/layout.html @@ -0,0 +1,38 @@ + + + + + + Bootstrap demo + + + +{{template "content" .}} + + + + + + \ No newline at end of file diff --git a/src/apps/jobapp/view/components/nav.html b/src/apps/jobapp/view/components/nav.html new file mode 100644 index 0000000..beaa858 --- /dev/null +++ b/src/apps/jobapp/view/components/nav.html @@ -0,0 +1,40 @@ + +
+ +
+ diff --git a/src/apps/jobapp/view/index.html b/src/apps/jobapp/view/index.html new file mode 100644 index 0000000..2fd9d32 --- /dev/null +++ b/src/apps/jobapp/view/index.html @@ -0,0 +1,59 @@ +{{template "layout.html" .}} + +{{define "content"}} + +{{template "nav.html" .}} + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#名称运行间隔Handle
1MarkOtto@mdo
2JacobThornton@fat
3JohnDoe@social
+
+ + + +{{end}} + + + + diff --git a/src/apps/jobapp/view/redirect.html b/src/apps/jobapp/view/redirect.html new file mode 100644 index 0000000..147432a --- /dev/null +++ b/src/apps/jobapp/view/redirect.html @@ -0,0 +1,30 @@ + + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/src/apps/jobapp/view/user.html b/src/apps/jobapp/view/user.html new file mode 100644 index 0000000..96b4cac --- /dev/null +++ b/src/apps/jobapp/view/user.html @@ -0,0 +1,12 @@ +{{template "layout.html" .}} + +{{define "content"}} + +{{template "nav.html" .}} + +
+

User

+
+ +{{end}} + diff --git a/src/apps/userapp/api_server.go b/src/apps/userapp/api_server.go index 1ac38f8..295da6a 100644 --- a/src/apps/userapp/api_server.go +++ b/src/apps/userapp/api_server.go @@ -163,8 +163,12 @@ func NewApiServer(conf *configstc.UserAppConfig, logger v1log.ILog, s.WithCors() //开启跨域设置 //用户端auth - s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) - s.AdminAuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AdminAuthConfig.SecretKey)) + if conf.AuthConfig.Enable { + s.AuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey)) + } + if conf.AdminAuthConfig.Enable { + s.AdminAuthOption.AuthHandler = gin_http.AuthHandler(jwtauth.NewJwtTokenHandler(conf.AdminAuthConfig.SecretKey)) + } return s } diff --git a/src/domain/dto/job.go b/src/domain/dto/job.go new file mode 100644 index 0000000..ce1e904 --- /dev/null +++ b/src/domain/dto/job.go @@ -0,0 +1,6 @@ +package dto + +type JobWorkerItem struct { + Name string `json:"name"` + Interval string `json:"interval"` +} diff --git a/src/domain/interfaces/controller_gin.go b/src/domain/interfaces/controller_gin.go index 3ccd7b2..a96703c 100644 --- a/src/domain/interfaces/controller_gin.go +++ b/src/domain/interfaces/controller_gin.go @@ -156,7 +156,9 @@ type ItfBlockContentController interface { } type ItfJobController interface { - SearchJobs(ctx *gin.Context) + RegJobs(ctx *gin.Context) //已注册jobs + + RunningJobs(ctx *gin.Context) //运行中jobs //QueryJob(ctx *gin.Context) //JobTypes(ctx *gin.Context) } diff --git a/src/pkg/jobworker/mgr.go b/src/pkg/jobworker/mgr.go index 409e7b9..3b10392 100644 --- a/src/pkg/jobworker/mgr.go +++ b/src/pkg/jobworker/mgr.go @@ -1,9 +1,11 @@ package jobworker import ( + "context" "fmt" "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/domain/configstc" + "gitee.com/captials-team/ubdframe/src/pkg/contexts" v1log "gitee.com/captials-team/ubdframe/src/pkg/logs" "gitee.com/captials-team/ubdframe/src/pkg/metrics" "go.uber.org/dig" @@ -16,6 +18,7 @@ type WorkerMgr struct { di *dig.Container cfg *configstc.JobConfig l v1log.ILog + ctx *contexts.AdvancedContext //运行的worker workers []IWorker @@ -39,6 +42,7 @@ func NewWorkerMgr(di *dig.Container, c *configstc.JobConfig, l v1log.ILog) *Work l: l, regWorkers: map[string]YieldWorkerFunc{}, monitor: make(chan metrics.IndicData, 64), + ctx: contexts.NewCancelAdvancedContext(context.Background()), } return mgr @@ -53,6 +57,11 @@ func (mgr *WorkerMgr) Start() error { return nil } +func (mgr *WorkerMgr) Run() error { + mgr.RunOnce(1) + return nil +} + func (mgr *WorkerMgr) RunOnce(times ...int64) { mgr.loadFiles() mgr.loadWorkers() @@ -66,6 +75,18 @@ func (mgr *WorkerMgr) Stop() error { return nil } +func (mgr *WorkerMgr) Workers() []IWorker { + return mgr.workers +} + +func (mgr *WorkerMgr) RegWorkers() []IWorker { + var list []IWorker + for _, v := range mgr.regWorkers { + list = append(list, v(mgr.di, nil)) + } + return list +} + // StartAction 单独启动一个action处理 func (mgr *WorkerMgr) StartAction(item configstc.MultiActionItem) error { workers := mgr.actionItem2Workers(0, item) @@ -99,11 +120,18 @@ func (mgr *WorkerMgr) runWorkers(workers []IWorker, times ...int64) { go func() { defer wg.Done() + //只运行count次数 if count > 0 { - copyW.Run(count) - } else { - copyW.Start() + go mgr.runWorkerByCount(copyW, count) + return + } + //受控运行 + if ctrW, ok := copyW.(IWorkerCtr); ok { + go mgr.runWorkerByCtr(ctrW) + return } + + go mgr.runWorkerByInterval(copyW) }() } @@ -111,10 +139,50 @@ func (mgr *WorkerMgr) runWorkers(workers []IWorker, times ...int64) { wg.Wait() } -func (mgr *WorkerMgr) stopWorkers(workers []IWorker) { - for _, v := range workers { - v.Stop() +// runWorkerByCount 指定次数运行 +func (mgr *WorkerMgr) runWorkerByCount(w IWorker, count int64) { + for i := int64(1); i <= count; i++ { + select { + case <-mgr.ctx.Done(): + return + default: + w.Run(1) + } + } +} + +// runWorkerByCtr 受控运行 +func (mgr *WorkerMgr) runWorkerByCtr(w IWorkerCtr) { + for { + select { + case <-mgr.ctx.Done(): + w.Stop() + return + } + } +} + +// runWorkerByInterval 间隔运行 +func (mgr *WorkerMgr) runWorkerByInterval(w IWorker) { + ti := time.NewTicker(time.Second) + if w.Interval() > 0 { + ti = time.NewTicker(w.Interval()) } + i := 0 + + for { + i++ + select { + case <-mgr.ctx.Done(): + return + case <-ti.C: + w.Run(1) + } + } +} + +func (mgr *WorkerMgr) stopWorkers(workers []IWorker) { + mgr.ctx.Cancel(nil) } func (mgr *WorkerMgr) runMonitor(ch chan metrics.IndicData) { diff --git a/src/pkg/jobworker/worker.go b/src/pkg/jobworker/worker.go index d8173f6..a56d607 100644 --- a/src/pkg/jobworker/worker.go +++ b/src/pkg/jobworker/worker.go @@ -16,9 +16,6 @@ type IWorker interface { Actions() []string //支持的actions Params() map[string]string //支持的参数 - Start() //一直运行 - Stop() - Run(int64) //只运行一次 Debug() //debug用 @@ -26,6 +23,17 @@ type IWorker interface { //Tps() float64 //总tps } +// IWorkerCtr 可受控worker +type IWorkerCtr interface { + Start() //自行处理start + Stop() +} + +// IWorkerRun worker运行信息 +type IWorkerRun interface { + RunTimes() int64 +} + type IWorkerMonitor interface { SetMonitor(chan metrics.IndicData) } diff --git a/src/pkg/viewhandler/view.go b/src/pkg/viewhandler/view.go index aaacebc..948df34 100644 --- a/src/pkg/viewhandler/view.go +++ b/src/pkg/viewhandler/view.go @@ -30,6 +30,11 @@ type ViewHandlerConfig struct { ParamFunc func(r *http.Request) string //定制化获取view渲染模板名称的方法(不存在则默认方式获取) OptionLayoutFlag string //布局文件前缀标志 + + //加载文件前缀标志 + // 如: fs目录为 view/components/* 下的文件都是只作为template引入的文件 + // 则可以设置 OptionIncludeFlag= view/components/ 来只引入这下面的文件作为解析模板用的文件 + OptionIncludeFlag string } // ViewHandler viewHandler @@ -84,7 +89,7 @@ func (h *ViewHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // GinRouter 推荐的ginRouter配置 -func (h *ViewHandler) GinRouter(r *gin.Engine, prefix string) { +func (h *ViewHandler) GinRouter(r gin.IRouter, prefix string) { prefix = utils.KeepHasPrefix(prefix, "/") r.GET(prefix, h.ViewForGin) @@ -141,18 +146,23 @@ func (h *ViewHandler) templateName(name string) string { func (h *ViewHandler) loadRenderFromFs(embFs embed.FS, funcs ...template.FuncMap) multitemplate.Renderer { r := multitemplate.NewRenderer() templates := [][2]string{} - layouts := []string{} + includes := []string{} layoutPrefix := h.LayoutFilePrefix() + includePrefix := h.IncludeFilePrefix() //扫描分为模板文件和布局文件(l_开头的为布局文件) err := utils.ScanEmbedFsDo(embFs, func(file string, path string) error { + if strings.HasPrefix(path, includePrefix) { + includes = append(includes, path) + return nil + } if strings.HasPrefix(file, layoutPrefix) { - layouts = append(layouts, path) - //logger.Info("content:%s", bt) - } else { - layouts = append(layouts, path) - templates = append(templates, [2]string{file, path}) + includes = append(includes, path) + return nil } + templates = append(templates, [2]string{file, path}) + v1log.Out.Info("load template %s,%s", file, path) + return nil }) common.ErrPanic(err) @@ -160,7 +170,8 @@ func (h *ViewHandler) loadRenderFromFs(embFs embed.FS, funcs ...template.FuncMap h.Error("scan embed err: %s", err) return r } - //h.Info("templates %+v", templates) + h.Info("templates %+v", templates) + h.Info("includes %+v", includes) for _, f := range templates { file := f[0] @@ -170,18 +181,10 @@ func (h *ViewHandler) loadRenderFromFs(embFs embed.FS, funcs ...template.FuncMap for _, v := range funcs { tpl = tpl.Funcs(v) } - tmpl := template.Must(tpl.ParseFS(embFs, append(layouts, path)...)) + //需要把当前文件和引入的文件共同解析 + tmpl := template.Must(tpl.ParseFS(embFs, append(includes, path)...)) r.Add(file, tmpl) - - //debug - //if file == "index.html" { - // tmpl.ExecuteTemplate(os.Stdout, file, map[string]string{ - // "action": "xxx", - // }) - //} - - //h.Info("%s", path) - //h.Info("templateAdd %s,%+v,%s", file, layouts, path) + v1log.Out.Info("AddTemplate %s", file) } return r @@ -191,18 +194,22 @@ func (h *ViewHandler) loadRenderFromFs(embFs embed.FS, funcs ...template.FuncMap func (h *ViewHandler) loadRenderFromDir(dir string, funcs ...template.FuncMap) multitemplate.Renderer { r := multitemplate.NewRenderer() templates := [][2]string{} - layouts := []string{} + includes := []string{} layoutPrefix := h.LayoutFilePrefix() + includePrefix := h.IncludeFilePrefix() //扫描分为模板文件和布局文件(l_开头的为布局文件) err := utils.ScanDirFileDo(dir, func(file string, path string) error { + if strings.HasPrefix(path, includePrefix) { + includes = append(includes, path) + return nil + } if strings.HasPrefix(file, layoutPrefix) { - layouts = append(layouts, path) - //logger.Info("content:%s", bt) - } else { - layouts = append(layouts, path) - templates = append(templates, [2]string{file, path}) + includes = append(includes, path) + return nil } + templates = append(templates, [2]string{file, path}) + v1log.Out.Info("load template %s,%s", file, path) return nil }) @@ -221,18 +228,10 @@ func (h *ViewHandler) loadRenderFromDir(dir string, funcs ...template.FuncMap) m for _, v := range funcs { tpl = tpl.Funcs(v) } - tmpl := template.Must(tpl.ParseFiles(append(layouts, path)...)) + //需要把当前文件和引入的文件共同解析 + tmpl := template.Must(tpl.ParseFiles(append(includes, path)...)) r.Add(file, tmpl) - - //debug - //if file == "index.html" { - // tmpl.ExecuteTemplate(os.Stdout, file, map[string]string{ - // "action": "xxx", - // }) - //} - - //h.Info("%s", path) - //h.Info("templateAdd %s,%+v,%s", file, layouts, path) + v1log.Out.Info("AddTemplate %s", file) } return r @@ -246,6 +245,14 @@ func (h *ViewHandler) LayoutFilePrefix() string { return "l_" } +// IncludeFilePrefix 加载文件前缀 +func (h *ViewHandler) IncludeFilePrefix() string { + if len(h.OptionIncludeFlag) > 0 { + return h.OptionIncludeFlag + } + return "" +} + // loadTemplates 直接使用目录 func (h *ViewHandler) loadTemplates(templatesDir string) multitemplate.Renderer { r := multitemplate.NewRenderer() -- Gitee From 3720b7e9df11b0daaf1a6cfe209235a0cf0aecd8 Mon Sep 17 00:00:00 2001 From: sage Date: Tue, 30 Sep 2025 11:50:55 +0800 Subject: [PATCH 39/83] modify job mananger page --- src/apps/jobapp/api_server.go | 22 ++++--- src/apps/jobapp/controllers/http/ctr_job.go | 18 ++++-- src/apps/jobapp/view/components/nav.html | 17 +---- src/apps/jobapp/view/{user.html => help.html} | 13 +++- src/apps/jobapp/view/index.html | 43 ++++++------- src/apps/jobapp/view/running.html | 62 +++++++++++++++++++ src/apps/jobapp/workers/worker_demo.go | 55 +++++----------- src/domain/dto/job.go | 12 +++- src/pkg/jobworker/mgr.go | 42 +++++++++++-- src/pkg/jobworker/worker.go | 7 +-- 10 files changed, 186 insertions(+), 105 deletions(-) rename src/apps/jobapp/view/{user.html => help.html} (43%) create mode 100644 src/apps/jobapp/view/running.html diff --git a/src/apps/jobapp/api_server.go b/src/apps/jobapp/api_server.go index 9d2621f..ae9f929 100644 --- a/src/apps/jobapp/api_server.go +++ b/src/apps/jobapp/api_server.go @@ -6,6 +6,7 @@ import ( httpController "gitee.com/captials-team/ubdframe/src/apps/jobapp/controllers/http" "gitee.com/captials-team/ubdframe/src/apps/jobapp/docs" "gitee.com/captials-team/ubdframe/src/common" + "gitee.com/captials-team/ubdframe/src/common/utils" "gitee.com/captials-team/ubdframe/src/domain/configstc" "gitee.com/captials-team/ubdframe/src/domain/interfaces" "gitee.com/captials-team/ubdframe/src/pkg/gin_http" @@ -15,6 +16,7 @@ import ( httplibs "gitee.com/captials-team/ubdframe/src/pkg/viewhandler" "github.com/gin-gonic/gin" "go.uber.org/dig" + "html/template" ) type ApiServer struct { @@ -39,10 +41,7 @@ func (s *ApiServer) InitRouter() { } func (s *ApiServer) InitRouterForGin(engine *gin.Engine) { - var g = engine.Group("") - if len(s.conf.RoutePrefix) > 0 { - g = engine.Group(s.conf.RoutePrefix) - } + var g = engine.Group(s.conf.RoutePrefix) //注册swagger s.SwaggerRouter(g) @@ -50,7 +49,7 @@ func (s *ApiServer) InitRouterForGin(engine *gin.Engine) { //注册pprof s.PProfRouter(engine) - s.router(g) + s.router(engine) return } @@ -58,12 +57,13 @@ func (s *ApiServer) InitRouterForGin(engine *gin.Engine) { //go:embed view/* var viewFs embed.FS -func (s *ApiServer) router(g gin.IRouter) { +func (s *ApiServer) router(g *gin.Engine) { + prefix := s.conf.RoutePrefix g.Use( gin_http.PanicHandler, gin_http.QPSLimiterHandler(10, 10), ) - adminGroup := g.Group("", s.AdminAuthOption.OptAuthHandler()) + adminGroup := g.Group(prefix, s.AdminAuthOption.OptAuthHandler()) //后台模块 common.ErrPanic(s.di.Invoke(func(ctr interfaces.ItfJobController) { @@ -79,9 +79,13 @@ func (s *ApiServer) router(g gin.IRouter) { viewHandler := httplibs.NewViewHandler(viewConfig) viewHandler.OptionIncludeFlag = "view/components/" //用于区分前缀目录进行加载 viewHandler.AddLogger(s.l) + viewHandler.FuncMaps = append(viewHandler.FuncMaps, template.FuncMap{ + "link": func(s string) string { + return utils.KeepHasPrefix(prefix+utils.KeepHasPrefix(s, "/"), "/") + }, + }) viewHandler.InitiateRender() - - viewHandler.GinRouterJump(s.Engine(), "/view") + viewHandler.GinRouterJump(s.Engine(), prefix+"/view") } func (s *ApiServer) Start() error { diff --git a/src/apps/jobapp/controllers/http/ctr_job.go b/src/apps/jobapp/controllers/http/ctr_job.go index 4b28d03..f8f4a32 100644 --- a/src/apps/jobapp/controllers/http/ctr_job.go +++ b/src/apps/jobapp/controllers/http/ctr_job.go @@ -40,10 +40,10 @@ func NewJobController(di *dig.Container, l v1log.ILog, conf *configstc.JobAppCon func (ctr *JobController) RegJobs(ctx *gin.Context) { workers := ctr.mgr.RegWorkers() var list []dto.JobWorkerItem - for _, v := range workers { + for k, v := range workers { list = append(list, dto.JobWorkerItem{ - Name: v.Name(), - Interval: v.Interval().String(), + Worker: v.Name(), + Action: k, }) } ctr.Response(ctx, respdata.CSuccess.MData(list)) @@ -62,9 +62,17 @@ func (ctr *JobController) RunningJobs(ctx *gin.Context) { workers := ctr.mgr.Workers() var list []dto.JobWorkerItem for _, v := range workers { + running := ctr.mgr.QueryRunning(v.Op().OpId) + list = append(list, dto.JobWorkerItem{ - Name: v.Name(), - Interval: v.Interval().String(), + Worker: v.Name(), + Action: v.Op().Action, + Running: &dto.JobRunningData{ + Interval: v.Interval().String(), + Count: running.Count, + StartAt: running.StartAt, + LastAt: running.LastAt, + }, }) } ctr.Response(ctx, respdata.CSuccess.MData(list)) diff --git a/src/apps/jobapp/view/components/nav.html b/src/apps/jobapp/view/components/nav.html index beaa858..9997516 100644 --- a/src/apps/jobapp/view/components/nav.html +++ b/src/apps/jobapp/view/components/nav.html @@ -9,24 +9,13 @@