diff --git a/.gitignore b/.gitignore index 3980640a..c6cf6d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ __debug_bin /data /custom /.custom +/run.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a1cf63c..41aa00ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,198 @@ # Changelog All notable changes to paopao-ce are documented in this file. -## 0.5.0+dev ([`dev`](https://github.com/rocboss/paopao-ce/tree/dev)) +## 0.6.0+dev ([`dev`](https://github.com/rocboss/paopao-ce/tree/dev)) +TODO; + +## 0.5.0 +### Added +- add `LoggerOpenObserve` feature use OpenObserve to collect log.[#370](https://github.com/rocboss/paopao-ce/pull/370) + add `LoggerOpenObserve` to `conf.yaml` 's `Features` section to enable this feature like below: + ```yaml + # file config.yaml + ... + Features: + Default: ["Base", "Postgres", "Meili", "LocalOSS", "LoggerOpenObserve", "BigCacheIndex", "web"] + LoggerOpenObserve: # 使用OpenObserve写日志 + Host: 127.0.0.1:5080 + Organization: paopao-ce + Stream: default + User: root@paopao.info + Password: tiFEI8UeJWuYA7kN + Secure: False + MinWorker: 5 # 最小后台工作者, 设置范围[5, 100], 默认5 + MaxLogBuffer: 100 # 最大log缓存条数, 设置范围[10, 10000], 默认100 + ... + ``` +- Added friend tweets bar feature support in home page. [#377](https://github.com/rocboss/paopao-ce/pull/377) +- web: add custom `Friendship` feature support. To custom setup `Friendship` use below configure in `web/.env` or `web/.env.local` + ``` + # 功能特性开启 + VITE_USE_FRIENDSHIP=true + + # 模块开启 + VITE_ENABLE_FRIENDS_BAR=true + ``` +- add Newest/Hots/Following tweets support in friend bar feature. + mirgration database first(sql ddl file in `scripts/migration/**/*_home_timeline.up.sql`): + ```sql + CREATE TABLE `p_post_metric` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `post_id` bigint unsigned NOT NULL, + `rank_score` bigint unsigned NOT NULL DEFAULT 0, + `incentive_score` int unsigned NOT NULL DEFAULT 0, + `decay_factor` int unsigned NOT NULL DEFAULT 0, + `motivation_factor` int unsigned NOT NULL DEFAULT 0, + `is_del` tinyint NOT NULL DEFAULT 0, -- 是否删除, 0否, 1是 + `created_on` bigint unsigned NOT NULL DEFAULT '0', + `modified_on` bigint unsigned NOT NULL DEFAULT '0', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_post_metric_post_id_rank_score` (`post_id`,`rank_score`) USING BTREE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + INSERT INTO p_post_metric (post_id, rank_score, created_on) + SELECT id AS post_id, + comment_count + upvote_count*2 + collection_count*4 AS rank_score, + created_on + FROM p_post + WHERE is_del=0; + + -- 原来的可见性: 0公开 1私密 2好友可见 3关注可见 + -- 现在的可见性: 0私密 10充电可见 20订阅可见 30保留 40保留 50好友可见 60关注可见 70保留 80保留 90公开 + UPDATE p_post a, p_post b + SET a.visibility = ( + CASE b.visibility + WHEN 0 THEN 90 + WHEN 1 THEN 0 + WHEN 2 THEN 50 + WHEN 3 THEN 60 + ELSE 0 + END + ) + WHERE a.ID = b.ID; + ``` +- add cache support for index/home etc. page. +- add hots comments support for post detail page. +- add highlight comments support for post detail page. + mirgration database first(sql ddl file in `scripts/migration/**/*_comment_esence.up.sql`): + ```sql + ALTER TABLE `p_comment` ADD COLUMN `is_essence` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '是否精选'; + ``` +- add follow/unfollow user support in index/home/collecion/message/post page. +- add simple prometheus metrics support. + add `Metrics` to `conf.yaml` 's `Features` section to enable this feature like below: + ```yaml + # file config.yaml + ... + Features: + Default: ["Base", "Postgres", "Meili", "LocalOSS", "Metrics", "web"] + JobManager: # Cron Job理器的配置参数 + MaxOnlineInterval: "@every 5m" # 更新最大在线人数,默认每5分钟更新一次 + UpdateMetricsInterval: "@every 5m" # 更新Prometheus指标,默认每5分钟更新一次 + MetricsServer: # Prometheus Metrics服务 + RunMode: debug + HttpIp: 0.0.0.0 + HttpPort: 6080 + ReadTimeout: 60 + WriteTimeout: 60 + ... + ``` +- add full support for tweet hots comment logic and add cache support for tweet comments. + mirgration database first(sql ddl file in `scripts/migration/**/*_rank_metrics.up.sql`): + ```sql + ALTER TABLE `p_comment` ADD COLUMN `reply_count` int unsigned NOT NULL DEFAULT 0 COMMENT '回复数'; + + UPDATE p_comment comment + SET reply_count = ( + SELECT count(*) FROM p_comment_reply reply WHERE reply.comment_id=comment.id AND reply.is_del=0 + ) + WHERE is_del=0; + + CREATE TABLE `p_comment_metric` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `comment_id` bigint unsigned NOT NULL, + `rank_score` bigint unsigned NOT NULL DEFAULT 0, + `incentive_score` int unsigned NOT NULL DEFAULT 0, + `decay_factor` int unsigned NOT NULL DEFAULT 0, + `motivation_factor` int unsigned NOT NULL DEFAULT 0, + `is_del` tinyint NOT NULL DEFAULT 0, + `created_on` bigint unsigned NOT NULL DEFAULT 0, + `modified_on` bigint unsigned NOT NULL DEFAULT 0, + `deleted_on` bigint unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_comment_metric_comment_id_rank_score` (`comment_id`, `rank_score`) USING BTREE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + INSERT INTO p_comment_metric (comment_id, rank_score, created_on) + SELECT id AS comment_id, + reply_count*2 + thumbs_up_count*4 - thumbs_down_count AS rank_score, + created_on + FROM p_comment + WHERE is_del=0; + + CREATE TABLE `p_user_metric` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint unsigned NOT NULL, + `tweets_count` int unsigned NOT NULL DEFAULT 0, + `latest_trends_on` bigint unsigned NOT NULL DEFAULT 0 COMMENT '最新动态时间', + `is_del` tinyint NOT NULL DEFAULT 0, + `created_on` bigint unsigned NOT NULL DEFAULT 0, + `modified_on` bigint unsigned NOT NULL DEFAULT 0, + `deleted_on` bigint unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_user_metric_user_id_tweets_count_trends` (`user_id`, `tweets_count`, `latest_trends_on`) USING BTREE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + INSERT INTO p_user_metric (user_id, tweets_count) + SELECT user_id, count(*) AS tweets_count + FROM p_post + WHERE is_del=0 + GROUP BY user_id; + ``` +- add message filter support for message page. +- add read all unread message and display unread message count support for message page. +- add support follow user embed to index trends enable navigation user tweets by slide bar. + mirgration database first(sql ddl file in `scripts/migration/**/*_user_relation.up.sql`): + ```sql + CREATE VIEW p_user_relation AS + SELECT user_id, friend_id he_uid, 5 AS style + FROM p_contact WHERE status=2 AND is_del=0 + UNION + SELECT user_id, follow_id he_uid, 10 AS style + FROM p_following WHERE is_del=0; + ``` +- add tweets count info in Home/Profile page. +- add custom web frontend features base by a profile that fetch from backend support. + can add custom config to conf.yaml to custom web frontend features: + ```yaml + ... + WebProfile: + UseFriendship: true # 前端是否使用好友体系 + EnableTrendsBar: true # 广场页面是否开启动态条栏功能 + EnableWallet: false # 是否开启钱包功能 + AllowTweetAttachment: true # 是否允许推文附件 + AllowTweetAttachmentPrice: true # 是否允许推文付费附件 + AllowTweetVideo: true # 是否允许视频推文 + AllowUserRegister: true # 是否允许用户注册 + AllowPhoneBind: true # 是否允许手机绑定 + DefaultTweetMaxLength: 2000 # 推文允许输入的最大长度, 默认2000字,值的范围需要查询后端支持的最大字数 + TweetWebEllipsisSize: 400 # Web端推文作为feed显示的最长字数,默认400字 + TweetMobileEllipsisSize: 300 # 移动端推文作为feed显示的最长字数,默认300字 + DefaultTweetVisibility: friend # 推文可见性,默认好友可见 值: public/following/friend/private + DefaultMsgLoopInterval: 5000 # 拉取未读消息的间隔,单位:毫秒, 默认5000ms + CopyrightTop: "2023 paopao.info" + CopyrightLeft: "Roc's Me" + CopyrightLeftLink: "" + CopyrightRight: "泡泡(PaoPao)开源社区" + CopyrightRightLink: "https://www.paopao.info" + ... + ``` +- add read more contents support for post card in tweets list. + +### Changed +- optimize jwt token generate logic. + ## 0.4.2 ### Fixed - fixed remove multi-objects no effects and occurs resource leak error when use Minio as OSS(Object Storage System).[#371](https://github.com/rocboss/paopao-ce/pull/371) [#372](https://github.com/rocboss/paopao-ce/pull/372) @@ -23,7 +214,7 @@ All notable changes to paopao-ce are documented in this file. ``` - add user highlight tweet support include custom tweet set to highlight and list in user/profile page. - add cli subcommand to start paopao-ce serve or other task. [#354](https://github.com/rocboss/paopao-ce/pull/354) -- add `Friendship` feature . [#355](https://github.com/rocboss/paopao-ce/pull/355) +- add `Followship` feature . [#355](https://github.com/rocboss/paopao-ce/pull/355) migration database first(sql ddl file in `scripts/migration/**/*_user_following.up.sql`): ```sql DROP TABLE IF EXISTS p_following; diff --git a/Makefile b/Makefile index 5a24d90c..1aa96136 100644 --- a/Makefile +++ b/Makefile @@ -18,15 +18,16 @@ RELEASE_DARWIN_AMD64 = $(RELEASE_ROOT)/darwin-amd64/$(PROJECT) RELEASE_DARWIN_ARM64 = $(RELEASE_ROOT)/darwin-arm64/$(PROJECT) RELEASE_WINDOWS_AMD64 = $(RELEASE_ROOT)/windows-amd64/$(PROJECT) -BUILD_VERSION := $(shell git describe --tags --always | cut -f 1 -f 2 -d "-") -BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S') +BUILD_VERSION := $(shell git describe --tags --always) +BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S %Z') SHA_SHORT := $(shell git rev-parse --short HEAD) -TAGS = "" MOD_NAME = github.com/rocboss/paopao-ce LDFLAGS = -X "${MOD_NAME}/pkg/version.version=${BUILD_VERSION}" \ -X "${MOD_NAME}/pkg/version.buildDate=${BUILD_DATE}" \ - -X "${MOD_NAME}/pkg/version.commitID=${SHA_SHORT}" -w -s + -X "${MOD_NAME}/pkg/version.commitID=${SHA_SHORT}" \ + -X "${MOD_NAME}/pkg/version.buildTags=${TAGS}" \ + -w -s all: fmt build diff --git a/README.md b/README.md index 7241c9fd..69a5c860 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Web端: 更多演示请前往[官网](https://www.paopao.info)体验(谢绝灌水) 桌面端: -![](docs/proposal/.assets/000-00.png) +![](docs/proposal/.assets/000-00.jpg)
@@ -288,7 +288,7 @@ make run TAGS='docs' ```sh cp config.yaml.sample config.yaml vim config.yaml # 修改参数 -paopao-ce +paopao serve ``` 配置文件中的 `Features` 小节是声明paopao-ce运行时开启哪些功能项: @@ -364,8 +364,8 @@ release/paopao serve --no-default-features --features sqlite3,localoss,loggerfil |`OSS:TempDir` | 对象存储 | 内测 |基于对象存储系统的对象拷贝/移动特性实现 先创建临时对象再持久化的功能| |`Redis` | 缓存 | 稳定 | Redis缓存功能 | |`SimpleCacheIndex` | 缓存 | Deprecated | 提供简单的 广场推文列表 的缓存功能 | -|`BigCacheIndex` | 缓存 | 稳定(推荐) | 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 | -|`RedisCacheIndex` | 缓存 | 内测(推荐) | 使用Redis缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 | +|`BigCacheIndex` | 缓存 | Deprecated | 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 | +|`RedisCacheIndex` | 缓存 | Deprecated | 使用Redis缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 | |`Zinc` | 搜索 | 稳定(推荐) | 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务 | |`Meili` | 搜索 | 稳定(推荐) | 基于[Meilisearch](https://github.com/meilisearch/meilisearch)搜索引擎提供推文搜索服务 | |`Bleve` | 搜索 | WIP | 基于[Bleve](https://github.com/blevesearch/bleve)搜索引擎提供推文搜索服务 | @@ -373,6 +373,7 @@ release/paopao serve --no-default-features --features sqlite3,localoss,loggerfil |`LoggerFile` | 日志 | 稳定 | 使用文件写日志 | |`LoggerZinc` | 日志 | 稳定(推荐) | 使用[Zinc](https://github.com/zinclabs/zinc)写日志 | |`LoggerMeili` | 日志 | 内测 | 使用[Meilisearch](https://github.com/meilisearch/meilisearch)写日志 | +|`LoggerOpenObserve` | 日志 | 内测 | 使用[OpenObserve](https://github.com/openobserve/openobserve)写日志 | |[`Friendship`](docs/proposal/22110410-关于Friendship功能项的设计.md) | 关系模式 | 内置 Builtin | 弱关系好友模式,类似微信朋友圈 | |[`Followship`](docs/proposal/22110409-关于Followship功能项的设计.md) | 关系模式 | 内置 Builtin | 关注者模式,类似Twitter的Follow模式 | |[`Lightship`](docs/proposal/22121409-关于Lightship功能项的设计.md) | 关系模式 | 弃用 Deprecated | 开放模式,所有推文都公开可见 | @@ -382,6 +383,8 @@ release/paopao serve --no-default-features --features sqlite3,localoss,loggerfil |[`Pyroscope`](docs/proposal/23021510-关于使用pyroscope用于性能调试的设计.md)| 性能优化 | 内测 | 开启Pyroscope功能用于性能调试 | |[`Pprof`](docs/proposal/23062905-添加Pprof功能特性用于获取Profile.md)| 性能优化 | 内测 | 开启Pprof功能收集Profile信息 | |`PhoneBind` | 其他 | 稳定 | 手机绑定功能 | +|`UseAuditHook` | 其他 | 内测 | 使用审核hook功能 | +|`DisableJobManager` | 其他 | 内测 | 禁止使用JobManager功能 | |`Web:DisallowUserRegister` | 功能特性 | 稳定 | 不允许用户注册 | > 功能项状态详情参考 [features-status](features-status.md). @@ -495,6 +498,35 @@ MinIO: # MinIO 存储配置 ... ``` +#### [OpenObserve](https://github.com/openobserve/openobserve) 日志收集、指标度量、轨迹跟踪 +* OpenObserve运行 +```sh +# 使用Docker运行 +mkdir data && docker run -v $PWD/data:/data -e ZO_DATA_DIR="/data" -p 5080:5080 \ + -e ZO_ROOT_USER_EMAIL="root@paopao.info" -e ZO_ROOT_USER_PASSWORD="paopao-ce" \ + public.ecr.aws/zinclabs/openobserve:latest + +# 使用docker compose运行, 需要删除docker-compose.yaml中关于openobserve的注释 +docker compose up -d openobserve +# visit http://loclahost:5080 +``` + +* 修改LoggerOpenObserve配置 +```yaml +# features中加上 LoggerOpenObserve +Features: + Default: ["Meili", "LoggerOpenObserve", "Base", "Sqlite3", "BigCacheIndex"] +... +LoggerOpenObserve: # 使用OpenObserve写日志 + Host: 127.0.0.1:5080 + Organization: paopao-ce + Stream: default + User: root@paopao.info + Password: tiFEI8UeJWuYA7kN + Secure: False +... +``` + #### [Pyroscope](https://github.com/pyroscope-io/pyroscope) 性能剖析 * Pyroscope运行 ```sh @@ -509,7 +541,7 @@ docker compose up -d pyroscope * 修改Pyroscope配置 ```yaml -# features中加上 MinIO +# features中加上 Pyroscope Features: Default: ["Meili", "LoggerMeili", "Base", "Sqlite3", "BigCacheIndex", "Pyroscope"] ... diff --git a/ROADMAP.md b/ROADMAP.md index 564278ed..b45e21df 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -56,7 +56,7 @@ ## paopao-ce-plus roadmap #### paopao-ce-plus/v0.5.0 -* [ ] adapt for paopao-ce v0.4.0 +* [x] adapt for paopao-ce v0.5.0 #### paopao-ce-plus/v0.4.0 * [x] adapt for paopao-ce v0.4.0 diff --git a/auto/api/v1/admin.go b/auto/api/v1/admin.go index ad4ac308..2ea0ed96 100644 --- a/auto/api/v1/admin.go +++ b/auto/api/v1/admin.go @@ -31,6 +31,7 @@ type Admin interface { // Chain provide handlers chain for gin Chain() gin.HandlersChain + SiteInfo(*web.SiteInfoReq) (*web.SiteInfoResp, mir.Error) ChangeUserStatus(*web.ChangeUserStatusReq) mir.Error mustEmbedUnimplementedAdminServant() @@ -44,6 +45,20 @@ func RegisterAdminServant(e *gin.Engine, s Admin) { router.Use(middlewares...) // register routes info to router + router.Handle("GET", "/admin/site/status", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.SiteInfoReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.SiteInfo(req) + s.Render(c, resp, err) + }) router.Handle("POST", "/admin/user/status", func(c *gin.Context) { select { case <-c.Request.Context().Done(): @@ -66,6 +81,10 @@ func (UnimplementedAdminServant) Chain() gin.HandlersChain { return nil } +func (UnimplementedAdminServant) SiteInfo(req *web.SiteInfoReq) (*web.SiteInfoResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + func (UnimplementedAdminServant) ChangeUserStatus(req *web.ChangeUserStatusReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } diff --git a/auto/api/v1/core.go b/auto/api/v1/core.go index 159e938f..1eee2f63 100644 --- a/auto/api/v1/core.go +++ b/auto/api/v1/core.go @@ -29,9 +29,9 @@ type Core interface { GetStars(*web.GetStarsReq) (*web.GetStarsResp, mir.Error) GetCollections(*web.GetCollectionsReq) (*web.GetCollectionsResp, mir.Error) SendUserWhisper(*web.SendWhisperReq) mir.Error + ReadAllMessage(*web.ReadAllMessageReq) mir.Error ReadMessage(*web.ReadMessageReq) mir.Error GetMessages(*web.GetMessagesReq) (*web.GetMessagesResp, mir.Error) - GetUnreadMsgCount(*web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) GetUserInfo(*web.UserInfoReq) (*web.UserInfoResp, mir.Error) SyncSearchIndex(*web.SyncSearchIndexReq) mir.Error @@ -201,47 +201,50 @@ func RegisterCoreServant(e *gin.Engine, s Core) { } s.Render(c, nil, s.SendUserWhisper(req)) }) - router.Handle("POST", "/user/message/read", func(c *gin.Context) { + router.Handle("POST", "/user/message/readall", func(c *gin.Context) { select { case <-c.Request.Context().Done(): return default: } - req := new(web.ReadMessageReq) + req := new(web.ReadAllMessageReq) if err := s.Bind(c, req); err != nil { s.Render(c, nil, err) return } - s.Render(c, nil, s.ReadMessage(req)) + s.Render(c, nil, s.ReadAllMessage(req)) }) - router.Handle("GET", "/user/messages", func(c *gin.Context) { + router.Handle("POST", "/user/message/read", func(c *gin.Context) { select { case <-c.Request.Context().Done(): return default: } - req := new(web.GetMessagesReq) - var bv _binding_ = req - if err := bv.Bind(c); err != nil { + req := new(web.ReadMessageReq) + if err := s.Bind(c, req); err != nil { s.Render(c, nil, err) return } - resp, err := s.GetMessages(req) - s.Render(c, resp, err) + s.Render(c, nil, s.ReadMessage(req)) }) - router.Handle("GET", "/user/msgcount/unread", func(c *gin.Context) { + router.Handle("GET", "/user/messages", func(c *gin.Context) { select { case <-c.Request.Context().Done(): return default: } - req := new(web.GetUnreadMsgCountReq) + req := new(web.GetMessagesReq) if err := s.Bind(c, req); err != nil { s.Render(c, nil, err) return } - resp, err := s.GetUnreadMsgCount(req) - s.Render(c, resp, err) + resp, err := s.GetMessages(req) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) }) router.Handle("GET", "/user/info", func(c *gin.Context) { select { @@ -324,15 +327,15 @@ func (UnimplementedCoreServant) SendUserWhisper(req *web.SendWhisperReq) mir.Err return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedCoreServant) ReadMessage(req *web.ReadMessageReq) mir.Error { +func (UnimplementedCoreServant) ReadAllMessage(req *web.ReadAllMessageReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedCoreServant) GetMessages(req *web.GetMessagesReq) (*web.GetMessagesResp, mir.Error) { - return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedCoreServant) ReadMessage(req *web.ReadMessageReq) mir.Error { + return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedCoreServant) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) { +func (UnimplementedCoreServant) GetMessages(req *web.GetMessagesReq) (*web.GetMessagesResp, mir.Error) { return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } diff --git a/auto/api/v1/loose.go b/auto/api/v1/loose.go index 4c9f9c6f..536c3d1d 100644 --- a/auto/api/v1/loose.go +++ b/auto/api/v1/loose.go @@ -18,6 +18,7 @@ type Loose interface { // Chain provide handlers chain for gin Chain() gin.HandlersChain + TweetDetail(*web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) TweetComments(*web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) TopicList(*web.TopicListReq) (*web.TopicListResp, mir.Error) GetUserProfile(*web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) @@ -35,6 +36,20 @@ func RegisterLooseServant(e *gin.Engine, s Loose) { router.Use(middlewares...) // register routes info to router + router.Handle("GET", "/post", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.TweetDetailReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.TweetDetail(req) + s.Render(c, resp, err) + }) router.Handle("GET", "/post/comments", func(c *gin.Context) { select { case <-c.Request.Context().Done(): @@ -47,7 +62,12 @@ func RegisterLooseServant(e *gin.Engine, s Loose) { return } resp, err := s.TweetComments(req) - s.Render(c, resp, err) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) }) router.Handle("GET", "/tags", func(c *gin.Context) { select { @@ -89,7 +109,12 @@ func RegisterLooseServant(e *gin.Engine, s Loose) { return } resp, err := s.GetUserTweets(req) - s.Render(c, resp, err) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) }) router.Handle("GET", "/posts", func(c *gin.Context) { select { @@ -104,7 +129,12 @@ func RegisterLooseServant(e *gin.Engine, s Loose) { return } resp, err := s.Timeline(req) - s.Render(c, resp, err) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) }) } @@ -115,6 +145,10 @@ func (UnimplementedLooseServant) Chain() gin.HandlersChain { return nil } +func (UnimplementedLooseServant) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + func (UnimplementedLooseServant) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) { return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } diff --git a/auto/api/v1/priv.go b/auto/api/v1/priv.go index f4adccfc..063f00af 100644 --- a/auto/api/v1/priv.go +++ b/auto/api/v1/priv.go @@ -27,6 +27,7 @@ type Priv interface { ThumbsUpTweetComment(*web.TweetCommentThumbsReq) mir.Error DeleteCommentReply(*web.DeleteCommentReplyReq) mir.Error CreateCommentReply(*web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error) + HighlightComment(*web.HighlightCommentReq) (*web.HighlightCommentResp, mir.Error) DeleteComment(*web.DeleteCommentReq) mir.Error CreateComment(*web.CreateCommentReq) (*web.CreateCommentResp, mir.Error) VisibleTweet(*web.VisibleTweetReq) (*web.VisibleTweetResp, mir.Error) @@ -44,8 +45,20 @@ type Priv interface { mustEmbedUnimplementedPrivServant() } +type PrivChain interface { + ChainCreateTweet() gin.HandlersChain + + mustEmbedUnimplementedPrivChain() +} + // RegisterPrivServant register Priv servant to gin -func RegisterPrivServant(e *gin.Engine, s Priv) { +func RegisterPrivServant(e *gin.Engine, s Priv, m ...PrivChain) { + var cc PrivChain + if len(m) > 0 { + cc = m[0] + } else { + cc = &UnimplementedPrivChain{} + } router := e.Group("v1") // use chain for router middlewares := s.Chain() @@ -172,6 +185,20 @@ func RegisterPrivServant(e *gin.Engine, s Priv) { resp, err := s.CreateCommentReply(req) s.Render(c, resp, err) }) + router.Handle("POST", "/post/comment/highlight", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.HighlightCommentReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.HighlightComment(req) + s.Render(c, resp, err) + }) router.Handle("DELETE", "/post/comment", func(c *gin.Context) { select { case <-c.Request.Context().Done(): @@ -297,7 +324,7 @@ func RegisterPrivServant(e *gin.Engine, s Priv) { } s.Render(c, nil, s.DeleteTweet(req)) }) - router.Handle("POST", "/post", func(c *gin.Context) { + router.Handle("POST", "/post", append(cc.ChainCreateTweet(), func(c *gin.Context) { select { case <-c.Request.Context().Done(): return @@ -310,8 +337,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv) { return } resp, err := s.CreateTweet(req) - s.Render(c, resp, err) - }) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) + })...) router.Handle("GET", "/attachment", func(c *gin.Context) { select { case <-c.Request.Context().Done(): @@ -402,6 +434,10 @@ func (UnimplementedPrivServant) CreateCommentReply(req *web.CreateCommentReplyRe return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } +func (UnimplementedPrivServant) HighlightComment(req *web.HighlightCommentReq) (*web.HighlightCommentResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + func (UnimplementedPrivServant) DeleteComment(req *web.DeleteCommentReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } @@ -455,3 +491,12 @@ func (UnimplementedPrivServant) UploadAttachment(req *web.UploadAttachmentReq) ( } func (UnimplementedPrivServant) mustEmbedUnimplementedPrivServant() {} + +// UnimplementedPrivChain can be embedded to have forward compatible implementations. +type UnimplementedPrivChain struct{} + +func (b *UnimplementedPrivChain) ChainCreateTweet() gin.HandlersChain { + return nil +} + +func (b *UnimplementedPrivChain) mustEmbedUnimplementedPrivChain() {} diff --git a/auto/api/v1/pub.go b/auto/api/v1/pub.go index 850a7906..2a68a600 100644 --- a/auto/api/v1/pub.go +++ b/auto/api/v1/pub.go @@ -15,7 +15,6 @@ import ( type Pub interface { _default_ - TweetDetail(*web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) SendCaptcha(*web.SendCaptchaReq) mir.Error GetCaptcha() (*web.GetCaptchaResp, mir.Error) Register(*web.RegisterReq) (*web.RegisterResp, mir.Error) @@ -30,20 +29,6 @@ func RegisterPubServant(e *gin.Engine, s Pub) { router := e.Group("v1") // register routes info to router - router.Handle("GET", "/post", func(c *gin.Context) { - select { - case <-c.Request.Context().Done(): - return - default: - } - req := new(web.TweetDetailReq) - if err := s.Bind(c, req); err != nil { - s.Render(c, nil, err) - return - } - resp, err := s.TweetDetail(req) - s.Render(c, resp, err) - }) router.Handle("POST", "/captcha", func(c *gin.Context) { select { case <-c.Request.Context().Done(): @@ -110,10 +95,6 @@ func RegisterPubServant(e *gin.Engine, s Pub) { // UnimplementedPubServant can be embedded to have forward compatible implementations. type UnimplementedPubServant struct{} -func (UnimplementedPubServant) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) { - return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) -} - func (UnimplementedPubServant) SendCaptcha(req *web.SendCaptchaReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } diff --git a/auto/api/v1/relax.go b/auto/api/v1/relax.go new file mode 100644 index 00000000..2cd41803 --- /dev/null +++ b/auto/api/v1/relax.go @@ -0,0 +1,87 @@ +// Code generated by go-mir. DO NOT EDIT. +// versions: +// - mir v4.0.0 + +package v1 + +import ( + "net/http" + + "github.com/alimy/mir/v4" + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +type Relax interface { + _default_ + + // Chain provide handlers chain for gin + Chain() gin.HandlersChain + + GetUnreadMsgCount(*web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) + + mustEmbedUnimplementedRelaxServant() +} + +type RelaxChain interface { + ChainGetUnreadMsgCount() gin.HandlersChain + + mustEmbedUnimplementedRelaxChain() +} + +// RegisterRelaxServant register Relax servant to gin +func RegisterRelaxServant(e *gin.Engine, s Relax, m ...RelaxChain) { + var cc RelaxChain + if len(m) > 0 { + cc = m[0] + } else { + cc = &UnimplementedRelaxChain{} + } + router := e.Group("v1") + // use chain for router + middlewares := s.Chain() + router.Use(middlewares...) + + // register routes info to router + router.Handle("GET", "/user/msgcount/unread", append(cc.ChainGetUnreadMsgCount(), func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.GetUnreadMsgCountReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.GetUnreadMsgCount(req) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) + })...) +} + +// UnimplementedRelaxServant can be embedded to have forward compatible implementations. +type UnimplementedRelaxServant struct{} + +func (UnimplementedRelaxServant) Chain() gin.HandlersChain { + return nil +} + +func (UnimplementedRelaxServant) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedRelaxServant) mustEmbedUnimplementedRelaxServant() {} + +// UnimplementedRelaxChain can be embedded to have forward compatible implementations. +type UnimplementedRelaxChain struct{} + +func (b *UnimplementedRelaxChain) ChainGetUnreadMsgCount() gin.HandlersChain { + return nil +} + +func (b *UnimplementedRelaxChain) mustEmbedUnimplementedRelaxChain() {} diff --git a/auto/api/v1/site.go b/auto/api/v1/site.go new file mode 100644 index 00000000..7f4f16cf --- /dev/null +++ b/auto/api/v1/site.go @@ -0,0 +1,63 @@ +// Code generated by go-mir. DO NOT EDIT. +// versions: +// - mir v4.0.0 + +package v1 + +import ( + "net/http" + + "github.com/alimy/mir/v4" + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +type Site interface { + _default_ + + Profile() (*conf.WebProfileConf, mir.Error) + Version() (*web.VersionResp, mir.Error) + + mustEmbedUnimplementedSiteServant() +} + +// RegisterSiteServant register Site servant to gin +func RegisterSiteServant(e *gin.Engine, s Site) { + router := e.Group("v1") + + // register routes info to router + router.Handle("GET", "/site/profile", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + + resp, err := s.Profile() + s.Render(c, resp, err) + }) + router.Handle("GET", "/site/version", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + + resp, err := s.Version() + s.Render(c, resp, err) + }) +} + +// UnimplementedSiteServant can be embedded to have forward compatible implementations. +type UnimplementedSiteServant struct{} + +func (UnimplementedSiteServant) Profile() (*conf.WebProfileConf, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedSiteServant) Version() (*web.VersionResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedSiteServant) mustEmbedUnimplementedSiteServant() {} diff --git a/auto/api/v1/trends.go b/auto/api/v1/trends.go new file mode 100644 index 00000000..9576ca84 --- /dev/null +++ b/auto/api/v1/trends.go @@ -0,0 +1,66 @@ +// Code generated by go-mir. DO NOT EDIT. +// versions: +// - mir v4.0.0 + +package v1 + +import ( + "net/http" + + "github.com/alimy/mir/v4" + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +type Trends interface { + _default_ + + // Chain provide handlers chain for gin + Chain() gin.HandlersChain + + GetIndexTrends(*web.GetIndexTrendsReq) (*web.GetIndexTrendsResp, mir.Error) + + mustEmbedUnimplementedTrendsServant() +} + +// RegisterTrendsServant register Trends servant to gin +func RegisterTrendsServant(e *gin.Engine, s Trends) { + router := e.Group("v1") + // use chain for router + middlewares := s.Chain() + router.Use(middlewares...) + + // register routes info to router + router.Handle("GET", "/trends/index", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.GetIndexTrendsReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.GetIndexTrends(req) + if err != nil { + s.Render(c, nil, err) + return + } + var rv _render_ = resp + rv.Render(c) + }) +} + +// UnimplementedTrendsServant can be embedded to have forward compatible implementations. +type UnimplementedTrendsServant struct{} + +func (UnimplementedTrendsServant) Chain() gin.HandlersChain { + return nil +} + +func (UnimplementedTrendsServant) GetIndexTrends(req *web.GetIndexTrendsReq) (*web.GetIndexTrendsResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedTrendsServant) mustEmbedUnimplementedTrendsServant() {} diff --git a/build-image.sh b/build-image.sh index dba722d3..f30affde 100755 --- a/build-image.sh +++ b/build-image.sh @@ -2,7 +2,7 @@ # eg.1 : sh build-image.sh # eg.2, set image: sh build-image.sh bitbus/paopao-ce -VERSION=`git describe --tags --always | cut -f1 -f2 -d "-"` # eg.: 0.2.5 +VERSION=`git describe --tags --always | cut -f1,2 -d "-"` # eg.: 0.2.5 IMAGE="bitbus/paopao-ce" if [ -n "$1" ]; then diff --git a/cmd/serve/serve.go b/cmd/serve/serve.go index 5ffd7070..b7dcbff1 100644 --- a/cmd/serve/serve.go +++ b/cmd/serve/serve.go @@ -12,7 +12,7 @@ import ( "syscall" "time" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/fatih/color" "github.com/getsentry/sentry-go" "github.com/rocboss/paopao-ce/cmd" diff --git a/config.yaml.sample b/config.yaml.sample index 33f9b73d..635a9811 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -183,4 +183,23 @@ Sqlite3: # Sqlite3数据库 Redis: InitAddress: - redis:6379 +WebProfile: + UseFriendship: true # 前端是否使用好友体系 + EnableTrendsBar: true # 广场页面是否开启动态条栏功能 + EnableWallet: false # 是否开启钱包功能 + AllowTweetAttachment: true # 是否允许推文附件 + AllowTweetAttachmentPrice: true # 是否允许推文付费附件 + AllowTweetVideo: true # 是否允许视频推文 + AllowUserRegister: true # 是否允许用户注册 + AllowPhoneBind: true # 是否允许手机绑定 + DefaultTweetMaxLength: 2000 # 推文允许输入的最大长度, 默认2000字,值的范围需要查询后端支持的最大字数 + TweetWebEllipsisSize: 400 # Web端推文作为feed显示的最长字数,默认400字 + TweetMobileEllipsisSize: 300 # 移动端推文作为feed显示的最长字数,默认300字 + DefaultTweetVisibility: friend # 推文可见性,默认好友可见 值: public/following/friend/private + DefaultMsgLoopInterval: 5000 # 拉取未读消息的间隔,单位:毫秒, 默认5000ms + CopyrightTop: "2023 paopao.info" + CopyrightLeft: "Roc's Me" + CopyrightLeftLink: "" + CopyrightRight: "泡泡(PaoPao)开源社区" + CopyrightRightLink: "https://www.paopao.info" \ No newline at end of file diff --git a/default.pgo b/default.pgo index 38d83f51..ff77ea14 100644 Binary files a/default.pgo and b/default.pgo differ diff --git a/docker-compose.yaml b/docker-compose.yaml index f562109d..3536580f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,6 +78,28 @@ services: networks: - paopao-network + # meilisearch-ui: + # image: riccoxie/meilisearch-ui:latest + # restart: always + # ports: + # - 24900:24900 + # networks: + # - paopao-network + + # openobserve: + # image: public.ecr.aws/zinclabs/openobserve:latest + # restart: always + # ports: + # - 5080:5080 + # volumes: + # - ./custom/data/openobserve/data:/data + # environment: + # ZO_DATA_DIR: /data + # ZO_ROOT_USER_EMAIL: root@paopao.info + # ZO_ROOT_USER_PASSWORD: paopao-ce + # networks: + # - paopao-network + # pyroscope: # image: pyroscope/pyroscope:latest # restart: always @@ -88,21 +110,21 @@ services: # networks: # - paopao-network - phpmyadmin: - image: phpmyadmin:5.2 - depends_on: - - db - ports: - - 8080:80 - environment: - - PMA_HOST=db - - PMA_USER=paopao - - PMA_PASSWORD=paopao - networks: - - paopao-network + # phpmyadmin: + # image: phpmyadmin:5.2 + # depends_on: + # - db + # ports: + # - 8080:80 + # environment: + # - PMA_HOST=db + # - PMA_USER=paopao + # - PMA_PASSWORD=paopao + # networks: + # - paopao-network backend: - image: bitbus/paopao-ce:0.4 + image: bitbus/paopao-ce:0.5 restart: always depends_on: - db diff --git a/docs/proposal/.assets/000-00.jpg b/docs/proposal/.assets/000-00.jpg new file mode 100644 index 00000000..21ab3b16 Binary files /dev/null and b/docs/proposal/.assets/000-00.jpg differ diff --git a/features-status.md b/features-status.md index e630c77d..b2781944 100644 --- a/features-status.md +++ b/features-status.md @@ -101,11 +101,11 @@ * [ ] 提按文档 * [x] 接口定义 * [x] 业务逻辑实现 -* `BigCacheIndex` 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面(推荐使用); +* `BigCacheIndex` 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面(目前状态: Deprecated); * [ ] 提按文档 * [x] 接口定义 * [x] 业务逻辑实现 -* `RedisCacheIndex` 使用Redis缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面(目前状态: 推荐使用); +* `RedisCacheIndex` 使用Redis缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面(目前状态: Deprecated); * [ ] 提按文档 * [x] 接口定义 * [x] 业务逻辑实现 @@ -137,6 +137,10 @@ * [ ] 提按文档 * [x] 接口定义 * [x] 业务逻辑实现 +* `LoggerOpenObserve` 使用[OpenObserve](https://github.com/openobserve/openobserve)写日志(目前状态: 内测阶段); + * [ ] 提按文档 + * [x] 接口定义 + * [x] 业务逻辑实现 #### 监控: * `Sentry` 使用Sentry进行错误跟踪与性能监控(目前状态: 内测); @@ -192,7 +196,17 @@ * `PhoneBind` 手机绑定功能; * [ ] 提按文档 * [x] 接口定义 - * [x] 业务逻辑实现 + * [x] 业务逻辑实现 + +* `UseAuditHook` 使用审核hook功能 (目前状态: 内测 待完善后将转为Builtin) + * [ ] 提按文档 + * [x] 接口定义 + * [x] 业务逻辑实现 + +* `DisableJobManager` 禁止使用JobManager功能 (目前状态: 内测 待完善后将转为Builtin) + * [ ] 提按文档 + * [x] 接口定义 + * [x] 业务逻辑实现 ### 功能特性: * `Web:DisallowUserRegister` 不允许用户注册; diff --git a/go.mod b/go.mod index 8b9f0783..694c4964 100644 --- a/go.mod +++ b/go.mod @@ -4,41 +4,44 @@ go 1.20 require ( github.com/Masterminds/semver/v3 v3.2.1 + github.com/RoaringBitmap/roaring v1.5.0 github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 - github.com/alimy/cfg v0.4.0 github.com/alimy/mir/v4 v4.0.0 - github.com/aliyun/aliyun-oss-go-sdk v2.2.8+incompatible + github.com/alimy/tryst v0.8.3 + github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible github.com/allegro/bigcache/v3 v3.1.0 github.com/bufbuild/connect-go v1.10.0 - github.com/bytedance/sonic v1.10.0 - github.com/cockroachdb/errors v1.10.0 + github.com/bytedance/sonic v1.10.1 + github.com/cockroachdb/errors v1.11.1 github.com/disintegration/imaging v1.6.2 github.com/fatih/color v1.15.0 - github.com/getsentry/sentry-go v0.23.0 + github.com/getsentry/sentry-go v0.24.1 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-resty/resty/v2 v2.7.0 + github.com/go-resty/resty/v2 v2.9.1 github.com/goccy/go-json v0.10.2 github.com/gofrs/uuid/v5 v5.0.0 - github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang-jwt/jwt/v5 v5.0.0 github.com/golang-migrate/migrate/v4 v4.15.2 - github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.9+incompatible github.com/json-iterator/go v1.1.12 - github.com/meilisearch/meilisearch-go v0.25.0 - github.com/minio/minio-go/v7 v7.0.62 - github.com/onsi/ginkgo/v2 v2.11.0 - github.com/onsi/gomega v1.27.10 + github.com/meilisearch/meilisearch-go v0.25.1 + github.com/minio/minio-go/v7 v7.0.63 + github.com/onsi/ginkgo/v2 v2.12.1 + github.com/onsi/gomega v1.28.0 + github.com/prometheus/client_golang v1.16.0 github.com/pyroscope-io/client v0.7.2 - github.com/redis/rueidis v1.0.15 + github.com/redis/rueidis v1.0.19 + github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.9.3 - github.com/smartwalle/alipay/v3 v3.2.15 + github.com/smartwalle/alipay/v3 v3.2.16 github.com/sourcegraph/conc v0.3.0 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 - github.com/tencentyun/cos-go-sdk-v5 v0.7.42 + github.com/tencentyun/cos-go-sdk-v5 v0.7.43 github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc go.uber.org/automaxprocs v1.5.3 - google.golang.org/grpc v1.57.0 + google.golang.org/grpc v1.58.2 google.golang.org/protobuf v1.31.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/resty.v1 v1.12.0 @@ -48,11 +51,14 @@ require ( gorm.io/gorm v1.25.4 gorm.io/plugin/dbresolver v1.4.7 gorm.io/plugin/soft_delete v1.2.1 - modernc.org/sqlite v1.25.0 + modernc.org/sqlite v1.26.0 ) require ( github.com/andybalholm/brotli v1.0.5 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/clbanning/mxj v1.8.4 // indirect @@ -69,6 +75,7 @@ require ( github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect @@ -77,6 +84,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -97,20 +105,25 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/mschoch/smat v0.2.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rs/xid v1.5.0 // indirect - github.com/smartwalle/ncrypto v1.0.2 // indirect - github.com/smartwalle/ngx v1.0.6 // indirect + github.com/smartwalle/ncrypto v1.0.3 // indirect + github.com/smartwalle/ngx v1.0.7 // indirect github.com/smartwalle/nsign v1.0.8 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect @@ -123,15 +136,15 @@ require ( github.com/valyala/fasthttp v1.40.0 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/image v0.0.0-20210216034530-4410531fe030 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + golang.org/x/tools v0.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect diff --git a/go.sum b/go.sum index 8252693e..0fc6b105 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/RoaringBitmap/roaring v1.5.0 h1:V0VCSiHjroItEYCM3guC8T83ehi5QMt3oM9EefTTOms= +github.com/RoaringBitmap/roaring v1.5.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 h1:uFrPOl1VBt/Abfl2z+A/DFc+AwmFLxEHR1+Yq6cXvww= github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868/go.mod h1:srphKZ1i+yGXxl/LpBS7ZIECTjCTPzZzAMtJWoG3sLo= @@ -123,12 +125,12 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= -github.com/alimy/cfg v0.4.0 h1:SslKPndmxRViT1ePWLmNsEq7okYP0GVeuowQlRWZPkw= -github.com/alimy/cfg v0.4.0/go.mod h1:rOxbasTH2srl6StAjNF5Vyi8bfrdkl3fLGmOYtSw81c= github.com/alimy/mir/v4 v4.0.0 h1:MzGfmoLjjvR69jbZEmpKJO3tUuqB0RGRv1UWPbtukBg= github.com/alimy/mir/v4 v4.0.0/go.mod h1:d58dBvw2KImcVbAUANrciEV/of0arMNsI9c/5UNCMMc= -github.com/aliyun/aliyun-oss-go-sdk v2.2.8+incompatible h1:6JF1bjhT0WN2srEmijfOFtVWwV91KZ6dJY1/JbdtGrI= -github.com/aliyun/aliyun-oss-go-sdk v2.2.8+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/alimy/tryst v0.8.3 h1:k54a9YesCGUTqfyDp9NL55TI8CxIj8HNJZyzbIoNab8= +github.com/alimy/tryst v0.8.3/go.mod h1:ua2eJbFrisHPh7z93Bgc0jNBE8Khu1SCx2p/6t3OzZI= +github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k= +github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -174,10 +176,12 @@ github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiU github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -195,8 +199,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= -github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= +github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -206,6 +210,8 @@ github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= @@ -243,8 +249,8 @@ github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3K github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/errors v1.10.0 h1:lfxS8zZz1+OjtV4MtNWgboi/W5tyLEB6VQZBXN+0VUU= -github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= @@ -456,8 +462,8 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= -github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/getsentry/sentry-go v0.24.1 h1:W6/0GyTy8J6ge6lVCc94WB6Gx2ZuLrgopnn9w8Hiwuk= +github.com/getsentry/sentry-go v0.24.1/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= @@ -518,8 +524,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM= +github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -581,6 +587,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc= github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -727,6 +735,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= +github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -734,8 +744,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8= -github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.9+incompatible h1:zUhCrGMMpJxZGAB30GbQzluDhQuPENxRQfxss7KlpKU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -932,15 +942,17 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= -github.com/meilisearch/meilisearch-go v0.25.0 h1:xIp+8YWterHuDvpdYlwQ4Qp7im3JlRHmSKiP0NvjyXs= -github.com/meilisearch/meilisearch-go v0.25.0/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= +github.com/meilisearch/meilisearch-go v0.25.1 h1:D5wY22sn5kkpRH3uYMGlwltdUEq5regIFmO7awHz3Vo= +github.com/meilisearch/meilisearch-go v0.25.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.62 h1:qNYsFZHEzl+NfH8UxW4jpmlKav1qUAgfY30YNRneVhc= -github.com/minio/minio-go/v7 v7.0.62/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4= +github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ= +github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= @@ -982,6 +994,8 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= @@ -1008,8 +1022,8 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1019,8 +1033,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1087,11 +1101,15 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1100,6 +1118,8 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1112,17 +1132,21 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.2 h1:OX2qdUQsS8RSkn/3C8isD7f/P0YiZQlRbAlecAaj/R8= github.com/pyroscope-io/client v0.7.2/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8= github.com/pyroscope-io/godeltaprof v0.1.2 h1:MdlEmYELd5w+lvIzmZvXGNMVzW2Qc9jDMuJaPOR75g4= github.com/pyroscope-io/godeltaprof v0.1.2/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= -github.com/redis/rueidis v1.0.15 h1:KjTaoP4ab6lpxyCwgIEZ3/rqvKfKnbICe83tVaaItxQ= -github.com/redis/rueidis v1.0.15/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= +github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= +github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1164,12 +1188,12 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartwalle/alipay/v3 v3.2.15 h1:3fvFJnINKKAOXHR/Iv20k1Z7KJ+nOh3oK214lELPqG8= -github.com/smartwalle/alipay/v3 v3.2.15/go.mod h1:niTNB609KyUYuAx9Bex/MawEjv2yPx4XOjxSAkqmGjE= -github.com/smartwalle/ncrypto v1.0.2 h1:pTAhCqtPCMhpOwFXX+EcMdR6PNzruBNoGQrN2S1GbGI= -github.com/smartwalle/ncrypto v1.0.2/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk= -github.com/smartwalle/ngx v1.0.6 h1:JPNqNOIj+2nxxFtrSkJO+vKJfeNUSEQueck/Wworjps= -github.com/smartwalle/ngx v1.0.6/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= +github.com/smartwalle/alipay/v3 v3.2.16 h1:oSzcQgV+kUHH7ko7FjYowU4RIm6chuQjgXeuChUbj0M= +github.com/smartwalle/alipay/v3 v3.2.16/go.mod h1:5EC6QZNr51TjmDAJFHSEJMLLSoTtge7583W2vuNmOYc= +github.com/smartwalle/ncrypto v1.0.3 h1:fnzjoriZt2LZeD8ljEtRe2eU33Au7i8vIF4Gafz5RuI= +github.com/smartwalle/ncrypto v1.0.3/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk= +github.com/smartwalle/ngx v1.0.7 h1:BIQo6wmAnERehogNKUnthoxwBavTWxbR9oLFcGjWXKQ= +github.com/smartwalle/ngx v1.0.7/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= github.com/smartwalle/nsign v1.0.8 h1:78KWtwKPrdt4Xsn+tNEBVxaTLIJBX9YRX0ZSrMUeuHo= github.com/smartwalle/nsign v1.0.8/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -1239,8 +1263,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= -github.com/tencentyun/cos-go-sdk-v5 v0.7.42 h1:Up1704BJjI5orycXKjpVpvuOInt9GC5pqY4knyE9Uds= -github.com/tencentyun/cos-go-sdk-v5 v0.7.42/go.mod h1:LUFnaqRmGk6pEHOaRmdn2dCZR2j0cSsM5xowWFPTPao= +github.com/tencentyun/cos-go-sdk-v5 v0.7.43 h1:aPCPWy85T3C3Ga3hn7va2DC4c0hAf8Dx0kpKj/uB/vw= +github.com/tencentyun/cos-go-sdk-v5 v0.7.43/go.mod h1:LUFnaqRmGk6pEHOaRmdn2dCZR2j0cSsM5xowWFPTPao= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1290,6 +1314,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= @@ -1385,8 +1410,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1436,8 +1461,10 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1500,14 +1527,16 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1538,7 +1567,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1661,18 +1692,24 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1682,8 +1719,10 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1692,6 +1731,7 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1774,8 +1814,10 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1902,8 +1944,8 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1937,8 +1979,8 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2110,8 +2152,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= -modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= -modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= +modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= diff --git a/internal/conf/alipay.go b/internal/conf/alipay.go index 4f118d64..8d1b8d0f 100644 --- a/internal/conf/alipay.go +++ b/internal/conf/alipay.go @@ -21,16 +21,16 @@ func MustAlipayClient() *alipay.Client { logrus.Fatalf("alipay.New err: %s", err) } // 加载应用公钥证书 - if err = client.LoadAppPublicCertFromFile(s.AppPublicCertFile); err != nil { - logrus.Fatalf("client.LoadAppPublicCertFromFile err: %s\n", err) + if err = client.LoadAppCertPublicKeyFromFile(s.AppPublicCertFile); err != nil { + logrus.Fatalf("client.LoadAppCertPublicKeyFromFile err: %s\n", err) } // 加载支付宝根证书 if err = client.LoadAliPayRootCertFromFile(s.RootCertFile); err != nil { logrus.Fatalf("client.LoadAliPayRootCertFromFile err: %s\n", err) } // 加载支付宝公钥证书 - if err = client.LoadAliPayPublicCertFromFile(s.PublicCertFile); err != nil { - logrus.Fatalf("client.LoadAliPayPublicCertFromFile err: %s\n", err) + if err = client.LoadAlipayCertPublicKeyFromFile(s.PublicCertFile); err != nil { + logrus.Fatalf("client.LoadAlipayCertPublicKeyFromFile err: %s\n", err) } _alipayClient = client }) diff --git a/internal/conf/cache.go b/internal/conf/cache.go new file mode 100644 index 00000000..27652c5f --- /dev/null +++ b/internal/conf/cache.go @@ -0,0 +1,91 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package conf + +import ( + "fmt" + + "github.com/alimy/tryst/cache" + "github.com/rocboss/paopao-ce/pkg/types" +) + +const ( + _defaultKeyPoolSize = 128 +) + +// 以下包含一些在cache中会用到的key的前缀 +const ( + InfixCommentDefault = "default" + InfixCommentHots = "hots" + InfixCommentNewest = "newest" + PrefixNewestTweets = "paopao:newesttweets:" + PrefixHotsTweets = "paopao:hotstweets:" + PrefixFollowingTweets = "paopao:followingtweets:" + PrefixUserTweets = "paopao:usertweets:" + PrefixUnreadmsg = "paopao:unreadmsg:" + PrefixOnlineUser = "paopao:onlineuser:" + PrefixIdxTweetsNewest = "paopao:index:tweets:newest:" + PrefixIdxTweetsHots = "paopao:index:tweets:hots:" + PrefixIdxTweetsFollowing = "paopao:index:tweets:following:" + PrefixIdxTrends = "paopao:index:trends:" + PrefixMessages = "paopao:messages:" + PrefixUserInfo = "paopao:user:info:" + PrefixUserProfile = "paopao:user:profile:" + PrefixUserInfoById = "paopao:user:info:id:" + PrefixUserInfoByName = "paopao:user:info:name:" + prefixUserProfileByName = "paopao:user:profile:name:" + PrefixMyFriendIds = "paopao:myfriendids:" + PrefixMyFollowIds = "paopao:myfollowids:" + PrefixTweetComment = "paopao:comment:" + KeySiteStatus = "paopao:sitestatus" + KeyHistoryMaxOnline = "history.max.online" +) + +// 以下包含一些在cache中会用到的池化后的key +var ( + KeyNewestTweets cache.KeyPool[int] + KeyHotsTweets cache.KeyPool[int] + KeyFollowingTweets cache.KeyPool[string] + KeyUnreadMsg cache.KeyPool[int64] + KeyOnlineUser cache.KeyPool[int64] + KeyUserInfoById cache.KeyPool[int64] + KeyUserInfoByName cache.KeyPool[string] + KeyUserProfileByName cache.KeyPool[string] + KeyMyFriendIds cache.KeyPool[int64] + KeyMyFollowIds cache.KeyPool[int64] +) + +func initCacheKeyPool() { + poolSize := _defaultKeyPoolSize + if poolSize < CacheSetting.KeyPoolSize { + poolSize = CacheSetting.KeyPoolSize + } + KeyNewestTweets = intKeyPool[int](poolSize, PrefixNewestTweets) + KeyHotsTweets = intKeyPool[int](poolSize, PrefixHotsTweets) + KeyFollowingTweets = strKeyPool(poolSize, PrefixFollowingTweets) + KeyUnreadMsg = intKeyPool[int64](poolSize, PrefixUnreadmsg) + KeyOnlineUser = intKeyPool[int64](poolSize, PrefixOnlineUser) + KeyUserInfoById = intKeyPool[int64](poolSize, PrefixUserInfoById) + KeyUserInfoByName = strKeyPool(poolSize, PrefixUserInfoByName) + KeyUserProfileByName = strKeyPool(poolSize, prefixUserProfileByName) + KeyMyFriendIds = intKeyPool[int64](poolSize, PrefixMyFriendIds) + KeyMyFollowIds = intKeyPool[int64](poolSize, PrefixMyFollowIds) +} + +func strKeyPool(size int, prefix string) cache.KeyPool[string] { + return cache.MustKeyPool(size, func(key string) string { + return fmt.Sprintf("%s%s", prefix, key) + }) +} + +func intKeyPool[T types.Integer](size int, prefix string) cache.KeyPool[T] { + return cache.MustKeyPool[T](size, intKey[T](prefix)) +} + +func intKey[T types.Integer](prefix string) func(T) string { + return func(key T) string { + return fmt.Sprintf("%s%d", prefix, key) + } +} diff --git a/internal/conf/redis.go b/internal/conf/cache_redis.go similarity index 92% rename from internal/conf/redis.go rename to internal/conf/cache_redis.go index 93799b82..00564a31 100644 --- a/internal/conf/redis.go +++ b/internal/conf/cache_redis.go @@ -29,6 +29,8 @@ func MustRedisClient() rueidis.Client { log.Fatalf("create a redis client failed: %s", err) } _redisClient = client + // 顺便初始化一下CacheKeyPool + initCacheKeyPool() }) return _redisClient } diff --git a/internal/conf/conf.go b/internal/conf/conf.go index a0dc88df..075c2b06 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -8,16 +8,17 @@ import ( "log" "time" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" ) var ( - loggerSetting *loggerConf - loggerFileSetting *loggerFileConf - loggerZincSetting *loggerZincConf - loggerMeiliSetting *loggerMeiliConf - sentrySetting *sentryConf - redisSetting *redisConf + loggerSetting *loggerConf + loggerFileSetting *loggerFileConf + loggerZincSetting *loggerZincConf + loggerMeiliSetting *loggerMeiliConf + loggerOpenObserveSetting *loggerOpenObserveConf + sentrySetting *sentryConf + redisSetting *redisConf PyroscopeSetting *pyroscopeConf DatabaseSetting *databaseConf @@ -25,6 +26,7 @@ var ( PostgresSetting *postgresConf Sqlite3Setting *sqlite3Conf PprofServerSetting *httpServerConf + MetricsServerSetting *httpServerConf WebServerSetting *httpServerConf AdminServerSetting *httpServerConf SpaceXServerSetting *httpServerConf @@ -34,6 +36,10 @@ var ( DocsServerSetting *httpServerConf MobileServerSetting *grpcServerConf AppSetting *appConf + CacheSetting *cacheConf + EventManagerSetting *eventManagerConf + MetricManagerSetting *metricManagerConf + JobManagerSetting *jobManagerConf CacheIndexSetting *cacheIndexConf SimpleCacheIndexSetting *simpleCacheIndexConf BigCacheIndexSetting *bigCacheIndexConf @@ -51,6 +57,7 @@ var ( S3Setting *s3Conf LocalOSSSetting *localossConf JWTSetting *jwtConf + WebProfileSetting *WebProfileConf ) func setupSetting(suite []string, noDefault bool) error { @@ -68,7 +75,12 @@ func setupSetting(suite []string, noDefault bool) error { objects := map[string]any{ "App": &AppSetting, + "Cache": &CacheSetting, + "EventManager": &EventManagerSetting, + "MetricManager": &MetricManagerSetting, + "JobManager": &JobManagerSetting, "PprofServer": &PprofServerSetting, + "MetricsServer": &MetricsServerSetting, "WebServer": &WebServerSetting, "AdminServer": &AdminServerSetting, "SpaceXServer": &SpaceXServerSetting, @@ -89,6 +101,7 @@ func setupSetting(suite []string, noDefault bool) error { "LoggerFile": &loggerFileSetting, "LoggerZinc": &loggerZincSetting, "LoggerMeili": &loggerMeiliSetting, + "LoggerOpenObserve": &loggerOpenObserveSetting, "Database": &DatabaseSetting, "MySQL": &MysqlSetting, "Postgres": &PostgresSetting, @@ -105,6 +118,7 @@ func setupSetting(suite []string, noDefault bool) error { "MinIO": &MinIOSetting, "LocalOSS": &LocalOSSSetting, "S3": &S3Setting, + "WebProfile": &WebProfileSetting, } for k, v := range objects { err := vp.UnmarshalKey(k, v) @@ -113,6 +127,9 @@ func setupSetting(suite []string, noDefault bool) error { } } + CacheSetting.CientSideCacheExpire *= time.Second + EventManagerSetting.TickWaitTime *= time.Second + MetricManagerSetting.TickWaitTime *= time.Second JWTSetting.Expire *= time.Second SimpleCacheIndexSetting.CheckTickDuration *= time.Second SimpleCacheIndexSetting.ExpireTickDuration *= time.Second diff --git a/internal/conf/config.yaml b/internal/conf/config.yaml index fc86dfd4..42231d87 100644 --- a/internal/conf/config.yaml +++ b/internal/conf/config.yaml @@ -2,9 +2,39 @@ App: # APP基础设置项 RunMode: debug AttachmentIncomeRate: 0.8 MaxCommentCount: 1000 + MaxWhisperDaily: 1000 # 一天可以发送的最大私信总数,临时措施,后续将去掉这个限制 + MaxCaptchaTimes: 2 # 最大获取captcha的次数 DefaultContextTimeout: 60 DefaultPageSize: 10 MaxPageSize: 100 +Cache: + KeyPoolSize: 256 # 键的池大小, 设置范围[128, ++], 默认256 + CientSideCacheExpire: 60 # 客户端缓存过期时间 默认60s + UnreadMsgExpire: 60 # 未读消息过期时间,单位秒, 默认60s + UserTweetsExpire: 60 # 获取用户推文列表过期时间,单位秒, 默认60s + IndexTweetsExpire: 120 # 获取广场推文列表过期时间,单位秒, 默认120s + TweetCommentsExpire: 120 # 获取推文评论过期时间,单位秒, 默认120s + IndexTrendsExpire: 120 # 获取广场动态信息过期时间,单位秒, 默认120s + OnlineUserExpire: 300 # 标记在线用户 过期时间,单位秒, 默认300s + UserInfoExpire: 120 # 获取用户信息过期时间,单位秒, 默认120s + UserProfileExpire: 120 # 获取用户概要过期时间,单位秒, 默认120s + UserRelationExpire: 120 # 用户关系信息过期时间,单位秒, 默认120s + MessagesExpire: 60 # 消息列表过期时间,单位秒, 默认60s +EventManager: # 事件管理器的配置参数 + MinWorker: 64 # 最小后台工作者, 设置范围[5, ++], 默认64 + MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128 + MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256 + MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60 + TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s +MetricManager: # 指标监控管理器的配置参数 + MinWorker: 32 # 最小后台工作者, 设置范围[5, ++], 默认32 + MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128 + MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256 + MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60 + TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s +JobManager: # Cron Job理器的配置参数 + MaxOnlineInterval: "@every 5m" # 更新最大在线人数,默认每5分钟更新一次 + UpdateMetricsInterval: "@every 5m" # 更新Prometheus指标,默认每5分钟更新一次 Features: Default: [] WebServer: # Web服务 @@ -43,6 +73,12 @@ PprofServer: # Pprof服务 HttpPort: 6060 ReadTimeout: 60 WriteTimeout: 60 +MetricsServer: # Prometheus Metrics服务 + RunMode: debug + HttpIp: 0.0.0.0 + HttpPort: 6080 + ReadTimeout: 60 + WriteTimeout: 60 FrontendWebServer: # Web前端静态资源服务 RunMode: debug HttpIp: 0.0.0.0 @@ -64,8 +100,9 @@ SmsJuhe: TplID: TplVal: "#code#=%sm#=%d" Alipay: - AppID: + AppID: "paopao-ce-app-id" InProduction: True + PrivateKey: "MIICXAIBAAKBgQCzXV/spaX9+eOjM5f12W6eDTtszU9f9rgpXG4EQwzZI3WM5+Fe+9Bn6NQQILfF1o3Z+3BEzHMMcYwxrQw/toq2o6JPchbUK7eArKc6pl/GV3uIefZdKncz5bZvCFMgiJrpy75lYKhJgotQFEfQd+ks2t0gtC007uOjmY9QDB2EVQIDAQABAoGAMruhi0UbW2gYHCxWuiJDKI9jlJXJ8sHNO126fJgehTiDYlSgKYaeXxW7DcjDUkEqpFJ7YepWTFm9prtksIzIVQFNNjstI6cvowVF2t+lWf7mIB4w0ugarVd+SXssQK830Og3kjtZ84a3BbC6uf3a/qcgoIO8Sj1VnzOJ8fEYl+0CQQDeG6JhauGDOC8oCTwbFs9QPpjwGnp7UkYAJNg7jn4uBSVeg4lwb5uj9TshLSp49geNkPcWeCythuiz1jvoTqEjAkEAzrwIBxUPT1WmcDUXAkVPaQNADDbhMZLdw5nHZEUVwmO3o1FkJky4MLjLjT977400mhsnsQCy4sAWUZs6aEyoJwJARK3U2zy6eOHhqwaYAGRgPJbuoaf+Ya3CGX9LIbdhCwfqUzxnPk40mVFWNF8L+BVTppHB5b/JSOsjf6BqK95McwJBAL+kvUhbdHrV6lmgTXkUaV3u3mO0SCPdgui9WIKSLG6sY+LpI48BlcnMtR12WVyjKL0nKS9Dd5EOAmKaJJXlYgcCQGWbWCn9KUDUqpm4o3wr5nwXzlS74XYZo65UAM7TSzHRpcovfv5uiQ0VRLImWeiSXKK2aTOBGn5eKbevRTxN07k=" RootCertFile: "custom/alipay/RootCert.crt" PublicCertFile: "custom/alipay/CertPublicKey_RSA2.crt" AppPublicCertFile: "custom/alipay/AppCertPublicKey.crt" @@ -114,6 +151,15 @@ LoggerMeili: # 使用Meili写日志 Secure: False MinWorker: 5 # 最小后台工作者, 设置范围[5, 100], 默认5 MaxLogBuffer: 100 # 最大log缓存条数, 设置范围[10, 10000], 默认100 +LoggerOpenObserve: # 使用OpenObserve写日志 + Host: 127.0.0.1:5080 + Organization: paopao-ce + Stream: default + User: root@paopao.info + Password: tiFEI8UeJWuYA7kN + Secure: False + MinWorker: 5 # 最小后台工作者, 设置范围[5, 100], 默认5 + MaxLogBuffer: 100 # 最大log缓存条数, 设置范围[10, 10000], 默认100 JWT: # 鉴权加密 Secret: 18a6413dc4fe394c66345ebe501b2f26 Issuer: paopao-api @@ -202,4 +248,22 @@ Redis: Password: SelectDB: ConnWriteTimeout: 60 # 连接写超时时间 多少秒 默认 60秒 - \ No newline at end of file +WebProfile: + UseFriendship: true # 前端是否使用好友体系 + EnableTrendsBar: true # 广场页面是否开启动态条栏功能 + EnableWallet: false # 是否开启钱包功能 + AllowTweetAttachment: true # 是否允许推文附件 + AllowTweetAttachmentPrice: true # 是否允许推文付费附件 + AllowTweetVideo: true # 是否允许视频推文 + AllowUserRegister: true # 是否允许用户注册 + AllowPhoneBind: true # 是否允许手机绑定 + DefaultTweetMaxLength: 2000 # 推文允许输入的最大长度, 默认2000字,值的范围需要查询后端支持的最大字数 + TweetWebEllipsisSize: 400 # Web端推文作为feed显示的最长字数,默认400字 + TweetMobileEllipsisSize: 300 # 移动端推文作为feed显示的最长字数,默认300字 + DefaultTweetVisibility: friend # 推文默认可见性,默认好友可见 值: public/following/friend/private + DefaultMsgLoopInterval: 5000 # 拉取未读消息的间隔,单位:毫秒, 默认5000ms + CopyrightTop: "2023 paopao.info" + CopyrightLeft: "Roc's Me" + CopyrightLeftLink: "" + CopyrightRight: "泡泡(PaoPao)开源社区" + CopyrightRightLink: "https://www.paopao.info" diff --git a/internal/conf/db.go b/internal/conf/db.go index 32867a9c..db0a1e43 100644 --- a/internal/conf/db.go +++ b/internal/conf/db.go @@ -8,7 +8,7 @@ import ( "database/sql" "sync" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/sirupsen/logrus" ) @@ -23,6 +23,7 @@ const ( TableAttachment = "attachment" TableCaptcha = "captcha" TableComment = "comment" + TableCommentMetric = "comment_metric" TableCommentContent = "comment_content" TableCommentReply = "comment_reply" TableFollowing = "following" @@ -30,6 +31,7 @@ const ( TableContactGroup = "contact_group" TableMessage = "message" TablePost = "post" + TablePostMetric = "post_metric" TablePostByComment = "post_by_comment" TablePostByMedia = "post_by_media" TablePostAttachmentBill = "post_attachment_bill" @@ -38,6 +40,8 @@ const ( TablePostStar = "post_star" TableTag = "tag" TableUser = "user" + TableUserRelation = "user_relation" + TableUserMetric = "user_metric" TableWalletRecharge = "wallet_recharge" TableWalletStatement = "wallet_statement" ) diff --git a/internal/conf/db_gorm.go b/internal/conf/db_gorm.go index f667ff61..eeff6236 100644 --- a/internal/conf/db_gorm.go +++ b/internal/conf/db_gorm.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/sirupsen/logrus" "gorm.io/driver/mysql" "gorm.io/driver/postgres" diff --git a/internal/conf/logger.go b/internal/conf/logger.go index f6d08723..3e999bf3 100644 --- a/internal/conf/logger.go +++ b/internal/conf/logger.go @@ -8,7 +8,7 @@ import ( "io" "time" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/getsentry/sentry-go" sentrylogrus "github.com/getsentry/sentry-go/logrus" "github.com/sirupsen/logrus" @@ -28,7 +28,7 @@ func setupLogger() { logrus.SetFormatter(&logrus.JSONFormatter{}) logrus.SetLevel(loggerSetting.logLevel()) - cfg.In(cfg.Actions{ + cfg.On(cfg.Actions{ "LoggerFile": func() { out := newFileLogger() logrus.SetOutput(out) @@ -43,6 +43,11 @@ func setupLogger() { logrus.SetOutput(io.Discard) logrus.AddHook(hook) }, + "LoggerOpenObserve": func() { + hook := newObserveLogHook() + logrus.SetOutput(io.Discard) + logrus.AddHook(hook) + }, }) } diff --git a/internal/conf/logger_observe.go b/internal/conf/logger_observe.go new file mode 100644 index 00000000..dc83a83e --- /dev/null +++ b/internal/conf/logger_observe.go @@ -0,0 +1,71 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package conf + +import ( + "log" + "net/http" + "time" + + hx "github.com/rocboss/paopao-ce/pkg/http" + "github.com/rocboss/paopao-ce/pkg/json" + "github.com/rocboss/paopao-ce/pkg/obx" + "github.com/sirupsen/logrus" +) + +type observeLogData struct { + Time time.Time `json:"time"` + Level logrus.Level `json:"level"` + Message string `json:"message"` + Data logrus.Fields `json:"data"` +} + +type observeLogHook struct { + client obx.OpenObserveClient +} + +func (h *observeLogHook) Fire(entry *logrus.Entry) error { + info := []observeLogData{{ + Time: entry.Time, + Level: entry.Level, + Message: entry.Message, + Data: entry.Data, + }} + data, _ := json.Marshal(info) + h.client.LogJson(data) + return nil +} + +func (h *observeLogHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func newObserveLogHook() *observeLogHook { + s := loggerOpenObserveSetting + obc := &obx.Config{ + Host: s.Host, + User: s.User, + Password: s.Password, + Organization: s.Organization, + Stream: s.Stream, + Secure: s.Secure, + } + acc := &hx.AsyncClientConf{ + MinWorker: s.MinWorker, + MaxRequestBuf: s.MaxLogBuffer, + MaxRequestTempBuf: 100, + MaxTickCount: 60, + TickWaitTime: time.Second, + } + return &observeLogHook{ + client: obx.NewClient(obc, acc, func(req *http.Request, resp *http.Response, err error) { + if err == nil && resp != nil && resp.Body != nil { + resp.Body.Close() + } else if err != nil { + log.Printf("logrus use observe do LogJson error: %s", err) + } + }), + } +} diff --git a/internal/conf/sentry.go b/internal/conf/sentry.go index 70c534e4..0cd64d76 100644 --- a/internal/conf/sentry.go +++ b/internal/conf/sentry.go @@ -7,7 +7,7 @@ package conf import ( "time" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/getsentry/sentry-go" "github.com/rocboss/paopao-ce/pkg/version" ) diff --git a/internal/conf/setting.go b/internal/conf/setting.go index ccb05cbf..fdd776c9 100644 --- a/internal/conf/setting.go +++ b/internal/conf/setting.go @@ -63,6 +63,17 @@ type loggerMeiliConf struct { MinWorker int } +type loggerOpenObserveConf struct { + Host string + Organization string + Stream string + User string + Password string + Secure bool + MaxLogBuffer int + MinWorker int +} + type httpServerConf struct { RunMode string HttpIp string @@ -79,12 +90,50 @@ type grpcServerConf struct { type appConf struct { RunMode string MaxCommentCount int64 + MaxWhisperDaily int64 + MaxCaptchaTimes int AttachmentIncomeRate float64 DefaultContextTimeout time.Duration DefaultPageSize int MaxPageSize int } +type cacheConf struct { + KeyPoolSize int + CientSideCacheExpire time.Duration + UnreadMsgExpire int64 + UserTweetsExpire int64 + IndexTweetsExpire int64 + MessagesExpire int64 + IndexTrendsExpire int64 + TweetCommentsExpire int64 + OnlineUserExpire int64 + UserInfoExpire int64 + UserProfileExpire int64 + UserRelationExpire int64 +} + +type eventManagerConf struct { + MinWorker int + MaxEventBuf int + MaxTempEventBuf int + MaxTickCount int + TickWaitTime time.Duration +} + +type metricManagerConf struct { + MinWorker int + MaxEventBuf int + MaxTempEventBuf int + MaxTickCount int + TickWaitTime time.Duration +} + +type jobManagerConf struct { + MaxOnlineInterval string + UpdateMetricsInterval string +} + type cacheIndexConf struct { MaxUpdateQPS int MinWorker int @@ -234,6 +283,27 @@ type jwtConf struct { Expire time.Duration } +type WebProfileConf struct { + UseFriendship bool `json:"use_friendship"` + EnableTrendsBar bool `json:"enable_trends_bar"` + EnableWallet bool `json:"enable_wallet"` + AllowTweetAttachment bool `json:"allow_tweet_attachment"` + AllowTweetAttachmentPrice bool `json:"allow_tweet_attachment_price"` + AllowTweetVideo bool `json:"allow_tweet_video"` + AllowUserRegister bool `json:"allow_user_register"` + AllowPhoneBind bool `json:"allow_phone_bind"` + DefaultTweetMaxLength int `json:"default_tweet_max_length"` + TweetWebEllipsisSize int `json:"tweet_web_ellipsis_size"` + TweetMobileEllipsisSize int `json:"tweet_mobile_ellipsis_size"` + DefaultTweetVisibility string `json:"default_tweet_visibility"` + DefaultMsgLoopInterval int `json:"default_msg_loop_interval"` + CopyrightTop string `json:"copyright_top"` + CopyrightLeft string `json:"copyright_left"` + CopyrightLeftLink string `json:"copyright_left_link"` + CopyrightRight string `json:"copyright_right"` + CopyrightRightLink string `json:"copyright_right_link"` +} + func (s *httpServerConf) GetReadTimeout() time.Duration { return s.ReadTimeout * time.Second } @@ -303,6 +373,7 @@ func (s *databaseConf) TableNames() (res TableNameMap) { TableAttachment, TableCaptcha, TableComment, + TableCommentMetric, TableCommentContent, TableCommentReply, TableFollowing, @@ -310,6 +381,7 @@ func (s *databaseConf) TableNames() (res TableNameMap) { TableContactGroup, TableMessage, TablePost, + TablePostMetric, TablePostByComment, TablePostByMedia, TablePostAttachmentBill, @@ -318,6 +390,8 @@ func (s *databaseConf) TableNames() (res TableNameMap) { TablePostStar, TableTag, TableUser, + TableUserRelation, + TableUserMetric, TableWalletRecharge, TableWalletStatement, } diff --git a/internal/core/cache.go b/internal/core/cache.go index 8d8c3b42..bff7ca5c 100644 --- a/internal/core/cache.go +++ b/internal/core/cache.go @@ -68,14 +68,14 @@ func NewIndexActionA(act IdxAct, tweet *cs.TweetInfo) *IndexActionA { // CacheIndexService cache index service interface type CacheIndexService interface { - IndexPostsService + // IndexPostsService SendAction(act IdxAct, post *dbr.Post) } // CacheIndexServantA cache index service interface type CacheIndexServantA interface { - IndexPostsServantA + // IndexPostsServantA SendAction(act IdxAct, tweet *cs.TweetInfo) } @@ -97,3 +97,22 @@ type RedisCache interface { SetRechargeStatus(ctx context.Context, tradeNo string) error DelRechargeStatus(ctx context.Context, tradeNo string) error } + +type AppCache interface { + Get(key string) ([]byte, error) + Set(key string, data []byte, ex int64) error + SetNx(key string, data []byte, ex int64) error + Delete(key ...string) error + DelAny(pattern string) error + Exist(key string) bool + Keys(pattern string) ([]string, error) +} + +type WebCache interface { + AppCache + GetUnreadMsgCountResp(uid int64) ([]byte, error) + PutUnreadMsgCountResp(uid int64, data []byte) error + DelUnreadMsgCountResp(uid int64) error + ExistUnreadMsgCountResp(uid int64) bool + PutHistoryMaxOnline(newScore int) (int, error) +} diff --git a/internal/core/comments.go b/internal/core/comments.go index cf917a82..a7115069 100644 --- a/internal/core/comments.go +++ b/internal/core/comments.go @@ -11,9 +11,8 @@ import ( // CommentService 评论检索服务 type CommentService interface { - GetComments(conditions *ms.ConditionsT, offset, limit int) ([]*ms.Comment, error) + GetComments(tweetId int64, style cs.StyleCommentType, limit int, offset int) ([]*ms.Comment, int64, error) GetCommentByID(id int64) (*ms.Comment, error) - GetCommentCount(conditions *ms.ConditionsT) (int64, error) GetCommentReplyByID(id int64) (*ms.CommentReply, error) GetCommentContentsByIDs(ids []int64) ([]*ms.CommentContent, error) GetCommentRepliesByID(ids []int64) ([]*ms.CommentReplyFormated, error) @@ -22,6 +21,7 @@ type CommentService interface { // CommentManageService 评论管理服务 type CommentManageService interface { + HighlightComment(userId, commentId int64) (int8, error) DeleteComment(comment *ms.Comment) error CreateComment(comment *ms.Comment) (*ms.Comment, error) CreateCommentReply(reply *ms.CommentReply) (*ms.CommentReply, error) diff --git a/internal/core/core.go b/internal/core/core.go index 810d1e97..bb3a0d5c 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -15,14 +15,19 @@ type DataService interface { // 话题服务 TopicService - // 广场泡泡服务 - IndexPostsService - // 推文服务 TweetService TweetManageService TweetHelpService + // 推文指标服务 + UserMetricServantA + TweetMetricServantA + CommentMetricServantA + + // 动态信息相关服务 + TrendsManageServantA + // 评论服务 CommentService CommentManageService @@ -31,6 +36,7 @@ type DataService interface { UserManageService ContactManageService FollowingManageService + UserRelationService // 安全服务 SecurityService diff --git a/internal/core/cs/comment_thumbs.go b/internal/core/cs/comments.go similarity index 82% rename from internal/core/cs/comment_thumbs.go rename to internal/core/cs/comments.go index cb6f7d0d..84a287b0 100644 --- a/internal/core/cs/comment_thumbs.go +++ b/internal/core/cs/comments.go @@ -4,6 +4,14 @@ package cs +const ( + StyleCommentDefault StyleCommentType = iota + StyleCommentHots + StyleCommentNewest +) + +type StyleCommentType uint8 + type CommentThumbs struct { UserID int64 `json:"user_id"` TweetID int64 `json:"tweet_id"` diff --git a/internal/core/cs/messages.go b/internal/core/cs/messages.go new file mode 100644 index 00000000..5ff45fdf --- /dev/null +++ b/internal/core/cs/messages.go @@ -0,0 +1,16 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cs + +const ( + // 消息列表样式 + StyleMsgAll MessageStyle = "all" + StyleMsgSystem MessageStyle = "system" + StyleMsgWhisper MessageStyle = "whisper" + StyleMsgRequesting MessageStyle = "requesting" + StyleMsgUnread MessageStyle = "unread" +) + +type MessageStyle string diff --git a/internal/core/cs/metrics.go b/internal/core/cs/metrics.go new file mode 100644 index 00000000..c9d2a5c0 --- /dev/null +++ b/internal/core/cs/metrics.go @@ -0,0 +1,41 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cs + +const ( + MetricActionCreateTweet uint8 = iota + MetricActionDeleteTweet +) + +type TweetMetric struct { + PostId int64 + CommentCount int64 + UpvoteCount int64 + CollectionCount int64 + ShareCount int64 + ThumbsUpCount int64 + ThumbsDownCount int64 +} + +type CommentMetric struct { + CommentId int64 + ReplyCount int32 + ThumbsUpCount int32 + ThumbsDownCount int32 +} + +func (m *TweetMetric) RankScore(motivationFactor int) int64 { + if motivationFactor == 0 { + motivationFactor = 1 + } + return (m.CommentCount + m.UpvoteCount*2 + m.CollectionCount*4 + m.ShareCount*8) * int64(motivationFactor) +} + +func (m *CommentMetric) RankScore(motivationFactor int) int64 { + if motivationFactor == 0 { + motivationFactor = 1 + } + return int64(m.ReplyCount*2+m.ThumbsUpCount*4-m.ThumbsDownCount) * int64(motivationFactor) +} diff --git a/internal/core/cs/trends.go b/internal/core/cs/trends.go new file mode 100644 index 00000000..5739e8aa --- /dev/null +++ b/internal/core/cs/trends.go @@ -0,0 +1,30 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cs + +import "github.com/rocboss/paopao-ce/pkg/types" + +type TrendsItem struct { + Username string `json:"username"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + IsFresh bool `json:"is_fresh" gorm:"-"` +} + +func DistinctTrends(items []*TrendsItem) []*TrendsItem { + if len(items) == 0 { + return items + } + res := make([]*TrendsItem, 0, len(items)) + set := make(map[string]types.Empty, len(items)) + for _, item := range items { + if _, exist := set[item.Username]; exist { + continue + } + res = append(res, item) + set[item.Username] = types.Empty{} + } + return res +} diff --git a/internal/core/cs/tweets.go b/internal/core/cs/tweets.go index 34060030..49aa7763 100644 --- a/internal/core/cs/tweets.go +++ b/internal/core/cs/tweets.go @@ -16,10 +16,17 @@ const ( TweetBlockChargeAttachment // 推文可见性 - TweetVisitPublic TweetVisibleType = iota - TweetVisitPrivate - TweetVisitFriend - TweetVisitInvalid + TweetVisitPublic TweetVisibleType = 90 + TweetVisitPrivate TweetVisibleType = 0 + TweetVisitFriend TweetVisibleType = 50 + TweetVisitFollowing TweetVisibleType = 60 + + // 用户推文列表样式 + StyleUserTweetsGuest uint8 = iota + StyleUserTweetsSelf + StyleUserTweetsAdmin + StyleUserTweetsFriend + StyleUserTweetsFollowing // 附件类型 AttachmentTypeImage AttachmentType = iota + 1 @@ -32,7 +39,7 @@ type ( // TODO: 优化一下类型为 uint8, 需要底层数据库同步修改 TweetBlockType int - // TweetVisibleType 推文可见性,0公开,1私密,2好友 + // TweetVisibleType 推文可见性: 0私密 10充电可见 20订阅可见 30保留 40保留 50好友可见 60关注可见 70保留 80保留 90公开', TweetVisibleType uint8 // AttachmentType 附件类型, 1图片, 2视频, 3其他 @@ -139,3 +146,19 @@ type NewTweetReq struct { Visibility TweetVisibleType `json:"visibility"` ClientIP string `json:"-" binding:"-"` } + +func (t TweetVisibleType) ToOutValue() (res uint8) { + switch t { + case TweetVisitPublic: + res = 0 + case TweetVisitPrivate: + res = 1 + case TweetVisitFriend: + res = 2 + case TweetVisitFollowing: + res = 3 + default: + res = 1 + } + return +} diff --git a/internal/core/cs/user.go b/internal/core/cs/user.go index 240c235a..1d445283 100644 --- a/internal/core/cs/user.go +++ b/internal/core/cs/user.go @@ -5,7 +5,7 @@ package cs const ( - RelationUnknow RelationTyp = iota + RelationUnknown RelationTyp = iota RelationSelf RelationFriend RelationFollower @@ -39,6 +39,19 @@ type UserInfo struct { CreatedOn int64 `json:"created_on"` } +type UserProfile struct { + ID int64 `json:"id" db:"id"` + Nickname string `json:"nickname"` + Username string `json:"username"` + Phone string `json:"phone"` + Status int `json:"status"` + Avatar string `json:"avatar"` + Balance int64 `json:"balance"` + IsAdmin bool `json:"is_admin"` + CreatedOn int64 `json:"created_on"` + TweetsCount int `json:"tweets_count"` +} + func (t RelationTyp) String() string { switch t { case RelationSelf: @@ -51,9 +64,9 @@ func (t RelationTyp) String() string { return "following" case RelationAdmin: return "admin" - case RelationUnknow: + case RelationUnknown: fallthrough default: - return "unknow relation" + return "unknown" } } diff --git a/internal/core/messages.go b/internal/core/messages.go index 519e4e49..f45389e5 100644 --- a/internal/core/messages.go +++ b/internal/core/messages.go @@ -5,6 +5,7 @@ package core import ( + "github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/core/ms" ) @@ -14,6 +15,6 @@ type MessageService interface { GetUnreadCount(userID int64) (int64, error) GetMessageByID(id int64) (*ms.Message, error) ReadMessage(message *ms.Message) error - GetMessages(conditions *ms.ConditionsT, offset, limit int) ([]*ms.MessageFormated, error) - GetMessageCount(conditions *ms.ConditionsT) (int64, error) + ReadAllMessage(userId int64) error + GetMessages(userId int64, style cs.MessageStyle, limit, offset int) ([]*ms.MessageFormated, int64, error) } diff --git a/internal/core/metrics.go b/internal/core/metrics.go new file mode 100644 index 00000000..d2dbc0fb --- /dev/null +++ b/internal/core/metrics.go @@ -0,0 +1,27 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package core + +import ( + "github.com/rocboss/paopao-ce/internal/core/cs" +) + +type TweetMetricServantA interface { + UpdateTweetMetric(metric *cs.TweetMetric) error + AddTweetMetric(postId int64) error + DeleteTweetMetric(postId int64) error +} + +type CommentMetricServantA interface { + UpdateCommentMetric(metric *cs.CommentMetric) error + AddCommentMetric(commentId int64) error + DeleteCommentMetric(commentId int64) error +} + +type UserMetricServantA interface { + UpdateUserMetric(userId int64, action uint8) error + AddUserMetric(userId int64) error + DeleteUserMetric(userId int64) error +} diff --git a/internal/core/ms/tweets.go b/internal/core/ms/tweets.go index eab6d85c..f0d4e1e6 100644 --- a/internal/core/ms/tweets.go +++ b/internal/core/ms/tweets.go @@ -25,10 +25,10 @@ const ( ) const ( - PostVisitPublic PostVisibleT = iota - PostVisitPrivate - PostVisitFriend - PostVisitInvalid + PostVisitPublic = dbr.PostVisitPublic + PostVisitPrivate = dbr.PostVisitPrivate + PostVisitFriend = dbr.PostVisitFriend + PostVisitFollowing = dbr.PostVisitFollowing ) type ( diff --git a/internal/core/search.go b/internal/core/search.go index dc08e127..1cce77e0 100644 --- a/internal/core/search.go +++ b/internal/core/search.go @@ -15,14 +15,14 @@ const ( ) const ( - PostVisitPublic = dbr.PostVisitPublic - PostVisitPrivate = dbr.PostVisitPrivate - PostVisitFriend = dbr.PostVisitFriend - PostVisitInvalid = dbr.PostVisitInvalid + PostVisitPublic = dbr.PostVisitPublic + PostVisitPrivate = dbr.PostVisitPrivate + PostVisitFriend = dbr.PostVisitFriend + PostVisitFollowing = dbr.PostVisitFollowing ) type ( - // PostVisibleT 可访问类型,0公开,1私密,2好友 + // PostVisibleT 可访问类型,可见性: 0私密 10充电可见 20订阅可见 30保留 40保留 50好友可见 60关注可见 70保留 80保留 90公开 PostVisibleT = dbr.PostVisibleT SearchType string diff --git a/internal/core/trends.go b/internal/core/trends.go new file mode 100644 index 00000000..6d04c4e1 --- /dev/null +++ b/internal/core/trends.go @@ -0,0 +1,14 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package core + +import ( + "github.com/rocboss/paopao-ce/internal/core/cs" +) + +// TrendsManageServantA 动态信息管理服务 +type TrendsManageServantA interface { + GetIndexTrends(userId int64, limit int, offset int) ([]*cs.TrendsItem, int64, error) +} diff --git a/internal/core/tweets.go b/internal/core/tweets.go index b5aee860..3c49136c 100644 --- a/internal/core/tweets.go +++ b/internal/core/tweets.go @@ -26,6 +26,11 @@ type TweetService interface { ListUserStarTweets(user *cs.VistUser, limit int, offset int) ([]*ms.PostStar, int64, error) ListUserMediaTweets(user *cs.VistUser, limit int, offset int) ([]*ms.Post, int64, error) ListUserCommentTweets(user *cs.VistUser, limit int, offset int) ([]*ms.Post, int64, error) + ListUserTweets(userId int64, style uint8, justEssence bool, limit, offset int) ([]*ms.Post, int64, error) + ListFollowingTweets(userId int64, limit, offset int) ([]*ms.Post, int64, error) + ListIndexNewestTweets(limit, offset int) ([]*ms.Post, int64, error) + ListIndexHotsTweets(limit, offset int) ([]*ms.Post, int64, error) + ListSyncSearchTweets(limit, offset int) ([]*ms.Post, int64, error) } // TweetManageService 推文管理服务,包括创建/删除/更新推文 @@ -35,7 +40,7 @@ type TweetManageService interface { LockPost(post *ms.Post) error StickPost(post *ms.Post) error HighlightPost(userId, postId int64) (int, error) - VisiblePost(post *ms.Post, visibility PostVisibleT) error + VisiblePost(post *ms.Post, visibility cs.TweetVisibleType) error UpdatePost(post *ms.Post) error CreatePostStar(postID, userID int64) (*ms.PostStar, error) DeletePostStar(p *ms.PostStar) error diff --git a/internal/core/user.go b/internal/core/user.go index fa1cd9b8..78ac1c8d 100644 --- a/internal/core/user.go +++ b/internal/core/user.go @@ -4,7 +4,10 @@ package core -import "github.com/rocboss/paopao-ce/internal/core/ms" +import ( + "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/core/ms" +) // UserManageService 用户管理服务 type UserManageService interface { @@ -13,8 +16,10 @@ type UserManageService interface { GetUserByPhone(phone string) (*ms.User, error) GetUsersByIDs(ids []int64) ([]*ms.User, error) GetUsersByKeyword(keyword string) ([]*ms.User, error) + UserProfileByName(username string) (*cs.UserProfile, error) CreateUser(user *ms.User) (*ms.User, error) UpdateUser(user *ms.User) error + GetRegisterUserCount() (int64, error) } // ContactManageService 联系人管理服务 @@ -36,3 +41,11 @@ type FollowingManageService interface { GetFollowCount(userId int64) (int64, int64, error) IsFollow(userId int64, followId int64) bool } + +// UserRelationService 用户关系服务 +type UserRelationService interface { + MyFriendIds(userId int64) ([]int64, error) + MyFollowIds(userId int64) ([]int64, error) + IsMyFriend(userId int64, friendIds ...int64) (map[int64]bool, error) + IsMyFollow(userId int64, followIds ...int64) (map[int64]bool, error) +} diff --git a/internal/dao/cache/cache.go b/internal/dao/cache/cache.go index 3a2938cb..2cee6684 100644 --- a/internal/dao/cache/cache.go +++ b/internal/dao/cache/cache.go @@ -6,6 +6,7 @@ package cache import ( "context" + "sync" "time" "github.com/allegro/bigcache/v3" @@ -14,6 +15,10 @@ import ( "github.com/sirupsen/logrus" ) +var ( + _onceInit sync.Once +) + func NewRedisCache() core.RedisCache { return &redisCache{ c: conf.MustRedisClient(), @@ -48,6 +53,16 @@ func NewRedisCacheIndexService(ips core.IndexPostsService, ams core.Authorizatio return cacheIndex, cacheIndex } +func NewWebCache() core.WebCache { + lazyInitial() + return _webCache +} + +func NewAppCache() core.AppCache { + lazyInitial() + return _appCache +} + func NewSimpleCacheIndexService(indexPosts core.IndexPostsService) (core.CacheIndexService, core.VersionInfo) { s := conf.SimpleCacheIndexSetting cacheIndex := &simpleCacheIndexServant{ @@ -88,3 +103,10 @@ func NewNoneCacheIndexService(indexPosts core.IndexPostsService) (core.CacheInde } return obj, obj } + +func lazyInitial() { + _onceInit.Do(func() { + _appCache = newAppCache() + _webCache = newWebCache(_appCache) + }) +} diff --git a/internal/dao/cache/common.go b/internal/dao/cache/common.go new file mode 100644 index 00000000..fe22b895 --- /dev/null +++ b/internal/dao/cache/common.go @@ -0,0 +1,124 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cache + +import ( + "bytes" + "encoding/gob" + + "github.com/RoaringBitmap/roaring/roaring64" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/core/ms" +) + +type cacheDataService struct { + core.DataService + ac core.AppCache +} + +func NewCacheDataService(ds core.DataService) core.DataService { + lazyInitial() + return &cacheDataService{ + DataService: ds, + ac: _appCache, + } +} + +func (s *cacheDataService) GetUserByID(id int64) (res *ms.User, err error) { + // 先从缓存获取, 不处理错误 + key := conf.KeyUserInfoById.Get(id) + if data, xerr := s.ac.Get(key); xerr == nil { + buf := bytes.NewBuffer(data) + res = &ms.User{} + err = gob.NewDecoder(buf).Decode(res) + return + } + // 最后查库 + if res, err = s.DataService.GetUserByID(id); err == nil { + // 更新缓存 + onCacheUserInfoEvent(key, res) + } + return +} + +func (s *cacheDataService) GetUserByUsername(username string) (res *ms.User, err error) { + // 先从缓存获取, 不处理错误 + key := conf.KeyUserInfoByName.Get(username) + if data, xerr := s.ac.Get(key); xerr == nil { + buf := bytes.NewBuffer(data) + res = &ms.User{} + err = gob.NewDecoder(buf).Decode(res) + return + } + // 最后查库 + if res, err = s.DataService.GetUserByUsername(username); err == nil { + // 更新缓存 + onCacheUserInfoEvent(key, res) + } + return +} + +func (s *cacheDataService) UserProfileByName(username string) (res *cs.UserProfile, err error) { + // 先从缓存获取, 不处理错误 + key := conf.KeyUserProfileByName.Get(username) + if data, xerr := s.ac.Get(key); xerr == nil { + buf := bytes.NewBuffer(data) + res = &cs.UserProfile{} + err = gob.NewDecoder(buf).Decode(res) + return + } + // 最后查库 + if res, err = s.DataService.UserProfileByName(username); err == nil { + // 更新缓存 + onCacheObjectEvent(key, res, conf.CacheSetting.UserProfileExpire) + } + return +} + +func (s *cacheDataService) IsMyFriend(userId int64, friendIds ...int64) (res map[int64]bool, err error) { + size := len(friendIds) + res = make(map[int64]bool, size) + if size == 0 { + return + } + // 从缓存中获取 + key := conf.KeyMyFriendIds.Get(userId) + if data, xerr := s.ac.Get(key); xerr == nil { + bitmap := roaring64.New() + if err = bitmap.UnmarshalBinary(data); err == nil { + for _, friendId := range friendIds { + res[friendId] = bitmap.Contains(uint64(friendId)) + } + return + } + } + // 直接查库并触发缓存更新事件 + OnCacheMyFriendIdsEvent(s.DataService, userId) + return s.DataService.IsMyFriend(userId, friendIds...) +} + +func (s *cacheDataService) IsMyFollow(userId int64, followIds ...int64) (res map[int64]bool, err error) { + size := len(followIds) + res = make(map[int64]bool, size) + if size == 0 { + return + } + // 从缓存中获取 + key := conf.KeyMyFollowIds.Get(userId) + if data, xerr := s.ac.Get(key); xerr == nil { + bitmap := roaring64.New() + if err = bitmap.UnmarshalBinary(data); err == nil { + for _, followId := range followIds { + res[followId] = bitmap.Contains(uint64(followId)) + } + return + } + } + // 直接查库并触发缓存更新事件 + OnCacheMyFollowIdsEvent(s.DataService, userId, key) + return s.DataService.IsMyFollow(userId, followIds...) +} diff --git a/internal/dao/cache/events.go b/internal/dao/cache/events.go new file mode 100644 index 00000000..e8c0e62b --- /dev/null +++ b/internal/dao/cache/events.go @@ -0,0 +1,289 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cache + +import ( + "bytes" + "encoding/gob" + "fmt" + + "github.com/RoaringBitmap/roaring/roaring64" + "github.com/alimy/tryst/event" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/sirupsen/logrus" +) + +type BaseCacheEvent struct { + event.UnimplementedEvent + ac core.AppCache +} + +type expireIndexTweetsEvent struct { + event.UnimplementedEvent + ac core.AppCache + keysPattern []string +} + +type expireHotsTweetsEvent struct { + event.UnimplementedEvent + ac core.AppCache + keyPattern string +} + +type expireFollowTweetsEvent struct { + event.UnimplementedEvent + tweet *ms.Post + ac core.AppCache + keyPattern string +} + +type cacheObjectEvent struct { + event.UnimplementedEvent + ac core.AppCache + key string + data any + expire int64 +} + +type cacheUserInfoEvent struct { + event.UnimplementedEvent + ac core.AppCache + key string + data *ms.User + expire int64 +} + +type cacheMyFriendIdsEvent struct { + event.UnimplementedEvent + ac core.AppCache + urs core.UserRelationService + userIds []int64 + expire int64 +} + +type cacheMyFollowIdsEvent struct { + event.UnimplementedEvent + ac core.AppCache + urs core.UserRelationService + userId int64 + key string + expire int64 +} + +func NewBaseCacheEvent(ac core.AppCache) *BaseCacheEvent { + return &BaseCacheEvent{ + ac: ac, + } +} + +func OnExpireIndexTweetEvent(userId int64) { + // TODO: 这里暴躁的将所有 最新/热门/关注 的推文列表缓存都过期掉,后续需要更精细话处理 + events.OnEvent(&expireIndexTweetsEvent{ + ac: _appCache, + keysPattern: []string{ + conf.PrefixIdxTweetsNewest + "*", + conf.PrefixIdxTweetsHots + "*", + conf.PrefixIdxTweetsFollowing + "*", + fmt.Sprintf("%s%d:*", conf.PrefixUserTweets, userId), + }, + }) +} + +func OnExpireHotsTweetEvent() { + events.OnEvent(&expireHotsTweetsEvent{ + ac: _appCache, + keyPattern: conf.PrefixHotsTweets + "*", + }) +} + +func onExpireFollowTweetEvent(tweet *ms.Post) { + events.OnEvent(&expireFollowTweetsEvent{ + tweet: tweet, + ac: _appCache, + keyPattern: conf.PrefixFollowingTweets + "*", + }) +} + +func onCacheUserInfoEvent(key string, data *ms.User) { + events.OnEvent(&cacheUserInfoEvent{ + key: key, + data: data, + ac: _appCache, + expire: conf.CacheSetting.UserInfoExpire, + }) +} + +func onCacheObjectEvent(key string, data any, expire int64) { + events.OnEvent(&cacheObjectEvent{ + key: key, + data: data, + ac: _appCache, + expire: expire, + }) +} + +func OnCacheMyFriendIdsEvent(urs core.UserRelationService, userIds ...int64) { + if len(userIds) == 0 { + return + } + events.OnEvent(&cacheMyFriendIdsEvent{ + userIds: userIds, + urs: urs, + ac: _appCache, + expire: conf.CacheSetting.UserRelationExpire, + }) +} + +func OnCacheMyFollowIdsEvent(urs core.UserRelationService, userId int64, key ...string) { + cacheKey := "" + if len(key) > 0 { + cacheKey = key[0] + } else { + cacheKey = conf.KeyMyFollowIds.Get(userId) + } + events.OnEvent(&cacheMyFollowIdsEvent{ + userId: userId, + urs: urs, + key: cacheKey, + ac: _appCache, + expire: conf.CacheSetting.UserRelationExpire, + }) +} + +func (e *BaseCacheEvent) ExpireUserInfo(id int64, name string) error { + keys := make([]string, 0, 2) + if id >= 0 { + keys = append(keys, conf.KeyUserInfoById.Get(id)) + } + if len(name) > 0 { + keys = append(keys, conf.KeyUserInfoByName.Get(name)) + } + return e.ac.Delete(keys...) +} + +func (e *BaseCacheEvent) ExpireUserProfile(name string) error { + if len(name) > 0 { + return e.ac.Delete(conf.KeyUserProfileByName.Get(name)) + } + return nil +} + +func (e *BaseCacheEvent) ExpireUserData(id int64, name string) error { + keys := make([]string, 0, 3) + if id >= 0 { + keys = append(keys, conf.KeyUserInfoById.Get(id)) + } + if len(name) > 0 { + keys = append(keys, conf.KeyUserInfoByName.Get(name), conf.KeyUserProfileByName.Get(name)) + } + return e.ac.Delete(keys...) +} + +func (e *expireIndexTweetsEvent) Name() string { + return "expireIndexTweetsEvent" +} + +func (e *expireIndexTweetsEvent) Action() (err error) { + // logrus.Debug("expireIndexTweetsEvent action running") + for _, pattern := range e.keysPattern { + e.ac.DelAny(pattern) + } + return +} + +func (e *expireHotsTweetsEvent) Name() string { + return "expireHotsTweetsEvent" +} + +func (e *expireHotsTweetsEvent) Action() (err error) { + // logrus.Debug("expireHotsTweetsEvent action running") + e.ac.DelAny(e.keyPattern) + return +} + +func (e *expireFollowTweetsEvent) Name() string { + return "expireFollowTweetsEvent" +} + +func (e *expireFollowTweetsEvent) Action() (err error) { + // logrus.Debug("expireFollowTweetsEvent action running") + e.ac.DelAny(e.keyPattern) + return +} + +func (e *cacheUserInfoEvent) Name() string { + return "cacheUserInfoEvent" +} + +func (e *cacheUserInfoEvent) Action() (err error) { + buffer := &bytes.Buffer{} + ge := gob.NewEncoder(buffer) + if err = ge.Encode(e.data); err == nil { + e.ac.Set(e.key, buffer.Bytes(), e.expire) + } + return +} + +func (e *cacheObjectEvent) Name() string { + return "cacheObjectEvent" +} + +func (e *cacheObjectEvent) Action() (err error) { + buffer := &bytes.Buffer{} + ge := gob.NewEncoder(buffer) + if err = ge.Encode(e.data); err == nil { + e.ac.Set(e.key, buffer.Bytes(), e.expire) + } + return +} + +func (e *cacheMyFriendIdsEvent) Name() string { + return "cacheMyFriendIdsEvent" +} + +func (e *cacheMyFriendIdsEvent) Action() error { + logrus.Debug("cacheMyFriendIdsEvent action runnging") + for _, userId := range e.userIds { + myFriendIds, err := e.urs.MyFriendIds(userId) + if err != nil { + return err + } + bitmap := roaring64.New() + for _, friendId := range myFriendIds { + bitmap.Add(uint64(friendId)) + } + data, err := bitmap.MarshalBinary() + if err != nil { + return err + } + e.ac.Set(conf.KeyMyFriendIds.Get(userId), data, e.expire) + } + return nil +} + +func (e *cacheMyFollowIdsEvent) Name() string { + return "cacheMyFollowIdsEvent" +} + +func (e *cacheMyFollowIdsEvent) Action() (err error) { + logrus.Debug("cacheMyFollowIdsEvent action runnging") + myFollowIds, err := e.urs.MyFollowIds(e.userId) + if err != nil { + return err + } + bitmap := roaring64.New() + for _, followId := range myFollowIds { + bitmap.Add(uint64(followId)) + } + data, err := bitmap.MarshalBinary() + if err != nil { + return err + } + e.ac.Set(e.key, data, e.expire) + return nil +} diff --git a/internal/dao/cache/redis.go b/internal/dao/cache/redis.go index 50b581a7..0e106c5a 100644 --- a/internal/dao/cache/redis.go +++ b/internal/dao/cache/redis.go @@ -59,9 +59,22 @@ func (s *redisCacheTweetsCache) delTweets(keys []string) error { return s.c.Do(context.Background(), cmd).Error() } -func (s *redisCacheTweetsCache) allKeys() ([]string, error) { - cmd := s.c.B().Keys().Pattern(_cacheIndexKeyPattern).Build() - return s.c.Do(context.Background(), cmd).AsStrSlice() +func (s *redisCacheTweetsCache) allKeys() (res []string, err error) { + ctx, cursor := context.Background(), uint64(0) + for { + cmd := s.c.B().Scan().Cursor(cursor).Match(_cacheIndexKeyPattern).Count(50).Build() + entry, err := s.c.Do(ctx, cmd).AsScanEntry() + if err != nil { + return nil, err + } + res = append(res, entry.Elements...) + if entry.Cursor != 0 { + cursor = entry.Cursor + continue + } + break + } + return } func (s *redisCacheTweetsCache) Name() string { diff --git a/internal/dao/cache/tweets.go b/internal/dao/cache/tweets.go new file mode 100644 index 00000000..ecabf0bd --- /dev/null +++ b/internal/dao/cache/tweets.go @@ -0,0 +1,49 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cache + +import ( + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/sirupsen/logrus" +) + +type eventCacheIndexSrv struct { + tms core.TweetMetricServantA +} + +func (s *eventCacheIndexSrv) SendAction(act core.IdxAct, post *ms.Post) { + err := error(nil) + switch act { + case core.IdxActUpdatePost: + err = s.tms.UpdateTweetMetric(&cs.TweetMetric{ + PostId: post.ID, + CommentCount: post.CommentCount, + UpvoteCount: post.UpvoteCount, + CollectionCount: post.CollectionCount, + ShareCount: post.ShareCount, + }) + OnExpireIndexTweetEvent(post.UserID) + case core.IdxActCreatePost: + err = s.tms.AddTweetMetric(post.ID) + OnExpireIndexTweetEvent(post.UserID) + case core.IdxActDeletePost: + err = s.tms.DeleteTweetMetric(post.ID) + OnExpireIndexTweetEvent(post.UserID) + case core.IdxActStickPost, core.IdxActVisiblePost: + OnExpireIndexTweetEvent(post.UserID) + } + if err != nil { + logrus.Errorf("eventCacheIndexSrv.SendAction(%s) occurs error: %s", act, err) + } +} + +func NewEventCacheIndexSrv(tms core.TweetMetricServantA) core.CacheIndexService { + lazyInitial() + return &eventCacheIndexSrv{ + tms: tms, + } +} diff --git a/internal/dao/cache/web.go b/internal/dao/cache/web.go new file mode 100644 index 00000000..60418194 --- /dev/null +++ b/internal/dao/cache/web.go @@ -0,0 +1,163 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package cache + +import ( + "context" + "time" + + "github.com/redis/rueidis" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/pkg/utils" +) + +var ( + _webCache core.WebCache = (*webCache)(nil) + _appCache core.AppCache = (*appCache)(nil) +) + +type appCache struct { + cscExpire time.Duration + c rueidis.Client +} + +type webCache struct { + core.AppCache + c rueidis.Client + unreadMsgExpire int64 +} + +func (s *appCache) Get(key string) ([]byte, error) { + res, err := rueidis.MGetCache(s.c, context.Background(), s.cscExpire, []string{key}) + if err != nil { + return nil, err + } + message := res[key] + return message.AsBytes() +} + +func (s *appCache) Set(key string, data []byte, ex int64) error { + ctx := context.Background() + cmd := s.c.B().Set().Key(key).Value(utils.String(data)) + if ex > 0 { + return s.c.Do(ctx, cmd.ExSeconds(ex).Build()).Error() + } + return s.c.Do(ctx, cmd.Build()).Error() +} + +func (s *appCache) SetNx(key string, data []byte, ex int64) error { + ctx := context.Background() + cmd := s.c.B().Set().Key(key).Value(utils.String(data)).Nx() + if ex > 0 { + return s.c.Do(ctx, cmd.ExSeconds(ex).Build()).Error() + } + return s.c.Do(ctx, cmd.Build()).Error() +} + +func (s *appCache) Delete(keys ...string) (err error) { + if len(keys) != 0 { + err = s.c.Do(context.Background(), s.c.B().Del().Key(keys...).Build()).Error() + } + return +} + +func (s *appCache) DelAny(pattern string) (err error) { + var ( + keys []string + cursor uint64 + entry rueidis.ScanEntry + ) + ctx := context.Background() + for { + cmd := s.c.B().Scan().Cursor(cursor).Match(pattern).Count(50).Build() + if entry, err = s.c.Do(ctx, cmd).AsScanEntry(); err != nil { + return + } + keys = append(keys, entry.Elements...) + if entry.Cursor != 0 { + cursor = entry.Cursor + continue + } + break + } + if len(keys) != 0 { + err = s.c.Do(ctx, s.c.B().Del().Key(keys...).Build()).Error() + } + return +} + +func (s *appCache) Exist(key string) bool { + cmd := s.c.B().Exists().Key(key).Build() + count, _ := s.c.Do(context.Background(), cmd).AsInt64() + return count > 0 +} + +func (s *appCache) Keys(pattern string) (res []string, err error) { + ctx, cursor := context.Background(), uint64(0) + for { + cmd := s.c.B().Scan().Cursor(cursor).Match(pattern).Count(50).Build() + entry, err := s.c.Do(ctx, cmd).AsScanEntry() + if err != nil { + return nil, err + } + res = append(res, entry.Elements...) + if entry.Cursor != 0 { + cursor = entry.Cursor + continue + } + break + } + return +} + +func (s *webCache) GetUnreadMsgCountResp(uid int64) ([]byte, error) { + key := conf.KeyUnreadMsg.Get(uid) + return s.Get(key) +} + +func (s *webCache) PutUnreadMsgCountResp(uid int64, data []byte) error { + return s.Set(conf.KeyUnreadMsg.Get(uid), data, s.unreadMsgExpire) +} + +func (s *webCache) DelUnreadMsgCountResp(uid int64) error { + return s.Delete(conf.KeyUnreadMsg.Get(uid)) +} + +func (s *webCache) ExistUnreadMsgCountResp(uid int64) bool { + return s.Exist(conf.KeyUnreadMsg.Get(uid)) +} + +func (s *webCache) PutHistoryMaxOnline(newScore int) (int, error) { + ctx := context.Background() + cmd := s.c.B().Zadd(). + Key(conf.KeySiteStatus). + Gt().ScoreMember(). + ScoreMember(float64(newScore), conf.KeyHistoryMaxOnline).Build() + if err := s.c.Do(ctx, cmd).Error(); err != nil { + return 0, err + } + cmd = s.c.B().Zscore().Key(conf.KeySiteStatus).Member(conf.KeyHistoryMaxOnline).Build() + if score, err := s.c.Do(ctx, cmd).ToFloat64(); err == nil { + return int(score), nil + } else { + return 0, err + } +} + +func newAppCache() *appCache { + return &appCache{ + cscExpire: conf.CacheSetting.CientSideCacheExpire, + c: conf.MustRedisClient(), + } +} + +func newWebCache(ac core.AppCache) *webCache { + return &webCache{ + AppCache: ac, + c: conf.MustRedisClient(), + unreadMsgExpire: conf.CacheSetting.UnreadMsgExpire, + } +} diff --git a/internal/dao/dao.go b/internal/dao/dao.go index b1f7d9f2..8b355e36 100644 --- a/internal/dao/dao.go +++ b/internal/dao/dao.go @@ -7,7 +7,7 @@ package dao import ( "sync" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/dao/jinzhu" "github.com/rocboss/paopao-ce/internal/dao/sakila" diff --git a/internal/dao/jinzhu/comments.go b/internal/dao/jinzhu/comments.go index 4ac08e18..d4b2671c 100644 --- a/internal/dao/jinzhu/comments.go +++ b/internal/dao/jinzhu/comments.go @@ -5,6 +5,7 @@ package jinzhu import ( + "fmt" "time" "github.com/rocboss/paopao-ce/internal/core" @@ -60,8 +61,27 @@ func (s *commentSrv) GetCommentThumbsMap(userId int64, tweetId int64) (cs.Commen return commentThumbs, replyThumbs, nil } -func (s *commentSrv) GetComments(conditions *ms.ConditionsT, offset, limit int) ([]*ms.Comment, error) { - return (&dbr.Comment{}).List(s.db, conditions, offset, limit) +func (s *commentSrv) GetComments(tweetId int64, style cs.StyleCommentType, limit int, offset int) (res []*ms.Comment, total int64, err error) { + db := s.db.Table(_comment_) + sort := "is_essence DESC, id ASC" + switch style { + case cs.StyleCommentHots: + // rank_score=评论回复数*2+点赞*4-点踩, order byrank_score DESC + db = db.Joins(fmt.Sprintf("LEFT JOIN %s m ON %s.id=m.comment_id AND m.is_del=0", _commentMetric_, _comment_)) + sort = fmt.Sprintf("is_essence DESC, m.rank_score DESC, %s.id DESC", _comment_) + case cs.StyleCommentNewest: + sort = "is_essence DESC, id DESC" + case cs.StyleCommentDefault: + fallthrough + default: + // nothing + } + db = db.Where("post_id=?", tweetId) + if err = db.Count(&total).Error; err != nil { + return + } + err = db.Order(sort).Limit(limit).Offset(offset).Find(&res).Error + return } func (s *commentSrv) GetCommentByID(id int64) (*ms.Comment, error) { @@ -99,11 +119,9 @@ func (s *commentSrv) GetCommentRepliesByID(ids []int64) ([]*ms.CommentReplyForma "comment_id IN ?": ids, "ORDER": "id ASC", }, 0, 0) - if err != nil { return nil, err } - userIds := []int64{} for _, reply := range replies { userIds = append(userIds, reply.UserID, reply.AtUserID) @@ -124,44 +142,61 @@ func (s *commentSrv) GetCommentRepliesByID(ids []int64) ([]*ms.CommentReplyForma replyFormated.AtUser = user.Format() } } - repliesFormated = append(repliesFormated, replyFormated) } return repliesFormated, nil } -func (s *commentManageSrv) DeleteComment(comment *ms.Comment) error { +func (s *commentManageSrv) HighlightComment(userId, commentId int64) (res int8, err error) { + post := &dbr.Post{} + comment := &dbr.Comment{} + db := s.db.Model(comment) + if err = db.Where("id=?", commentId).First(comment).Error; err != nil { + return + } + if err = s.db.Table(_post_).Where("id=?", comment.PostID).First(post).Error; err != nil { + return + } + if post.UserID != userId { + return 0, cs.ErrNoPermission + } + comment.IsEssence = 1 - comment.IsEssence + return comment.IsEssence, db.Save(comment).Error +} + +func (s *commentManageSrv) DeleteComment(comment *ms.Comment) (err error) { db := s.db.Begin() defer db.Rollback() - - err := comment.Delete(s.db) - if err != nil { - return err + if err = comment.Delete(db); err != nil { + return } err = db.Model(&dbr.TweetCommentThumbs{}).Where("user_id=? AND tweet_id=? AND comment_id=?", comment.UserID, comment.PostID, comment.ID).Updates(map[string]any{ "deleted_on": time.Now().Unix(), "is_del": 1, }).Error if err != nil { - return err + return } db.Commit() - return nil + return } func (s *commentManageSrv) CreateComment(comment *ms.Comment) (*ms.Comment, error) { return comment.Create(s.db) } -func (s *commentManageSrv) CreateCommentReply(reply *ms.CommentReply) (*ms.CommentReply, error) { - return reply.Create(s.db) +func (s *commentManageSrv) CreateCommentReply(reply *ms.CommentReply) (res *ms.CommentReply, err error) { + if res, err = reply.Create(s.db); err == nil { + // 宽松处理错误 + s.db.Table(_comment_).Where("id=?", reply.CommentID).Update("reply_count", gorm.Expr("reply_count+1")) + } + return } func (s *commentManageSrv) DeleteCommentReply(reply *ms.CommentReply) (err error) { db := s.db.Begin() defer db.Rollback() - err = reply.Delete(s.db) if err != nil { return @@ -174,6 +209,8 @@ func (s *commentManageSrv) DeleteCommentReply(reply *ms.CommentReply) (err error if err != nil { return } + // 宽松处理错误 + db.Table(_comment_).Where("id=?", reply.CommentID).Update("reply_count", gorm.Expr("reply_count-1")) db.Commit() return } diff --git a/internal/dao/jinzhu/dbr/comment.go b/internal/dao/jinzhu/dbr/comment.go index 4f673336..364253c4 100644 --- a/internal/dao/jinzhu/dbr/comment.go +++ b/internal/dao/jinzhu/dbr/comment.go @@ -17,6 +17,8 @@ type Comment struct { UserID int64 `json:"user_id"` IP string `json:"ip"` IPLoc string `json:"ip_loc"` + IsEssence int8 `json:"is_essense"` + ReplyCount int32 `json:"reply_count"` ThumbsUpCount int32 `json:"thumbs_up_count"` ThumbsDownCount int32 `json:"-"` } @@ -29,7 +31,9 @@ type CommentFormated struct { Contents []*CommentContent `json:"contents"` Replies []*CommentReplyFormated `json:"replies"` IPLoc string `json:"ip_loc"` + ReplyCount int32 `json:"reply_count"` ThumbsUpCount int32 `json:"thumbs_up_count"` + IsEssence int8 `json:"is_essence"` IsThumbsUp int8 `json:"is_thumbs_up"` IsThumbsDown int8 `json:"is_thumbs_down"` CreatedOn int64 `json:"created_on"` @@ -48,7 +52,9 @@ func (c *Comment) Format() *CommentFormated { Contents: []*CommentContent{}, Replies: []*CommentReplyFormated{}, IPLoc: c.IPLoc, + ReplyCount: c.ReplyCount, ThumbsUpCount: c.ThumbsUpCount, + IsEssence: c.IsEssence, IsThumbsUp: types.No, IsThumbsDown: types.No, CreatedOn: c.CreatedOn, @@ -116,7 +122,6 @@ func (c *Comment) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { func (c *Comment) Create(db *gorm.DB) (*Comment, error) { err := db.Create(&c).Error - return c, err } diff --git a/internal/dao/jinzhu/dbr/message.go b/internal/dao/jinzhu/dbr/message.go index 6c952bae..cac72cbf 100644 --- a/internal/dao/jinzhu/dbr/message.go +++ b/internal/dao/jinzhu/dbr/message.go @@ -38,6 +38,7 @@ type MessageFormated struct { SenderUserID int64 `json:"sender_user_id"` SenderUser *UserFormated `json:"sender_user"` ReceiverUserID int64 `json:"receiver_user_id"` + ReceiverUser *UserFormated `json:"receiver_user,omitempty"` Type MessageT `json:"type"` Brief string `json:"brief"` Content string `json:"content"` @@ -61,6 +62,7 @@ func (m *Message) Format() *MessageFormated { SenderUserID: m.SenderUserID, SenderUser: &UserFormated{}, ReceiverUserID: m.ReceiverUserID, + ReceiverUser: &UserFormated{}, Type: m.Type, Brief: m.Brief, Content: m.Content, @@ -114,39 +116,20 @@ func (m *Message) FetchBy(db *gorm.DB, predicates Predicates) ([]*Message, error return messages, nil } -func (c *Message) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Message, error) { - var messages []*Message - var err error +func (c *Message) List(db *gorm.DB, userId int64, offset, limit int) (res []*Message, err error) { if offset >= 0 && limit > 0 { db = db.Offset(offset).Limit(limit) } - - for k, v := range *conditions { - if k == "ORDER" { - db = db.Order(v) - } else { - db = db.Where(k, v) - } - } - - if err = db.Where("is_del = ?", 0).Find(&messages).Error; err != nil { - return nil, err - } - - return messages, nil + err = db.Where("receiver_user_id=? OR (sender_user_id=? AND type=4)", userId, userId).Order("id DESC").Find(&res).Error + return } -func (m *Message) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { - var count int64 - - for k, v := range *conditions { - if k != "ORDER" { - db = db.Where(k, v) - } - } - if err := db.Model(m).Count(&count).Error; err != nil { - return 0, err - } +func (m *Message) Count(db *gorm.DB, userId int64) (res int64, err error) { + err = db.Model(m).Where("receiver_user_id=? OR (sender_user_id=? AND type=4)", userId, userId).Count(&res).Error + return +} - return count, nil +func (m *Message) CountUnread(db *gorm.DB, userId int64) (res int64, err error) { + err = db.Model(m).Where("receiver_user_id=? AND is_read=0", userId).Count(&res).Error + return } diff --git a/internal/dao/jinzhu/dbr/metrics.go b/internal/dao/jinzhu/dbr/metrics.go new file mode 100644 index 00000000..4210c701 --- /dev/null +++ b/internal/dao/jinzhu/dbr/metrics.go @@ -0,0 +1,72 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package dbr + +import ( + "time" + + "gorm.io/gorm" +) + +type PostMetric struct { + *Model + PostId int64 + RankScore int64 + IncentiveScore int + DecayFactor int + MotivationFactor int +} + +type CommentMetric struct { + *Model + CommentId int64 + RankScore int64 + IncentiveScore int + DecayFactor int + MotivationFactor int +} + +type UserMetric struct { + *Model + UserId int64 + TweetsCount int + LatestTrendsOn int64 +} + +func (m *PostMetric) Create(db *gorm.DB) (*PostMetric, error) { + err := db.Create(&m).Error + return m, err +} + +func (m *PostMetric) Delete(db *gorm.DB) error { + return db.Model(m).Where("post_id", m.PostId).Updates(map[string]any{ + "deleted_on": time.Now().Unix(), + "is_del": 1, + }).Error +} + +func (m *CommentMetric) Create(db *gorm.DB) (*CommentMetric, error) { + err := db.Create(&m).Error + return m, err +} + +func (m *CommentMetric) Delete(db *gorm.DB) error { + return db.Model(m).Where("comment_id", m.CommentId).Updates(map[string]any{ + "deleted_on": time.Now().Unix(), + "is_del": 1, + }).Error +} + +func (m *UserMetric) Create(db *gorm.DB) (*UserMetric, error) { + err := db.Create(&m).Error + return m, err +} + +func (m *UserMetric) Delete(db *gorm.DB) error { + return db.Model(m).Where("user_id", m.UserId).Updates(map[string]any{ + "deleted_on": time.Now().Unix(), + "is_del": 1, + }).Error +} diff --git a/internal/dao/jinzhu/dbr/post.go b/internal/dao/jinzhu/dbr/post.go index f3d3e824..76aa57c9 100644 --- a/internal/dao/jinzhu/dbr/post.go +++ b/internal/dao/jinzhu/dbr/post.go @@ -11,14 +11,14 @@ import ( "gorm.io/gorm" ) -// PostVisibleT 可访问类型,0公开,1私密,2好友 +// PostVisibleT 可访问类型,可见性: 0私密 10充电可见 20订阅可见 30保留 40保留 50好友可见 60关注可见 70保留 80保留 90公开', type PostVisibleT uint8 const ( - PostVisitPublic PostVisibleT = iota - PostVisitPrivate - PostVisitFriend - PostVisitInvalid + PostVisitPublic PostVisibleT = 90 + PostVisitPrivate PostVisibleT = 0 + PostVisitFriend PostVisibleT = 50 + PostVisitFollowing PostVisibleT = 60 ) type PostByMedia = Post @@ -64,6 +64,22 @@ type PostFormated struct { IPLoc string `json:"ip_loc"` } +func (t PostVisibleT) ToOutValue() (res uint8) { + switch t { + case PostVisitPublic: + res = 0 + case PostVisitPrivate: + res = 1 + case PostVisitFriend: + res = 2 + case PostVisitFollowing: + res = 3 + default: + res = 1 + } + return +} + func (p *Post) Format() *PostFormated { if p.Model != nil { tagsMap := map[string]int8{} @@ -211,8 +227,6 @@ func (p PostVisibleT) String() string { return "private" case PostVisitFriend: return "friend" - case PostVisitInvalid: - return "invalid" default: return "unknow" } diff --git a/internal/dao/jinzhu/dbr/user.go b/internal/dao/jinzhu/dbr/user.go index 7297e39f..4d7f7f6d 100644 --- a/internal/dao/jinzhu/dbr/user.go +++ b/internal/dao/jinzhu/dbr/user.go @@ -28,12 +28,14 @@ type User struct { } type UserFormated struct { - ID int64 `db:"id" json:"id"` - Nickname string `json:"nickname"` - Username string `json:"username"` - Status int `json:"status"` - Avatar string `json:"avatar"` - IsAdmin bool `json:"is_admin"` + ID int64 `db:"id" json:"id"` + Nickname string `json:"nickname"` + Username string `json:"username"` + Status int `json:"status"` + Avatar string `json:"avatar"` + IsAdmin bool `json:"is_admin"` + IsFriend bool `json:"is_friend"` + IsFollowing bool `json:"is_following"` } func (u *User) Format() *UserFormated { @@ -97,7 +99,6 @@ func (u *User) ListUserInfoById(db *gorm.DB, ids []int64) (res cs.UserInfoList, func (u *User) Create(db *gorm.DB) (*User, error) { err := db.Create(&u).Error - return u, err } diff --git a/internal/dao/jinzhu/gorm.go b/internal/dao/jinzhu/gorm.go index c5b0d4f6..0a0cdb27 100644 --- a/internal/dao/jinzhu/gorm.go +++ b/internal/dao/jinzhu/gorm.go @@ -15,6 +15,7 @@ var ( _attachment_ string _captcha_ string _comment_ string + _commentMetric_ string _commentContent_ string _commentReply_ string _following_ string @@ -22,6 +23,7 @@ var ( _contactGroup_ string _message_ string _post_ string + _post_metric_ string _post_by_comment_ string _post_by_media_ string _postAttachmentBill_ string @@ -30,6 +32,8 @@ var ( _postStar_ string _tag_ string _user_ string + _userRelation_ string + _userMetric_ string _walletRecharge_ string _walletStatement_ string ) @@ -41,6 +45,7 @@ func initTableName() { _attachment_ = m[conf.TableAttachment] _captcha_ = m[conf.TableCaptcha] _comment_ = m[conf.TableComment] + _commentMetric_ = m[conf.TableCommentMetric] _commentContent_ = m[conf.TableCommentContent] _commentReply_ = m[conf.TableCommentReply] _following_ = m[conf.TableFollowing] @@ -48,6 +53,7 @@ func initTableName() { _contactGroup_ = m[conf.TableContactGroup] _message_ = m[conf.TableMessage] _post_ = m[conf.TablePost] + _post_metric_ = m[conf.TablePostMetric] _post_by_comment_ = m[conf.TablePostByComment] _post_by_media_ = m[conf.TablePostByMedia] _postAttachmentBill_ = m[conf.TablePostAttachmentBill] @@ -56,6 +62,8 @@ func initTableName() { _postStar_ = m[conf.TablePostStar] _tag_ = m[conf.TableTag] _user_ = m[conf.TableUser] + _userRelation_ = m[conf.TableUserRelation] + _userMetric_ = m[conf.TableUserMetric] _walletRecharge_ = m[conf.TableWalletRecharge] _walletStatement_ = m[conf.TableWalletStatement] } diff --git a/internal/dao/jinzhu/jinzhu.go b/internal/dao/jinzhu/jinzhu.go index a3255010..16b6d64a 100644 --- a/internal/dao/jinzhu/jinzhu.go +++ b/internal/dao/jinzhu/jinzhu.go @@ -12,12 +12,10 @@ import ( "sync" "github.com/Masterminds/semver/v3" - "github.com/alimy/cfg" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/dao/cache" "github.com/rocboss/paopao-ce/internal/dao/security" - "github.com/sirupsen/logrus" ) var ( @@ -31,18 +29,22 @@ var ( ) type dataSrv struct { - core.IndexPostsService core.WalletService core.MessageService core.TopicService core.TweetService core.TweetManageService core.TweetHelpService + core.TweetMetricServantA core.CommentService core.CommentManageService + core.CommentMetricServantA + core.TrendsManageServantA core.UserManageService + core.UserMetricServantA core.ContactManageService core.FollowingManageService + core.UserRelationService core.SecurityService core.AttachmentCheckService } @@ -56,38 +58,16 @@ type webDataSrvA struct { func NewDataService() (core.DataService, core.VersionInfo) { lazyInitial() - - var ( - v core.VersionInfo - cis core.CacheIndexService - ) db := conf.MustGormDB() pvs := security.NewPhoneVerifyService() - ams := NewAuthorizationManageService() - ths := newTweetHelpService(db) - ips := newShipIndexService(db, ams, ths) - - // initialize core.CacheIndexService - cfg.On(cfg.Actions{ - "SimpleCacheIndex": func() { - // simpleCache use special post index service - ips = newSimpleIndexPostsService(db, ths) - cis, v = cache.NewSimpleCacheIndexService(ips) - }, - "BigCacheIndex": func() { - cis, v = cache.NewBigCacheIndexService(ips, ams) - }, - "RedisCacheIndex": func() { - cis, v = cache.NewRedisCacheIndexService(ips, ams) - }, - }, func() { - // defualt no cache - cis, v = cache.NewNoneCacheIndexService(ips) - }) - logrus.Infof("use %s as cache index service by version: %s", v.Name(), v.Version()) - + tms := newTweetMetricServentA(db) + ums := newUserMetricServentA(db) + cms := newCommentMetricServentA(db) + cis := cache.NewEventCacheIndexSrv(tms) ds := &dataSrv{ - IndexPostsService: cis, + TweetMetricServantA: tms, + CommentMetricServantA: cms, + UserMetricServantA: ums, WalletService: newWalletService(db), MessageService: newMessageService(db), TopicService: newTopicService(db), @@ -96,13 +76,15 @@ func NewDataService() (core.DataService, core.VersionInfo) { TweetHelpService: newTweetHelpService(db), CommentService: newCommentService(db), CommentManageService: newCommentManageService(db), - UserManageService: newUserManageService(db), + TrendsManageServantA: newTrendsManageServentA(db), + UserManageService: newUserManageService(db, ums), ContactManageService: newContactManageService(db), FollowingManageService: newFollowingManageService(db), + UserRelationService: newUserRelationService(db), SecurityService: newSecurityService(db, pvs), AttachmentCheckService: security.NewAttachmentCheckService(), } - return ds, ds + return cache.NewCacheDataService(ds), ds } func NewWebDataServantA() (core.WebDataServantA, core.VersionInfo) { diff --git a/internal/dao/jinzhu/messages.go b/internal/dao/jinzhu/messages.go index 00df949f..105618e9 100644 --- a/internal/dao/jinzhu/messages.go +++ b/internal/dao/jinzhu/messages.go @@ -6,6 +6,7 @@ package jinzhu import ( "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/core/ms" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" "gorm.io/gorm" @@ -30,10 +31,7 @@ func (s *messageSrv) CreateMessage(msg *ms.Message) (*ms.Message, error) { } func (s *messageSrv) GetUnreadCount(userID int64) (int64, error) { - return (&dbr.Message{}).Count(s.db, &dbr.ConditionsT{ - "receiver_user_id": userID, - "is_read": dbr.MsgStatusUnread, - }) + return (&dbr.Message{}).CountUnread(s.db, userID) } func (s *messageSrv) GetMessageByID(id int64) (*ms.Message, error) { @@ -49,21 +47,39 @@ func (s *messageSrv) ReadMessage(message *ms.Message) error { return message.Update(s.db) } -func (s *messageSrv) GetMessages(conditions *ms.ConditionsT, offset, limit int) ([]*ms.MessageFormated, error) { - messages, err := (&dbr.Message{}).List(s.db, conditions, offset, limit) - if err != nil { - return nil, err - } +func (s *messageSrv) ReadAllMessage(userId int64) error { + return s.db.Table(_message_).Where("receiver_user_id=? AND is_del=0", userId).Update("is_read", 1).Error +} - mfs := []*dbr.MessageFormated{} +func (s *messageSrv) GetMessages(userId int64, style cs.MessageStyle, limit int, offset int) (res []*ms.MessageFormated, total int64, err error) { + var messages []*dbr.Message + db := s.db.Table(_message_) + // 1动态,2评论,3回复,4私信,5好友申请,99系统通知' + switch style { + case cs.StyleMsgSystem: + db = db.Where("receiver_user_id=? AND type IN (1, 2, 3, 99)", userId) + case cs.StyleMsgWhisper: + db = db.Where("(receiver_user_id=? OR sender_user_id=?) AND type=4", userId, userId) + case cs.StyleMsgRequesting: + db = db.Where("receiver_user_id=? AND type=5", userId) + case cs.StyleMsgUnread: + db = db.Where("receiver_user_id=? AND is_read=0", userId) + case cs.StyleMsgAll: + fallthrough + default: + db = db.Where("receiver_user_id=? OR (sender_user_id=? AND type=4)", userId, userId) + } + if err = db.Count(&total).Error; err != nil || total == 0 { + return + } + if offset >= 0 && limit > 0 { + db = db.Limit(limit).Offset(offset) + } + if err = db.Order("id DESC").Find(&messages).Error; err != nil { + return + } for _, message := range messages { - mf := message.Format() - mfs = append(mfs, mf) + res = append(res, message.Format()) } - - return mfs, nil -} - -func (s *messageSrv) GetMessageCount(conditions *ms.ConditionsT) (int64, error) { - return (&dbr.Message{}).Count(s.db, conditions) + return } diff --git a/internal/dao/jinzhu/metrics.go b/internal/dao/jinzhu/metrics.go new file mode 100644 index 00000000..5df75fe9 --- /dev/null +++ b/internal/dao/jinzhu/metrics.go @@ -0,0 +1,113 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package jinzhu + +import ( + "time" + + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" + "gorm.io/gorm" +) + +type tweetMetricSrvA struct { + db *gorm.DB +} + +type commentMetricSrvA struct { + db *gorm.DB +} + +type userMetricSrvA struct { + db *gorm.DB +} + +func (s *tweetMetricSrvA) UpdateTweetMetric(metric *cs.TweetMetric) error { + return s.db.Transaction(func(tx *gorm.DB) (err error) { + postMetric := &dbr.PostMetric{PostId: metric.PostId} + db := s.db.Model(postMetric).Where("post_id=?", metric.PostId) + db.First(postMetric) + postMetric.RankScore = metric.RankScore(postMetric.MotivationFactor) + err = db.Save(postMetric).Error + return + }) +} + +func (s *tweetMetricSrvA) AddTweetMetric(postId int64) (err error) { + _, err = (&dbr.PostMetric{PostId: postId}).Create(s.db) + return +} + +func (s *tweetMetricSrvA) DeleteTweetMetric(postId int64) (err error) { + return (&dbr.PostMetric{PostId: postId}).Delete(s.db) +} + +func (s *commentMetricSrvA) UpdateCommentMetric(metric *cs.CommentMetric) error { + return s.db.Transaction(func(tx *gorm.DB) (err error) { + commentMetric := &dbr.CommentMetric{CommentId: metric.CommentId} + db := s.db.Model(commentMetric).Where("comment_id=?", metric.CommentId) + db.First(commentMetric) + commentMetric.RankScore = metric.RankScore(commentMetric.MotivationFactor) + err = db.Save(commentMetric).Error + return + }) +} + +func (s *commentMetricSrvA) AddCommentMetric(commentId int64) (err error) { + _, err = (&dbr.CommentMetric{CommentId: commentId}).Create(s.db) + return +} + +func (s *commentMetricSrvA) DeleteCommentMetric(commentId int64) (err error) { + return (&dbr.CommentMetric{CommentId: commentId}).Delete(s.db) +} + +func (s *userMetricSrvA) UpdateUserMetric(userId int64, action uint8) (err error) { + metric := &dbr.UserMetric{} + db := s.db.Model(metric) + if err = db.Where("user_id=?", userId).First(metric).Error; err != nil { + metric = &dbr.UserMetric{ + UserId: userId, + } + } + metric.LatestTrendsOn = time.Now().Unix() + switch action { + case cs.MetricActionCreateTweet: + metric.TweetsCount++ + case cs.MetricActionDeleteTweet: + if metric.TweetsCount > 0 { + metric.TweetsCount-- + } + } + return db.Save(metric).Error +} + +func (s *userMetricSrvA) AddUserMetric(userId int64) (err error) { + _, err = (&dbr.UserMetric{UserId: userId}).Create(s.db) + return +} + +func (s *userMetricSrvA) DeleteUserMetric(userId int64) (err error) { + return (&dbr.UserMetric{UserId: userId}).Delete(s.db) +} + +func newTweetMetricServentA(db *gorm.DB) core.TweetMetricServantA { + return &tweetMetricSrvA{ + db: db, + } +} + +func newCommentMetricServentA(db *gorm.DB) core.CommentMetricServantA { + return &commentMetricSrvA{ + db: db, + } +} + +func newUserMetricServentA(db *gorm.DB) core.UserMetricServantA { + return &userMetricSrvA{ + db: db, + } +} diff --git a/internal/dao/jinzhu/trends.go b/internal/dao/jinzhu/trends.go new file mode 100644 index 00000000..2d4aff4b --- /dev/null +++ b/internal/dao/jinzhu/trends.go @@ -0,0 +1,40 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package jinzhu + +import ( + "fmt" + + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" + "gorm.io/gorm" +) + +type trendsSrvA struct { + db *gorm.DB +} + +func (s *trendsSrvA) GetIndexTrends(userId int64, limit int, offset int) (res []*cs.TrendsItem, total int64, err error) { + db := s.db.Table(_user_). + Joins(fmt.Sprintf("JOIN %s r ON r.he_uid=%s.id", _userRelation_, _user_)). + Joins(fmt.Sprintf("JOIN %s m ON r.he_uid=m.user_id", _userMetric_)). + Where("r.user_id=? AND m.tweets_count>0 AND m.is_del=0", userId) + if err = db.Count(&total).Error; err != nil || total == 0 { + return + } + if offset >= 0 && limit > 0 { + db = db.Limit(limit).Offset(offset) + } + if err = db.Order("r.style ASC, m.latest_trends_on DESC").Select("username", "nickname", "avatar").Find(&res).Error; err == nil { + res = cs.DistinctTrends(res) + } + return +} + +func newTrendsManageServentA(db *gorm.DB) core.TrendsManageServantA { + return &trendsSrvA{ + db: db, + } +} diff --git a/internal/dao/jinzhu/tweets.go b/internal/dao/jinzhu/tweets.go index d7bead87..8139b1c1 100644 --- a/internal/dao/jinzhu/tweets.go +++ b/internal/dao/jinzhu/tweets.go @@ -5,6 +5,7 @@ package jinzhu import ( + "fmt" "strings" "time" @@ -214,7 +215,6 @@ func (s *tweetManageSrv) CreatePost(post *ms.Post) (*ms.Post, error) { func (s *tweetManageSrv) DeletePost(post *ms.Post) ([]string, error) { var mediaContents []string - postId := post.ID postContent := &dbr.PostContent{} err := s.db.Transaction( @@ -326,15 +326,15 @@ func (s *tweetManageSrv) HighlightPost(userId int64, postId int64) (res int, err return post.IsEssence, nil } -func (s *tweetManageSrv) VisiblePost(post *ms.Post, visibility core.PostVisibleT) (err error) { +func (s *tweetManageSrv) VisiblePost(post *ms.Post, visibility cs.TweetVisibleType) (err error) { oldVisibility := post.Visibility - post.Visibility = visibility + post.Visibility = ms.PostVisibleT(visibility) // TODO: 这个判断是否可以不要呢 - if oldVisibility == visibility { + if oldVisibility == ms.PostVisibleT(visibility) { return nil } // 私密推文 特殊处理 - if visibility == dbr.PostVisitPrivate { + if visibility == cs.TweetVisitPrivate { // 强制取消置顶 // TODO: 置顶推文用户是否有权设置成私密? 后续完善 post.IsTop = 0 @@ -350,7 +350,7 @@ func (s *tweetManageSrv) VisiblePost(post *ms.Post, visibility core.PostVisibleT if oldVisibility == dbr.PostVisitPrivate { // 从私密转为非私密才需要重新创建tag createTags(tx, post.UserID, tags) - } else if visibility == dbr.PostVisitPrivate { + } else if visibility == cs.TweetVisitPrivate { // 从非私密转为私密才需要删除tag deleteTags(tx, tags) } @@ -392,6 +392,131 @@ func (s *tweetSrv) GetPosts(conditions ms.ConditionsT, offset, limit int) ([]*ms return (&dbr.Post{}).List(s.db, conditions, offset, limit) } +func (s *tweetSrv) ListUserTweets(userId int64, style uint8, justEssence bool, limit, offset int) (res []*ms.Post, total int64, err error) { + db := s.db.Model(&dbr.Post{}).Where("user_id = ?", userId) + switch style { + case cs.StyleUserTweetsAdmin: + fallthrough + case cs.StyleUserTweetsSelf: + db = db.Where("visibility >= ?", cs.TweetVisitPrivate) + case cs.StyleUserTweetsFriend: + db = db.Where("visibility >= ?", cs.TweetVisitFriend) + case cs.StyleUserTweetsFollowing: + db = db.Where("visibility >= ?", cs.TweetVisitFollowing) + case cs.StyleUserTweetsGuest: + fallthrough + default: + db = db.Where("visibility >= ?", cs.TweetVisitPublic) + } + if justEssence { + db = db.Where("is_essence=1") + } + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + if err = db.Order("is_top DESC, latest_replied_on DESC").Find(&res).Error; err != nil { + return + } + return +} + +func (s *tweetSrv) ListIndexNewestTweets(limit, offset int) (res []*ms.Post, total int64, err error) { + db := s.db.Table(_post_).Where("visibility >= ?", cs.TweetVisitPublic) + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + if err = db.Order("is_top DESC, latest_replied_on DESC").Find(&res).Error; err != nil { + return + } + return +} + +func (s *tweetSrv) ListIndexHotsTweets(limit, offset int) (res []*ms.Post, total int64, err error) { + db := s.db.Table(_post_).Joins(fmt.Sprintf("LEFT JOIN %s metric ON %s.id=metric.post_id", _post_metric_, _post_)).Where(fmt.Sprintf("visibility >= ? AND %s.is_del=0 AND metric.is_del=0", _post_), cs.TweetVisitPublic) + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + if err = db.Order("is_top DESC, metric.rank_score DESC, latest_replied_on DESC").Find(&res).Error; err != nil { + return + } + return +} + +func (s *tweetSrv) ListSyncSearchTweets(limit, offset int) (res []*ms.Post, total int64, err error) { + db := s.db.Table(_post_).Where("visibility >= ?", cs.TweetVisitFriend) + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + if err = db.Find(&res).Error; err != nil { + return + } + return +} + +func (s *tweetSrv) ListFollowingTweets(userId int64, limit, offset int) (res []*ms.Post, total int64, err error) { + beFriendIds, beFollowIds, xerr := s.getUserRelation(userId) + if xerr != nil { + return nil, 0, xerr + } + beFriendCount, beFollowCount := len(beFriendIds), len(beFollowIds) + db := s.db.Model(&dbr.Post{}) + //可见性: 0私密 10充电可见 20订阅可见 30保留 40保留 50好友可见 60关注可见 70保留 80保留 90公开', + switch { + case beFriendCount > 0 && beFollowCount > 0: + db = db.Where("user_id=? OR (visibility>=50 AND user_id IN(?)) OR (visibility>=60 AND user_id IN(?))", userId, beFriendIds, beFollowIds) + case beFriendCount > 0 && beFollowCount == 0: + db = db.Where("user_id=? OR (visibility>=50 AND user_id IN(?))", userId, beFriendIds) + case beFriendCount == 0 && beFollowCount > 0: + db = db.Where("user_id=? OR (visibility>=60 AND user_id IN(?))", userId, beFollowIds) + case beFriendCount == 0 && beFollowCount == 0: + db = db.Where("user_id = ?", userId) + } + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + if err = db.Order("is_top DESC, latest_replied_on DESC").Find(&res).Error; err != nil { + return + } + return +} + +func (s *tweetSrv) getUserRelation(userId int64) (beFriendIds []int64, beFollowIds []int64, err error) { + if err = s.db.Table(_contact_).Where("friend_id=? AND status=2 AND is_del=0", userId).Select("user_id").Find(&beFriendIds).Error; err != nil { + return + } + if err = s.db.Table(_following_).Where("user_id=? AND is_del=0", userId).Select("follow_id").Find(&beFollowIds).Error; err != nil { + return + } + // 即是好友又是关注者,保留好友去除关注者 + for _, id := range beFriendIds { + for i := 0; i < len(beFollowIds); i++ { + // 找到item即删,数据库已经保证唯一性 + if beFollowIds[i] == id { + lastIdx := len(beFollowIds) - 1 + beFollowIds[i] = beFollowIds[lastIdx] + beFollowIds = beFollowIds[:lastIdx] + break + } + } + } + return +} + func (s *tweetSrv) GetPostCount(conditions ms.ConditionsT) (int64, error) { return (&dbr.Post{}).Count(s.db, conditions) } diff --git a/internal/dao/jinzhu/user.go b/internal/dao/jinzhu/user.go index 6bc18de3..6f5c2c71 100644 --- a/internal/dao/jinzhu/user.go +++ b/internal/dao/jinzhu/user.go @@ -5,9 +5,11 @@ package jinzhu import ( + "fmt" "strings" "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/core/ms" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" "gorm.io/gorm" @@ -18,11 +20,41 @@ var ( ) type userManageSrv struct { + db *gorm.DB + ums core.UserMetricServantA + + _userProfileJoins string + _userProfileWhere string + _userProfileColoumns []string +} + +type userRelationSrv struct { db *gorm.DB } -func newUserManageService(db *gorm.DB) core.UserManageService { +func newUserManageService(db *gorm.DB, ums core.UserMetricServantA) core.UserManageService { return &userManageSrv{ + db: db, + ums: ums, + _userProfileJoins: fmt.Sprintf("LEFT JOIN %s m ON %s.id=m.user_id", _userMetric_, _user_), + _userProfileWhere: fmt.Sprintf("%s.username=? AND %s.is_del=0", _user_, _user_), + _userProfileColoumns: []string{ + fmt.Sprintf("%s.id", _user_), + fmt.Sprintf("%s.username", _user_), + fmt.Sprintf("%s.nickname", _user_), + fmt.Sprintf("%s.phone", _user_), + fmt.Sprintf("%s.status", _user_), + fmt.Sprintf("%s.avatar", _user_), + fmt.Sprintf("%s.balance", _user_), + fmt.Sprintf("%s.is_admin", _user_), + fmt.Sprintf("%s.created_on", _user_), + "m.tweets_count", + }, + } +} + +func newUserRelationService(db *gorm.DB) core.UserRelationService { + return &userRelationSrv{ db: db, } } @@ -43,6 +75,14 @@ func (s *userManageSrv) GetUserByUsername(username string) (*ms.User, error) { return user.Get(s.db) } +func (s *userManageSrv) UserProfileByName(username string) (res *cs.UserProfile, err error) { + err = s.db.Table(_user_).Joins(s._userProfileJoins). + Where(s._userProfileWhere, username). + Select(s._userProfileColoumns). + First(&res).Error + return +} + func (s *userManageSrv) GetUserByPhone(phone string) (*ms.User, error) { user := &dbr.User{ Phone: phone, @@ -71,10 +111,73 @@ func (s *userManageSrv) GetUsersByKeyword(keyword string) ([]*ms.User, error) { } } -func (s *userManageSrv) CreateUser(user *dbr.User) (*ms.User, error) { - return user.Create(s.db) +func (s *userManageSrv) CreateUser(user *dbr.User) (res *ms.User, err error) { + if res, err = user.Create(s.db); err == nil { + // 宽松处理错误 + s.ums.AddUserMetric(res.ID) + } + return } func (s *userManageSrv) UpdateUser(user *ms.User) error { return user.Update(s.db) } + +func (s *userManageSrv) GetRegisterUserCount() (res int64, err error) { + err = s.db.Model(&dbr.User{}).Count(&res).Error + return +} + +func (s *userRelationSrv) MyFriendIds(userId int64) (res []int64, err error) { + err = s.db.Table(_contact_).Where("user_id=? AND status=2 AND is_del=0", userId).Select("friend_id").Find(&res).Error + return +} + +func (s *userRelationSrv) MyFollowIds(userId int64) (res []int64, err error) { + err = s.db.Table(_following_).Where("user_id=? AND is_del=0", userId).Select("follow_id").Find(&res).Error + return +} + +func (s *userRelationSrv) IsMyFriend(userId int64, friendIds ...int64) (map[int64]bool, error) { + size := len(friendIds) + res := make(map[int64]bool, size) + if size == 0 { + return res, nil + } + myFriendIds, err := s.MyFriendIds(userId) + if err != nil { + return nil, err + } + for _, friendId := range friendIds { + res[friendId] = false + for _, myFriendId := range myFriendIds { + if friendId == myFriendId { + res[friendId] = true + break + } + } + } + return res, nil +} + +func (s *userRelationSrv) IsMyFollow(userId int64, followIds ...int64) (map[int64]bool, error) { + size := len(followIds) + res := make(map[int64]bool, size) + if size == 0 { + return res, nil + } + myFollowIds, err := s.MyFollowIds(userId) + if err != nil { + return nil, err + } + for _, followId := range followIds { + res[followId] = false + for _, myFollowId := range myFollowIds { + if followId == myFollowId { + res[followId] = true + break + } + } + } + return res, nil +} diff --git a/internal/dao/search/bridge.go b/internal/dao/search/bridge.go index bc4eb039..6c9ed164 100644 --- a/internal/dao/search/bridge.go +++ b/internal/dao/search/bridge.go @@ -65,7 +65,7 @@ func (s *bridgeTweetSearchServant) updateDocs(doc *documents) { // watch updateDocsTempch to continue handle update if needed. // cancel loop if no item had watched in 1 minute. - for count := 0; count > 60; count++ { + for count := 0; count < 60; count++ { select { case item := <-s.updateDocsTempCh: // reset count to continue handle docs update diff --git a/internal/dao/security/security.go b/internal/dao/security/security.go index 75cd003b..1556ad73 100644 --- a/internal/dao/security/security.go +++ b/internal/dao/security/security.go @@ -3,7 +3,7 @@ package security import ( "strings" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/rocboss/paopao-ce/internal/core" ) diff --git a/internal/dao/storage/storage.go b/internal/dao/storage/storage.go index 0f03fa20..c7b57947 100644 --- a/internal/dao/storage/storage.go +++ b/internal/dao/storage/storage.go @@ -12,7 +12,7 @@ import ( "strconv" "time" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" "github.com/minio/minio-go/v7" diff --git a/internal/events/events.go b/internal/events/events.go new file mode 100644 index 00000000..69a976ea --- /dev/null +++ b/internal/events/events.go @@ -0,0 +1,112 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package events + +import ( + "sync" + + "github.com/alimy/tryst/cfg" + "github.com/alimy/tryst/pool" + "github.com/robfig/cron/v3" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/sirupsen/logrus" +) + +var ( + _defaultEventManager EventManager + _defaultJobManager JobManager = emptyJobManager{} + _onceInitial sync.Once +) + +func StartEventManager() { + _defaultEventManager.Start() +} + +func StopEventManager() { + _defaultEventManager.Stop() +} + +// OnEvent push event to gorotine pool then handled automatic. +func OnEvent(event Event) { + _defaultEventManager.OnEvent(event) +} + +func StartJobManager() { + _defaultJobManager.Start() +} + +func StopJobManager() { + _defaultJobManager.Stop() +} + +// NewJob create new Job instance +func NewJob(s cron.Schedule, fn JobFn) Job { + return &simpleJob{ + Schedule: s, + Job: fn, + } +} + +// RemoveJob an entry from being run in the future. +func RemoveJob(id EntryID) { + _defaultJobManager.Remove(id) +} + +// Schedule adds a Job to the Cron to be run on the given schedule. +// The job is wrapped with the configured Chain. +func Schedule(job Job) EntryID { + return _defaultJobManager.Schedule(job) +} + +// OnTask adds a Job to the Cron to be run on the given schedule. +// The job is wrapped with the configured Chain. +func OnTask(s cron.Schedule, fn JobFn) EntryID { + job := &simpleJob{ + Schedule: s, + Job: fn, + } + return _defaultJobManager.Schedule(job) +} + +func Initial() { + _onceInitial.Do(func() { + initEventManager() + cfg.Not("DisableJobManager", func() { + initJobManager() + logrus.Debugln("initial JobManager") + }) + }) +} + +func initJobManager() { + _defaultJobManager = NewJobManager() + StartJobManager() +} + +func initEventManager() { + var opts []pool.Option + s := conf.EventManagerSetting + if s.MinWorker > 5 { + opts = append(opts, pool.MinWorkerOpt(s.MinWorker)) + } else { + opts = append(opts, pool.MinWorkerOpt(5)) + } + if s.MaxEventBuf > 10 { + opts = append(opts, pool.MaxRequestBufOpt(s.MaxEventBuf)) + } else { + opts = append(opts, pool.MaxRequestBufOpt(10)) + } + if s.MaxTempEventBuf > 10 { + opts = append(opts, pool.MaxRequestTempBufOpt(s.MaxTempEventBuf)) + } else { + opts = append(opts, pool.MaxRequestTempBufOpt(10)) + } + opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime)) + _defaultEventManager = NewEventManager(func(req Event, err error) { + if err != nil { + logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err) + } + }, opts...) +} diff --git a/internal/events/events_tryst.go b/internal/events/events_tryst.go new file mode 100644 index 00000000..0aaac020 --- /dev/null +++ b/internal/events/events_tryst.go @@ -0,0 +1,40 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package events + +import ( + "github.com/alimy/tryst/event" + "github.com/alimy/tryst/pool" +) + +type Event = event.Event + +type EventManager interface { + Start() + Stop() + OnEvent(event Event) +} + +type simpleEventManager struct { + em event.EventManager +} + +func (s *simpleEventManager) Start() { + s.em.Start() +} + +func (s *simpleEventManager) Stop() { + s.em.Stop() +} + +func (s *simpleEventManager) OnEvent(event Event) { + s.em.OnEvent(event) +} + +func NewEventManager(fn pool.RespFn[Event], opts ...pool.Option) EventManager { + return &simpleEventManager{ + em: event.NewEventManager(fn, opts...), + } +} diff --git a/internal/events/jobs.go b/internal/events/jobs.go new file mode 100644 index 00000000..40dc30eb --- /dev/null +++ b/internal/events/jobs.go @@ -0,0 +1,87 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package events + +import ( + "github.com/robfig/cron/v3" + "github.com/rocboss/paopao-ce/pkg/types" +) + +type ( + EntryID = cron.EntryID +) + +// JobFn job help function that implement cron.Job interface +type JobFn func() + +func (fn JobFn) Run() { + fn() +} + +// Job job interface +type Job interface { + cron.Schedule + cron.Job +} + +type simpleJob struct { + cron.Schedule + cron.Job +} + +// JobManager job manger interface +type JobManager interface { + Start() + Stop() + Remove(id EntryID) + Schedule(Job) EntryID +} + +type emptyJobManager types.Empty + +type simpleJobManager struct { + m *cron.Cron +} + +func (emptyJobManager) Start() { + // nothing +} + +func (emptyJobManager) Stop() { + // nothing +} + +func (emptyJobManager) Remove(id EntryID) { + // nothing +} + +func (emptyJobManager) Schedule(job Job) EntryID { + return 0 +} + +func (j *simpleJobManager) Start() { + j.m.Start() +} + +func (j *simpleJobManager) Stop() { + j.m.Stop() +} + +// Remove an entry from being run in the future. +func (j *simpleJobManager) Remove(id EntryID) { + j.m.Remove(id) +} + +// Schedule adds a Job to the Cron to be run on the given schedule. +// The job is wrapped with the configured Chain. +func (j *simpleJobManager) Schedule(job Job) EntryID { + return j.m.Schedule(job, job) +} + +func NewJobManager() JobManager { + return &simpleJobManager{ + m: cron.New(), + } +} diff --git a/internal/internal.go b/internal/internal.go index 926c9f75..20780a10 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -5,10 +5,16 @@ package internal import ( + "github.com/rocboss/paopao-ce/internal/events" + "github.com/rocboss/paopao-ce/internal/metrics" "github.com/rocboss/paopao-ce/internal/migration" ) func Initial() { // migrate database if needed migration.Run() + // event manager system initialize + events.Initial() + // metric manager system initialize + metrics.Initial() } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 00000000..ffe7d12a --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,74 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package metrics + +import ( + "sync" + + "github.com/alimy/tryst/event" + "github.com/alimy/tryst/pool" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/sirupsen/logrus" +) + +var ( + _defaultMetricManager event.EventManager + _onceInitial sync.Once +) + +type Metric = event.Event + +type BaseMetric = event.UnimplementedEvent + +type MetricManager interface { + Start() + Stop() + OnMeasure(metric Metric) +} + +func StartMetricManager() { + _defaultMetricManager.Start() +} + +func StopMetricManager() { + _defaultMetricManager.Stop() +} + +// OnMeasure push Metric to gorotine pool then handled automatic. +func OnMeasure(metric Metric) { + _defaultMetricManager.OnEvent(metric) +} + +func Initial() { + _onceInitial.Do(func() { + initMetricManager() + }) +} + +func initMetricManager() { + var opts []pool.Option + s := conf.EventManagerSetting + if s.MinWorker > 5 { + opts = append(opts, pool.MinWorkerOpt(s.MinWorker)) + } else { + opts = append(opts, pool.MinWorkerOpt(5)) + } + if s.MaxEventBuf > 10 { + opts = append(opts, pool.MaxRequestBufOpt(s.MaxEventBuf)) + } else { + opts = append(opts, pool.MaxRequestBufOpt(10)) + } + if s.MaxTempEventBuf > 10 { + opts = append(opts, pool.MaxRequestTempBufOpt(s.MaxTempEventBuf)) + } else { + opts = append(opts, pool.MaxRequestTempBufOpt(10)) + } + opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime)) + _defaultMetricManager = event.NewEventManager(func(req Metric, err error) { + if err != nil { + logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err) + } + }, opts...) +} diff --git a/internal/metrics/metrics_tryst.go b/internal/metrics/metrics_tryst.go new file mode 100644 index 00000000..ea0eb28d --- /dev/null +++ b/internal/metrics/metrics_tryst.go @@ -0,0 +1,32 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package metrics + +import ( + "github.com/alimy/tryst/event" + "github.com/alimy/tryst/pool" +) + +type simpleMetricManager struct { + mm event.EventManager +} + +func (s *simpleMetricManager) Start() { + s.mm.Start() +} + +func (s *simpleMetricManager) Stop() { + s.mm.Stop() +} + +func (s *simpleMetricManager) OnMeasure(metric Metric) { + s.mm.OnEvent(metric) +} + +func NewMetricManager(fn pool.RespFn[Metric], opts ...pool.Option) MetricManager { + return &simpleMetricManager{ + mm: event.NewEventManager(fn, opts...), + } +} diff --git a/internal/metrics/prometheus/metrics.go b/internal/metrics/prometheus/metrics.go new file mode 100644 index 00000000..d7c073b1 --- /dev/null +++ b/internal/metrics/prometheus/metrics.go @@ -0,0 +1,57 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package prometheus + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/sirupsen/logrus" +) + +type metrics struct { + siteInfo *prometheus.GaugeVec + ds core.DataService + wc core.WebCache +} + +func (m *metrics) updateSiteInfo() { + if onlineUserKeys, err := m.wc.Keys(conf.PrefixOnlineUser + "*"); err == nil { + maxOnline := len(onlineUserKeys) + m.siteInfo.With(prometheus.Labels{"name": "max_online"}).Set(float64(maxOnline)) + } else { + logrus.Warnf("update promethues metrics[site_info_max_online] occurs error: %s", err) + } + if registerUserCount, err := m.ds.GetRegisterUserCount(); err == nil { + m.siteInfo.With(prometheus.Labels{"name": "register_user_count"}).Set(float64(registerUserCount)) + } else { + logrus.Warnf("update promethues metrics[site_info_register_user_count] occurs error: %s", err) + } +} + +func (m *metrics) onUpdate() { + logrus.Debugf("update promethues metrics job running") + m.updateSiteInfo() +} + +func newMetrics(reg prometheus.Registerer, ds core.DataService, wc core.WebCache) *metrics { + m := &metrics{ + siteInfo: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "paopao", + Subsystem: "site", + Name: "simple_info", + Help: "paopao-ce site simple information.", + }, + []string{ + // metric name + "name", + }), + ds: ds, + wc: wc, + } + reg.MustRegister(m.siteInfo) + return m +} diff --git a/internal/metrics/prometheus/prometheus.go b/internal/metrics/prometheus/prometheus.go new file mode 100644 index 00000000..f9764464 --- /dev/null +++ b/internal/metrics/prometheus/prometheus.go @@ -0,0 +1,41 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package prometheus + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/robfig/cron/v3" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/sirupsen/logrus" +) + +func scheduleJobs(metrics *metrics) { + spec := conf.JobManagerSetting.UpdateMetricsInterval + schedule, err := cron.ParseStandard(spec) + if err != nil { + panic(err) + } + events.OnTask(schedule, metrics.onUpdate) + logrus.Debug("shedule prometheus metrics update jobs complete") +} + +func NewHandler(ds core.DataService, wc core.WebCache) http.Handler { + // Create non-global registry. + registry := prometheus.NewRegistry() + // Add go runtime metrics and process collectors. + registry.MustRegister( + collectors.NewGoCollector(), + collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), + ) + metrics := newMetrics(registry, ds, wc) + scheduleJobs(metrics) + return promhttp.HandlerFor(registry, promhttp.HandlerOpts{EnableOpenMetrics: true}) +} diff --git a/internal/migration/migration.go b/internal/migration/migration.go index 1cac6012..5f3c667d 100644 --- a/internal/migration/migration.go +++ b/internal/migration/migration.go @@ -8,7 +8,7 @@ package migration import ( - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/sirupsen/logrus" ) diff --git a/internal/migration/migration_embed.go b/internal/migration/migration_embed.go index c2232d29..da3fbab9 100644 --- a/internal/migration/migration_embed.go +++ b/internal/migration/migration_embed.go @@ -10,7 +10,7 @@ package migration import ( "database/sql" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database" "github.com/golang-migrate/migrate/v4/database/mysql" diff --git a/internal/model/joint/joint.go b/internal/model/joint/joint.go index fa7e4729..79246ece 100644 --- a/internal/model/joint/joint.go +++ b/internal/model/joint/joint.go @@ -14,3 +14,9 @@ type BasePageInfo struct { func (r *BasePageInfo) SetPageInfo(page int, pageSize int) { r.Page, r.PageSize = page, pageSize } + +type JsonResp struct { + Code int `json:"code"` + Msg string `json:"msg,omitempty"` + Data any `json:"data,omitempty"` +} diff --git a/internal/model/joint/json.go b/internal/model/joint/json.go new file mode 100644 index 00000000..0b4da5c6 --- /dev/null +++ b/internal/model/joint/json.go @@ -0,0 +1,34 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package joint + +import ( + stdJson "encoding/json" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/pkg/json" +) + +type CachePageResp struct { + Data *PageResp + JsonResp stdJson.RawMessage +} + +func (r *CachePageResp) Render(c *gin.Context) { + if len(r.JsonResp) != 0 { + c.JSON(http.StatusOK, r.JsonResp) + } else { + c.JSON(http.StatusOK, &JsonResp{ + Code: 0, + Msg: "success", + Data: r.Data, + }) + } +} + +func RespMarshal(data any) (stdJson.RawMessage, error) { + return json.Marshal(data) +} diff --git a/internal/model/joint/page.go b/internal/model/joint/page.go new file mode 100644 index 00000000..f2ba5d9c --- /dev/null +++ b/internal/model/joint/page.go @@ -0,0 +1,27 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package joint + +type Pager struct { + Page int `json:"page"` + PageSize int `json:"page_size"` + TotalRows int64 `json:"total_rows"` +} + +type PageResp struct { + List any `json:"list"` + Pager Pager `json:"pager"` +} + +func PageRespFrom(list any, page int, pageSize int, totalRows int64) *PageResp { + return &PageResp{ + List: list, + Pager: Pager{ + Page: page, + PageSize: pageSize, + TotalRows: totalRows, + }, + } +} diff --git a/internal/model/web/admin.go b/internal/model/web/admin.go index 6b1980ca..53f8af67 100644 --- a/internal/model/web/admin.go +++ b/internal/model/web/admin.go @@ -9,3 +9,14 @@ type ChangeUserStatusReq struct { ID int64 `json:"id" form:"id" binding:"required"` Status int `json:"status" form:"status" binding:"required,oneof=1 2"` } + +type SiteInfoReq struct { + SimpleInfo `json:"-" binding:"-"` +} + +type SiteInfoResp struct { + RegisterUserCount int64 `json:"register_user_count"` + OnlineUserCount int `json:"online_user_count"` + HistoryMaxOnline int `json:"history_max_online"` + ServerUpTime int64 `json:"server_up_time"` +} diff --git a/internal/model/web/audit.go b/internal/model/web/audit.go new file mode 100644 index 00000000..848ffbf6 --- /dev/null +++ b/internal/model/web/audit.go @@ -0,0 +1,40 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +const ( + AuditStyleUnknown AuditStyle = iota + AuditStyleUserTweet + AuditStyleUserTweetComment + AuditStyleUserTweetReply +) + +const ( + AuditHookCtxKey = "audit_ctx_key" + OnlineUserCtxKey = "online_user_ctx_key" +) + +type AuditStyle uint8 + +type AuditMetaInfo struct { + Style AuditStyle + Id int64 +} + +func (s AuditStyle) String() (res string) { + switch s { + case AuditStyleUserTweet: + res = "UserTweet" + case AuditStyleUserTweetComment: + res = "UserTweetComment" + case AuditStyleUserTweetReply: + res = "UserTweetReply" + case AuditStyleUnknown: + fallthrough + default: + res = "Unknown" + } + return +} diff --git a/internal/model/web/core.go b/internal/model/web/core.go index 78c04bb3..67a43b6a 100644 --- a/internal/model/web/core.go +++ b/internal/model/web/core.go @@ -7,11 +7,15 @@ package web import ( "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/model/joint" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/pkg/convert" "github.com/rocboss/paopao-ce/pkg/xerror" ) +type MessageStyle = cs.MessageStyle + type ChangeAvatarReq struct { BaseInfo `json:"-" binding:"-"` Avatar string `json:"avatar" form:"avatar" binding:"required"` @@ -27,35 +31,39 @@ type UserInfoReq struct { } type UserInfoResp struct { - Id int64 `json:"id"` - Nickname string `json:"nickname"` - Username string `json:"username"` - Status int `json:"status"` - Avatar string `json:"avatar"` - Balance int64 `json:"balance"` - Phone string `json:"phone"` - IsAdmin bool `json:"is_admin"` - CreatedOn int64 `json:"created_on"` - Follows int64 `json:"follows"` - Followings int64 `json:"followings"` -} - -type GetUnreadMsgCountReq struct { + Id int64 `json:"id"` + Nickname string `json:"nickname"` + Username string `json:"username"` + Status int `json:"status"` + Avatar string `json:"avatar"` + Balance int64 `json:"balance"` + Phone string `json:"phone"` + IsAdmin bool `json:"is_admin"` + CreatedOn int64 `json:"created_on"` + Follows int64 `json:"follows"` + Followings int64 `json:"followings"` + TweetsCount int `json:"tweets_count"` +} + +type GetMessagesReq struct { SimpleInfo `json:"-" binding:"-"` + joint.BasePageInfo + Style MessageStyle `form:"style" binding:"required"` } -type GetUnreadMsgCountResp struct { - Count int64 `json:"count"` +type GetMessagesResp struct { + joint.CachePageResp } -type GetMessagesReq BasePageReq - -type GetMessagesResp base.PageResp - type ReadMessageReq struct { SimpleInfo `json:"-" binding:"-"` ID int64 `json:"id" binding:"required"` } + +type ReadAllMessageReq struct { + SimpleInfo `json:"-" binding:"-"` +} + type SendWhisperReq struct { SimpleInfo `json:"-" binding:"-"` UserID int64 `json:"user_id" binding:"required"` @@ -128,10 +136,6 @@ func (r *UserInfoReq) Bind(c *gin.Context) mir.Error { return nil } -func (r *GetMessagesReq) Bind(c *gin.Context) mir.Error { - return (*BasePageReq)(r).Bind(c) -} - func (r *GetCollectionsReq) Bind(c *gin.Context) mir.Error { return (*BasePageReq)(r).Bind(c) } diff --git a/internal/model/web/friendship.go b/internal/model/web/friendship.go index 08f5fd27..144f0870 100644 --- a/internal/model/web/friendship.go +++ b/internal/model/web/friendship.go @@ -4,7 +4,9 @@ package web -import "github.com/rocboss/paopao-ce/internal/servants/base" +import ( + "github.com/rocboss/paopao-ce/internal/servants/base" +) type RequestingFriendReq struct { BaseInfo `json:"-" binding:"-"` diff --git a/internal/model/web/loose.go b/internal/model/web/loose.go index 8ab19584..edeec03b 100644 --- a/internal/model/web/loose.go +++ b/internal/model/web/loose.go @@ -7,8 +7,11 @@ package web import ( "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/model/joint" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/pkg/app" ) @@ -26,30 +29,41 @@ const ( UserPostsStyleHighlight = "highlight" UserPostsStyleMedia = "media" UserPostsStyleStar = "star" + + StyleTweetsNewest = "newest" + StyleTweetsHots = "hots" + StyleTweetsFollowing = "following" ) type TagType = cs.TagType +type CommentStyleType string + type TweetCommentsReq struct { - SimpleInfo `form:"-" binding:"-"` - TweetId int64 `form:"id" binding:"required"` - SortStrategy string `form:"sort_strategy"` - Page int `form:"-" binding:"-"` - PageSize int `form:"-" binding:"-"` + SimpleInfo `form:"-" binding:"-"` + TweetId int64 `form:"id" binding:"required"` + Style CommentStyleType `form:"style"` + Page int `form:"-" binding:"-"` + PageSize int `form:"-" binding:"-"` } -type TweetCommentsResp base.PageResp +type TweetCommentsResp struct { + joint.CachePageResp +} type TimelineReq struct { BaseInfo `form:"-" binding:"-"` Query string `form:"query"` Visibility []core.PostVisibleT `form:"query"` Type string `form:"type"` + Style string `form:"style"` Page int `form:"-" binding:"-"` PageSize int `form:"-" binding:"-"` } -type TimelineResp base.PageResp +type TimelineResp struct { + joint.CachePageResp +} type GetUserTweetsReq struct { BaseInfo `form:"-" binding:"-"` @@ -59,7 +73,9 @@ type GetUserTweetsReq struct { PageSize int `form:"-" binding:"-"` } -type GetUserTweetsResp base.PageResp +type GetUserTweetsResp struct { + joint.CachePageResp +} type GetUserProfileReq struct { BaseInfo `form:"-" binding:"-"` @@ -78,6 +94,7 @@ type GetUserProfileResp struct { CreatedOn int64 `json:"created_on"` Follows int64 `json:"follows"` Followings int64 `json:"followings"` + TweetsCount int `json:"tweets_count"` } type TopicListReq struct { @@ -94,6 +111,13 @@ type TopicListResp struct { ExtralTopics cs.TagList `json:"extral_topics,omitempty"` } +type TweetDetailReq struct { + SimpleInfo `form:"-" binding:"-"` + TweetId int64 `form:"id"` +} + +type TweetDetailResp ms.PostFormated + func (r *GetUserTweetsReq) SetPageInfo(page int, pageSize int) { r.Page, r.PageSize = page, pageSize } @@ -108,6 +132,34 @@ func (r *TimelineReq) Bind(c *gin.Context) mir.Error { User: user, } r.Page, r.PageSize = app.GetPageInfo(c) - r.Query, r.Type = c.Query("query"), "search" + r.Query, r.Type, r.Style = c.Query("query"), "search", c.Query("style") return nil } + +func (s CommentStyleType) ToInnerValue() (res cs.StyleCommentType) { + switch s { + case "hots": + res = cs.StyleCommentHots + case "newest": + res = cs.StyleCommentNewest + case "default": + fallthrough + default: + res = cs.StyleCommentDefault + } + return +} + +func (s CommentStyleType) String() (res string) { + switch s { + case "default": + res = conf.InfixCommentDefault + case "hots": + res = conf.InfixCommentHots + case "newest": + res = conf.InfixCommentNewest + default: + res = "_" + } + return +} diff --git a/internal/model/web/priv.go b/internal/model/web/priv.go index 7ce1aba0..239799f6 100644 --- a/internal/model/web/priv.go +++ b/internal/model/web/priv.go @@ -7,17 +7,31 @@ package web import ( "fmt" "mime/multipart" + "net/http" "strings" "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/model/joint" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/pkg/convert" "github.com/rocboss/paopao-ce/pkg/xerror" ) +const ( + // 推文可见性 + TweetVisitPublic TweetVisibleType = iota + TweetVisitPrivate + TweetVisitFriend + TweetVisitFollowing + TweetVisitInvalid +) + +type TweetVisibleType cs.TweetVisibleType + type TweetCommentThumbsReq struct { SimpleInfo `json:"-" binding:"-"` TweetId int64 `json:"tweet_id" binding:"required"` @@ -43,7 +57,7 @@ type CreateTweetReq struct { Tags []string `json:"tags" binding:"required"` Users []string `json:"users" binding:"required"` AttachmentPrice int64 `json:"attachment_price"` - Visibility core.PostVisibleT `json:"visibility"` + Visibility TweetVisibleType `json:"visibility"` ClientIP string `json:"-" binding:"-"` } @@ -101,12 +115,12 @@ type HighlightTweetResp struct { type VisibleTweetReq struct { BaseInfo `json:"-" binding:"-"` - ID int64 `json:"id"` - Visibility core.PostVisibleT `json:"visibility"` + ID int64 `json:"id"` + Visibility TweetVisibleType `json:"visibility"` } type VisibleTweetResp struct { - Visibility core.PostVisibleT `json:"visibility"` + Visibility TweetVisibleType `json:"visibility"` } type CreateCommentReq struct { @@ -133,6 +147,16 @@ type DeleteCommentReq struct { BaseInfo `json:"-" binding:"-"` ID int64 `json:"id" binding:"required"` } + +type HighlightCommentReq struct { + SimpleInfo `json:"-" binding:"-"` + CommentId int64 `json:"id" binding:"required"` +} + +type HighlightCommentResp struct { + HighlightStatus int8 `json:"highlight_status"` +} + type DeleteCommentReplyReq struct { BaseInfo `json:"-" binding:"-"` ID int64 `json:"id" binding:"required"` @@ -281,3 +305,35 @@ func (r *CreateCommentReq) Bind(c *gin.Context) mir.Error { r.ClientIP = c.ClientIP() return bindAny(c, r) } + +func (r *CreateTweetResp) Render(c *gin.Context) { + c.JSON(http.StatusOK, &joint.JsonResp{ + Code: 0, + Msg: "success", + Data: r, + }) + // 设置审核元信息,用于接下来的审核逻辑 + c.Set(AuditHookCtxKey, &AuditMetaInfo{ + Style: AuditStyleUserTweet, + Id: r.ID, + }) +} + +func (t TweetVisibleType) ToVisibleValue() (res cs.TweetVisibleType) { + // 原来的可见性: 0公开 1私密 2好友可见 3关注可见 + // 现在的可见性: 0私密 10充电可见 20订阅可见 30保留 40保留 50好友可见 60关注可见 70保留 80保留 90公开 + switch t { + case TweetVisitPublic: + res = cs.TweetVisitPublic + case TweetVisitPrivate: + res = cs.TweetVisitPrivate + case TweetVisitFriend: + res = cs.TweetVisitFriend + case TweetVisitFollowing: + res = cs.TweetVisitFollowing + default: + // TODO: 默认私密 + res = cs.TweetVisitPrivate + } + return +} diff --git a/internal/model/web/pub.go b/internal/model/web/pub.go index 058288e8..3aa796c6 100644 --- a/internal/model/web/pub.go +++ b/internal/model/web/pub.go @@ -4,17 +4,6 @@ package web -import ( - "github.com/rocboss/paopao-ce/internal/core/ms" - "github.com/rocboss/paopao-ce/pkg/version" -) - -type TweetDetailReq struct { - TweetId int64 `form:"id"` -} - -type TweetDetailResp ms.PostFormated - type GetCaptchaResp struct { Id string `json:"id"` Content string `json:"b64s"` @@ -26,10 +15,6 @@ type SendCaptchaReq struct { ImgCaptchaID string `json:"img_captcha_id" form:"img_captcha_id" binding:"required"` } -type VersionResp struct { - BuildInfo *version.BuildInfo `json:"build_info"` -} - type LoginReq struct { Username string `json:"username" form:"username" binding:"required"` Password string `json:"password" form:"password" binding:"required"` diff --git a/internal/model/web/relax.go b/internal/model/web/relax.go new file mode 100644 index 00000000..e75ae315 --- /dev/null +++ b/internal/model/web/relax.go @@ -0,0 +1,34 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "encoding/json" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/model/joint" +) + +type GetUnreadMsgCountReq struct { + SimpleInfo `json:"-" binding:"-"` +} + +type GetUnreadMsgCountResp struct { + Count int64 `json:"count"` + JsonResp json.RawMessage `json:"-"` +} + +func (r *GetUnreadMsgCountResp) Render(c *gin.Context) { + if len(r.JsonResp) != 0 { + c.JSON(http.StatusOK, r.JsonResp) + } else { + c.JSON(http.StatusOK, &joint.JsonResp{ + Code: 0, + Msg: "success", + Data: r, + }) + } +} diff --git a/internal/model/web/site.go b/internal/model/web/site.go new file mode 100644 index 00000000..9173dc20 --- /dev/null +++ b/internal/model/web/site.go @@ -0,0 +1,16 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/pkg/version" +) + +type VersionResp struct { + BuildInfo *version.BuildInfo `json:"build_info"` +} + +type SiteProfileResp = conf.WebProfileConf diff --git a/internal/model/web/trends.go b/internal/model/web/trends.go new file mode 100644 index 00000000..61dd87df --- /dev/null +++ b/internal/model/web/trends.go @@ -0,0 +1,18 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "github.com/rocboss/paopao-ce/internal/model/joint" +) + +type GetIndexTrendsReq struct { + SimpleInfo `json:"-" binding:"-"` + joint.BasePageInfo +} + +type GetIndexTrendsResp struct { + joint.CachePageResp +} diff --git a/internal/model/web/xerror.go b/internal/model/web/xerror.go index 50c2f479..0d22a9ea 100644 --- a/internal/model/web/xerror.go +++ b/internal/model/web/xerror.go @@ -47,15 +47,18 @@ var ( ErrStickPostFailed = xerror.NewError(30011, "动态置顶失败") ErrVisblePostFailed = xerror.NewError(30012, "更新可见性失败") ErrHighlightPostFailed = xerror.NewError(30013, "动态设为亮点失败") + ErrGetPostsUnknowStyle = xerror.NewError(30014, "使用未知样式参数获取动态列表") + ErrGetPostsNilUser = xerror.NewError(30015, "使用游客账户获取动态详情失败") - ErrGetCommentsFailed = xerror.NewError(40001, "获取评论列表失败") - ErrCreateCommentFailed = xerror.NewError(40002, "评论发布失败") - ErrGetCommentFailed = xerror.NewError(40003, "获取评论详情失败") - ErrDeleteCommentFailed = xerror.NewError(40004, "评论删除失败") - ErrCreateReplyFailed = xerror.NewError(40005, "评论回复失败") - ErrGetReplyFailed = xerror.NewError(40006, "获取评论详情失败") - ErrMaxCommentCount = xerror.NewError(40007, "评论数已达最大限制") - ErrGetCommentThumbs = xerror.NewError(40008, "获取评论点赞信息失败") + ErrGetCommentsFailed = xerror.NewError(40001, "获取评论列表失败") + ErrCreateCommentFailed = xerror.NewError(40002, "评论发布失败") + ErrGetCommentFailed = xerror.NewError(40003, "获取评论详情失败") + ErrDeleteCommentFailed = xerror.NewError(40004, "评论删除失败") + ErrCreateReplyFailed = xerror.NewError(40005, "评论回复失败") + ErrGetReplyFailed = xerror.NewError(40006, "获取评论详情失败") + ErrMaxCommentCount = xerror.NewError(40007, "评论数已达最大限制") + ErrGetCommentThumbs = xerror.NewError(40008, "获取评论点赞信息失败") + ErrHighlightCommentFailed = xerror.NewError(40009, "设置精选评论失败") ErrGetMessagesFailed = xerror.NewError(50001, "获取消息列表失败") ErrReadMessageFailed = xerror.NewError(50002, "标记消息已读失败") @@ -87,6 +90,8 @@ var ( ErrNotAllowFollowSelf = xerror.NewError(80105, "不能关注自己") ErrNotAllowUnfollowSelf = xerror.NewError(80106, "不能取消关注自己") + ErrGetIndexTrendsFailed = xerror.NewError(802001, "获取动态条栏信息失败") + ErrFollowTopicFailed = xerror.NewError(90001, "关注话题失败") ErrUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败") ErrStickTopicFailed = xerror.NewError(90003, "更行话题置顶状态失败") diff --git a/internal/servants/base/base.go b/internal/servants/base/base.go index e94e75af..4edf5e03 100644 --- a/internal/servants/base/base.go +++ b/internal/servants/base/base.go @@ -21,9 +21,11 @@ import ( "github.com/rocboss/paopao-ce/internal/core/ms" "github.com/rocboss/paopao-ce/internal/dao" "github.com/rocboss/paopao-ce/internal/dao/cache" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/rocboss/paopao-ce/internal/model/joint" "github.com/rocboss/paopao-ce/pkg/app" + "github.com/rocboss/paopao-ce/pkg/types" "github.com/rocboss/paopao-ce/pkg/xerror" - "github.com/sirupsen/logrus" ) type BaseServant struct { @@ -39,12 +41,6 @@ type DaoServant struct { Redis core.RedisCache } -type JsonResp struct { - Code int `json:"code"` - Msg string `json:"msg,omitempty"` - Data any `json:"data,omitempty"` -} - type SentryHubSetter interface { SetSentryHub(hub *sentry.Hub) } @@ -145,13 +141,13 @@ func bindAnySentry(c *gin.Context, obj any) mir.Error { func RenderAny(c *gin.Context, data any, err mir.Error) { if err == nil { - c.JSON(http.StatusOK, &JsonResp{ + c.JSON(http.StatusOK, &joint.JsonResp{ Code: 0, Msg: "success", Data: data, }) } else { - c.JSON(xerror.HttpStatusCode(err), &JsonResp{ + c.JSON(xerror.HttpStatusCode(err), &joint.JsonResp{ Code: err.StatusCode(), Msg: err.Error(), }) @@ -164,19 +160,122 @@ func (s *BaseServant) Bind(c *gin.Context, obj any) mir.Error { func (s *BaseServant) Render(c *gin.Context, data any, err mir.Error) { if err == nil { - c.JSON(http.StatusOK, &JsonResp{ + c.JSON(http.StatusOK, &joint.JsonResp{ Code: 0, Msg: "success", Data: data, }) } else { - c.JSON(xerror.HttpStatusCode(err), &JsonResp{ + c.JSON(xerror.HttpStatusCode(err), &joint.JsonResp{ Code: err.StatusCode(), Msg: err.Error(), }) } } +func (s *DaoServant) PrepareUser(userId int64, user *ms.UserFormated) error { + // guest用户的userId<0 + if userId < 0 { + return nil + } + // friendMap, err := s.Ds.IsMyFriend(userId, user.ID) + // if err != nil { + // return err + // } + followMap, err := s.Ds.IsMyFollow(userId, user.ID) + if err != nil { + return err + } + // user.IsFriend, user.IsFollowing = friendMap[user.ID], followMap[user.ID] + user.IsFollowing = followMap[user.ID] + return nil +} + +func (s *DaoServant) PrepareMessages(userId int64, messages []*ms.MessageFormated) error { + // guest用户的userId<0 + if userId < 0 { + return nil + } + userIds := make([]int64, 0, len(messages)) + for _, msg := range messages { + if msg.SenderUser != nil { + userIds = append(userIds, msg.SenderUserID) + } + if msg.ReceiverUser != nil { + userIds = append(userIds, msg.ReceiverUserID) + } + } + // friendMap, err := s.Ds.IsMyFriend(userId, userIds...) + // if err != nil { + // return err + // } + followMap, err := s.Ds.IsMyFollow(userId, userIds...) + if err != nil { + return err + } + for _, msg := range messages { + if msg.SenderUser != nil { + // msg.SenderUser.IsFriend, msg.SenderUser.IsFollowing = friendMap[msg.SenderUserID], followMap[msg.SenderUserID] + msg.SenderUser.IsFollowing = followMap[msg.SenderUserID] + } + if msg.ReceiverUser != nil { + // msg.ReceiverUser.IsFriend, msg.ReceiverUser.IsFollowing = friendMap[msg.ReceiverUserID], followMap[msg.ReceiverUserID] + msg.ReceiverUser.IsFollowing = followMap[msg.ReceiverUserID] + } + } + return nil +} + +func (s *DaoServant) PrepareTweet(userId int64, tweet *ms.PostFormated) error { + // 转换一下可见性的值 + tweet.Visibility = ms.PostVisibleT(tweet.Visibility.ToOutValue()) + // guest用户的userId<0 + if userId < 0 { + return nil + } + // friendMap, err := s.Ds.IsMyFriend(userId, userIds) + // if err != nil { + // return err + // } + followMap, err := s.Ds.IsMyFollow(userId, tweet.UserID) + if err != nil { + return err + } + // tweet.User.IsFriend, tweet.User.IsFollowing = friendMap[tweet.UserID], followMap[tweet.UserID] + tweet.User.IsFollowing = followMap[tweet.UserID] + return nil +} + +func (s *DaoServant) PrepareTweets(userId int64, tweets []*ms.PostFormated) error { + userIdSet := make(map[int64]types.Empty, len(tweets)) + for _, tweet := range tweets { + userIdSet[tweet.UserID] = types.Empty{} + // 顺便转换一下可见性的值 + tweet.Visibility = ms.PostVisibleT(tweet.Visibility.ToOutValue()) + } + // guest用户的userId<0 + if userId < 0 { + return nil + } + userIds := make([]int64, 0, len(userIdSet)) + for id := range userIdSet { + userIds = append(userIds, id) + } + // friendMap, err := s.Ds.IsMyFriend(userId, userIds...) + // if err != nil { + // return err + // } + followMap, err := s.Ds.IsMyFollow(userId, userIds...) + if err != nil { + return err + } + for _, tweet := range tweets { + // tweet.User.IsFriend, tweet.User.IsFollowing = friendMap[tweet.UserID], followMap[tweet.UserID] + tweet.User.IsFollowing = followMap[tweet.UserID] + } + return nil +} + func (s *DaoServant) GetTweetBy(id int64) (*ms.PostFormated, error) { post, err := s.Ds.GetPostByID(id) if err != nil { @@ -203,20 +302,25 @@ func (s *DaoServant) GetTweetBy(id int64) (*ms.PostFormated, error) { return postFormated, nil } -func (s *DaoServant) PushPostsToSearch(c context.Context) { - if err := s.Redis.SetPushToSearchJob(c); err == nil { - defer s.Redis.DelPushToSearchJob(c) +func (s *DaoServant) PushAllPostToSearch() { + events.OnEvent(&pushAllPostToSearchEvent{ + fn: s.pushAllPostToSearch, + }) +} +func (s *DaoServant) pushAllPostToSearch() error { + ctx := context.Background() + if err := s.Redis.SetPushToSearchJob(ctx); err == nil { + defer s.Redis.DelPushToSearchJob(ctx) splitNum := 1000 - conditions := ms.ConditionsT{ - "visibility IN ?": []core.PostVisibleT{core.PostVisitPublic, core.PostVisitFriend}, + posts, totalRows, err := s.Ds.ListSyncSearchTweets(splitNum, 0) + if err != nil { + return fmt.Errorf("get first page tweets push to search failed: %s", err) } - totalRows, _ := s.Ds.GetPostCount(conditions) - pages := math.Ceil(float64(totalRows) / float64(splitNum)) - nums := int(pages) - for i := 0; i < nums; i++ { - posts, postsFormated, err := s.GetTweetList(conditions, i*splitNum, splitNum) - if err != nil || len(posts) != len(postsFormated) { + i, nums := 0, int(math.Ceil(float64(totalRows)/float64(splitNum))) + for { + postsFormated, xerr := s.Ds.MergePosts(posts) + if xerr != nil || len(posts) != len(postsFormated) { continue } for i, pf := range postsFormated { @@ -232,13 +336,27 @@ func (s *DaoServant) PushPostsToSearch(c context.Context) { }} s.Ts.AddDocuments(docs, fmt.Sprintf("%d", posts[i].ID)) } + if i++; i >= nums { + break + } + if posts, _, err = s.Ds.ListSyncSearchTweets(splitNum, i*splitNum); err != nil { + return fmt.Errorf("get tweets push to search failed: %s, limit[%d] offset[%d]", err, splitNum, i*splitNum) + } } } else { - logrus.Errorf("redis: set JOB_PUSH_TO_SEARCH error: %s", err) + return fmt.Errorf("redis: set JOB_PUSH_TO_SEARCH error: %w", err) } + return nil } func (s *DaoServant) PushPostToSearch(post *ms.Post) { + events.OnEvent(&pushPostToSearchEvent{ + fn: s.pushPostToSearch, + post: post, + }) +} + +func (s *DaoServant) pushPostToSearch(post *ms.Post) { postFormated := post.Format() postFormated.User = &ms.UserFormated{ ID: post.UserID, @@ -247,14 +365,12 @@ func (s *DaoServant) PushPostToSearch(post *ms.Post) { for _, content := range contents { postFormated.Contents = append(postFormated.Contents, content.Format()) } - contentFormated := "" for _, content := range postFormated.Contents { if content.Type == ms.ContentTypeText || content.Type == ms.ContentTypeTitle { contentFormated = contentFormated + content.Content + "\n" } } - docs := []core.TsDocItem{{ Post: post, Content: contentFormated, @@ -266,15 +382,6 @@ func (s *DaoServant) DeleteSearchPost(post *ms.Post) error { return s.Ts.DeleteDocuments([]string{fmt.Sprintf("%d", post.ID)}) } -func (s *DaoServant) GetTweetList(conditions ms.ConditionsT, offset, limit int) ([]*ms.Post, []*ms.PostFormated, error) { - posts, err := s.Ds.GetPosts(conditions, offset, limit) - if err != nil { - return nil, nil, err - } - postFormated, err := s.Ds.MergePosts(posts) - return posts, postFormated, err -} - func (s *DaoServant) RelationTypFrom(me *ms.User, username string) (res *cs.VistUser, err error) { res = &cs.VistUser{ RelTyp: cs.RelationSelf, diff --git a/internal/servants/base/events.go b/internal/servants/base/events.go new file mode 100644 index 00000000..2c840a2b --- /dev/null +++ b/internal/servants/base/events.go @@ -0,0 +1,129 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package base + +import ( + "fmt" + + "github.com/alimy/tryst/event" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/rocboss/paopao-ce/internal/model/joint" + "github.com/rocboss/paopao-ce/pkg/json" +) + +type CacheRespEvent struct { + event.UnimplementedEvent + ac core.AppCache + key string + data any + expire int64 +} + +type ExpireRespEvent struct { + event.UnimplementedEvent + ac core.AppCache + keys []string +} + +type ExpireAnyRespEvent struct { + event.UnimplementedEvent + ac core.AppCache + pattern string +} + +type pushPostToSearchEvent struct { + event.UnimplementedEvent + fn func(*ms.Post) + post *ms.Post +} + +type pushAllPostToSearchEvent struct { + event.UnimplementedEvent + fn func() error +} + +func OnCacheRespEvent(ac core.AppCache, key string, data any, expire int64) { + events.OnEvent(&CacheRespEvent{ + ac: ac, + key: key, + data: data, + expire: expire, + }) +} + +func OnExpireRespEvent(ac core.AppCache, keys ...string) { + if len(keys) != 0 { + events.OnEvent(&ExpireRespEvent{ + ac: ac, + keys: keys, + }) + } +} + +func OnExpireAnyRespEvent(ac core.AppCache, pattern string) { + events.OnEvent(&ExpireAnyRespEvent{ + ac: ac, + pattern: pattern, + }) +} + +func (p *CacheRespEvent) Name() string { + return "servants.base.CacheRespEvent" +} + +func (p *CacheRespEvent) Action() error { + if p.ac.Exist(p.key) { + // do nothing + return nil + } + resp := &joint.JsonResp{ + Code: 0, + Msg: "success", + Data: p.data, + } + data, err := json.Marshal(resp) + if err != nil { + return fmt.Errorf("CacheRespEvent action marshal resp occurs error: %w", err) + } + if err = p.ac.Set(p.key, data, p.expire); err != nil { + return fmt.Errorf("CacheRespEvent action put resp data to redis cache occurs error: %w", err) + } + return nil +} + +func (p *ExpireRespEvent) Name() string { + return "servants.base.ExpireRespEvent" +} + +func (p *ExpireRespEvent) Action() error { + return p.ac.Delete(p.keys...) +} + +func (p *ExpireAnyRespEvent) Name() string { + return "servants.base.ExpireAnyRespEvent" +} + +func (p *ExpireAnyRespEvent) Action() error { + return p.ac.DelAny(p.pattern) +} + +func (p *pushPostToSearchEvent) Name() string { + return "servants.base.pushPostToSearchEvent" +} + +func (p *pushPostToSearchEvent) Action() (err error) { + p.fn(p.post) + return +} + +func (p *pushAllPostToSearchEvent) Name() string { + return "servants.base.pushAllPostToSearchEvent" +} + +func (p *pushAllPostToSearchEvent) Action() error { + return p.fn() +} diff --git a/internal/servants/chain/audit.go b/internal/servants/chain/audit.go new file mode 100644 index 00000000..01976777 --- /dev/null +++ b/internal/servants/chain/audit.go @@ -0,0 +1,25 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package chain + +import ( + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +func AuditHook() gin.HandlerFunc { + return func(c *gin.Context) { + // 此midleware后面是真正的http handlder,让handler先执行 + c.Next() + // 审核hook 后处理逻辑 + var ami *web.AuditMetaInfo + if val, ok := c.Get(web.AuditHookCtxKey); ok { + if ami, ok = val.(*web.AuditMetaInfo); !ok { + return + } + } + OnAudiotHookEvent(ami) + } +} diff --git a/internal/servants/chain/chain.go b/internal/servants/chain/chain.go index 84faeca5..2a611bfc 100644 --- a/internal/servants/chain/chain.go +++ b/internal/servants/chain/chain.go @@ -9,16 +9,19 @@ import ( "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/dao" + "github.com/rocboss/paopao-ce/internal/dao/cache" ) var ( _ums core.UserManageService + _ac core.AppCache _onceUms sync.Once ) func userManageService() core.UserManageService { _onceUms.Do(func() { _ums = dao.DataService() + _ac = cache.NewAppCache() }) return _ums } diff --git a/internal/servants/chain/events.go b/internal/servants/chain/events.go new file mode 100644 index 00000000..d1d73777 --- /dev/null +++ b/internal/servants/chain/events.go @@ -0,0 +1,35 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package chain + +import ( + "github.com/alimy/tryst/event" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/rocboss/paopao-ce/internal/model/web" + "github.com/sirupsen/logrus" +) + +type AuditHookEvent struct { + event.UnimplementedEvent + ami *web.AuditMetaInfo +} + +func (e *AuditHookEvent) Name() string { + return "AuditHookEvent" +} + +func (e *AuditHookEvent) Action() error { + // TODO: just log event now, will add real logic in future. + logrus.Debugf("auditHook event action style[%s] id[%d]", e.ami.Style, e.ami.Id) + return nil +} + +func OnAudiotHookEvent(ami *web.AuditMetaInfo) { + if ami != nil { + events.OnEvent(&AuditHookEvent{ + ami: ami, + }) + } +} diff --git a/internal/servants/chain/jwt.go b/internal/servants/chain/jwt.go index ae96d550..1400808d 100644 --- a/internal/servants/chain/jwt.go +++ b/internal/servants/chain/jwt.go @@ -5,11 +5,11 @@ package chain import ( + "errors" "strings" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v4" - "github.com/rocboss/paopao-ce/internal/conf" + "github.com/golang-jwt/jwt/v5" "github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/xerror" ) @@ -40,7 +40,7 @@ func JWT() gin.HandlerFunc { // 加载用户信息 if user, err := ums.GetUserByID(claims.UID); err == nil { // 强制下线机制 - if (conf.JWTSetting.Issuer + ":" + user.Salt) == claims.Issuer { + if app.IssuerFrom(user.Salt) == claims.Issuer { c.Set("USER", user) c.Set("UID", claims.UID) c.Set("USERNAME", claims.Username) @@ -51,10 +51,53 @@ func JWT() gin.HandlerFunc { ecode = xerror.UnauthorizedAuthNotExist } } else { - switch err.(*jwt.ValidationError).Errors { - case jwt.ValidationErrorExpired: + if errors.Is(err, jwt.ErrTokenExpired) { ecode = xerror.UnauthorizedTokenTimeout - default: + } else { + ecode = xerror.UnauthorizedTokenError + } + } + } else { + ecode = xerror.InvalidParams + } + if ecode != xerror.Success { + response := app.NewResponse(c) + response.ToErrorResponse(ecode) + c.Abort() + return + } + c.Next() + } +} + +func JwtSurely() gin.HandlerFunc { + return func(c *gin.Context) { + var ( + token string + ecode = xerror.Success + ) + if s, exist := c.GetQuery("token"); exist { + token = s + } else { + token = c.GetHeader("Authorization") + // 验证前端传过来的token格式,不为空,开头为Bearer + if token == "" || !strings.HasPrefix(token, "Bearer ") { + response := app.NewResponse(c) + response.ToErrorResponse(xerror.UnauthorizedTokenError) + c.Abort() + return + } + // 验证通过,提取有效部分(除去Bearer) + token = token[7:] + } + if token != "" { + if claims, err := app.ParseToken(token); err == nil { + c.Set("UID", claims.UID) + c.Set("USERNAME", claims.Username) + } else { + if errors.Is(err, jwt.ErrTokenExpired) { + ecode = xerror.UnauthorizedTokenTimeout + } else { ecode = xerror.UnauthorizedTokenError } } @@ -89,7 +132,7 @@ func JwtLoose() gin.HandlerFunc { if claims, err := app.ParseToken(token); err == nil { // 加载用户信息 user, err := ums.GetUserByID(claims.UID) - if err == nil && (conf.JWTSetting.Issuer+":"+user.Salt) == claims.Issuer { + if err == nil && app.IssuerFrom(user.Salt) == claims.Issuer { c.Set("UID", claims.UID) c.Set("USERNAME", claims.Username) c.Set("USER", user) diff --git a/internal/servants/chain/measure.go b/internal/servants/chain/measure.go new file mode 100644 index 00000000..e3d94c52 --- /dev/null +++ b/internal/servants/chain/measure.go @@ -0,0 +1,21 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package chain + +import ( + "github.com/gin-gonic/gin" + "github.com/rocboss/paopao-ce/internal/servants/base" +) + +func OnlineUserMeasure() gin.HandlerFunc { + return func(c *gin.Context) { + // 此midleware后面是真正的http handlder,让handler先执行 + c.Next() + // 更新用户在线状态 + if uid, ok := base.UserIdFrom(c); ok { + OnUserOnlineMetric(_ac, uid) + } + } +} diff --git a/internal/servants/chain/metrics.go b/internal/servants/chain/metrics.go new file mode 100644 index 00000000..a6d550c6 --- /dev/null +++ b/internal/servants/chain/metrics.go @@ -0,0 +1,36 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package chain + +import ( + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/metrics" +) + +type OnlineUserMetric struct { + metrics.BaseMetric + ac core.AppCache + uid int64 + expire int64 +} + +func OnUserOnlineMetric(ac core.AppCache, uid int64) { + metrics.OnMeasure(&OnlineUserMetric{ + ac: ac, + uid: uid, + expire: conf.CacheSetting.OnlineUserExpire, + }) +} + +func (m *OnlineUserMetric) Name() string { + return "OnlineUserMetric" +} + +func (m *OnlineUserMetric) Action() (err error) { + // 暂时仅做标记,不存储其他相关信息 + m.ac.SetNx(conf.KeyOnlineUser.Get(m.uid), []byte{}, m.expire) + return +} diff --git a/internal/servants/chain/priv.go b/internal/servants/chain/priv.go index 420c3a9c..f3e43904 100644 --- a/internal/servants/chain/priv.go +++ b/internal/servants/chain/priv.go @@ -5,7 +5,7 @@ package chain import ( - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/gin-gonic/gin" "github.com/rocboss/paopao-ce/internal/core/ms" "github.com/rocboss/paopao-ce/pkg/app" diff --git a/internal/servants/docs/docs_embed.go b/internal/servants/docs/docs_embed.go index 8879896c..b7c76e86 100644 --- a/internal/servants/docs/docs_embed.go +++ b/internal/servants/docs/docs_embed.go @@ -8,7 +8,7 @@ package docs import ( - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/gin-gonic/gin" "github.com/rocboss/paopao-ce/docs/openapi" ) diff --git a/internal/servants/servants.go b/internal/servants/servants.go index 35af4bdc..39d26d42 100644 --- a/internal/servants/servants.go +++ b/internal/servants/servants.go @@ -7,7 +7,7 @@ package servants import ( "net/http" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/bufbuild/connect-go" "github.com/gin-gonic/gin" "github.com/rocboss/paopao-ce/internal/servants/admin" diff --git a/internal/servants/web/admin.go b/internal/servants/web/admin.go index 1d8241e6..1e996ad5 100644 --- a/internal/servants/web/admin.go +++ b/internal/servants/web/admin.go @@ -5,13 +5,18 @@ package web import ( + "time" + "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/chain" "github.com/rocboss/paopao-ce/pkg/xerror" + "github.com/sirupsen/logrus" ) var ( @@ -21,6 +26,8 @@ var ( type adminSrv struct { api.UnimplementedAdminServant *base.DaoServant + wc core.WebCache + serverUpTime int64 } func (s *adminSrv) Chain() gin.HandlersChain { @@ -40,8 +47,29 @@ func (s *adminSrv) ChangeUserStatus(req *web.ChangeUserStatusReq) mir.Error { return nil } -func newAdminSrv(s *base.DaoServant) api.Admin { +func (s *adminSrv) SiteInfo(req *web.SiteInfoReq) (*web.SiteInfoResp, mir.Error) { + res, err := &web.SiteInfoResp{ServerUpTime: s.serverUpTime}, error(nil) + res.RegisterUserCount, err = s.Ds.GetRegisterUserCount() + if err != nil { + logrus.Errorf("get SiteInfo[1] occurs error: %s", err) + } + onlineUserKeys, xerr := s.wc.Keys(conf.PrefixOnlineUser + "*") + if xerr == nil { + res.OnlineUserCount = len(onlineUserKeys) + if res.HistoryMaxOnline, err = s.wc.PutHistoryMaxOnline(res.OnlineUserCount); err != nil { + logrus.Errorf("get Siteinfo[3] occurs error: %s", err) + } + } else { + logrus.Errorf("get Siteinfo[2] occurs error: %s", err) + } + // 错误进行宽松赦免处理 + return res, nil +} + +func newAdminSrv(s *base.DaoServant, wc core.WebCache) api.Admin { return &adminSrv{ - DaoServant: s, + DaoServant: s, + wc: wc, + serverUpTime: time.Now().Unix(), } } diff --git a/internal/servants/web/core.go b/internal/servants/web/core.go index de092a60..4430f6ee 100644 --- a/internal/servants/web/core.go +++ b/internal/servants/web/core.go @@ -6,6 +6,7 @@ package web import ( "context" + "fmt" "time" "unicode/utf8" @@ -13,8 +14,10 @@ import ( "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/model/joint" "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/chain" @@ -22,10 +25,10 @@ import ( "github.com/sirupsen/logrus" ) -const ( +var ( // _MaxWhisperNumDaily 当日单用户私信总数限制(TODO 配置化、积分兑换等) - _MaxWhisperNumDaily = 20 - _MaxCaptchaTimes = 2 + _maxWhisperNumDaily int64 = 200 + _maxCaptchaTimes int = 2 ) var ( @@ -35,8 +38,10 @@ var ( type coreSrv struct { api.UnimplementedCoreServant *base.DaoServant - - oss core.ObjectStorageService + oss core.ObjectStorageService + wc core.WebCache + messagesExpire int64 + prefixMessages string } func (s *coreSrv) Chain() gin.HandlersChain { @@ -45,7 +50,7 @@ func (s *coreSrv) Chain() gin.HandlersChain { func (s *coreSrv) SyncSearchIndex(req *web.SyncSearchIndexReq) mir.Error { if req.User != nil && req.User.IsAdmin { - go s.PushPostsToSearch(context.Background()) + s.PushAllPostToSearch() } else { logrus.Warnf("sync search index need admin permision user: %#v", req.User) } @@ -53,11 +58,9 @@ func (s *coreSrv) SyncSearchIndex(req *web.SyncSearchIndexReq) mir.Error { } func (s *coreSrv) GetUserInfo(req *web.UserInfoReq) (*web.UserInfoResp, mir.Error) { - user, err := s.Ds.GetUserByUsername(req.Username) + user, err := s.Ds.UserProfileByName(req.Username) if err != nil { - return nil, xerror.UnauthorizedAuthNotExist - } - if user.Model == nil || user.ID < 0 { + logrus.Errorf("coreSrv.GetUserInfo occurs error[1]: %s", err) return nil, xerror.UnauthorizedAuthNotExist } follows, followings, err := s.Ds.GetFollowCount(user.ID) @@ -65,16 +68,17 @@ func (s *coreSrv) GetUserInfo(req *web.UserInfoReq) (*web.UserInfoResp, mir.Erro return nil, web.ErrGetFollowCountFailed } resp := &web.UserInfoResp{ - Id: user.ID, - Nickname: user.Nickname, - Username: user.Username, - Status: user.Status, - Avatar: user.Avatar, - Balance: user.Balance, - IsAdmin: user.IsAdmin, - CreatedOn: user.CreatedOn, - Follows: follows, - Followings: followings, + Id: user.ID, + Nickname: user.Nickname, + Username: user.Username, + Status: user.Status, + Avatar: user.Avatar, + Balance: user.Balance, + IsAdmin: user.IsAdmin, + CreatedOn: user.CreatedOn, + Follows: follows, + Followings: followings, + TweetsCount: user.TweetsCount, } if user.Phone != "" && len(user.Phone) == 11 { resp.Phone = user.Phone[0:3] + "****" + user.Phone[7:] @@ -82,29 +86,31 @@ func (s *coreSrv) GetUserInfo(req *web.UserInfoReq) (*web.UserInfoResp, mir.Erro return resp, nil } -func (s *coreSrv) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) { - count, err := s.Ds.GetUnreadCount(req.Uid) - if err != nil { - return nil, xerror.ServerError +func (s *coreSrv) GetMessages(req *web.GetMessagesReq) (res *web.GetMessagesResp, _ mir.Error) { + limit, offset := req.PageSize, (req.Page-1)*req.PageSize + // 尝试直接从缓存中获取数据 + key, ok := "", false + if res, key, ok = s.messagesFromCache(req, limit, offset); ok { + // logrus.Debugf("coreSrv.GetMessages from cache key:%s", key) + return } - return &web.GetUnreadMsgCountResp{ - Count: count, - }, nil -} - -func (s *coreSrv) GetMessages(req *web.GetMessagesReq) (*web.GetMessagesResp, mir.Error) { - conditions := &ms.ConditionsT{ - "receiver_user_id": req.UserId, - "ORDER": "id DESC", + messages, totalRows, err := s.Ds.GetMessages(req.Uid, req.Style, limit, offset) + if err != nil { + logrus.Errorf("Ds.GetMessages err[1]: %s", err) + return nil, web.ErrGetMessagesFailed } - messages, err := s.Ds.GetMessages(conditions, (req.Page-1)*req.PageSize, req.PageSize) for _, mf := range messages { + // TODO: 优化处理这里的user获取逻辑以及错误处理 if mf.SenderUserID > 0 { - user, err := s.Ds.GetUserByID(mf.SenderUserID) - if err == nil { + if user, err := s.Ds.GetUserByID(mf.SenderUserID); err == nil { mf.SenderUser = user.Format() } } + if mf.Type == ms.MsgTypeWhisper && mf.ReceiverUserID != req.Uid { + if user, err := s.Ds.GetUserByID(mf.ReceiverUserID); err == nil { + mf.ReceiverUser = user.Format() + } + } // 好友申请消息不需要获取其他信息 if mf.Type == ms.MsgTypeRequestingFriend { continue @@ -129,12 +135,21 @@ func (s *coreSrv) GetMessages(req *web.GetMessagesReq) (*web.GetMessagesResp, mi } } if err != nil { - logrus.Errorf("Ds.GetMessages err: %v\n", err) + logrus.Errorf("Ds.GetMessages err[2]: %s", err) + return nil, web.ErrGetMessagesFailed + } + if err = s.PrepareMessages(req.Uid, messages); err != nil { + logrus.Errorf("get messages err[3]: %s", err) return nil, web.ErrGetMessagesFailed } - totalRows, _ := s.Ds.GetMessageCount(conditions) - resp := base.PageRespFrom(messages, req.Page, req.PageSize, totalRows) - return (*web.GetMessagesResp)(resp), nil + resp := joint.PageRespFrom(messages, req.Page, req.PageSize, totalRows) + // 缓存处理 + base.OnCacheRespEvent(s.wc, key, resp, s.messagesExpire) + return &web.GetMessagesResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil } func (s *coreSrv) ReadMessage(req *web.ReadMessageReq) mir.Error { @@ -149,6 +164,18 @@ func (s *coreSrv) ReadMessage(req *web.ReadMessageReq) mir.Error { logrus.Errorf("Ds.ReadMessage err: %s", err) return web.ErrReadMessageFailed } + // 缓存处理 + onMessageActionEvent(_messageActionRead, req.Uid) + return nil +} + +func (s *coreSrv) ReadAllMessage(req *web.ReadAllMessageReq) mir.Error { + if err := s.Ds.ReadAllMessage(req.Uid); err != nil { + logrus.Errorf("coreSrv.Ds.ReadAllMessage err: %s", err) + return web.ErrReadMessageFailed + } + // 缓存处理 + onMessageActionEvent(_messageActionRead, req.Uid) return nil } @@ -157,13 +184,11 @@ func (s *coreSrv) SendUserWhisper(req *web.SendWhisperReq) mir.Error { if req.Uid == req.UserID { return web.ErrNoWhisperToSelf } - // 今日频次限制 ctx := context.Background() - if count, _ := s.Redis.GetCountWhisper(ctx, req.Uid); count >= _MaxWhisperNumDaily { + if count, _ := s.Redis.GetCountWhisper(ctx, req.Uid); count >= _maxWhisperNumDaily { return web.ErrTooManyWhisperNum } - // 创建私信 _, err := s.Ds.CreateMessage(&ms.Message{ SenderUserID: req.Uid, @@ -176,7 +201,8 @@ func (s *coreSrv) SendUserWhisper(req *web.SendWhisperReq) mir.Error { logrus.Errorf("Ds.CreateWhisper err: %s", err) return web.ErrSendWhisperFailed } - + // 缓存处理, 不需要处理错误 + onMessageActionEvent(_messageActionSendWhisper, req.Uid, req.UserID) // 写入当日(自然日)计数缓存 s.Redis.IncrCountWhisper(ctx, req.Uid) @@ -194,7 +220,6 @@ func (s *coreSrv) GetCollections(req *web.GetCollectionsReq) (*web.GetCollection logrus.Errorf("Ds.GetUserPostCollectionCount err: %s", err) return nil, web.ErrGetCollectionsFailed } - var posts []*ms.Post for _, collection := range collections { posts = append(posts, collection.Post) @@ -204,8 +229,11 @@ func (s *coreSrv) GetCollections(req *web.GetCollectionsReq) (*web.GetCollection logrus.Errorf("Ds.MergePosts err: %s", err) return nil, web.ErrGetCollectionsFailed } + if err = s.PrepareTweets(req.UserId, postsFormated); err != nil { + logrus.Errorf("get collections prepare tweets err: %s", err) + return nil, web.ErrGetCollectionsFailed + } resp := base.PageRespFrom(postsFormated, req.Page, req.PageSize, totalRows) - return (*web.GetCollectionsResp)(resp), nil } @@ -228,7 +256,7 @@ func (s *coreSrv) UserPhoneBind(req *web.UserPhoneBindReq) mir.Error { if c.ExpiredOn < time.Now().Unix() { return web.ErrErrorPhoneCaptcha } - if c.UseTimes >= _MaxCaptchaTimes { + if c.UseTimes >= _maxCaptchaTimes { return web.ErrMaxPhoneCaptchaUseTimes } // 更新检测次数 @@ -325,6 +353,8 @@ func (s *coreSrv) ChangeNickname(req *web.ChangeNicknameReq) mir.Error { logrus.Errorf("Ds.UpdateUser err: %s", err) return xerror.ServerError } + // 缓存处理 + onChangeUsernameEvent(user.ID, user.Username) return nil } @@ -349,6 +379,8 @@ func (s *coreSrv) ChangeAvatar(req *web.ChangeAvatarReq) (xerr mir.Error) { logrus.Errorf("Ds.UpdateUser failed: %s", err) return xerror.ServerError } + // 缓存处理 + onChangeUsernameEvent(user.ID, user.Username) return nil } @@ -374,9 +406,25 @@ func (s *coreSrv) TweetStarStatus(req *web.TweetStarStatusReq) (*web.TweetStarSt return resp, nil } -func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Core { +func (s *coreSrv) messagesFromCache(req *web.GetMessagesReq, limit int, offset int) (res *web.GetMessagesResp, key string, ok bool) { + key = fmt.Sprintf("%s%d:%s:%d:%d", s.prefixMessages, req.Uid, req.Style, limit, offset) + if data, err := s.wc.Get(key); err == nil { + ok, res = true, &web.GetMessagesResp{ + CachePageResp: joint.CachePageResp{ + JsonResp: data, + }, + } + } + return +} + +func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService, wc core.WebCache) api.Core { + cs := conf.CacheSetting return &coreSrv{ - DaoServant: s, - oss: oss, + DaoServant: s, + oss: oss, + wc: wc, + messagesExpire: cs.MessagesExpire, + prefixMessages: conf.PrefixMessages, } } diff --git a/internal/servants/web/events.go b/internal/servants/web/events.go new file mode 100644 index 00000000..79c23598 --- /dev/null +++ b/internal/servants/web/events.go @@ -0,0 +1,347 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "encoding/json" + "fmt" + + "github.com/alimy/tryst/event" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/cs" + "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/dao/cache" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/rocboss/paopao-ce/internal/model/joint" + "github.com/rocboss/paopao-ce/internal/model/web" + "github.com/sirupsen/logrus" +) + +const ( + _tweetActionCreate uint8 = iota + _tweetActionDelete +) + +const ( + _commentActionCreate uint8 = iota + _commentActionDelete + _commentActionThumbsUp + _commentActionThumbsDown + _commentActionReplyCreate + _commentActionReplyDelete + _commentActionReplyThumbsUp + _commentActionReplyThumbsDown + _commentActionHighlight +) + +const ( + _messageActionCreate uint8 = iota + _messageActionRead + _messageActionFollow + _messageActionSendWhisper +) + +const ( + _trendsActionCreateTweet uint8 = iota + _trendsActionDeleteTweet + _trendsActionFollowUser + _trendsActionUnfollowUser + _trendsActionAddFriend + _trendsActionDeleteFriend +) + +type cacheUnreadMsgEvent struct { + event.UnimplementedEvent + ds core.DataService + wc core.WebCache + uid int64 +} + +type createMessageEvent struct { + event.UnimplementedEvent + ds core.DataService + wc core.WebCache + message *ms.Message +} + +type tweetActionEvent struct { + event.UnimplementedEvent + ac core.AppCache + userId int64 + username string + action uint8 +} + +type commentActionEvent struct { + event.UnimplementedEvent + ds core.DataService + ac core.AppCache + tweetId int64 + commentId int64 + action uint8 +} + +type messageActionEvent struct { + event.UnimplementedEvent + wc core.WebCache + action uint8 + userId []int64 +} + +type trendsActionEvent struct { + event.UnimplementedEvent + ac core.AppCache + ds core.DataService + action uint8 + userIds []int64 +} + +type changeUserEvent struct { + *cache.BaseCacheEvent + userId int64 + username string +} + +func onChangeUsernameEvent(id int64, name string) { + events.OnEvent(&changeUserEvent{ + BaseCacheEvent: cache.NewBaseCacheEvent(_ac), + userId: id, + username: name, + }) +} + +func onTrendsActionEvent(action uint8, userIds ...int64) { + events.OnEvent(&trendsActionEvent{ + ac: _ac, + ds: _ds, + action: action, + userIds: userIds, + }) +} + +func onTweetActionEvent(action uint8, userId int64, username string) { + events.OnEvent(&tweetActionEvent{ + ac: _ac, + action: action, + userId: userId, + username: username, + }) +} + +func onMessageActionEvent(action uint8, userIds ...int64) { + events.OnEvent(&messageActionEvent{ + wc: _wc, + action: action, + userId: userIds, + }) +} + +func onCommentActionEvent(tweetId int64, commentId int64, action uint8) { + events.OnEvent(&commentActionEvent{ + ds: _ds, + ac: _ac, + tweetId: tweetId, + commentId: commentId, + action: action, + }) +} + +func onCacheUnreadMsgEvent(uid int64) { + events.OnEvent(&cacheUnreadMsgEvent{ + ds: _ds, + wc: _wc, + uid: uid, + }) +} + +func onCreateMessageEvent(data *ms.Message) { + events.OnEvent(&createMessageEvent{ + ds: _ds, + wc: _wc, + message: data, + }) +} + +func (e *cacheUnreadMsgEvent) Name() string { + return "cacheUnreadMsgEvent" +} + +func (e *cacheUnreadMsgEvent) Action() error { + if e.wc.ExistUnreadMsgCountResp(e.uid) { + // do nothing + return nil + } + count, err := e.ds.GetUnreadCount(e.uid) + if err != nil { + return fmt.Errorf("cacheUnreadMsgEvent action occurs error: %w", err) + } + resp := &joint.JsonResp{ + Code: 0, + Msg: "success", + Data: &web.GetUnreadMsgCountResp{ + Count: count, + }, + } + data, err := json.Marshal(resp) + if err != nil { + return fmt.Errorf("cacheUnreadMsgEvent action marshal resp occurs error: %w", err) + } + if err = e.wc.PutUnreadMsgCountResp(e.uid, data); err != nil { + return fmt.Errorf("cacheUnreadMsgEvent action put resp data to redis cache occurs error: %w", err) + } + return nil +} + +func (e *createMessageEvent) Name() string { + return "createMessageEvent" +} + +func (e *createMessageEvent) Action() (err error) { + if _, err = e.ds.CreateMessage(e.message); err == nil { + err = e.wc.DelUnreadMsgCountResp(e.message.ReceiverUserID) + } + return +} + +func (e *commentActionEvent) Name() string { + return "updateCommentMetricEvent" +} + +func (e *commentActionEvent) Action() (err error) { + // logrus.Debugf("trigger commentActionEvent action commentId[%d]", e.commentId) + switch e.action { + case _commentActionCreate: + err = e.ds.AddCommentMetric(e.commentId) + e.expireAllStyleComments() + case _commentActionDelete: + err = e.ds.DeleteCommentMetric(e.commentId) + e.expireAllStyleComments() + case _commentActionReplyCreate, _commentActionReplyDelete: + err = e.updateCommentMetric() + e.expireAllStyleComments() + case _commentActionThumbsUp, _commentActionThumbsDown: + err = e.updateCommentMetric() + e.expireHotsComments() + case _commentActionHighlight: + e.expireAllStyleComments() + default: + // nothing + } + return +} + +func (e *commentActionEvent) expireHotsComments() { + e.ac.DelAny(fmt.Sprintf("%s%d:%s:*", conf.PrefixTweetComment, e.tweetId, conf.InfixCommentHots)) +} + +func (e *commentActionEvent) expireAllStyleComments() { + e.ac.DelAny(fmt.Sprintf("%s%d:*", conf.PrefixTweetComment, e.tweetId)) +} + +func (e *commentActionEvent) updateCommentMetric() error { + // logrus.Debug("trigger commentActionEvent action[updateCommentMetric]") + comment, err := e.ds.GetCommentByID(e.commentId) + if err != nil { + return err + } + e.ds.UpdateCommentMetric(&cs.CommentMetric{ + CommentId: e.commentId, + ReplyCount: comment.ReplyCount, + ThumbsUpCount: comment.ThumbsUpCount, + ThumbsDownCount: comment.ThumbsDownCount, + }) + return nil +} + +func (e *messageActionEvent) Name() string { + return "expireMessagesEvent" +} + +func (e *messageActionEvent) Action() (err error) { + for _, userId := range e.userId { + switch e.action { + case _messageActionRead, + _messageActionSendWhisper: + // 清除未读消息数缓存,不需要处理错误 + e.wc.DelUnreadMsgCountResp(userId) + case _messageActionCreate, + _messageActionFollow: + fallthrough + default: + // TODO + } + //清除该用户所有消息缓存 + err = e.wc.DelAny(fmt.Sprintf("%s%d:*", conf.PrefixMessages, userId)) + } + return +} + +func (e *trendsActionEvent) Name() string { + return "trendsActionEvent" +} + +func (e *trendsActionEvent) Action() (err error) { + switch e.action { + case _trendsActionCreateTweet: + logrus.Debug("trigger trendsActionEvent by create tweet ") + e.updateUserMetric(cs.MetricActionCreateTweet) + e.expireFriendTrends() + case _trendsActionDeleteTweet: + logrus.Debug("trigger trendsActionEvent by delete tweet ") + e.updateUserMetric(cs.MetricActionDeleteTweet) + e.expireFriendTrends() + case _trendsActionAddFriend, _trendsActionDeleteFriend, + _trendsActionFollowUser, _trendsActionUnfollowUser: + e.expireMyTrends() + default: + // nothing + } + return +} + +func (e *trendsActionEvent) updateUserMetric(action uint8) { + for _, userId := range e.userIds { + e.ds.UpdateUserMetric(userId, action) + } +} + +func (e *trendsActionEvent) expireFriendTrends() { + for _, userId := range e.userIds { + if friendIds, err := e.ds.MyFriendIds(userId); err == nil { + for _, id := range friendIds { + e.ac.DelAny(fmt.Sprintf("%s%d:*", conf.PrefixIdxTrends, id)) + } + } + } +} + +func (e *trendsActionEvent) expireMyTrends() { + for _, userId := range e.userIds { + e.ac.DelAny(fmt.Sprintf("%s%d:*", conf.PrefixIdxTrends, userId)) + } +} + +func (e *tweetActionEvent) Name() string { + return "tweetActionEvent" +} + +func (e *tweetActionEvent) Action() (err error) { + switch e.action { + case _tweetActionCreate, _tweetActionDelete: + e.ac.Delete(conf.KeyUserProfileByName.Get(e.username)) + default: + // nothing + } + return +} + +func (e *changeUserEvent) Name() string { + return "changeUserEvent" +} + +func (e *changeUserEvent) Action() error { + return e.ExpireUserData(e.userId, e.username) +} diff --git a/internal/servants/web/followship.go b/internal/servants/web/followship.go index def1e10e..22b41635 100644 --- a/internal/servants/web/followship.go +++ b/internal/servants/web/followship.go @@ -8,6 +8,7 @@ import ( "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/dao/cache" "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/chain" @@ -84,6 +85,12 @@ func (s *followshipSrv) UnfollowUser(r *web.UnfollowUserReq) mir.Error { logrus.Errorf("Ds.UnfollowUser err: %s userId: %d followId: %d", err, r.User.ID, r.UserId) return web.ErrUnfollowUserFailed } + // 触发缓存更新事件 + // TODO: 合并成一个事件 + cache.OnCacheMyFollowIdsEvent(s.Ds, r.User.ID) + cache.OnExpireIndexTweetEvent(r.User.ID) + onMessageActionEvent(_messageActionFollow, r.User.ID) + onTrendsActionEvent(_trendsActionUnfollowUser, r.User.ID) return nil } @@ -97,6 +104,12 @@ func (s *followshipSrv) FollowUser(r *web.FollowUserReq) mir.Error { logrus.Errorf("Ds.FollowUser err: %s userId: %d followId: %d", err, r.User.ID, r.UserId) return web.ErrUnfollowUserFailed } + // 触发缓存更新事件 + // TODO: 合并成一个事件 + cache.OnCacheMyFollowIdsEvent(s.Ds, r.User.ID) + cache.OnExpireIndexTweetEvent(r.User.ID) + onMessageActionEvent(_messageActionFollow, r.User.ID) + onTrendsActionEvent(_trendsActionFollowUser, r.User.ID) return nil } diff --git a/internal/servants/web/friendship.go b/internal/servants/web/friendship.go index 4a419666..e0436b72 100644 --- a/internal/servants/web/friendship.go +++ b/internal/servants/web/friendship.go @@ -55,6 +55,9 @@ func (s *friendshipSrv) DeleteFriend(req *web.DeleteFriendReq) mir.Error { logrus.Errorf("Ds.DeleteFriend err: %s", err) return web.ErrDeleteFriendFailed } + // 触发用户关系缓存更新事件 + // cache.OnCacheMyFriendIdsEvent(s.Ds, req.User.ID, req.UserId) + onTrendsActionEvent(_trendsActionDeleteFriend, req.User.ID, req.UserId) return nil } @@ -89,6 +92,9 @@ func (s *friendshipSrv) AddFriend(req *web.AddFriendReq) mir.Error { logrus.Errorf("Ds.AddFriend err: %s", err) return web.ErrAddFriendFailed } + // 触发用户关系缓存更新事件 + // cache.OnCacheMyFriendIdsEvent(s.Ds, req.User.ID, req.UserId) + onTrendsActionEvent(_trendsActionAddFriend, req.User.ID, req.UserId) return nil } diff --git a/internal/servants/web/jobs.go b/internal/servants/web/jobs.go new file mode 100644 index 00000000..eb06589c --- /dev/null +++ b/internal/servants/web/jobs.go @@ -0,0 +1,39 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "github.com/alimy/tryst/cfg" + "github.com/robfig/cron/v3" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/events" + "github.com/sirupsen/logrus" +) + +func onMaxOnlineJob() { + spec := conf.JobManagerSetting.MaxOnlineInterval + schedule, err := cron.ParseStandard(spec) + if err != nil { + panic(err) + } + events.OnTask(schedule, func() { + onlineUserKeys, err := _wc.Keys(conf.PrefixOnlineUser + "*") + if maxOnline := len(onlineUserKeys); err == nil && maxOnline > 0 { + if _, err = _wc.PutHistoryMaxOnline(maxOnline); err != nil { + logrus.Warnf("onMaxOnlineJob[2] occurs error: %s", err) + } + } else if err != nil { + logrus.Warnf("onMaxOnlineJob[1] occurs error: %s", err) + } + }) +} + +func scheduleJobs() { + cfg.Not("DisableJobManager", func() { + lazyInitial() + onMaxOnlineJob() + logrus.Debug("schedule inner jobs complete") + }) +} diff --git a/internal/servants/web/loose.go b/internal/servants/web/loose.go index 27f33f77..3fe3517f 100644 --- a/internal/servants/web/loose.go +++ b/internal/servants/web/loose.go @@ -5,13 +5,17 @@ package web import ( + "fmt" + "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/core/ms" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" + "github.com/rocboss/paopao-ce/internal/model/joint" "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/chain" @@ -25,6 +29,15 @@ var ( type looseSrv struct { api.UnimplementedLooseServant *base.DaoServant + ac core.AppCache + userTweetsExpire int64 + idxTweetsExpire int64 + tweetCommentsExpire int64 + prefixUserTweets string + prefixIdxTweetsNewest string + prefixIdxTweetsHots string + prefixIdxTweetsFollowing string + prefixTweetComment string } func (s *looseSrv) Chain() gin.HandlersChain { @@ -32,33 +45,130 @@ func (s *looseSrv) Chain() gin.HandlersChain { } func (s *looseSrv) Timeline(req *web.TimelineReq) (*web.TimelineResp, mir.Error) { - var resp *base.PageResp - offset, limit := (req.Page-1)*req.PageSize, req.PageSize + limit, offset := req.PageSize, (req.Page-1)*req.PageSize if req.Query == "" && req.Type == "search" { - res, err := s.Ds.IndexPosts(req.User, offset, limit) - if err != nil { - logrus.Errorf("Ds.IndexPosts err: %s", err) - return nil, web.ErrGetPostsFailed - } - resp = base.PageRespFrom(res.Tweets, req.Page, req.PageSize, res.Total) - } else { - q := &core.QueryReq{ - Query: req.Query, - Type: core.SearchType(req.Type), + return s.getIndexTweets(req, limit, offset) + } + q := &core.QueryReq{ + Query: req.Query, + Type: core.SearchType(req.Type), + } + res, err := s.Ts.Search(req.User, q, offset, limit) + if err != nil { + logrus.Errorf("Ts.Search err: %s", err) + return nil, web.ErrGetPostsFailed + } + posts, err := s.Ds.RevampPosts(res.Items) + if err != nil { + logrus.Errorf("Ds.RevampPosts err: %s", err) + return nil, web.ErrGetPostsFailed + } + userId := int64(-1) + if req.User != nil { + userId = req.User.ID + } + if err := s.PrepareTweets(userId, posts); err != nil { + logrus.Errorf("timeline occurs error[2]: %s", err) + return nil, web.ErrGetPostsFailed + } + resp := joint.PageRespFrom(posts, req.Page, req.PageSize, res.Total) + return &web.TimelineResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil +} + +func (s *looseSrv) getIndexTweets(req *web.TimelineReq, limit int, offset int) (res *web.TimelineResp, err mir.Error) { + // 尝试直接从缓存中获取数据 + key, ok := "", false + if res, key, ok = s.indexTweetsFromCache(req, limit, offset); ok { + // logrus.Debugf("getIndexTweets from cache key:%s", key) + return + } + var ( + posts []*ms.Post + total int64 + xerr error + ) + switch req.Style { + case web.StyleTweetsFollowing: + if req.User != nil { + posts, total, xerr = s.Ds.ListFollowingTweets(req.User.ID, limit, offset) + } else { + // return nil, web.ErrGetPostsNilUser + // 宽松处理,前端退出登录后马上获取动态列表,可能错误走到这里 + posts, total, xerr = s.Ds.ListIndexNewestTweets(limit, offset) } - res, err := s.Ts.Search(req.User, q, offset, limit) - if err != nil { - logrus.Errorf("Ts.Search err: %s", err) - return nil, web.ErrGetPostsFailed + case web.StyleTweetsNewest: + posts, total, xerr = s.Ds.ListIndexNewestTweets(limit, offset) + case web.StyleTweetsHots: + posts, total, xerr = s.Ds.ListIndexHotsTweets(limit, offset) + default: + return nil, web.ErrGetPostsUnknowStyle + } + if xerr != nil { + logrus.Errorf("getIndexTweets occurs error[1]: %s", xerr) + return nil, web.ErrGetPostFailed + } + postsFormated, verr := s.Ds.MergePosts(posts) + if verr != nil { + logrus.Errorf("getIndexTweets in merge posts occurs error: %s", verr) + return nil, web.ErrGetPostFailed + } + userId := int64(-1) + if req.User != nil { + userId = req.User.ID + } + if err := s.PrepareTweets(userId, postsFormated); err != nil { + logrus.Errorf("getIndexTweets occurs error[2]: %s", err) + return nil, web.ErrGetPostsFailed + } + resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, total) + // 缓存处理 + base.OnCacheRespEvent(s.ac, key, resp, s.idxTweetsExpire) + return &web.TimelineResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil +} + +func (s *looseSrv) indexTweetsFromCache(req *web.TimelineReq, limit int, offset int) (res *web.TimelineResp, key string, ok bool) { + username := "_" + if req.User != nil { + username = req.User.Username + } + switch req.Style { + case web.StyleTweetsFollowing: + key = fmt.Sprintf("%s%s:%d:%d", s.prefixIdxTweetsFollowing, username, offset, limit) + case web.StyleTweetsNewest: + key = fmt.Sprintf("%s%s:%d:%d", s.prefixIdxTweetsNewest, username, offset, limit) + case web.StyleTweetsHots: + key = fmt.Sprintf("%s%s:%d:%d", s.prefixIdxTweetsHots, username, offset, limit) + default: + return + } + if data, err := s.ac.Get(key); err == nil { + ok, res = true, &web.TimelineResp{ + CachePageResp: joint.CachePageResp{ + JsonResp: data, + }, } - posts, err := s.Ds.RevampPosts(res.Items) - if err != nil { - logrus.Errorf("Ds.RevampPosts err: %s", err) - return nil, web.ErrGetPostsFailed + } + return +} + +func (s *looseSrv) tweetCommentsFromCache(req *web.TweetCommentsReq, limit int, offset int) (res *web.TweetCommentsResp, key string, ok bool) { + key = fmt.Sprintf("%s%d:%s:%d:%d", s.prefixTweetComment, req.TweetId, req.Style, limit, offset) + if data, err := s.ac.Get(key); err == nil { + ok, res = true, &web.TweetCommentsResp{ + CachePageResp: joint.CachePageResp{ + JsonResp: data, + }, } - resp = base.PageRespFrom(posts, req.Page, req.PageSize, res.Total) } - return (*web.TimelineResp)(resp), nil + return } func (s *looseSrv) GetUserTweets(req *web.GetUserTweetsReq) (res *web.GetUserTweetsResp, err mir.Error) { @@ -66,6 +176,13 @@ func (s *looseSrv) GetUserTweets(req *web.GetUserTweetsReq) (res *web.GetUserTwe if xerr != nil { return nil, err } + // 尝试直接从缓存中获取数据 + key, ok := "", false + if res, key, ok = s.userTweetsFromCache(req, user); ok { + // logrus.Debugf("GetUserTweets from cache key:%s", key) + return + } + // 缓存获取未成功,只能查库了 switch req.Style { case web.UserPostsStyleComment, web.UserPostsStyleMedia: res, err = s.listUserTweets(req, user) @@ -78,13 +195,38 @@ func (s *looseSrv) GetUserTweets(req *web.GetUserTweetsReq) (res *web.GetUserTwe default: res, err = s.getUserPostTweets(req, user, false) } + // 缓存处理 + if err == nil { + base.OnCacheRespEvent(s.ac, key, res.Data, s.userTweetsExpire) + } + return +} + +func (s *looseSrv) userTweetsFromCache(req *web.GetUserTweetsReq, user *cs.VistUser) (res *web.GetUserTweetsResp, key string, ok bool) { + switch req.Style { + case web.UserPostsStylePost, web.UserPostsStyleHighlight, web.UserPostsStyleMedia: + key = fmt.Sprintf("%s%d:%s:%s:%d:%d", s.prefixUserTweets, user.UserId, req.Style, user.RelTyp, req.Page, req.PageSize) + default: + meName := "_" + if user.RelTyp != cs.RelationGuest { + meName = req.User.Username + } + key = fmt.Sprintf("%s%d:%s:%s:%d:%d", s.prefixUserTweets, user.UserId, req.Style, meName, req.Page, req.PageSize) + } + if data, err := s.ac.Get(key); err == nil { + ok, res = true, &web.GetUserTweetsResp{ + CachePageResp: joint.CachePageResp{ + JsonResp: data, + }, + } + } return } func (s *looseSrv) getUserStarTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) { stars, totalRows, err := s.Ds.ListUserStarTweets(user, req.PageSize, (req.Page-1)*req.PageSize) if err != nil { - logrus.Errorf("Ds.GetUserPostStars err: %s", err) + logrus.Errorf("getUserStarTweets err[1]: %s", err) return nil, web.ErrGetStarsFailed } var posts []*ms.Post @@ -98,8 +240,20 @@ func (s *looseSrv) getUserStarTweets(req *web.GetUserTweetsReq, user *cs.VistUse logrus.Errorf("Ds.MergePosts err: %s", err) return nil, web.ErrGetStarsFailed } - resp := base.PageRespFrom(postsFormated, req.Page, req.PageSize, totalRows) - return (*web.GetUserTweetsResp)(resp), nil + userId := int64(-1) + if req.User != nil { + userId = req.User.ID + } + if err := s.PrepareTweets(userId, postsFormated); err != nil { + logrus.Errorf("getUserStarTweets err[2]: %s", err) + return nil, web.ErrGetPostsFailed + } + resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, totalRows) + return &web.GetUserTweetsResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil } func (s *looseSrv) listUserTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) { @@ -113,63 +267,80 @@ func (s *looseSrv) listUserTweets(req *web.GetUserTweetsReq, user *cs.VistUser) } else if req.Style == web.UserPostsStyleMedia { tweets, total, err = s.Ds.ListUserMediaTweets(user, req.PageSize, (req.Page-1)*req.PageSize) } else { - logrus.Errorf("s.listUserTweets unknow style: %s", req.Style) + logrus.Errorf("s.listUserTweets unknow style[1]: %s", req.Style) return nil, web.ErrGetPostsFailed } if err != nil { - logrus.Errorf("s.listUserTweets err: %s", err) + logrus.Errorf("s.listUserTweets err[2]: %s", err) return nil, web.ErrGetPostsFailed } - postFormated, err := s.Ds.MergePosts(tweets) + postsFormated, err := s.Ds.MergePosts(tweets) if err != nil { - logrus.Errorf("s.listUserTweets err: %s", err) + logrus.Errorf("s.listUserTweets err[3]: %s", err) return nil, web.ErrGetPostsFailed } - resp := base.PageRespFrom(postFormated, req.Page, req.PageSize, total) - return (*web.GetUserTweetsResp)(resp), nil + userId := int64(-1) + if req.User != nil { + userId = req.User.ID + } + if err := s.PrepareTweets(userId, postsFormated); err != nil { + logrus.Errorf("s.listUserTweets err[4]: %s", err) + return nil, web.ErrGetPostsFailed + } + resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, total) + return &web.GetUserTweetsResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil } func (s *looseSrv) getUserPostTweets(req *web.GetUserTweetsReq, user *cs.VistUser, isHighlight bool) (*web.GetUserTweetsResp, mir.Error) { - visibilities := []core.PostVisibleT{core.PostVisitPublic} + style := cs.StyleUserTweetsGuest switch user.RelTyp { - case cs.RelationAdmin, cs.RelationSelf: - visibilities = append(visibilities, core.PostVisitPrivate, core.PostVisitFriend) + case cs.RelationAdmin: + style = cs.StyleUserTweetsAdmin + case cs.RelationSelf: + style = cs.StyleUserTweetsSelf case cs.RelationFriend: - visibilities = append(visibilities, core.PostVisitFriend) + style = cs.StyleUserTweetsFriend + case cs.RelationFollowing: + style = cs.StyleUserTweetsFollowing case cs.RelationGuest: fallthrough default: // nothing } - conditions := ms.ConditionsT{ - "user_id": user.UserId, - "visibility IN ?": visibilities, - "ORDER": "latest_replied_on DESC", - } - if isHighlight { - conditions["is_essence"] = 1 - } - _, posts, err := s.GetTweetList(conditions, (req.Page-1)*req.PageSize, req.PageSize) + posts, total, err := s.Ds.ListUserTweets(user.UserId, style, isHighlight, req.PageSize, (req.Page-1)*req.PageSize) if err != nil { - logrus.Errorf("s.GetTweetList err: %s", err) + logrus.Errorf("s.GetTweetList error[1]: %s", err) return nil, web.ErrGetPostsFailed } - totalRows, err := s.Ds.GetPostCount(conditions) - if err != nil { - logrus.Errorf("s.GetPostCount err: %s", err) + postsFormated, xerr := s.Ds.MergePosts(posts) + if xerr != nil { + logrus.Errorf("s.GetTweetList error[2]: %s", err) + return nil, web.ErrGetPostsFailed + } + userId := int64(-1) + if req.User != nil { + userId = req.User.ID + } + if err := s.PrepareTweets(userId, postsFormated); err != nil { + logrus.Errorf("s.GetTweetList error[3]: %s", err) return nil, web.ErrGetPostsFailed } - resp := base.PageRespFrom(posts, req.Page, req.PageSize, totalRows) - return (*web.GetUserTweetsResp)(resp), nil + resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, total) + return &web.GetUserTweetsResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil } func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) { - he, err := s.Ds.GetUserByUsername(req.Username) + he, err := s.Ds.UserProfileByName(req.Username) if err != nil { - logrus.Errorf("Ds.GetUserByUsername err: %s", err) - return nil, web.ErrNoExistUsername - } - if he.Model == nil && he.ID <= 0 { + logrus.Errorf("looseSrv.GetUserProfile occurs error[1]: %s", err) return nil, web.ErrNoExistUsername } // 设定自己不是自己的朋友 @@ -197,6 +368,7 @@ func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfi CreatedOn: he.CreatedOn, Follows: follows, Followings: followings, + TweetsCount: he.TweetsCount, }, nil } @@ -235,18 +407,18 @@ func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Err }, nil } -func (s *looseSrv) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) { - sort := "id ASC" - if req.SortStrategy == "newest" { - sort = "id DESC" - } - conditions := &ms.ConditionsT{ - "post_id": req.TweetId, - "ORDER": sort, +func (s *looseSrv) TweetComments(req *web.TweetCommentsReq) (res *web.TweetCommentsResp, err mir.Error) { + limit, offset := req.PageSize, (req.Page-1)*req.PageSize + // 尝试直接从缓存中获取数据 + key, ok := "", false + if res, key, ok = s.tweetCommentsFromCache(req, limit, offset); ok { + logrus.Debugf("looseSrv.TweetComments from cache key:%s", key) + return } - comments, err := s.Ds.GetComments(conditions, (req.Page-1)*req.PageSize, req.PageSize) - if err != nil { + comments, totalRows, xerr := s.Ds.GetComments(req.TweetId, req.Style.ToInnerValue(), limit, offset) + if xerr != nil { + logrus.Errorf("looseSrv.TweetComments occurs error[1]: %s", xerr) return nil, web.ErrGetCommentsFailed } @@ -257,25 +429,29 @@ func (s *looseSrv) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsR commentIDs = append(commentIDs, comment.ID) } - users, err := s.Ds.GetUsersByIDs(userIDs) - if err != nil { + users, xerr := s.Ds.GetUsersByIDs(userIDs) + if xerr != nil { + logrus.Errorf("looseSrv.TweetComments occurs error[2]: %s", xerr) return nil, web.ErrGetCommentsFailed } - contents, err := s.Ds.GetCommentContentsByIDs(commentIDs) - if err != nil { + contents, xerr := s.Ds.GetCommentContentsByIDs(commentIDs) + if xerr != nil { + logrus.Errorf("looseSrv.TweetComments occurs error[3]: %s", xerr) return nil, web.ErrGetCommentsFailed } - replies, err := s.Ds.GetCommentRepliesByID(commentIDs) - if err != nil { + replies, xerr := s.Ds.GetCommentRepliesByID(commentIDs) + if xerr != nil { + logrus.Errorf("looseSrv.TweetComments occurs error[4]: %s", xerr) return nil, web.ErrGetCommentsFailed } var commentThumbs, replyThumbs cs.CommentThumbsMap if req.Uid > 0 { - commentThumbs, replyThumbs, err = s.Ds.GetCommentThumbsMap(req.Uid, req.TweetId) - if err != nil { + commentThumbs, replyThumbs, xerr = s.Ds.GetCommentThumbsMap(req.Uid, req.TweetId) + if xerr != nil { + logrus.Errorf("looseSrv.TweetComments occurs error[5]: %s", xerr) return nil, web.ErrGetCommentsFailed } } @@ -315,15 +491,55 @@ func (s *looseSrv) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsR } commentsFormated = append(commentsFormated, commentFormated) } + resp := joint.PageRespFrom(commentsFormated, req.Page, req.PageSize, totalRows) + // 缓存处理 + base.OnCacheRespEvent(s.ac, key, resp, s.tweetCommentsExpire) + return &web.TweetCommentsResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil +} - // 获取总量 - totalRows, _ := s.Ds.GetCommentCount(conditions) - resp := base.PageRespFrom(commentsFormated, req.Page, req.PageSize, totalRows) - return (*web.TweetCommentsResp)(resp), nil +func (s *looseSrv) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) { + post, err := s.Ds.GetPostByID(req.TweetId) + if err != nil { + return nil, web.ErrGetPostFailed + } + postContents, err := s.Ds.GetPostContentsByIDs([]int64{post.ID}) + if err != nil { + return nil, web.ErrGetPostFailed + } + users, err := s.Ds.GetUsersByIDs([]int64{post.UserID}) + if err != nil { + return nil, web.ErrGetPostFailed + } + // 数据整合 + postFormated := post.Format() + for _, user := range users { + postFormated.User = user.Format() + } + for _, content := range postContents { + if content.PostID == post.ID { + postFormated.Contents = append(postFormated.Contents, content.Format()) + } + } + s.PrepareTweet(req.Uid, postFormated) + return (*web.TweetDetailResp)(postFormated), nil } -func newLooseSrv(s *base.DaoServant) api.Loose { +func newLooseSrv(s *base.DaoServant, ac core.AppCache) api.Loose { + cs := conf.CacheSetting return &looseSrv{ - DaoServant: s, + DaoServant: s, + ac: ac, + userTweetsExpire: cs.UserTweetsExpire, + idxTweetsExpire: cs.IndexTweetsExpire, + tweetCommentsExpire: cs.TweetCommentsExpire, + prefixUserTweets: conf.PrefixUserTweets, + prefixIdxTweetsNewest: conf.PrefixIdxTweetsNewest, + prefixIdxTweetsHots: conf.PrefixIdxTweetsHots, + prefixIdxTweetsFollowing: conf.PrefixIdxTweetsFollowing, + prefixTweetComment: conf.PrefixTweetComment, } } diff --git a/internal/servants/web/priv.go b/internal/servants/web/priv.go index f3654415..875a48bf 100644 --- a/internal/servants/web/priv.go +++ b/internal/servants/web/priv.go @@ -10,6 +10,7 @@ import ( "time" "github.com/alimy/mir/v4" + "github.com/alimy/tryst/cfg" "github.com/disintegration/imaging" "github.com/gin-gonic/gin" "github.com/gofrs/uuid/v5" @@ -27,7 +28,8 @@ import ( ) var ( - _ api.Priv = (*privSrv)(nil) + _ api.Priv = (*privSrv)(nil) + _ api.PrivChain = (*privChain)(nil) _uploadAttachmentTypeMap = map[string]ms.AttachmentType{ "public/image": ms.AttachmentTypeImage, @@ -35,12 +37,6 @@ var ( "public/video": ms.AttachmentTypeVideo, "attachment": ms.AttachmentTypeOther, } - _uploadAttachmentTypes = map[string]cs.AttachmentType{ - "public/image": cs.AttachmentTypeImage, - "public/avatar": cs.AttachmentTypeImage, - "public/video": cs.AttachmentTypeVideo, - "attachment": cs.AttachmentTypeOther, - } ) type privSrv struct { @@ -50,6 +46,17 @@ type privSrv struct { oss core.ObjectStorageService } +type privChain struct { + api.UnimplementedPrivChain +} + +func (s *privChain) ChainCreateTweet() (res gin.HandlersChain) { + if cfg.If("UseAuditHook") { + res = gin.HandlersChain{chain.AuditHook()} + } + return +} + func (s *privSrv) Chain() gin.HandlersChain { return gin.HandlersChain{chain.JWT(), chain.Priv()} } @@ -75,6 +82,8 @@ func (s *privSrv) ThumbsDownTweetComment(req *web.TweetCommentThumbsReq) mir.Err logrus.Errorf("thumbs down tweet comment error: %s req:%v", err, req) return web.ErrThumbsDownTweetComment } + // 缓存处理 + onCommentActionEvent(req.TweetId, req.CommentId, _commentActionThumbsDown) return nil } @@ -83,6 +92,8 @@ func (s *privSrv) ThumbsUpTweetComment(req *web.TweetCommentThumbsReq) mir.Error logrus.Errorf("thumbs up tweet comment error: %s req:%v", err, req) return web.ErrThumbsUpTweetComment } + // 缓存处理 + onCommentActionEvent(req.TweetId, req.CommentId, _commentActionThumbsUp) return nil } @@ -242,7 +253,7 @@ func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp, IP: req.ClientIP, IPLoc: utils.GetIPLoc(req.ClientIP), AttachmentPrice: req.AttachmentPrice, - Visibility: req.Visibility, + Visibility: ms.PostVisibleT(req.Visibility.ToVisibleValue()), } post, err = s.Ds.CreatePost(post) if err != nil { @@ -286,8 +297,7 @@ func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp, } // 创建消息提醒 - // TODO: 优化消息提醒处理机制 - go s.Ds.CreateMessage(&ms.Message{ + onCreateMessageEvent(&ms.Message{ SenderUserID: req.User.ID, ReceiverUserID: user.ID, Type: ms.MsgTypePost, @@ -303,6 +313,10 @@ func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp, logrus.Infof("Ds.RevampPosts err: %s", err) return nil, web.ErrCreatePostFailed } + // 缓存处理 + // TODO: 缓存逻辑合并处理 + onTrendsActionEvent(_trendsActionCreateTweet, req.User.ID) + onTweetActionEvent(_tweetActionCreate, req.User.ID, req.User.Username) return (*web.CreateTweetResp)(formatedPosts[0]), nil } @@ -331,6 +345,10 @@ func (s *privSrv) DeleteTweet(req *web.DeleteTweetReq) mir.Error { logrus.Errorf("s.DeleteSearchPost failed: %s", err) return web.ErrDeletePostFailed } + // 缓存处理 + // TODO: 缓存逻辑合并处理 + onTrendsActionEvent(_trendsActionDeleteTweet, req.User.ID) + onTweetActionEvent(_tweetActionDelete, req.User.ID, req.User.Username) return nil } @@ -349,10 +367,14 @@ func (s *privSrv) DeleteCommentReply(req *web.DeleteCommentReplyReq) mir.Error { logrus.Errorf("s.deletePostCommentReply err: %s", err) return web.ErrDeleteCommentFailed } + // 缓存处理, 宽松处理错误 + if comment, err := s.Ds.GetCommentByID(reply.CommentID); err == nil { + onCommentActionEvent(comment.PostID, comment.ID, _commentActionReplyDelete) + } return nil } -func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error) { +func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (_ *web.CreateCommentReplyResp, xerr mir.Error) { var ( post *ms.Post comment *ms.Comment @@ -390,7 +412,7 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat // 创建用户消息提醒 commentMaster, err := s.Ds.GetUserByID(comment.UserID) if err == nil && commentMaster.ID != req.Uid { - go s.Ds.CreateMessage(&ms.Message{ + onCreateMessageEvent(&ms.Message{ SenderUserID: req.Uid, ReceiverUserID: commentMaster.ID, Type: ms.MsgTypeReply, @@ -402,7 +424,7 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat } postMaster, err := s.Ds.GetUserByID(post.UserID) if err == nil && postMaster.ID != req.Uid && commentMaster.ID != postMaster.ID { - go s.Ds.CreateMessage(&ms.Message{ + onCreateMessageEvent(&ms.Message{ SenderUserID: req.Uid, ReceiverUserID: postMaster.ID, Type: ms.MsgTypeReply, @@ -416,7 +438,7 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat user, err := s.Ds.GetUserByID(atUserID) if err == nil && user.ID != req.Uid && commentMaster.ID != user.ID && postMaster.ID != user.ID { // 创建消息提醒 - go s.Ds.CreateMessage(&ms.Message{ + onCreateMessageEvent(&ms.Message{ SenderUserID: req.Uid, ReceiverUserID: user.ID, Type: ms.MsgTypeReply, @@ -427,6 +449,8 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat }) } } + // 缓存处理 + onCommentActionEvent(comment.PostID, comment.ID, _commentActionReplyCreate) return (*web.CreateCommentReplyResp)(reply), nil } @@ -455,9 +479,26 @@ func (s *privSrv) DeleteComment(req *web.DeleteCommentReq) mir.Error { logrus.Errorf("Ds.DeleteComment err: %s", err) return web.ErrDeleteCommentFailed } + onCommentActionEvent(comment.PostID, comment.ID, _commentActionDelete) return nil } +func (s *privSrv) HighlightComment(req *web.HighlightCommentReq) (*web.HighlightCommentResp, mir.Error) { + status, err := s.Ds.HighlightComment(req.Uid, req.CommentId) + if err == cs.ErrNoPermission { + return nil, web.ErrNoPermission + } else if err != nil { + return nil, web.ErrHighlightCommentFailed + } + // 缓存处理, 宽松处理错误 + if comment, err := s.Ds.GetCommentByID(req.CommentId); err == nil { + onCommentActionEvent(comment.PostID, comment.ID, _commentActionHighlight) + } + return &web.HighlightCommentResp{ + HighlightStatus: status, + }, nil +} + func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateCommentResp, xerr mir.Error) { var ( mediaContents []string @@ -522,7 +563,7 @@ func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateComment // 创建用户消息提醒 postMaster, err := s.Ds.GetUserByID(post.UserID) if err == nil && postMaster.ID != req.Uid { - go s.Ds.CreateMessage(&ms.Message{ + onCreateMessageEvent(&ms.Message{ SenderUserID: req.Uid, ReceiverUserID: postMaster.ID, Type: ms.MsgtypeComment, @@ -538,7 +579,7 @@ func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateComment } // 创建消息提醒 - go s.Ds.CreateMessage(&ms.Message{ + onCreateMessageEvent(&ms.Message{ SenderUserID: req.Uid, ReceiverUserID: user.ID, Type: ms.MsgtypeComment, @@ -547,7 +588,8 @@ func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateComment CommentID: comment.ID, }) } - + // 缓存处理 + onCommentActionEvent(comment.PostID, comment.ID, _commentActionCreate) return (*web.CreateCommentResp)(comment), nil } @@ -592,7 +634,7 @@ func (s *privSrv) StarTweet(req *web.StarTweetReq) (*web.StarTweetResp, mir.Erro } func (s *privSrv) VisibleTweet(req *web.VisibleTweetReq) (*web.VisibleTweetResp, mir.Error) { - if req.Visibility >= core.PostVisitInvalid { + if req.Visibility >= web.TweetVisitInvalid { return nil, xerror.InvalidParams } post, err := s.Ds.GetPostByID(req.ID) @@ -602,13 +644,13 @@ func (s *privSrv) VisibleTweet(req *web.VisibleTweetReq) (*web.VisibleTweetResp, if xerr := checkPermision(req.User, post.UserID); xerr != nil { return nil, xerr } - if err = s.Ds.VisiblePost(post, req.Visibility); err != nil { + if err = s.Ds.VisiblePost(post, req.Visibility.ToVisibleValue()); err != nil { logrus.Warnf("s.Ds.VisiblePost: %s", err) return nil, web.ErrVisblePostFailed } // 推送Search - post.Visibility = req.Visibility + post.Visibility = ms.PostVisibleT(req.Visibility.ToVisibleValue()) s.PushPostToSearch(post) return &web.VisibleTweetResp{ @@ -847,3 +889,7 @@ func newPrivSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Priv { oss: oss, } } + +func newPrivChain() api.PrivChain { + return &privChain{} +} diff --git a/internal/servants/web/pub.go b/internal/servants/web/pub.go index f12f74ee..a1611034 100644 --- a/internal/servants/web/pub.go +++ b/internal/servants/web/pub.go @@ -42,32 +42,6 @@ type pubSrv struct { *base.DaoServant } -func (s *pubSrv) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) { - post, err := s.Ds.GetPostByID(req.TweetId) - if err != nil { - return nil, web.ErrGetPostFailed - } - postContents, err := s.Ds.GetPostContentsByIDs([]int64{post.ID}) - if err != nil { - return nil, web.ErrGetPostFailed - } - users, err := s.Ds.GetUsersByIDs([]int64{post.UserID}) - if err != nil { - return nil, web.ErrGetPostFailed - } - // 数据整合 - postFormated := post.Format() - for _, user := range users { - postFormated.User = user.Format() - } - for _, content := range postContents { - if content.PostID == post.ID { - postFormated.Contents = append(postFormated.Contents, content.Format()) - } - } - return (*web.TweetDetailResp)(postFormated), nil -} - func (s *pubSrv) SendCaptcha(req *web.SendCaptchaReq) mir.Error { ctx := context.Background() diff --git a/internal/servants/web/relax.go b/internal/servants/web/relax.go new file mode 100644 index 00000000..4c230f66 --- /dev/null +++ b/internal/servants/web/relax.go @@ -0,0 +1,64 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "github.com/alimy/mir/v4" + "github.com/gin-gonic/gin" + "github.com/redis/rueidis" + api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model/web" + "github.com/rocboss/paopao-ce/internal/servants/base" + "github.com/rocboss/paopao-ce/internal/servants/chain" + "github.com/sirupsen/logrus" +) + +var ( + _ api.Relax = (*relaxSrv)(nil) +) + +type relaxSrv struct { + api.UnimplementedRelaxServant + *base.DaoServant + wc core.WebCache +} + +type relaxChain struct { + api.UnimplementedRelaxChain +} + +func (s *relaxChain) ChainGetUnreadMsgCount() gin.HandlersChain { + return gin.HandlersChain{chain.OnlineUserMeasure()} +} + +func (s *relaxSrv) Chain() gin.HandlersChain { + return gin.HandlersChain{chain.JwtSurely()} +} + +func (s *relaxSrv) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) { + if data, xerr := s.wc.GetUnreadMsgCountResp(req.Uid); xerr == nil && len(data) > 0 { + // logrus.Debugln("GetUnreadMsgCount get resp from cache") + return &web.GetUnreadMsgCountResp{ + JsonResp: data, + }, nil + } else if !rueidis.IsRedisNil(xerr) { + logrus.Warnf("GetUnreadMsgCount from cache occurs error: %s", xerr) + } + // 使用缓存机制特殊处理 + onCacheUnreadMsgEvent(req.Uid) + return &web.GetUnreadMsgCountResp{}, nil +} + +func newRelaxSrv(s *base.DaoServant, wc core.WebCache) api.Relax { + return &relaxSrv{ + DaoServant: s, + wc: wc, + } +} + +func newRelaxChain() api.RelaxChain { + return &relaxChain{} +} diff --git a/internal/servants/web/site.go b/internal/servants/web/site.go new file mode 100644 index 00000000..ce137fbf --- /dev/null +++ b/internal/servants/web/site.go @@ -0,0 +1,39 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "github.com/alimy/mir/v4" + api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/model/web" + "github.com/rocboss/paopao-ce/internal/servants/base" + "github.com/rocboss/paopao-ce/pkg/version" +) + +var ( + _ api.Site = (*siteSrv)(nil) +) + +type siteSrv struct { + api.UnimplementedSiteServant + *base.BaseServant +} + +func (*siteSrv) Profile() (*web.SiteProfileResp, mir.Error) { + return conf.WebProfileSetting, nil +} + +func (*siteSrv) Version() (*web.VersionResp, mir.Error) { + return &web.VersionResp{ + BuildInfo: version.ReadBuildInfo(), + }, nil +} + +func newSiteSrv() api.Site { + return &siteSrv{ + BaseServant: base.NewBaseServant(), + } +} diff --git a/internal/servants/web/trends.go b/internal/servants/web/trends.go new file mode 100644 index 00000000..58171606 --- /dev/null +++ b/internal/servants/web/trends.go @@ -0,0 +1,81 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package web + +import ( + "fmt" + + "github.com/alimy/mir/v4" + "github.com/gin-gonic/gin" + api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model/joint" + "github.com/rocboss/paopao-ce/internal/model/web" + "github.com/rocboss/paopao-ce/internal/servants/base" + "github.com/rocboss/paopao-ce/internal/servants/chain" + "github.com/sirupsen/logrus" +) + +var ( + _ api.Trends = (*trendsSrv)(nil) +) + +type trendsSrv struct { + api.UnimplementedTrendsServant + *base.DaoServant + ac core.AppCache + indexTrendsExpire int64 + prefixTrends string +} + +func (s *trendsSrv) Chain() gin.HandlersChain { + return gin.HandlersChain{chain.JWT()} +} + +func (s *trendsSrv) GetIndexTrends(req *web.GetIndexTrendsReq) (res *web.GetIndexTrendsResp, _ mir.Error) { + limit, offset := req.PageSize, (req.Page-1)*req.PageSize + // 尝试直接从缓存中获取数据 + key, ok := "", false + if res, key, ok = s.trendsFromCache(req, limit, offset); ok { + // logrus.Debugf("trendsSrv.GetIndexTrends from cache key:%s", key) + return + } + trends, totalRows, err := s.Ds.GetIndexTrends(req.Uid, limit, offset) + if err != nil { + logrus.Errorf("Ds.GetIndexTrends err[1]: %s", err) + return nil, web.ErrGetIndexTrendsFailed + } + resp := joint.PageRespFrom(trends, req.Page, req.PageSize, totalRows) + // 缓存处理 + base.OnCacheRespEvent(s.ac, key, resp, s.indexTrendsExpire) + return &web.GetIndexTrendsResp{ + CachePageResp: joint.CachePageResp{ + Data: resp, + }, + }, nil +} + +func (s *trendsSrv) trendsFromCache(req *web.GetIndexTrendsReq, limit int, offset int) (res *web.GetIndexTrendsResp, key string, ok bool) { + key = fmt.Sprintf("%s%d:%d:%d", s.prefixTrends, req.Uid, limit, offset) + if data, err := s.ac.Get(key); err == nil { + ok, res = true, &web.GetIndexTrendsResp{ + CachePageResp: joint.CachePageResp{ + JsonResp: data, + }, + } + } + return +} + +func newTrendsSrv(s *base.DaoServant) api.Trends { + cs := conf.CacheSetting + return &trendsSrv{ + DaoServant: s, + ac: _ac, + indexTrendsExpire: cs.IndexTrendsExpire, + prefixTrends: conf.PrefixIdxTrends, + } +} diff --git a/internal/servants/web/web.go b/internal/servants/web/web.go index e58a426b..7f95eee2 100644 --- a/internal/servants/web/web.go +++ b/internal/servants/web/web.go @@ -7,39 +7,49 @@ package web import ( "sync" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/gin-gonic/gin" api "github.com/rocboss/paopao-ce/auto/api/v1" "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/dao" + "github.com/rocboss/paopao-ce/internal/dao/cache" "github.com/rocboss/paopao-ce/internal/servants/base" ) var ( _enablePhoneVerify bool _disallowUserRegister bool + _ds core.DataService + _ac core.AppCache + _wc core.WebCache + _oss core.ObjectStorageService _onceInitial sync.Once ) // RouteWeb register web route func RouteWeb(e *gin.Engine) { lazyInitial() - oss := dao.ObjectStorageService() ds := base.NewDaoServant() // aways register servants - api.RegisterAdminServant(e, newAdminSrv(ds)) - api.RegisterCoreServant(e, newCoreSrv(ds, oss)) - api.RegisterLooseServant(e, newLooseSrv(ds)) - api.RegisterPrivServant(e, newPrivSrv(ds, oss)) + api.RegisterAdminServant(e, newAdminSrv(ds, _wc)) + api.RegisterCoreServant(e, newCoreSrv(ds, _oss, _wc)) + api.RegisterRelaxServant(e, newRelaxSrv(ds, _wc), newRelaxChain()) + api.RegisterLooseServant(e, newLooseSrv(ds, _ac)) + api.RegisterPrivServant(e, newPrivSrv(ds, _oss), newPrivChain()) api.RegisterPubServant(e, newPubSrv(ds)) + api.RegisterTrendsServant(e, newTrendsSrv(ds)) api.RegisterFollowshipServant(e, newFollowshipSrv(ds)) api.RegisterFriendshipServant(e, newFriendshipSrv(ds)) + api.RegisterSiteServant(e, newSiteSrv()) // regster servants if needed by configure cfg.Be("Alipay", func() { client := conf.MustAlipayClient() api.RegisterAlipayPubServant(e, newAlipayPubSrv(ds)) api.RegisterAlipayPrivServant(e, newAlipayPrivSrv(ds, client)) }) + // shedule jobs if need + scheduleJobs() } // lazyInitial do some package lazy initialize for performance @@ -47,5 +57,11 @@ func lazyInitial() { _onceInitial.Do(func() { _enablePhoneVerify = cfg.If("Sms") _disallowUserRegister = cfg.If("Web:DisallowUserRegister") + _maxWhisperNumDaily = conf.AppSetting.MaxWhisperDaily + _maxCaptchaTimes = conf.AppSetting.MaxCaptchaTimes + _oss = dao.ObjectStorageService() + _ds = dao.DataService() + _ac = cache.NewAppCache() + _wc = cache.NewWebCache() }) } diff --git a/internal/service/http_service.go b/internal/service/http_service.go index df93786c..0cc094df 100644 --- a/internal/service/http_service.go +++ b/internal/service/http_service.go @@ -15,7 +15,9 @@ type baseHttpService struct { } func (s *baseHttpService) registerRoute(srv Service, h func(e *gin.Engine)) { - h(s.server.e) + if h != nil { + h(s.server.e) + } s.server.addService(srv) } diff --git a/internal/service/metrics.go b/internal/service/metrics.go new file mode 100644 index 00000000..d83a5fd4 --- /dev/null +++ b/internal/service/metrics.go @@ -0,0 +1,63 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package service + +import ( + "fmt" + "net/http" + + "github.com/Masterminds/semver/v3" + "github.com/fatih/color" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/dao" + "github.com/rocboss/paopao-ce/internal/dao/cache" + "github.com/rocboss/paopao-ce/internal/metrics/prometheus" +) + +var ( + _ Service = (*metricsService)(nil) +) + +type metricsService struct { + *baseHttpService +} + +func (s *metricsService) Name() string { + return "MetricsService" +} + +func (s *metricsService) Version() *semver.Version { + return semver.MustParse("v0.1.0") +} + +func (s *metricsService) OnInit() error { + s.registerRoute(s, nil) + return nil +} + +func (s *metricsService) String() string { + return fmt.Sprintf("listen on %s\n", color.GreenString("http://%s:%s", conf.MetricsServerSetting.HttpIp, conf.MetricsServerSetting.HttpPort)) +} + +func newMetricsService() Service { + addr := conf.MetricsServerSetting.HttpIp + ":" + conf.MetricsServerSetting.HttpPort + server := httpServers.from(addr, func() *httpServer { + ds, wc := dao.DataService(), cache.NewWebCache() + mux := http.NewServeMux() + mux.Handle("/metrics", prometheus.NewHandler(ds, wc)) + return &httpServer{ + baseServer: newBaseServe(), + server: &http.Server{ + Addr: addr, + Handler: mux, + }, + } + }) + return &metricsService{ + baseHttpService: &baseHttpService{ + server: server, + }, + } +} diff --git a/internal/service/pprof.go b/internal/service/pprof.go index 80f8c07e..97501c32 100644 --- a/internal/service/pprof.go +++ b/internal/service/pprof.go @@ -10,7 +10,6 @@ import ( "github.com/Masterminds/semver/v3" "github.com/fatih/color" - "github.com/gin-gonic/gin" "github.com/rocboss/paopao-ce/internal/conf" ) @@ -31,7 +30,7 @@ func (s *pprofService) Version() *semver.Version { } func (s *pprofService) OnInit() error { - s.registerRoute(s, func(*gin.Engine) {}) + s.registerRoute(s, nil) return nil } @@ -43,10 +42,8 @@ func newPprofService() Service { addr := conf.PprofServerSetting.HttpIp + ":" + conf.PprofServerSetting.HttpPort // notice this step just to register pprof server to start. don't share server with pprof. server := httpServers.from(addr, func() *httpServer { - engine := newWebEngine() return &httpServer{ baseServer: newBaseServe(), - e: engine, server: &http.Server{ Addr: addr, Handler: http.DefaultServeMux, diff --git a/internal/service/service.go b/internal/service/service.go index ad248695..d85692da 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -8,7 +8,7 @@ import ( "log" "github.com/Masterminds/semver/v3" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/rocboss/paopao-ce/pkg/types" ) @@ -75,6 +75,9 @@ func newService() (ss []Service) { "Pprof": func() { ss = append(ss, newPprofService()) }, + "Metrics": func() { + ss = append(ss, newMetricsService()) + }, }) return } diff --git a/internal/service/web.go b/internal/service/web.go index c158f732..ec1c353e 100644 --- a/internal/service/web.go +++ b/internal/service/web.go @@ -30,7 +30,7 @@ func (s *webService) Name() string { } func (s *webService) Version() *semver.Version { - return semver.MustParse("v0.1.0") + return semver.MustParse("v0.5.0") } func (s *webService) OnInit() error { diff --git a/mirc/web/v1/admin.go b/mirc/web/v1/admin.go index 49c07ce4..cbd71d26 100644 --- a/mirc/web/v1/admin.go +++ b/mirc/web/v1/admin.go @@ -16,5 +16,6 @@ type Admin struct { Group `mir:"v1"` // ChangeUserStatus 管理·禁言/解封用户 - ChangeUserStatus func(Post, web.ChangeUserStatusReq) `mir:"/admin/user/status"` + ChangeUserStatus func(Post, web.ChangeUserStatusReq) `mir:"/admin/user/status"` + SiteInfo func(Get, web.SiteInfoReq) web.SiteInfoResp `mir:"/admin/site/status"` } diff --git a/mirc/web/v1/core.go b/mirc/web/v1/core.go index f12c7018..28d93177 100644 --- a/mirc/web/v1/core.go +++ b/mirc/web/v1/core.go @@ -21,15 +21,15 @@ type Core struct { // GetUserInfo 获取当前用户信息 GetUserInfo func(Get, web.UserInfoReq) web.UserInfoResp `mir:"/user/info"` - // GetUnreadMsgCount 获取当前用户未读消息数量 - GetUnreadMsgCount func(Get, web.GetUnreadMsgCountReq) web.GetUnreadMsgCountResp `mir:"/user/msgcount/unread"` - // GetMessages 获取消息列表 GetMessages func(Get, web.GetMessagesReq) web.GetMessagesResp `mir:"/user/messages"` - // ReadMessage 标记消息已读 + // ReadMessage 标记未读消息已读 ReadMessage func(Post, web.ReadMessageReq) `mir:"/user/message/read"` + // ReadAllMessage 标记所有未读消息已读 + ReadAllMessage func(Post, web.ReadAllMessageReq) `mir:"/user/message/readall"` + // SendUserWhisper 发送用户私信 SendUserWhisper func(Post, web.SendWhisperReq) `mir:"/user/whisper"` diff --git a/mirc/web/v1/loose.go b/mirc/web/v1/loose.go index a7fe6d7e..0c6ecf51 100644 --- a/mirc/web/v1/loose.go +++ b/mirc/web/v1/loose.go @@ -29,4 +29,7 @@ type Loose struct { // TweetComments 获取动态评论 TweetComments func(Get, web.TweetCommentsReq) web.TweetCommentsResp `mir:"/post/comments"` + + // TweetDetail 获取动态详情 + TweetDetail func(Get, web.TweetDetailReq) web.TweetDetailResp `mir:"/post"` } diff --git a/mirc/web/v1/priv.go b/mirc/web/v1/priv.go index 567de00d..414fb55d 100644 --- a/mirc/web/v1/priv.go +++ b/mirc/web/v1/priv.go @@ -25,7 +25,7 @@ type Priv struct { DownloadAttachment func(Get, web.DownloadAttachmentReq) web.DownloadAttachmentResp `mir:"/attachment"` // CreateTweet 发布动态 - CreateTweet func(Post, web.CreateTweetReq) web.CreateTweetResp `mir:"/post"` + CreateTweet func(Post, Chain, web.CreateTweetReq) web.CreateTweetResp `mir:"/post"` // DeleteTweet 删除动态 DeleteTweet func(Delete, web.DeleteTweetReq) `mir:"/post"` @@ -54,6 +54,9 @@ type Priv struct { // DeletePostComment 删除动态评论 DeleteComment func(Delete, web.DeleteCommentReq) `mir:"/post/comment"` + // HighlightComment 精选动态评论 + HighlightComment func(Post, web.HighlightCommentReq) web.HighlightCommentResp `mir:"/post/comment/highlight"` + // CreateCommentReply 发布评论回复 CreateCommentReply func(Post, web.CreateCommentReplyReq) web.CreateCommentReplyResp `mir:"/post/comment/reply"` @@ -66,7 +69,7 @@ type Priv struct { // ThumbsDownTweetComment 点踩评论 ThumbsDownTweetComment func(Post, web.TweetCommentThumbsReq) `mir:"/tweet/comment/thumbsdown"` - // ThumbsUpTweetReply 点赞评论回复 + // ThumbsUpTweetReply 点赞评论回复· ThumbsUpTweetReply func(Post, web.TweetReplyThumbsReq) `mir:"/tweet/reply/thumbsup"` // ThumbsDownTweetReply 点踩评论回复 diff --git a/mirc/web/v1/pub.go b/mirc/web/v1/pub.go index 89a90162..60fb182d 100644 --- a/mirc/web/v1/pub.go +++ b/mirc/web/v1/pub.go @@ -28,7 +28,4 @@ type Pub struct { // SendCaptcha 发送验证码 SendCaptcha func(Post, web.SendCaptchaReq) `mir:"/captcha"` - - // TweetDetail 获取动态详情 - TweetDetail func(Get, web.TweetDetailReq) web.TweetDetailResp `mir:"/post"` } diff --git a/mirc/web/v1/relax.go b/mirc/web/v1/relax.go new file mode 100644 index 00000000..3bdfdb91 --- /dev/null +++ b/mirc/web/v1/relax.go @@ -0,0 +1,20 @@ +package v1 + +import ( + . "github.com/alimy/mir/v4" + . "github.com/alimy/mir/v4/engine" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +func init() { + Entry[Relax]() +} + +// Relax 放宽授权的服务 +type Relax struct { + Chain `mir:"-"` + Group `mir:"v1"` + + // GetUnreadMsgCount 获取当前用户未读消息数量 + GetUnreadMsgCount func(Get, Chain, web.GetUnreadMsgCountReq) web.GetUnreadMsgCountResp `mir:"/user/msgcount/unread"` +} diff --git a/mirc/web/v1/site.go b/mirc/web/v1/site.go new file mode 100644 index 00000000..ba8ca294 --- /dev/null +++ b/mirc/web/v1/site.go @@ -0,0 +1,22 @@ +package v1 + +import ( + . "github.com/alimy/mir/v4" + . "github.com/alimy/mir/v4/engine" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +func init() { + Entry[Site]() +} + +// Site 站点本身相关的信息服务 +type Site struct { + Group `mir:"v1"` + + // Version 获取后台版本信息 + Version func(Get) web.VersionResp `mir:"/site/version"` + + // Profile 站点配置概要信息 + Profile func(Get) web.SiteProfileResp `mir:"/site/profile"` +} diff --git a/mirc/web/v1/trends.go b/mirc/web/v1/trends.go new file mode 100644 index 00000000..c6bc0f91 --- /dev/null +++ b/mirc/web/v1/trends.go @@ -0,0 +1,20 @@ +package v1 + +import ( + . "github.com/alimy/mir/v4" + . "github.com/alimy/mir/v4/engine" + "github.com/rocboss/paopao-ce/internal/model/web" +) + +func init() { + Entry[Trends]() +} + +// Trends 动态相关 服务 +type Trends struct { + Chain `mir:"-"` + Group `mir:"v1"` + + // GetIndexTrends 获取广场页面动态条栏的索引item + GetIndexTrends func(Get, web.GetIndexTrendsReq) web.GetIndexTrendsResp `mir:"/trends/index"` +} diff --git a/pkg/app/jwt.go b/pkg/app/jwt.go index a861faa2..75a43f7a 100644 --- a/pkg/app/jwt.go +++ b/pkg/app/jwt.go @@ -5,9 +5,11 @@ package app import ( + "crypto/md5" + "encoding/hex" "time" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core/ms" ) @@ -22,14 +24,14 @@ func GetJWTSecret() []byte { return []byte(conf.JWTSetting.Secret) } -func GenerateToken(User *ms.User) (string, error) { +func GenerateToken(user *ms.User) (string, error) { expireTime := time.Now().Add(conf.JWTSetting.Expire) claims := Claims{ - UID: User.ID, - Username: User.Username, + UID: user.ID, + Username: user.Username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expireTime), - Issuer: conf.JWTSetting.Issuer + ":" + User.Salt, + Issuer: IssuerFrom(user.Salt), }, } @@ -38,18 +40,23 @@ func GenerateToken(User *ms.User) (string, error) { return token, err } -func ParseToken(token string) (*Claims, error) { - tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (any, error) { +func ParseToken(token string) (res *Claims, err error) { + var tokenClaims *jwt.Token + tokenClaims, err = jwt.ParseWithClaims(token, &Claims{}, func(_ *jwt.Token) (any, error) { return GetJWTSecret(), nil }) - if err != nil { - return nil, err - } - if tokenClaims != nil { - if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { - return claims, nil - } + if err == nil && tokenClaims != nil && tokenClaims.Valid { + res, _ = tokenClaims.Claims.(*Claims) + } else { + err = jwt.ErrTokenNotValidYet } + return +} - return nil, err +func IssuerFrom(data string) string { + contents := make([]byte, 0, len(conf.JWTSetting.Issuer)+len(data)) + copy(contents, []byte(conf.JWTSetting.Issuer)) + contents = append(contents, []byte(data)...) + res := md5.Sum(contents) + return hex.EncodeToString(res[:]) } diff --git a/pkg/debug/pyroscope.go b/pkg/debug/pyroscope.go index 60a8ed74..f34acfb5 100644 --- a/pkg/debug/pyroscope.go +++ b/pkg/debug/pyroscope.go @@ -8,7 +8,7 @@ package debug import ( - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/sirupsen/logrus" ) diff --git a/pkg/debug/pyroscope_embed.go b/pkg/debug/pyroscope_embed.go index 80a9e149..73db8870 100644 --- a/pkg/debug/pyroscope_embed.go +++ b/pkg/debug/pyroscope_embed.go @@ -10,7 +10,7 @@ package debug import ( "os" - "github.com/alimy/cfg" + "github.com/alimy/tryst/cfg" "github.com/pyroscope-io/client/pyroscope" "github.com/rocboss/paopao-ce/internal/conf" "github.com/sirupsen/logrus" diff --git a/pkg/http/client.go b/pkg/http/client.go new file mode 100644 index 00000000..d77c1d14 --- /dev/null +++ b/pkg/http/client.go @@ -0,0 +1,74 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package http + +import ( + "net/http" + "time" + + gp "github.com/alimy/tryst/pool" +) + +var ( + _ AsyncClient = (*wormClient)(nil) +) + +const ( + _minRequestBuf = 10 + _minRequestTempBuf = 10 + _minWorker = 5 +) + +// ResponseFn a function used handle the response of http.Client.Do +type ResponseFn = gp.ResponseFn[*http.Request, *http.Response] + +// AsyncClient asynchronous client interface +type AsyncClient interface { + Do(req *http.Request, fn ResponseFn) +} + +// AsyncClientConf client configure used to create an AsynClient instance +type AsyncClientConf struct { + MinWorker int + MaxRequestBuf int + MaxRequestTempBuf int + MaxTickCount int + TickWaitTime time.Duration +} + +type wormClient struct { + pool gp.GoroutinePool[*http.Request, *http.Response] +} + +func (s *wormClient) Do(req *http.Request, fn ResponseFn) { + s.pool.Do(req, fn) +} + +// NewAsyncClient create an AsyncClient instance +func NewAsyncClient(client *http.Client, conf *AsyncClientConf) AsyncClient { + minWorker := _minWorker + maxRequestBuf := _minRequestBuf + maxRequestTempBuf := _minRequestTempBuf + if conf.MaxRequestBuf > _minRequestBuf { + maxRequestBuf = conf.MaxRequestBuf + } + if conf.MaxRequestTempBuf > _minRequestTempBuf { + maxRequestTempBuf = conf.MaxRequestTempBuf + } + if conf.MinWorker > _minWorker { + minWorker = conf.MinWorker + } + return &wormClient{ + pool: gp.NewGoroutinePool(func(req *http.Request) (*http.Response, error) { + return client.Do(req) + }, + gp.MinWorkerOpt(minWorker), + gp.MaxRequestBufOpt(maxRequestBuf), + gp.MaxRequestTempBufOpt(maxRequestTempBuf), + gp.MaxTickCountOpt(conf.MaxTickCount), + gp.TickWaitTimeOpt(conf.TickWaitTime), + ), + } +} diff --git a/pkg/http/http.go b/pkg/http/http.go index efb096d7..86e5035b 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -1,5 +1,7 @@ -// Copyright 2023 Michael Li{yt[t]=rn(e,t)}),yt};nn(tn);let on=Jr;function an(e){let t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}const ln=/\B([A-Z])/g,sn=an(e=>e.replace(ln,"-$1").toLowerCase()),bt=(e,t,r,n="0")=>{if(typeof e=="string"&&typeof n=="string"&&Ve(t)){let a=e.length-t;if(a>0)return r?e.substr(0,t):e.substr(a,t);{const o=[];for(a=Math.abs(a)/n.length;a>0;a--)o.push(n);const i=o.join("");return r?e+i:i+e}}};on.random;const Bt=[31,28,31,30,31,30,31,31,30,31,30,31],cn=new RegExp("^(\\d{4})(/|-)(((0)?[1-9])|(1[0-2]))((/|-)(((0)?[1-9])|([1-2][0-9])|(3[0-1])))?( ((0)?[0-9]|1[0-9]|20|21|22|23):([0-5]?[0-9])((:([0-5]?[0-9]))?(.([0-9]{1,6}))?)?)?$"),dn=new RegExp("^(((0)?[1-9])|(1[0-2]))(/|-)(((0)?[1-9])|([1-2][0-9])|(3[0-1]))?(/|-)?(\\d{4})( ((0)?[0-9]|1[0-9]|20|21|22|23):([0-5]?[0-9])((:([0-5]?[0-9]))?(.([0-9]{1,6}))?)?)?$"),un=new RegExp("^(\\d{4})-(((0)?[1-9])|(1[0-2]))-(((0)?[1-9])|([1-2][0-9])|(3[0-1]))T(((0)?[0-9]|1[0-9]|20|21|22|23):([0-5]?[0-9])((:([0-5]?[0-9]))?(.([0-9]{1,6}))?)?)?(Z|([+-])((0)?[0-9]|1[0-9]|20|21|22|23):?([0-5]?[0-9]))$"),j={YEAR:9999,MONTH:11,DATE:31,HOUR:23,MINUTE:59,SECOND:59,MILLISECOND:999},pn="-12:00,-11:00,-10:00,-09:30,-08:00,-07:00,-06:00,-05:00,-04:30,-04:00,-03:30,-02:00,-01:00",fn="-00:00,+00:00,+01:00,+02:00,+03:00,+03:30,+04:00,+04:30,+05:00,+05:30,+05:45,+06:00",mn="+06:30,+07:00,+08:00,+09:00,+10:00,+10:30,+11:00,+11:30,+12:00,+12:45,+13:00,+14:00",gn=[].concat(pn.split(","),fn.split(","),mn.split(",")),Ut=e=>e%400===0||e%4===0&&e%100!==0,Ze=({year:e,month:t,date:r,hours:n,minutes:a,seconds:o,milliseconds:i})=>{let l=Bt[t];if(Ut(e)&&t===1&&(l+=1),r<=l)return new Date(e,t,r,n,a,o,i)},hn=e=>{if(e.length===23){const t=Number(e[1]),r=e[3]-1,n=Number(e[9]||1),a=e[15]||0,o=e[17]||0,i=e[20]||0,l=e[22]||0;return Ze({date:n,year:t,hours:a,month:r,seconds:i,minutes:o,milliseconds:l})}},vn=e=>{if(e.length===22){const t=Number(e[12]),r=e[1]-1,n=Number(e[6]||1),a=e[14]||0,o=e[16]||0,i=e[19]||0,l=e[21]||0;return Ze({year:t,month:r,date:n,hours:a,minutes:o,seconds:i,milliseconds:l})}},yn=e=>{if(e.length!==25)return;const t=Number(e[1]),r=e[2]-1,n=Number(e[6]),a=new Date(t,r,n).getTimezoneOffset(),o=e[12]||0,i=e[14]||0,l=e[17]||0,s=e[19]||0;let c=e[20];const d=e[21],u=e[22]||0,p=e[24]||0;let f=Bt[r],g,v;if(Ut(t)&&r===1&&(f+=1),n<=f){if(c==="Z")g=o-a/60,v=i;else{if(c.includes(":")||(c=c.substr(0,3)+":"+c.substr(3)),!gn.includes(c))return;g=d==="+"?o-u-a/60:Number(o)+Number(u)-a/60,v=d==="+"?i-p:Number(i)+Number(p)}return new Date(t,r,n,g,v,l,s)}},De=[[cn,hn],[dn,vn],[un,yn]],bn=e=>{for(let t=0,r=De.length;t{const t=new Set(e);return t.w=0,t.n=0,t},Qr=e=>(e.w&ze)>0,zr=e=>(e.n&ze)>0,Lo=({deps:e})=>{if(e.length)for(let t=0;t{const t=new Set(e);return t.w=0,t.n=0,t},hr=e=>(e.w&ke)>0,pr=e=>(e.n&ke)>0,Lo=({deps:e})=>{if(e.length)for(let t=0;t