From 9cb45992cbc19370c6ee40c14f428ca3c933bddb Mon Sep 17 00:00:00 2001 From: sage Date: Fri, 13 Jun 2025 15:18:08 +0800 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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