Merge pull request #398 from rocboss/r/paopao-ce

v0.5.0 prepare release code review request
beta
北野 - Michael Li 9 months ago committed by GitHub
commit 1bffc9243a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -12,3 +12,4 @@ __debug_bin
/data
/custom
/.custom
/run.sh

@ -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;

@ -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

@ -39,7 +39,7 @@ Web端
更多演示请前往[官网](https://www.paopao.info)体验(谢绝灌水)
桌面端:
![](docs/proposal/.assets/000-00.png)
![](docs/proposal/.assets/000-00.jpg)
<p align="right">(<a href="#top">back to top</a>)</p>
@ -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"]
...

@ -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

@ -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))
}

@ -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))
}

@ -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))
}

@ -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() {}

@ -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))
}

@ -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() {}

@ -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() {}

@ -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() {}

@ -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

@ -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"

@ -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"

Binary file not shown.

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

@ -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` 不允许用户注册;

@ -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

146
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=

@ -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
})

@ -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)
}
}

@ -29,6 +29,8 @@ func MustRedisClient() rueidis.Client {
log.Fatalf("create a redis client failed: %s", err)
}
_redisClient = client
// 顺便初始化一下CacheKeyPool
initCacheKeyPool()
})
return _redisClient
}

@ -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

@ -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#=%s&#m#=%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秒
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"

@ -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"
)

@ -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"

@ -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)
},
})
}

@ -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)
}
}),
}
}

@ -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"
)

@ -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,
}

@ -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)
}

@ -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)

@ -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

@ -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"`

@ -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

@ -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)
}

@ -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
}

@ -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
}

@ -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"
}
}

@ -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)
}

@ -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
}

@ -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 (

@ -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

@ -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)
}

@ -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

@ -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)
}

@ -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)
})
}

@ -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...)
}

@ -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
}

@ -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 {

@ -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,
}
}

@ -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,
}
}

@ -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"

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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"
}

@ -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
}

@ -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]
}

@ -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) {

@ -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
}

@ -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,
}
}

@ -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,
}
}

@ -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)
}

@ -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
}

@ -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

@ -3,7 +3,7 @@ package security
import (
"strings"
"github.com/alimy/cfg"
"github.com/alimy/tryst/cfg"
"github.com/rocboss/paopao-ce/internal/core"
)

@ -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"

@ -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...)
}

@ -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...),
}
}

@ -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(),
}
}

@ -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()
}

@ -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...)
}

@ -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...),
}
}

@ -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
}

@ -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})
}

@ -8,7 +8,7 @@
package migration
import (
"github.com/alimy/cfg"
"github.com/alimy/tryst/cfg"
"github.com/sirupsen/logrus"
)

@ -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"

@ -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"`
}

@ -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)
}

@ -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,
},
}
}

@ -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"`
}

@ -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
}

@ -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)
}

@ -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:"-"`

@ -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
}

@ -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
}

@ -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"`

@ -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,
})
}
}

@ -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

@ -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
}

@ -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, "更行话题置顶状态失败")

@ -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,

@ -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()
}

@ -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)
}
}

@ -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
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save