Merge pull request #563 from rocboss/dev

sync source code
pull/572/head
北野 - Michael Li 2 years ago committed by GitHub
commit eb9fd0fbf5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
.gitattributes vendored

@ -1,3 +1,4 @@
*.ts linguist-language=go
*.js linguist-language=go *.js linguist-language=go
*.css linguist-language=go *.css linguist-language=go
*.vue linguist-language=go *.vue linguist-language=go

@ -47,7 +47,7 @@ jobs:
name: Test name: Test
strategy: strategy:
matrix: matrix:
go-version: [ 1.20.x, 1.21.x ] go-version: [ 1.21.x, 1.22.x ]
platform: [ ubuntu-latest, macos-latest ] platform: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
@ -66,7 +66,7 @@ jobs:
name: TestOnWindows name: TestOnWindows
strategy: strategy:
matrix: matrix:
go-version: [ 1.20.x, 1.21.x ] go-version: [ 1.21.x, 1.22.x ]
platform: [ windows-latest ] platform: [ windows-latest ]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:

@ -2,7 +2,11 @@
All notable changes to paopao-ce are documented in this file. All notable changes to paopao-ce are documented in this file.
## 0.6.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; ### Added
- add all-in-one docker image build scripts.
- frontend: add tweets filter support use tag for home page and make it as default behavior.
- add pin topic support.
- support upload webp format image as picture when send tweet.
## 0.5.2 ## 0.5.2
### Change ### Change

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:experimental # syntax=docker/dockerfile:1
# build frontend # build frontend
FROM node:19-alpine as frontend FROM node:19-alpine as frontend
@ -22,9 +22,9 @@ ARG USE_DIST=no
WORKDIR /paopao-ce WORKDIR /paopao-ce
COPY . . COPY . .
COPY --from=frontend /web/dist ./web/dist COPY --from=frontend /web/dist ./web/dist
ENV GOPROXY=https://goproxy.cn ENV GOPROXY=https://goproxy.cn,direct
RUN [ $EMBED_UI != yes ] || make build TAGS='go_json' RUN --mount=type=cache,target=$GOPATH/go/pkg,id=paopao-ce-gopkg [ $EMBED_UI != yes ] || make buildx TAGS='go_json'
RUN [ $EMBED_UI = yes ] || make build TAGS='slim embed go_json' RUN --mount=type=cache,target=$GOPATH/go/pkg,id=paopao-ce-gopkg [ $EMBED_UI = yes ] || make buildx TAGS='slim embed go_json'
FROM bitbus/paopao-ce-backend-runner:latest FROM bitbus/paopao-ce-backend-runner:latest
ARG API_HOST ARG API_HOST

@ -0,0 +1,37 @@
# syntax=docker/dockerfile:1
# build frontend
FROM node:19-alpine as frontend
ARG API_HOST
ARG USE_API_HOST=yes
ARG EMBED_UI=yes
ARG USE_DIST=no
WORKDIR /web
COPY web/ ./
RUN [ $EMBED_UI != yes ] || [ $USE_API_HOST != yes ] || echo "VITE_HOST='$API_HOST'">.env.local
RUN [ $EMBED_UI != yes ] || [ $USE_DIST != no ] || (yarn && yarn build)
RUN [ $EMBED_UI = yes ] || mkdir dist || echo ""
# build backend
FROM bitbus/paopao-ce-backend-builder:latest AS backend
ARG API_HOST
ARG USE_API_HOST=yes
ARG EMBED_UI=yes
ARG USE_DIST=no
WORKDIR /paopao-ce
COPY . .
COPY --from=frontend /web/dist ./web/dist
ENV GOPROXY=https://goproxy.cn,direct
RUN --mount=type=cache,target=$GOPATH/go/pkg,id=paopao-ce-gopkg [ $EMBED_UI != yes ] || make buildx TAGS='go_json migration'
RUN --mount=type=cache,target=$GOPATH/go/pkg,id=paopao-ce-gopkg [ $EMBED_UI = yes ] || make buildx TAGS='slim embed go_json migration'
FROM bitbus/paopao-ce-allinone-runner:latest
ARG API_HOST
ARG USE_API_HOST=yes
ARG EMBED_UI=yes
ARG USE_DIST=no
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=backend /paopao-ce/release/paopao .

@ -32,6 +32,10 @@ LDFLAGS = -X "${MOD_NAME}/pkg/version.version=${BUILD_VERSION}" \
all: fmt build all: fmt build
build: build:
@echo Build paopao-ce
@go build -pgo=auto -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_ROOT)/$(TARGET)
buildx:
@go mod download @go mod download
@echo Build paopao-ce @echo Build paopao-ce
@go build -pgo=auto -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_ROOT)/$(TARGET) @go build -pgo=auto -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_ROOT)/$(TARGET)
@ -115,10 +119,10 @@ pre-commit: fmt
.PHONY: install-protobuf-plugins .PHONY: install-protobuf-plugins
install-protobuf-plugins: install-protobuf-plugins:
@go install github.com/bufbuild/buf/cmd/buf@v1.25.0 @go install github.com/bufbuild/buf/cmd/buf@v1.28.1
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking@v1.25.0 @go install github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking@v1.28.1
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-lint@v1.25.0 @go install github.com/bufbuild/buf/cmd/protoc-gen-buf-lint@v1.28.1
@go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest @go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
@go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest @go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
@go install google.golang.org/protobuf/cmd/protoc-gen-go@latest @go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest @go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

@ -52,7 +52,7 @@ PaoPao主要由以下优秀的开源项目/工具构建
* [Mir](https://github.com/alimy/mir 'go-mir') * [Mir](https://github.com/alimy/mir 'go-mir')
* [Buf](https://github.com/bufbuild/buf 'buf') * [Buf](https://github.com/bufbuild/buf 'buf')
* [gRPC](https://github.com/grpc/grpc-go 'grpc-go') * [gRPC](https://github.com/grpc/grpc-go 'grpc-go')
* [Zinc](https://zinclabs.io/ 'zinc') * [Meilisearch](https://www.meilisearch.com/ 'meilisearch')
#### 前端: #### 前端:
* [Naive UI](https://www.naiveui.com/) * [Naive UI](https://www.naiveui.com/)
@ -69,9 +69,7 @@ PaoPao主要由以下优秀的开源项目/工具构建
* Node.js (14+) * Node.js (14+)
* MySQL (5.7+) * MySQL (5.7+)
* Redis * Redis
* Zinc * Meilisearch
> Zinc是一款轻量级全文搜索引擎,可以查阅 <https://zincsearch.com/> 安装
以上环境版本为PaoPao官方的开发版本,仅供参考,其他版本的环境未进行充分测试 以上环境版本为PaoPao官方的开发版本,仅供参考,其他版本的环境未进行充分测试
@ -221,6 +219,23 @@ PaoPao主要由以下优秀的开源项目/工具构建
docker run -d -p 8010:80 your/paopao-ce:web docker run -d -p 8010:80 your/paopao-ce:web
``` ```
* All-In-One:
```sh
# 构建Image
docker buildx build --build-arg USE_DIST="yes" -t your/paopao-ce:all-in-one-latest -f Dockerfile.allinone .
# 运行
docker run --name paopao-ce-allinone -d -p 8000:8008 -p 7700:7700 -v ./data/custom:/app/custom -v ./data/meili_data:/app/meili_data your/paopao-ce:all-in-one-latest
# 或者使用官方Image运行
docker run --name paopao-ce-allinone -d -p 8000:8008 -p 7700:7700 -v ./data/custom:/app/custom -v ./data/meili_data:/app/meili_data bitbus/paopao-ce:all-in-one-latest
# 或者使用官方Image运行 + 自定义config.yaml
docker run --name paopao-ce-allinone -d -p 8000:8008 -p 7700:7700 -v ./config.yaml:/app/config.yaml -v ./data/custom:/app/custom -v ./data/meili_data:/app/meili_data bitbus/paopao-ce:all-in-one-latest
```
> 注意在`config.yaml` 中`Meili.ApiKey`的值必须与容器中meili启动时设定的`MEILI_MASTER_KEY`环境变量值相同,默认为`paopao-meilisearch`. 可以在docker启动容器时通过`-e MEILI_MASTER_KEY=<custom-key>`设置该值。
### 方式三. 使用 docker-compose 运行 ### 方式三. 使用 docker-compose 运行
```sh ```sh
git clone https://github.com/rocboss/paopao-ce.git git clone https://github.com/rocboss/paopao-ce.git

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1
@ -22,6 +22,7 @@ type _render_ interface {
type _default_ interface { type _default_ interface {
Bind(*gin.Context, any) mir.Error Bind(*gin.Context, any) mir.Error
BindJson(*gin.Context, any) mir.Error
Render(*gin.Context, any, mir.Error) Render(*gin.Context, any, mir.Error)
} }

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1
@ -56,7 +56,7 @@ func RegisterFriendshipServant(e *gin.Engine, s Friendship) {
default: default:
} }
req := new(web.DeleteFriendReq) req := new(web.DeleteFriendReq)
if err := s.Bind(c, req); err != nil { if err := s.BindJson(c, req); err != nil {
s.Render(c, nil, err) s.Render(c, nil, err)
return return
} }
@ -69,7 +69,7 @@ func RegisterFriendshipServant(e *gin.Engine, s Friendship) {
default: default:
} }
req := new(web.RejectFriendReq) req := new(web.RejectFriendReq)
if err := s.Bind(c, req); err != nil { if err := s.BindJson(c, req); err != nil {
s.Render(c, nil, err) s.Render(c, nil, err)
return return
} }
@ -82,7 +82,7 @@ func RegisterFriendshipServant(e *gin.Engine, s Friendship) {
default: default:
} }
req := new(web.AddFriendReq) req := new(web.AddFriendReq)
if err := s.Bind(c, req); err != nil { if err := s.BindJson(c, req); err != nil {
s.Render(c, nil, err) s.Render(c, nil, err)
return return
} }
@ -95,7 +95,7 @@ func RegisterFriendshipServant(e *gin.Engine, s Friendship) {
default: default:
} }
req := new(web.RequestingFriendReq) req := new(web.RequestingFriendReq)
if err := s.Bind(c, req); err != nil { if err := s.BindJson(c, req); err != nil {
s.Render(c, nil, err) s.Render(c, nil, err)
return return
} }

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1
@ -20,6 +20,7 @@ type Priv interface {
UnfollowTopic(*web.UnfollowTopicReq) mir.Error UnfollowTopic(*web.UnfollowTopicReq) mir.Error
FollowTopic(*web.FollowTopicReq) mir.Error FollowTopic(*web.FollowTopicReq) mir.Error
PinTopic(*web.PinTopicReq) (*web.PinTopicResp, mir.Error)
StickTopic(*web.StickTopicReq) (*web.StickTopicResp, mir.Error) StickTopic(*web.StickTopicReq) (*web.StickTopicResp, mir.Error)
ThumbsDownTweetReply(*web.TweetReplyThumbsReq) mir.Error ThumbsDownTweetReply(*web.TweetReplyThumbsReq) mir.Error
ThumbsUpTweetReply(*web.TweetReplyThumbsReq) mir.Error ThumbsUpTweetReply(*web.TweetReplyThumbsReq) mir.Error
@ -91,6 +92,20 @@ func RegisterPrivServant(e *gin.Engine, s Priv, m ...PrivChain) {
} }
s.Render(c, nil, s.FollowTopic(req)) s.Render(c, nil, s.FollowTopic(req))
}) })
router.Handle("POST", "/topic/pin", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req := new(web.PinTopicReq)
if err := s.Bind(c, req); err != nil {
s.Render(c, nil, err)
return
}
resp, err := s.PinTopic(req)
s.Render(c, resp, err)
})
router.Handle("POST", "/topic/stick", func(c *gin.Context) { router.Handle("POST", "/topic/stick", func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
@ -406,6 +421,10 @@ func (UnimplementedPrivServant) FollowTopic(req *web.FollowTopicReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
} }
func (UnimplementedPrivServant) PinTopic(req *web.PinTopicReq) (*web.PinTopicResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.Error) { func (UnimplementedPrivServant) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
} }

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v4.0.0 // - mir v4.2.0
package v1 package v1

@ -5,9 +5,9 @@
package corev1connect package corev1connect
import ( import (
connect "connectrpc.com/connect"
context "context" context "context"
errors "errors" errors "errors"
connect_go "github.com/bufbuild/connect-go"
v1 "github.com/rocboss/paopao-ce/auto/rpc/core/v1" v1 "github.com/rocboss/paopao-ce/auto/rpc/core/v1"
http "net/http" http "net/http"
strings "strings" strings "strings"
@ -18,7 +18,7 @@ import (
// generated with a version of connect newer than the one compiled into your binary. You can fix the // generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect // problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary. // version compiled into your binary.
const _ = connect_go.IsAtLeastVersion0_1_0 const _ = connect.IsAtLeastVersion1_13_0
const ( const (
// AuthenticateServiceName is the fully-qualified name of the AuthenticateService service. // AuthenticateServiceName is the fully-qualified name of the AuthenticateService service.
@ -44,11 +44,19 @@ const (
AuthenticateServiceLogoutProcedure = "/core.v1.AuthenticateService/logout" AuthenticateServiceLogoutProcedure = "/core.v1.AuthenticateService/logout"
) )
// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
var (
authenticateServiceServiceDescriptor = v1.File_core_v1_auth_proto.Services().ByName("AuthenticateService")
authenticateServicePreLoginMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("preLogin")
authenticateServiceLoginMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("login")
authenticateServiceLogoutMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("logout")
)
// AuthenticateServiceClient is a client for the core.v1.AuthenticateService service. // AuthenticateServiceClient is a client for the core.v1.AuthenticateService service.
type AuthenticateServiceClient interface { type AuthenticateServiceClient interface {
PreLogin(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
Login(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error) Login(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error)
Logout(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) Logout(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
} }
// NewAuthenticateServiceClient constructs a client for the core.v1.AuthenticateService service. By // NewAuthenticateServiceClient constructs a client for the core.v1.AuthenticateService service. By
@ -58,54 +66,57 @@ type AuthenticateServiceClient interface {
// //
// The URL supplied here should be the base URL for the Connect or gRPC server (for example, // The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc). // http://api.acme.com or https://acme.com/grpc).
func NewAuthenticateServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) AuthenticateServiceClient { func NewAuthenticateServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AuthenticateServiceClient {
baseURL = strings.TrimRight(baseURL, "/") baseURL = strings.TrimRight(baseURL, "/")
return &authenticateServiceClient{ return &authenticateServiceClient{
preLogin: connect_go.NewClient[v1.User, v1.ActionReply]( preLogin: connect.NewClient[v1.User, v1.ActionReply](
httpClient, httpClient,
baseURL+AuthenticateServicePreLoginProcedure, baseURL+AuthenticateServicePreLoginProcedure,
opts..., connect.WithSchema(authenticateServicePreLoginMethodDescriptor),
connect.WithClientOptions(opts...),
), ),
login: connect_go.NewClient[v1.User, v1.LoginReply]( login: connect.NewClient[v1.User, v1.LoginReply](
httpClient, httpClient,
baseURL+AuthenticateServiceLoginProcedure, baseURL+AuthenticateServiceLoginProcedure,
opts..., connect.WithSchema(authenticateServiceLoginMethodDescriptor),
connect.WithClientOptions(opts...),
), ),
logout: connect_go.NewClient[v1.User, v1.ActionReply]( logout: connect.NewClient[v1.User, v1.ActionReply](
httpClient, httpClient,
baseURL+AuthenticateServiceLogoutProcedure, baseURL+AuthenticateServiceLogoutProcedure,
opts..., connect.WithSchema(authenticateServiceLogoutMethodDescriptor),
connect.WithClientOptions(opts...),
), ),
} }
} }
// authenticateServiceClient implements AuthenticateServiceClient. // authenticateServiceClient implements AuthenticateServiceClient.
type authenticateServiceClient struct { type authenticateServiceClient struct {
preLogin *connect_go.Client[v1.User, v1.ActionReply] preLogin *connect.Client[v1.User, v1.ActionReply]
login *connect_go.Client[v1.User, v1.LoginReply] login *connect.Client[v1.User, v1.LoginReply]
logout *connect_go.Client[v1.User, v1.ActionReply] logout *connect.Client[v1.User, v1.ActionReply]
} }
// PreLogin calls core.v1.AuthenticateService.preLogin. // PreLogin calls core.v1.AuthenticateService.preLogin.
func (c *authenticateServiceClient) PreLogin(ctx context.Context, req *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) { func (c *authenticateServiceClient) PreLogin(ctx context.Context, req *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return c.preLogin.CallUnary(ctx, req) return c.preLogin.CallUnary(ctx, req)
} }
// Login calls core.v1.AuthenticateService.login. // Login calls core.v1.AuthenticateService.login.
func (c *authenticateServiceClient) Login(ctx context.Context, req *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error) { func (c *authenticateServiceClient) Login(ctx context.Context, req *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error) {
return c.login.CallUnary(ctx, req) return c.login.CallUnary(ctx, req)
} }
// Logout calls core.v1.AuthenticateService.logout. // Logout calls core.v1.AuthenticateService.logout.
func (c *authenticateServiceClient) Logout(ctx context.Context, req *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) { func (c *authenticateServiceClient) Logout(ctx context.Context, req *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return c.logout.CallUnary(ctx, req) return c.logout.CallUnary(ctx, req)
} }
// AuthenticateServiceHandler is an implementation of the core.v1.AuthenticateService service. // AuthenticateServiceHandler is an implementation of the core.v1.AuthenticateService service.
type AuthenticateServiceHandler interface { type AuthenticateServiceHandler interface {
PreLogin(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
Login(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error) Login(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error)
Logout(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) Logout(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
} }
// NewAuthenticateServiceHandler builds an HTTP handler from the service implementation. It returns // NewAuthenticateServiceHandler builds an HTTP handler from the service implementation. It returns
@ -113,21 +124,24 @@ type AuthenticateServiceHandler interface {
// //
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression. // and JSON codecs. They also support gzip compression.
func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) { func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
authenticateServicePreLoginHandler := connect_go.NewUnaryHandler( authenticateServicePreLoginHandler := connect.NewUnaryHandler(
AuthenticateServicePreLoginProcedure, AuthenticateServicePreLoginProcedure,
svc.PreLogin, svc.PreLogin,
opts..., connect.WithSchema(authenticateServicePreLoginMethodDescriptor),
connect.WithHandlerOptions(opts...),
) )
authenticateServiceLoginHandler := connect_go.NewUnaryHandler( authenticateServiceLoginHandler := connect.NewUnaryHandler(
AuthenticateServiceLoginProcedure, AuthenticateServiceLoginProcedure,
svc.Login, svc.Login,
opts..., connect.WithSchema(authenticateServiceLoginMethodDescriptor),
connect.WithHandlerOptions(opts...),
) )
authenticateServiceLogoutHandler := connect_go.NewUnaryHandler( authenticateServiceLogoutHandler := connect.NewUnaryHandler(
AuthenticateServiceLogoutProcedure, AuthenticateServiceLogoutProcedure,
svc.Logout, svc.Logout,
opts..., connect.WithSchema(authenticateServiceLogoutMethodDescriptor),
connect.WithHandlerOptions(opts...),
) )
return "/core.v1.AuthenticateService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return "/core.v1.AuthenticateService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path { switch r.URL.Path {
@ -146,14 +160,14 @@ func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...conne
// UnimplementedAuthenticateServiceHandler returns CodeUnimplemented from all methods. // UnimplementedAuthenticateServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedAuthenticateServiceHandler struct{} type UnimplementedAuthenticateServiceHandler struct{}
func (UnimplementedAuthenticateServiceHandler) PreLogin(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) { func (UnimplementedAuthenticateServiceHandler) PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("core.v1.AuthenticateService.preLogin is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.AuthenticateService.preLogin is not implemented"))
} }
func (UnimplementedAuthenticateServiceHandler) Login(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error) { func (UnimplementedAuthenticateServiceHandler) Login(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("core.v1.AuthenticateService.login is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.AuthenticateService.login is not implemented"))
} }
func (UnimplementedAuthenticateServiceHandler) Logout(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) { func (UnimplementedAuthenticateServiceHandler) Logout(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("core.v1.AuthenticateService.logout is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.AuthenticateService.logout is not implemented"))
} }

@ -5,9 +5,9 @@
package greetv1connect package greetv1connect
import ( import (
connect "connectrpc.com/connect"
context "context" context "context"
errors "errors" errors "errors"
connect_go "github.com/bufbuild/connect-go"
v1 "github.com/rocboss/paopao-ce/auto/rpc/greet/v1" v1 "github.com/rocboss/paopao-ce/auto/rpc/greet/v1"
http "net/http" http "net/http"
strings "strings" strings "strings"
@ -18,7 +18,7 @@ import (
// generated with a version of connect newer than the one compiled into your binary. You can fix the // generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect // problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary. // version compiled into your binary.
const _ = connect_go.IsAtLeastVersion0_1_0 const _ = connect.IsAtLeastVersion1_13_0
const ( const (
// GreetServiceName is the fully-qualified name of the GreetService service. // GreetServiceName is the fully-qualified name of the GreetService service.
@ -37,9 +37,15 @@ const (
GreetServiceGreetProcedure = "/greet.v1.GreetService/Greet" GreetServiceGreetProcedure = "/greet.v1.GreetService/Greet"
) )
// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
var (
greetServiceServiceDescriptor = v1.File_greet_v1_greet_proto.Services().ByName("GreetService")
greetServiceGreetMethodDescriptor = greetServiceServiceDescriptor.Methods().ByName("Greet")
)
// GreetServiceClient is a client for the greet.v1.GreetService service. // GreetServiceClient is a client for the greet.v1.GreetService service.
type GreetServiceClient interface { type GreetServiceClient interface {
Greet(context.Context, *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error) Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
} }
// NewGreetServiceClient constructs a client for the greet.v1.GreetService service. By default, it // NewGreetServiceClient constructs a client for the greet.v1.GreetService service. By default, it
@ -49,30 +55,31 @@ type GreetServiceClient interface {
// //
// The URL supplied here should be the base URL for the Connect or gRPC server (for example, // The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc). // http://api.acme.com or https://acme.com/grpc).
func NewGreetServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) GreetServiceClient { func NewGreetServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GreetServiceClient {
baseURL = strings.TrimRight(baseURL, "/") baseURL = strings.TrimRight(baseURL, "/")
return &greetServiceClient{ return &greetServiceClient{
greet: connect_go.NewClient[v1.GreetRequest, v1.GreetResponse]( greet: connect.NewClient[v1.GreetRequest, v1.GreetResponse](
httpClient, httpClient,
baseURL+GreetServiceGreetProcedure, baseURL+GreetServiceGreetProcedure,
opts..., connect.WithSchema(greetServiceGreetMethodDescriptor),
connect.WithClientOptions(opts...),
), ),
} }
} }
// greetServiceClient implements GreetServiceClient. // greetServiceClient implements GreetServiceClient.
type greetServiceClient struct { type greetServiceClient struct {
greet *connect_go.Client[v1.GreetRequest, v1.GreetResponse] greet *connect.Client[v1.GreetRequest, v1.GreetResponse]
} }
// Greet calls greet.v1.GreetService.Greet. // Greet calls greet.v1.GreetService.Greet.
func (c *greetServiceClient) Greet(ctx context.Context, req *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error) { func (c *greetServiceClient) Greet(ctx context.Context, req *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) {
return c.greet.CallUnary(ctx, req) return c.greet.CallUnary(ctx, req)
} }
// GreetServiceHandler is an implementation of the greet.v1.GreetService service. // GreetServiceHandler is an implementation of the greet.v1.GreetService service.
type GreetServiceHandler interface { type GreetServiceHandler interface {
Greet(context.Context, *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error) Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
} }
// NewGreetServiceHandler builds an HTTP handler from the service implementation. It returns the // NewGreetServiceHandler builds an HTTP handler from the service implementation. It returns the
@ -80,11 +87,12 @@ type GreetServiceHandler interface {
// //
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression. // and JSON codecs. They also support gzip compression.
func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) { func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
greetServiceGreetHandler := connect_go.NewUnaryHandler( greetServiceGreetHandler := connect.NewUnaryHandler(
GreetServiceGreetProcedure, GreetServiceGreetProcedure,
svc.Greet, svc.Greet,
opts..., connect.WithSchema(greetServiceGreetMethodDescriptor),
connect.WithHandlerOptions(opts...),
) )
return "/greet.v1.GreetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return "/greet.v1.GreetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path { switch r.URL.Path {
@ -99,6 +107,6 @@ func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect_go.HandlerO
// UnimplementedGreetServiceHandler returns CodeUnimplemented from all methods. // UnimplementedGreetServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedGreetServiceHandler struct{} type UnimplementedGreetServiceHandler struct{}
func (UnimplementedGreetServiceHandler) Greet(context.Context, *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error) { func (UnimplementedGreetServiceHandler) Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("greet.v1.GreetService.Greet is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("greet.v1.GreetService.Greet is not implemented"))
} }

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.31.0 // protoc-gen-go v1.32.0
// protoc (unknown) // protoc (unknown)
// source: core/v1/auth.proto // source: core/v1/auth.proto

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.31.0 // protoc-gen-go v1.32.0
// protoc (unknown) // protoc (unknown)
// source: greet/v1/greet.proto // source: greet/v1/greet.proto

@ -0,0 +1,26 @@
#!/bin/sh
# eg.1 : sh build-image.sh
# eg.2, set image: sh build-image.sh bitbus/paopao-ce
VERSION=`git describe --tags --always | cut -f1,2 -d "-"` # eg.: 0.2.5
IMAGE="bitbus/paopao-ce"
if [ -n "$1" ]; then
IMAGE="$1"
fi
if [ -n "$2" ]; then
VERSION="$2"
fi
# build image
docker buildx build \
--build-arg USE_DIST="yes" \
--tag "$IMAGE:all-in-one-${VERSION}" \
--tag "$IMAGE:all-in-one-latest" \
. -f Dockerfile.allinone
# push to image rep
# if [ -n "$1" ]; then
# docker push "$IMAGE:${VERSION}"
# docker push "$IMAGE:latest"
# fi

@ -2,7 +2,7 @@ version: '3.1'
services: services:
db: db:
image: mysql:8.0 image: mysql:${MYSQL_TAG:-8.0}
restart: always restart: always
environment: environment:
MYSQL_DATABASE: paopao MYSQL_DATABASE: paopao
@ -33,7 +33,7 @@ services:
# - paopao-network # - paopao-network
# redis: # redis:
# image: redis:7.2.1-alpine # image: redis:${REDIS_TAG:-7.2.1-alpine}
# restart: always # restart: always
# ports: # ports:
# - 6379:6379 # - 6379:6379
@ -41,7 +41,7 @@ services:
# - paopao-network # - paopao-network
redis: redis:
image: redis/redis-stack:7.2.0-v2 image: redis/redis-stack:${REDIS_STACK_TAG:-7.2.0-v6}
restart: always restart: always
ports: ports:
- 6379:6379 - 6379:6379
@ -67,7 +67,7 @@ services:
# - paopao-network # - paopao-network
meili: meili:
image: getmeili/meilisearch:v1.4 image: getmeili/meilisearch:${MEILI_TAG:-v1.7}
restart: always restart: always
ports: ports:
- 7700:7700 - 7700:7700
@ -87,7 +87,7 @@ services:
# - paopao-network # - paopao-network
# openobserve: # openobserve:
# image: public.ecr.aws/zinclabs/openobserve:latest # image: public.ecr.aws/zinclabs/openobserve:${OPENOBSERVE_TAG:-latest}
# restart: always # restart: always
# ports: # ports:
# - 5080:5080 # - 5080:5080
@ -101,7 +101,7 @@ services:
# - paopao-network # - paopao-network
# pyroscope: # pyroscope:
# image: pyroscope/pyroscope:latest # image: pyroscope/pyroscope:${PYROSCOPE_TAG:-latest}
# restart: always # restart: always
# ports: # ports:
# - 4040:4040 # - 4040:4040
@ -111,7 +111,7 @@ services:
# - paopao-network # - paopao-network
# phpmyadmin: # phpmyadmin:
# image: phpmyadmin:5.2 # image: phpmyadmin:${PHPMYADMIN_TAG:-5.2}
# depends_on: # depends_on:
# - db # - db
# ports: # ports:
@ -124,7 +124,7 @@ services:
# - paopao-network # - paopao-network
backend: backend:
image: bitbus/paopao-ce:0.5 image: bitbus/paopao-ce:${PAOPAOCE_TAG:-nightly}
restart: always restart: always
depends_on: depends_on:
- db - db

@ -0,0 +1,3 @@
# Docker for paopao-ce
TODO;

@ -0,0 +1,13 @@
#!/bin/sh
set -xe
# Create paopao user for paopao-ce
addgroup -S paopao
adduser -G paopao -H -D -g 'paopao User' paopao -h /app -s /bin/sh && usermod -p '*' paopao && passwd -u paopao
# echo "export PAOPAO_CUSTOM=${PAOPAO_CUSTOM}" >> /etc/profile
# Final cleaning
mv /app/docker/config.yaml /app/config.yaml
rm -rf /app/docker/build
rm /app/docker/README.md

@ -0,0 +1,71 @@
App: # APP基础设置项
RunMode: debug
AttachmentIncomeRate: 0.8
MaxCommentCount: 10
DefaultContextTimeout: 60
DefaultPageSize: 10
MaxPageSize: 100
Features:
Default: ["Web", "Frontend:EmbedWeb", "Meili", "LocalOSS", "Sqlite3", "LoggerFile", "Migration"]
Sms: "SmsJuhe"
WebServer: # Web服务
HttpIp: 0.0.0.0
HttpPort: 8008
ReadTimeout: 60
WriteTimeout: 60
SmsJuhe:
Gateway: https://v.juhe.cn/sms/send
Key:
TplID:
TplVal: "#code#=%s&#m#=%d"
Logger: # 日志通用配置
Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace
LoggerFile: # 使用File写日志
SavePath: custom/logs
FileName: app
FileExt: .log
JWT: # 鉴权加密
Secret: 18a6413dc4fe394c66345ebe501b2f26
Issuer: paopao-api
Expire: 86400
Meili: # Meili搜索配置
Host: 127.0.0.1:7700
Index: paopao-data
ApiKey: paopao-meilisearch
Secure: False
ObjectStorage: # 对象存储通用配置
RetainInDays: 2 # 临时对象过期时间多少天
TempDir: tmp # 临时对象存放目录名
LocalOSS: # 本地文件OSS存储配置
SavePath: custom/oss
Secure: False
Bucket: paopao
Domain: 127.0.0.1:8008
Database: # Database通用配置
LogLevel: error # 日志级别 silent|error|warn|info
TablePrefix: p_ # 表名前缀
Sqlite3: # Sqlite3数据库
Path: custom/paopao-ce.db
Redis:
InitAddress:
- 127.0.0.1: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"

@ -0,0 +1,3 @@
#!/bin/sh
# do nothing now

@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
# shellcheck disable=SC2039,SC1091,SC3046
source ./setup
fi
exec gosu ${USER} /bin/meilisearch

@ -0,0 +1,3 @@
#!/bin/sh
cd /app/meili_data || exit 1

@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
# shellcheck disable=SC2039,SC1091,SC3046
source ./setup
fi
exec gosu ${USER} /app/paopao serve

@ -0,0 +1,5 @@
#!/bin/sh
# sleep 10s等待meilisearch启动完成,这是笨方法,暂时先凑着,后面再找更靠谱的法子;这里之所以要等待meili启动完成,是因为paopao-ce首次启动时需要初始化一次meili的index,如果初始化失败就无法在后续使用。
sleep 10
cd /app || exit 1

@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
# shellcheck disable=SC2039,SC1091,SC3046
source ./setup
fi
exec docker-entrypoint.sh redis-server

@ -0,0 +1,35 @@
#!/bin/sh
create_volume_subfolder() {
# only change ownership if needed, if using an nfs mount this could be expensive
if [ "$USER:$USER" != "$(stat /app -c '%U:%G')" ]
then
# Modify the owner of /app dir, make $USER(paopao) user have permission to create sub-dir in /app.
chown -R "$USER:$USER" /app
fi
# Create VOLUME subfolder
for f in /app/custom /app/meili_data; do
if ! test -d $f; then
gosu "$USER" mkdir -p $f
fi
done
}
setids() {
export USER=paopao
PUID=${PUID:-1000}
PGID=${PGID:-1000}
groupmod -o -g "$PGID" $USER
usermod -o -u "$PUID" $USER
}
setids
create_volume_subfolder
# Exec CMD or S6 by default if nothing present
if [ $# -gt 0 ];then
exec "$@"
else
exec /bin/s6-svscan /app/gogs/docker/s6/
fi

186
go.mod

@ -1,111 +1,112 @@
module github.com/rocboss/paopao-ce module github.com/rocboss/paopao-ce
go 1.20 go 1.21
require ( require (
connectrpc.com/connect v1.16.2
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/RoaringBitmap/roaring v1.5.0 github.com/RoaringBitmap/roaring v1.9.4
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
github.com/alimy/mir/v4 v4.0.0 github.com/alimy/mir/v4 v4.2.0-alpha.5
github.com/alimy/tryst v0.8.3 github.com/alimy/tryst v0.21.0
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/allegro/bigcache/v3 v3.1.0 github.com/allegro/bigcache/v3 v3.1.0
github.com/bufbuild/connect-go v1.10.0 github.com/bytedance/sonic v1.11.9
github.com/bytedance/sonic v1.10.1 github.com/cockroachdb/errors v1.11.3
github.com/cockroachdb/errors v1.11.1
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/fatih/color v1.15.0 github.com/fatih/color v1.17.0
github.com/getsentry/sentry-go v0.24.1 github.com/getsentry/sentry-go v0.28.1
github.com/gin-contrib/cors v1.4.0 github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.10.0
github.com/go-resty/resty/v2 v2.9.1 github.com/go-resty/resty/v2 v2.12.0
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.3
github.com/gofrs/uuid/v5 v5.0.0 github.com/gofrs/uuid/v5 v5.2.0
github.com/golang-jwt/jwt/v5 v5.0.0 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-migrate/migrate/v4 v4.15.2 github.com/golang-migrate/migrate/v4 v4.17.1
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.9+incompatible github.com/grafana/pyroscope-go v1.1.2
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.6+incompatible
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/meilisearch/meilisearch-go v0.25.1 github.com/meilisearch/meilisearch-go v0.27.2
github.com/minio/minio-go/v7 v7.0.63 github.com/minio/minio-go/v7 v7.0.66
github.com/onsi/ginkgo/v2 v2.12.1 github.com/onsi/ginkgo/v2 v2.19.0
github.com/onsi/gomega v1.28.0 github.com/onsi/gomega v1.33.1
github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_golang v1.19.1
github.com/pyroscope-io/client v0.7.2 github.com/redis/rueidis v1.0.37
github.com/redis/rueidis v1.0.19
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/smartwalle/alipay/v3 v3.2.16 github.com/smartwalle/alipay/v3 v3.2.20
github.com/sourcegraph/conc v0.3.0 github.com/sourcegraph/conc v0.3.0
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.16.0 github.com/spf13/viper v1.18.2
github.com/tencentyun/cos-go-sdk-v5 v0.7.43 github.com/tencentyun/cos-go-sdk-v5 v0.7.52
github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
google.golang.org/grpc v1.58.2 golang.org/x/crypto v0.25.0
google.golang.org/protobuf v1.31.0 google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/resty.v1 v1.12.0 gopkg.in/resty.v1 v1.12.0
gorm.io/driver/mysql v1.5.1 gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.2 gorm.io/driver/postgres v1.5.7
gorm.io/driver/sqlite v1.5.3 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.4 gorm.io/gorm v1.25.10
gorm.io/plugin/dbresolver v1.4.7 gorm.io/plugin/dbresolver v1.5.1
gorm.io/plugin/soft_delete v1.2.1 gorm.io/plugin/soft_delete v1.2.1
modernc.org/sqlite v1.26.0 modernc.org/sqlite v1.29.6
) )
require ( require (
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect github.com/bits-and-blooms/bitset v1.12.0 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.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 github.com/clbanning/mxj v1.8.4 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/redact v1.1.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/logr v1.4.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // 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.6.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jackc/pgx/v5 v5.5.4 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.0 // indirect github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
@ -113,47 +114,46 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mozillazg/go-httpheader v0.2.1 // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/pyroscope-io/godeltaprof v0.1.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/xid v1.5.0 // indirect github.com/rs/xid v1.5.0 // indirect
github.com/smartwalle/ncrypto v1.0.3 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/smartwalle/ngx v1.0.7 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/smartwalle/nsign v1.0.8 // indirect github.com/smartwalle/ncrypto v1.0.4 // indirect
github.com/spf13/afero v1.9.5 // indirect github.com/smartwalle/ngx v1.0.9 // indirect
github.com/spf13/cast v1.5.1 // indirect github.com/smartwalle/nsign v1.0.9 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.40.0 // indirect github.com/valyala/fasthttp v1.52.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/arch v0.3.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.13.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/image v0.0.0-20210216034530-4410531fe030 // indirect golang.org/x/image v0.0.0-20210216034530-4410531fe030 // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/net v0.15.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.12.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.12.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/libc v1.41.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/libc v1.24.1 // indirect modernc.org/memory v1.7.2 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/memory v1.6.0 // indirect modernc.org/token v1.1.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
) )

2163
go.sum

File diff suppressed because it is too large Load Diff

@ -128,8 +128,8 @@ func setupSetting(suite []string, noDefault bool) error {
} }
CacheSetting.CientSideCacheExpire *= time.Second CacheSetting.CientSideCacheExpire *= time.Second
EventManagerSetting.TickWaitTime *= time.Second EventManagerSetting.MaxIdleTime *= time.Second
MetricManagerSetting.TickWaitTime *= time.Second MetricManagerSetting.MaxIdleTime *= time.Second
JWTSetting.Expire *= time.Second JWTSetting.Expire *= time.Second
SimpleCacheIndexSetting.CheckTickDuration *= time.Second SimpleCacheIndexSetting.CheckTickDuration *= time.Second
SimpleCacheIndexSetting.ExpireTickDuration *= time.Second SimpleCacheIndexSetting.ExpireTickDuration *= time.Second

@ -22,16 +22,16 @@ Cache:
MessagesExpire: 60 # 消息列表过期时间,单位秒, 默认60s MessagesExpire: 60 # 消息列表过期时间,单位秒, 默认60s
EventManager: # 事件管理器的配置参数 EventManager: # 事件管理器的配置参数
MinWorker: 64 # 最小后台工作者, 设置范围[5, ++], 默认64 MinWorker: 64 # 最小后台工作者, 设置范围[5, ++], 默认64
MaxTempWorker: -1 # 最大临时工作者, -1为无限制, 默认-1
MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128 MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128
MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256 MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256
MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60 MaxIdleTime: 60 # 临时工作者最大空闲等待时间,单位:秒 默认60
TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s
MetricManager: # 指标监控管理器的配置参数 MetricManager: # 指标监控管理器的配置参数
MinWorker: 32 # 最小后台工作者, 设置范围[5, ++], 默认32 MinWorker: 32 # 最小后台工作者, 设置范围[5, ++], 默认32
MaxTempWorker: -1 # 最大临时工作者, -1为无限制, 默认-1
MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128 MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128
MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256 MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256
MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60 MaxIdleTime: 60 # 临时工作者最大空闲等待时间,单位:秒 默认60
TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s
JobManager: # Cron Job理器的配置参数 JobManager: # Cron Job理器的配置参数
MaxOnlineInterval: "@every 5m" # 更新最大在线人数,默认每5分钟更新一次 MaxOnlineInterval: "@every 5m" # 更新最大在线人数,默认每5分钟更新一次
UpdateMetricsInterval: "@every 5m" # 更新Prometheus指标,默认每5分钟更新一次 UpdateMetricsInterval: "@every 5m" # 更新Prometheus指标,默认每5分钟更新一次
@ -250,7 +250,7 @@ Redis:
ConnWriteTimeout: 60 # 连接写超时时间 多少秒 默认 60秒 ConnWriteTimeout: 60 # 连接写超时时间 多少秒 默认 60秒
WebProfile: WebProfile:
UseFriendship: true # 前端是否使用好友体系 UseFriendship: true # 前端是否使用好友体系
EnableTrendsBar: true # 广场页面是否开启动态条栏功能 EnableTrendsBar: false # 广场页面是否开启动态条栏功能
EnableWallet: false # 是否开启钱包功能 EnableWallet: false # 是否开启钱包功能
AllowTweetAttachment: true # 是否允许推文附件 AllowTweetAttachment: true # 是否允许推文附件
AllowTweetAttachmentPrice: true # 是否允许推文付费附件 AllowTweetAttachmentPrice: true # 是否允许推文付费附件

@ -5,6 +5,7 @@
package conf package conf
import ( import (
"log"
"sync" "sync"
"time" "time"
@ -27,13 +28,13 @@ func MustGormDB() *gorm.DB {
_onceGorm.Do(func() { _onceGorm.Do(func() {
var err error var err error
if _gormdb, err = newGormDB(); err != nil { if _gormdb, err = newGormDB(); err != nil {
logrus.Fatalf("new gorm db failed: %s", err) log.Fatalf("new gorm db failed: %s", err)
} }
}) })
return _gormdb return _gormdb
} }
func newGormDB() (*gorm.DB, error) { func newGormDB() (db *gorm.DB, err error) {
newLogger := logger.New( newLogger := logger.New(
logrus.StandardLogger(), // io writer(日志输出的目标,前缀和日志包含的内容) logrus.StandardLogger(), // io writer(日志输出的目标,前缀和日志包含的内容)
logger.Config{ logger.Config{
@ -58,15 +59,9 @@ func newGormDB() (*gorm.DB, error) {
SetMaxIdleConns(MysqlSetting.MaxIdleConns). SetMaxIdleConns(MysqlSetting.MaxIdleConns).
SetMaxOpenConns(MysqlSetting.MaxOpenConns) SetMaxOpenConns(MysqlSetting.MaxOpenConns)
var (
db *gorm.DB
err error
)
if cfg.If("MySQL") { if cfg.If("MySQL") {
logrus.Debugln("use MySQL as db") logrus.Debugln("use MySQL as db")
if db, err = gorm.Open(mysql.Open(MysqlSetting.Dsn()), config); err == nil { db, err = gorm.Open(mysql.Open(MysqlSetting.Dsn()), config)
db.Use(plugin)
}
} else if cfg.If("Postgres") { } else if cfg.If("Postgres") {
logrus.Debugln("use PostgreSQL as db") logrus.Debugln("use PostgreSQL as db")
db, err = gorm.Open(postgres.Open(PostgresSetting.Dsn()), config) db, err = gorm.Open(postgres.Open(PostgresSetting.Dsn()), config)
@ -75,10 +70,10 @@ func newGormDB() (*gorm.DB, error) {
db, err = gormOpenSqlite3(config) db, err = gormOpenSqlite3(config)
} else { } else {
logrus.Debugln("use default of MySQL as db") logrus.Debugln("use default of MySQL as db")
if db, err = gorm.Open(mysql.Open(MysqlSetting.Dsn()), config); err == nil { db, err = gorm.Open(mysql.Open(MysqlSetting.Dsn()), config)
db.Use(plugin)
}
} }
if err == nil {
return db, err err = db.Use(plugin)
}
return
} }

@ -56,8 +56,7 @@ func newObserveLogHook() *observeLogHook {
MinWorker: s.MinWorker, MinWorker: s.MinWorker,
MaxRequestBuf: s.MaxLogBuffer, MaxRequestBuf: s.MaxLogBuffer,
MaxRequestTempBuf: 100, MaxRequestTempBuf: 100,
MaxTickCount: 60, MaxIdleTime: 60 * time.Second,
TickWaitTime: time.Second,
} }
return &observeLogHook{ return &observeLogHook{
client: obx.NewClient(obc, acc, func(req *http.Request, resp *http.Response, err error) { client: obx.NewClient(obc, acc, func(req *http.Request, resp *http.Response, err error) {

@ -11,7 +11,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/pyroscope-io/client/pyroscope" pyroscope "github.com/grafana/pyroscope-go"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
@ -115,18 +115,18 @@ type cacheConf struct {
type eventManagerConf struct { type eventManagerConf struct {
MinWorker int MinWorker int
MaxTempWorker int
MaxEventBuf int MaxEventBuf int
MaxTempEventBuf int MaxTempEventBuf int
MaxTickCount int MaxIdleTime time.Duration
TickWaitTime time.Duration
} }
type metricManagerConf struct { type metricManagerConf struct {
MinWorker int MinWorker int
MaxTempWorker int
MaxEventBuf int MaxEventBuf int
MaxTempEventBuf int MaxTempEventBuf int
MaxTickCount int MaxIdleTime time.Duration
TickWaitTime time.Duration
} }
type jobManagerConf struct { type jobManagerConf struct {

@ -116,3 +116,8 @@ type WebCache interface {
ExistUnreadMsgCountResp(uid int64) bool ExistUnreadMsgCountResp(uid int64) bool
PutHistoryMaxOnline(newScore int) (int, error) PutHistoryMaxOnline(newScore int) (int, error)
} }
type MetricCache interface {
SetEventTempWorkerCount(name string, count int32)
GetEventTempWorkerCount(name string) int32
}

@ -9,6 +9,7 @@ const (
TagTypeHot TagType = "hot" TagTypeHot TagType = "hot"
TagTypeNew TagType = "new" TagTypeNew TagType = "new"
TagTypeFollow TagType = "follow" TagTypeFollow TagType = "follow"
TagTypePin TagType = "pin"
TagTypeHotExtral TagType = "hot_extral" TagTypeHotExtral TagType = "hot_extral"
) )
@ -40,6 +41,7 @@ type TagItem struct {
QuoteNum int64 `json:"quote_num"` QuoteNum int64 `json:"quote_num"`
IsFollowing int8 `json:"is_following"` IsFollowing int8 `json:"is_following"`
IsTop int8 `json:"is_top"` IsTop int8 `json:"is_top"`
IsPin int8 `json:"is_pin"`
} }
func (t *TagInfo) Format() *TagItem { func (t *TagInfo) Format() *TagItem {
@ -51,5 +53,6 @@ func (t *TagInfo) Format() *TagItem {
QuoteNum: t.QuoteNum, QuoteNum: t.QuoteNum,
IsFollowing: 0, IsFollowing: 0,
IsTop: 0, IsTop: 0,
IsPin: 0,
} }
} }

@ -16,10 +16,11 @@ type TopicService interface {
TagsByKeyword(keyword string) (cs.TagInfoList, error) TagsByKeyword(keyword string) (cs.TagInfoList, error)
GetHotTags(userId int64, limit int, offset int) (cs.TagList, error) GetHotTags(userId int64, limit int, offset int) (cs.TagList, error)
GetNewestTags(userId int64, limit int, offset int) (cs.TagList, error) GetNewestTags(userId int64, limit int, offset int) (cs.TagList, error)
GetFollowTags(userId int64, limit int, offset int) (cs.TagList, error) GetFollowTags(userId int64, isPin bool, limit int, offset int) (cs.TagList, error)
FollowTopic(userId int64, topicId int64) error FollowTopic(userId int64, topicId int64) error
UnfollowTopic(userId int64, topicId int64) error UnfollowTopic(userId int64, topicId int64) error
StickTopic(userId int64, topicId int64) (int8, error) StickTopic(userId int64, topicId int64) (int8, error)
PinTopic(userId int64, topicId int64) (int8, error)
} }
// TopicServantA 话题服务(版本A) // TopicServantA 话题服务(版本A)

@ -148,7 +148,7 @@ func (s *commentSrv) GetCommentRepliesByID(ids []int64) ([]*ms.CommentReplyForma
return repliesFormated, nil return repliesFormated, nil
} }
func (s *commentManageSrv) HighlightComment(userId, commentId int64) (res int8, err error) { func (s *commentManageSrv) HighlightComment(userId, commentId int64) (isEssence int8, err error) {
post := &dbr.Post{} post := &dbr.Post{}
comment := &dbr.Comment{} comment := &dbr.Comment{}
db := s.db.Model(comment) db := s.db.Model(comment)
@ -161,8 +161,12 @@ func (s *commentManageSrv) HighlightComment(userId, commentId int64) (res int8,
if post.UserID != userId { if post.UserID != userId {
return 0, cs.ErrNoPermission return 0, cs.ErrNoPermission
} }
comment.IsEssence = 1 - comment.IsEssence isEssence = 1 - comment.IsEssence
return comment.IsEssence, db.Save(comment).Error err = s.db.Model(comment).UpdateColumns(map[string]any{
"is_essence": isEssence,
"modified_on": time.Now().Unix(),
}).Where("id=?", commentId).Error
return
} }
func (s *commentManageSrv) DeleteComment(comment *ms.Comment) (err error) { func (s *commentManageSrv) DeleteComment(comment *ms.Comment) (err error) {

@ -25,6 +25,7 @@ type TopicUser struct {
Remark string `json:"-"` Remark string `json:"-"`
QuoteNum int64 `json:"quote_num"` QuoteNum int64 `json:"quote_num"`
IsTop int8 `json:"is_top"` IsTop int8 `json:"is_top"`
IsPin int8 `json:"is_pin"`
ReserveA string `json:"-"` ReserveA string `json:"-"`
ReserveB string `json:"-"` ReserveB string `json:"-"`
} }

@ -33,6 +33,7 @@ type topicSrvA struct {
type topicInfo struct { type topicInfo struct {
TopicId int64 TopicId int64
IsTop int8 IsTop int8
IsPin int8
} }
func newTopicService(db *gorm.DB) core.TopicService { func newTopicService(db *gorm.DB) core.TopicService {
@ -110,17 +111,18 @@ func (s *topicSrv) GetNewestTags(userId int64, limit int, offset int) (cs.TagLis
return s.tagsFormatA(userId, tags) return s.tagsFormatA(userId, tags)
} }
func (s *topicSrv) GetFollowTags(userId int64, limit int, offset int) (cs.TagList, error) { func (s *topicSrv) GetFollowTags(userId int64, isPin bool, limit int, offset int) (cs.TagList, error) {
if userId < 0 { if userId < 0 {
return nil, nil return nil, nil
} }
userTopics := []*topicInfo{} userTopics := []*topicInfo{}
err := s.db.Model(&dbr.TopicUser{}). db := s.db.Model(&dbr.TopicUser{}).Order("is_top DESC").Limit(limit).Offset(offset)
Where("user_id=?", userId). if isPin {
Order("is_top DESC"). db = db.Where("user_id=? AND is_pin=1", userId)
Limit(limit). } else {
Offset(offset). db = db.Where("user_id=?", userId)
Find(&userTopics).Error }
err := db.Find(&userTopics).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -207,7 +209,7 @@ func (s *topicSrv) tagsFormatA(userId int64, tags cs.TagList) (cs.TagList, error
} }
for _, tag := range tags { for _, tag := range tags {
if info, exist := userTopicsMap[tag.ID]; exist { if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop tag.IsFollowing, tag.IsTop, tag.IsPin = 1, info.IsTop, info.IsPin
} }
} }
} }
@ -238,7 +240,7 @@ func (s *topicSrv) tagsFormatB(userTopicsMap map[int64]*topicInfo, tags cs.TagIn
if len(userTopicsMap) > 0 { if len(userTopicsMap) > 0 {
for _, tag := range tagList { for _, tag := range tagList {
if info, exist := userTopicsMap[tag.ID]; exist { if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop tag.IsFollowing, tag.IsTop, tag.IsPin = 1, info.IsTop, info.IsPin
} }
} }
} }
@ -409,3 +411,27 @@ func (s *topicSrv) StickTopic(userId int64, topicId int64) (status int8, err err
db.Commit() db.Commit()
return return
} }
func (s *topicSrv) PinTopic(userId int64, topicId int64) (status int8, err error) {
db := s.db.Begin()
defer db.Rollback()
m := &dbr.TopicUser{}
err = db.Model(m).
Where("user_id=? and topic_id=?", userId, topicId).
UpdateColumn("is_pin", gorm.Expr("1-is_pin")).Error
if err != nil {
return
}
status = -1
err = db.Model(m).Where("user_id=? and topic_id=?", userId, topicId).Select("is_pin").Scan(&status).Error
if err != nil {
return
}
if status < 0 {
return -1, errors.New("topic not exist")
}
db.Commit()
return
}

@ -11,6 +11,7 @@ import (
"github.com/alimy/tryst/pool" "github.com/alimy/tryst/pool"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/metrics/statistics"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -89,21 +90,24 @@ func initEventManager() {
var opts []pool.Option var opts []pool.Option
s := conf.EventManagerSetting s := conf.EventManagerSetting
if s.MinWorker > 5 { if s.MinWorker > 5 {
opts = append(opts, pool.MinWorkerOpt(s.MinWorker)) opts = append(opts, pool.WithMinWorker(s.MinWorker))
} else { } else {
opts = append(opts, pool.MinWorkerOpt(5)) opts = append(opts, pool.WithMinWorker(5))
} }
if s.MaxEventBuf > 10 { if s.MaxEventBuf > 10 {
opts = append(opts, pool.MaxRequestBufOpt(s.MaxEventBuf)) opts = append(opts, pool.WithMaxRequestBuf(s.MaxEventBuf))
} else { } else {
opts = append(opts, pool.MaxRequestBufOpt(10)) opts = append(opts, pool.WithMaxRequestBuf(10))
} }
if s.MaxTempEventBuf > 10 { if s.MaxTempEventBuf > 10 {
opts = append(opts, pool.MaxRequestTempBufOpt(s.MaxTempEventBuf)) opts = append(opts, pool.WithMaxRequestTempBuf(s.MaxTempEventBuf))
} else { } else {
opts = append(opts, pool.MaxRequestTempBufOpt(10)) opts = append(opts, pool.WithMaxRequestTempBuf(10))
} }
opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime)) if cfg.If("Metrics") {
opts = append(opts, pool.WithWorkerHook(NewEventWorkerHook("default", statistics.NewMetricCache())))
}
opts = append(opts, pool.WithMaxIdelTime(s.MaxIdleTime), pool.WithMaxTempWorker(s.MaxTempWorker))
_defaultEventManager = NewEventManager(func(req Event, err error) { _defaultEventManager = NewEventManager(func(req Event, err error) {
if err != nil { if err != nil {
logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err) logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err)

@ -7,6 +7,7 @@ package events
import ( import (
"github.com/alimy/tryst/event" "github.com/alimy/tryst/event"
"github.com/alimy/tryst/pool" "github.com/alimy/tryst/pool"
"github.com/rocboss/paopao-ce/internal/core"
) )
type Event = event.Event type Event = event.Event
@ -17,6 +18,11 @@ type EventManager interface {
OnEvent(event Event) OnEvent(event Event)
} }
type simpleWorkerHook struct {
name string
cache core.MetricCache
}
type simpleEventManager struct { type simpleEventManager struct {
em event.EventManager em event.EventManager
} }
@ -33,8 +39,23 @@ func (s *simpleEventManager) OnEvent(event Event) {
s.em.OnEvent(event) s.em.OnEvent(event)
} }
func (h *simpleWorkerHook) OnJoin(count int32) {
h.cache.SetEventTempWorkerCount(h.name, count)
}
func (h *simpleWorkerHook) OnLeave(count int32) {
h.cache.SetEventTempWorkerCount(h.name, count)
}
func NewEventManager(fn pool.RespFn[Event], opts ...pool.Option) EventManager { func NewEventManager(fn pool.RespFn[Event], opts ...pool.Option) EventManager {
return &simpleEventManager{ return &simpleEventManager{
em: event.NewEventManager(fn, opts...), em: event.NewEventManager(fn, opts...),
} }
} }
func NewEventWorkerHook(name string, mc core.MetricCache) pool.WorkerHook {
return &simpleWorkerHook{
name: name,
cache: mc,
}
}

@ -49,23 +49,23 @@ func Initial() {
func initMetricManager() { func initMetricManager() {
var opts []pool.Option var opts []pool.Option
s := conf.EventManagerSetting s := conf.MetricManagerSetting
if s.MinWorker > 5 { if s.MinWorker > 5 {
opts = append(opts, pool.MinWorkerOpt(s.MinWorker)) opts = append(opts, pool.WithMinWorker(s.MinWorker))
} else { } else {
opts = append(opts, pool.MinWorkerOpt(5)) opts = append(opts, pool.WithMinWorker(5))
} }
if s.MaxEventBuf > 10 { if s.MaxEventBuf > 10 {
opts = append(opts, pool.MaxRequestBufOpt(s.MaxEventBuf)) opts = append(opts, pool.WithMaxRequestBuf(s.MaxEventBuf))
} else { } else {
opts = append(opts, pool.MaxRequestBufOpt(10)) opts = append(opts, pool.WithMaxRequestBuf(10))
} }
if s.MaxTempEventBuf > 10 { if s.MaxTempEventBuf > 10 {
opts = append(opts, pool.MaxRequestTempBufOpt(s.MaxTempEventBuf)) opts = append(opts, pool.WithMaxRequestTempBuf(s.MaxTempEventBuf))
} else { } else {
opts = append(opts, pool.MaxRequestTempBufOpt(10)) opts = append(opts, pool.WithMaxRequestTempBuf(10))
} }
opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime)) opts = append(opts, pool.WithMaxIdelTime(s.MaxIdleTime), pool.WithMaxTempWorker(s.MaxTempWorker))
_defaultMetricManager = event.NewEventManager(func(req Metric, err error) { _defaultMetricManager = event.NewEventManager(func(req Metric, err error) {
if err != nil { if err != nil {
logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err) logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err)

@ -13,8 +13,10 @@ import (
type metrics struct { type metrics struct {
siteInfo *prometheus.GaugeVec siteInfo *prometheus.GaugeVec
runtime *prometheus.GaugeVec
ds core.DataService ds core.DataService
wc core.WebCache wc core.WebCache
mc core.MetricCache
} }
func (m *metrics) updateSiteInfo() { func (m *metrics) updateSiteInfo() {
@ -31,13 +33,21 @@ func (m *metrics) updateSiteInfo() {
} }
} }
func (m *metrics) updateRuntime() {
m.runtime.With(prometheus.Labels{"name": "default"}).Set(float64(m.mc.GetEventTempWorkerCount("default")))
}
func (m *metrics) onUpdate() { func (m *metrics) onUpdate() {
logrus.Debugf("update promethues metrics job running") logrus.Debugf("update promethues metrics job running")
m.updateSiteInfo() m.updateSiteInfo()
m.updateRuntime()
} }
func newMetrics(reg prometheus.Registerer, ds core.DataService, wc core.WebCache) *metrics { func newMetrics(reg prometheus.Registerer, ds core.DataService, wc core.WebCache, mc core.MetricCache) *metrics {
m := &metrics{ m := &metrics{
ds: ds,
wc: wc,
mc: mc,
siteInfo: prometheus.NewGaugeVec( siteInfo: prometheus.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Namespace: "paopao", Namespace: "paopao",
@ -49,9 +59,18 @@ func newMetrics(reg prometheus.Registerer, ds core.DataService, wc core.WebCache
// metric name // metric name
"name", "name",
}), }),
ds: ds, runtime: prometheus.NewGaugeVec(
wc: wc, prometheus.GaugeOpts{
Namespace: "runtime",
Subsystem: "event_manager",
Name: "temp_worker_count",
Help: "runtime's event manager temp worker count info",
},
[]string{
// metric name
"name",
}),
} }
reg.MustRegister(m.siteInfo) reg.MustRegister(m.siteInfo, m.runtime)
return m return m
} }

@ -27,7 +27,7 @@ func scheduleJobs(metrics *metrics) {
logrus.Debug("shedule prometheus metrics update jobs complete") logrus.Debug("shedule prometheus metrics update jobs complete")
} }
func NewHandler(ds core.DataService, wc core.WebCache) http.Handler { func NewHandler(ds core.DataService, wc core.WebCache, mc core.MetricCache) http.Handler {
// Create non-global registry. // Create non-global registry.
registry := prometheus.NewRegistry() registry := prometheus.NewRegistry()
// Add go runtime metrics and process collectors. // Add go runtime metrics and process collectors.
@ -35,7 +35,7 @@ func NewHandler(ds core.DataService, wc core.WebCache) http.Handler {
collectors.NewGoCollector(), collectors.NewGoCollector(),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
) )
metrics := newMetrics(registry, ds, wc) metrics := newMetrics(registry, ds, wc, mc)
scheduleJobs(metrics) scheduleJobs(metrics)
return promhttp.HandlerFor(registry, promhttp.HandlerOpts{EnableOpenMetrics: true}) return promhttp.HandlerFor(registry, promhttp.HandlerOpts{EnableOpenMetrics: true})
} }

@ -0,0 +1,38 @@
// 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 statistics
import (
"sync"
"github.com/rocboss/paopao-ce/internal/core"
)
var (
_metricCache core.MetricCache
_onceMetricCache sync.Once
)
type metricCache struct {
eventTempWorkerCount map[string]int32
}
func (m *metricCache) SetEventTempWorkerCount(name string, count int32) {
// 直接赋值,不需要加锁,因为这仅仅是一个统计信息
m.eventTempWorkerCount[name] = count
}
func (m *metricCache) GetEventTempWorkerCount(name string) int32 {
return m.eventTempWorkerCount[name]
}
func NewMetricCache() core.MetricCache {
_onceMetricCache.Do(func() {
_metricCache = &metricCache{
eventTempWorkerCount: make(map[string]int32),
}
})
return _metricCache
}

@ -20,6 +20,7 @@ const (
TagTypeHot = cs.TagTypeHot TagTypeHot = cs.TagTypeHot
TagTypeNew = cs.TagTypeNew TagTypeNew = cs.TagTypeNew
TagTypeFollow = cs.TagTypeFollow TagTypeFollow = cs.TagTypeFollow
TagTypePin = cs.TagTypePin
TagTypeHotExtral = cs.TagTypeHotExtral TagTypeHotExtral = cs.TagTypeHotExtral
) )

@ -207,6 +207,15 @@ type StickTopicResp struct {
StickStatus int8 `json:"top_status"` StickStatus int8 `json:"top_status"`
} }
type PinTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
type PinTopicResp struct {
PinStatus int8 `json:"pin_status"`
}
type FollowTopicReq struct { type FollowTopicReq struct {
SimpleInfo `json:"-" binding:"-"` SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"` TopicId int64 `json:"topic_id" binding:"required"`

@ -24,6 +24,8 @@ func fileCheck(uploadType string, size int64) mir.Error {
func getFileExt(s string) (string, mir.Error) { func getFileExt(s string) (string, mir.Error) {
switch s { switch s {
case "image/webp":
return ".webp", nil
case "image/png": case "image/png":
return ".png", nil return ".png", nil
case "image/jpg": case "image/jpg":
@ -42,6 +44,6 @@ func getFileExt(s string) (string, mir.Error) {
"application/x-zip-compressed": "application/x-zip-compressed":
return ".zip", nil return ".zip", nil
default: default:
return "", ErrFileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型") return "", ErrFileInvalidExt.WithDetails("仅允许 webp/png/jpg/gif/mp4/mov/zip 类型")
} }
} }

@ -95,6 +95,7 @@ var (
ErrFollowTopicFailed = xerror.NewError(90001, "关注话题失败") ErrFollowTopicFailed = xerror.NewError(90001, "关注话题失败")
ErrUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败") ErrUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败")
ErrStickTopicFailed = xerror.NewError(90003, "更行话题置顶状态失败") ErrStickTopicFailed = xerror.NewError(90003, "更行话题置顶状态失败")
ErrPinTopicFailed = xerror.NewError(90005, "更行话题钉住状态失败")
ErrThumbsUpTweetComment = xerror.NewError(90101, "评论点赞失败") ErrThumbsUpTweetComment = xerror.NewError(90101, "评论点赞失败")
ErrThumbsDownTweetComment = xerror.NewError(90102, "评论点踩失败") ErrThumbsDownTweetComment = xerror.NewError(90102, "评论点踩失败")
ErrThumbsUpTweetReply = xerror.NewError(90103, "评论回复点赞失败") ErrThumbsUpTweetReply = xerror.NewError(90103, "评论回复点赞失败")

@ -29,7 +29,8 @@ import (
) )
type BaseServant struct { type BaseServant struct {
bindAny func(c *gin.Context, obj any) mir.Error bindAny func(c *gin.Context, obj any) mir.Error
bindJson func(c *gin.Context, obj any) mir.Error
} }
type DaoServant struct { type DaoServant struct {
@ -158,6 +159,10 @@ func (s *BaseServant) Bind(c *gin.Context, obj any) mir.Error {
return s.bindAny(c, obj) return s.bindAny(c, obj)
} }
func (s *BaseServant) BindJson(c *gin.Context, obj any) mir.Error {
return s.bindJson(c, obj)
}
func (s *BaseServant) Render(c *gin.Context, data any, err mir.Error) { func (s *BaseServant) Render(c *gin.Context, data any, err mir.Error) {
if err == nil { if err == nil {
c.JSON(http.StatusOK, &joint.JsonResp{ c.JSON(http.StatusOK, &joint.JsonResp{
@ -420,9 +425,17 @@ func NewBindAnyFn() func(c *gin.Context, obj any) mir.Error {
return bindAny return bindAny
} }
func NewBindJsonFn() func(c *gin.Context, obj any) mir.Error {
if conf.UseSentryGin() {
return bindAnySentry
}
return bindAny
}
func NewBaseServant() *BaseServant { func NewBaseServant() *BaseServant {
return &BaseServant{ return &BaseServant{
bindAny: NewBindAnyFn(), bindAny: NewBindAnyFn(),
bindJson: NewBindJsonFn(),
} }
} }

@ -7,8 +7,8 @@ package servants
import ( import (
"net/http" "net/http"
"connectrpc.com/connect"
"github.com/alimy/tryst/cfg" "github.com/alimy/tryst/cfg"
"github.com/bufbuild/connect-go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/servants/admin" "github.com/rocboss/paopao-ce/internal/servants/admin"
"github.com/rocboss/paopao-ce/internal/servants/bot" "github.com/rocboss/paopao-ce/internal/servants/bot"

@ -7,7 +7,7 @@ package triplet
import ( import (
"net/http" "net/http"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
api "github.com/rocboss/paopao-ce/auto/connect/core/v1/corev1connect" api "github.com/rocboss/paopao-ce/auto/connect/core/v1/corev1connect"
) )

@ -384,7 +384,9 @@ func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Err
case web.TagTypeNew: case web.TagTypeNew:
tags, err = s.Ds.GetNewestTags(req.Uid, num, 0) tags, err = s.Ds.GetNewestTags(req.Uid, num, 0)
case web.TagTypeFollow: case web.TagTypeFollow:
tags, err = s.Ds.GetFollowTags(req.Uid, num, 0) tags, err = s.Ds.GetFollowTags(req.Uid, false, num, 0)
case web.TagTypePin:
tags, err = s.Ds.GetFollowTags(req.Uid, true, num, 0)
case web.TagTypeHotExtral: case web.TagTypeHotExtral:
extralNum := req.ExtralNum extralNum := req.ExtralNum
if extralNum <= 0 { if extralNum <= 0 {
@ -392,7 +394,7 @@ func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Err
} }
tags, err = s.Ds.GetHotTags(req.Uid, num, 0) tags, err = s.Ds.GetHotTags(req.Uid, num, 0)
if err == nil { if err == nil {
extralTags, err = s.Ds.GetFollowTags(req.Uid, extralNum, 0) extralTags, err = s.Ds.GetFollowTags(req.Uid, false, extralNum, 0)
} }
default: default:
// TODO: return good error // TODO: return good error

@ -6,6 +6,7 @@ package web
import ( import (
"image" "image"
"io"
"strings" "strings"
"time" "time"
@ -124,13 +125,28 @@ func (s *privSrv) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.E
}, nil }, nil
} }
func (s *privSrv) PinTopic(req *web.PinTopicReq) (*web.PinTopicResp, mir.Error) {
status, err := s.Ds.PinTopic(req.Uid, req.TopicId)
if err != nil {
logrus.Errorf("user(%d) pin topic(%d) failed: %s", req.Uid, req.TopicId, err)
return nil, web.ErrPinTopicFailed
}
return &web.PinTopicResp{
PinStatus: status,
}, nil
}
func (s *privSrv) UploadAttachment(req *web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) { func (s *privSrv) UploadAttachment(req *web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) {
defer req.File.Close() defer req.File.Close()
// 生成随机路径 // 生成随机路径
randomPath := uuid.Must(uuid.NewV4()).String() randomPath := uuid.Must(uuid.NewV4()).String()
ossSavePath := req.UploadType + "/" + generatePath(randomPath[:8]) + "/" + randomPath[9:] + req.FileExt ossSavePath := req.UploadType + "/" + generatePath(randomPath[:8]) + "/" + randomPath[9:] + req.FileExt
objectUrl, err := s.oss.PutObject(ossSavePath, req.File, req.FileSize, req.ContentType, false) // NOTE: 注意这里将req.File Wrap到一个io.Reader的实例对象中是为了避免下游接口去主动调Close,req.File本身是实现了
// io.Closer接口的,有的下游接口会断言传参是否实现了io.Closer接口,如果实现了会主动去调,我们这里因为下文中可能还要继续
// 使用req.File所以应避免下游Close,否则会出现潜在的bug,比如这里的场景就是传一个超大的图片(>10MB)可能就会触发bug了。
data := io.NopCloser(req.File)
objectUrl, err := s.oss.PutObject(ossSavePath, data, req.FileSize, req.ContentType, false)
if err != nil { if err != nil {
logrus.Errorf("oss.putObject err: %s", err) logrus.Errorf("oss.putObject err: %s", err)
return nil, web.ErrFileUploadFailed return nil, web.ErrFileUploadFailed

@ -8,7 +8,7 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
hx "github.com/rocboss/paopao-ce/pkg/http" hx "github.com/rocboss/paopao-ce/pkg/http"
) )

@ -7,7 +7,7 @@ package service
import ( import (
"net/http" "net/http"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
) )
type baseConnectService struct { type baseConnectService struct {

@ -14,6 +14,7 @@ import (
"github.com/rocboss/paopao-ce/internal/dao" "github.com/rocboss/paopao-ce/internal/dao"
"github.com/rocboss/paopao-ce/internal/dao/cache" "github.com/rocboss/paopao-ce/internal/dao/cache"
"github.com/rocboss/paopao-ce/internal/metrics/prometheus" "github.com/rocboss/paopao-ce/internal/metrics/prometheus"
"github.com/rocboss/paopao-ce/internal/metrics/statistics"
) )
var ( var (
@ -44,9 +45,9 @@ func (s *metricsService) String() string {
func newMetricsService() Service { func newMetricsService() Service {
addr := conf.MetricsServerSetting.HttpIp + ":" + conf.MetricsServerSetting.HttpPort addr := conf.MetricsServerSetting.HttpIp + ":" + conf.MetricsServerSetting.HttpPort
server := httpServers.from(addr, func() *httpServer { server := httpServers.from(addr, func() *httpServer {
ds, wc := dao.DataService(), cache.NewWebCache() ds, wc, mc := dao.DataService(), cache.NewWebCache(), statistics.NewMetricCache()
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/metrics", prometheus.NewHandler(ds, wc)) mux.Handle("/metrics", prometheus.NewHandler(ds, wc, mc))
return &httpServer{ return &httpServer{
baseServer: newBaseServe(), baseServer: newBaseServe(),
server: &http.Server{ server: &http.Server{

@ -16,16 +16,16 @@ type Friendship struct {
Group `mir:"v1"` Group `mir:"v1"`
// RequestingFriend 请求添加朋友 // RequestingFriend 请求添加朋友
RequestingFriend func(Post, web.RequestingFriendReq) `mir:"/friend/requesting"` RequestingFriend func(Post, web.RequestingFriendReq) `mir:"/friend/requesting" binding:"json"`
// AddFriend 同意添加好友 // AddFriend 同意添加好友
AddFriend func(Post, web.AddFriendReq) `mir:"/friend/add"` AddFriend func(Post, web.AddFriendReq) `mir:"/friend/add" binding:"json"`
// RejectFriend 拒绝添加好友 // RejectFriend 拒绝添加好友
RejectFriend func(Post, web.RejectFriendReq) `mir:"/friend/reject"` RejectFriend func(Post, web.RejectFriendReq) `mir:"/friend/reject" binding:"json"`
// DeleteFriend 删除好友 // DeleteFriend 删除好友
DeleteFriend func(Post, web.DeleteFriendReq) `mir:"/friend/delete"` DeleteFriend func(Post, web.DeleteFriendReq) `mir:"/friend/delete" binding:"json"`
// GetContacts 获取好友列表 // GetContacts 获取好友列表
GetContacts func(Get, web.GetContactsReq) web.GetContactsResp `mir:"/user/contacts"` GetContacts func(Get, web.GetContactsReq) web.GetContactsResp `mir:"/user/contacts"`

@ -75,9 +75,12 @@ type Priv struct {
// ThumbsDownTweetReply 点踩评论回复 // ThumbsDownTweetReply 点踩评论回复
ThumbsDownTweetReply func(Post, web.TweetReplyThumbsReq) `mir:"/tweet/reply/thumbsdown"` ThumbsDownTweetReply func(Post, web.TweetReplyThumbsReq) `mir:"/tweet/reply/thumbsdown"`
// StickTopic 置顶动态 // StickTopic 置顶话题
StickTopic func(Post, web.StickTopicReq) web.StickTopicResp `mir:"/topic/stick"` StickTopic func(Post, web.StickTopicReq) web.StickTopicResp `mir:"/topic/stick"`
// PinTopic 钉住话题
PinTopic func(Post, web.PinTopicReq) web.PinTopicResp `mir:"/topic/pin"`
// FollowTopic 关注话题 // FollowTopic 关注话题
FollowTopic func(Post, web.FollowTopicReq) `mir:"/topic/follow"` FollowTopic func(Post, web.FollowTopicReq) `mir:"/topic/follow"`

@ -55,8 +55,7 @@ func ParseToken(token string) (res *Claims, err error) {
func IssuerFrom(data string) string { func IssuerFrom(data string) string {
contents := make([]byte, 0, len(conf.JWTSetting.Issuer)+len(data)) contents := make([]byte, 0, len(conf.JWTSetting.Issuer)+len(data))
copy(contents, []byte(conf.JWTSetting.Issuer)) contents = append(append(contents, []byte(conf.JWTSetting.Issuer)...), []byte(data)...)
contents = append(contents, []byte(data)...)
res := md5.Sum(contents) res := md5.Sum(contents)
return hex.EncodeToString(res[:]) return hex.EncodeToString(res[:])
} }

@ -11,7 +11,7 @@ import (
"os" "os"
"github.com/alimy/tryst/cfg" "github.com/alimy/tryst/cfg"
"github.com/pyroscope-io/client/pyroscope" pyroscope "github.com/grafana/pyroscope-go"
"github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/conf"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )

@ -34,8 +34,7 @@ type AsyncClientConf struct {
MinWorker int MinWorker int
MaxRequestBuf int MaxRequestBuf int
MaxRequestTempBuf int MaxRequestTempBuf int
MaxTickCount int MaxIdleTime time.Duration
TickWaitTime time.Duration
} }
type wormClient struct { type wormClient struct {
@ -64,11 +63,10 @@ func NewAsyncClient(client *http.Client, conf *AsyncClientConf) AsyncClient {
pool: gp.NewGoroutinePool(func(req *http.Request) (*http.Response, error) { pool: gp.NewGoroutinePool(func(req *http.Request) (*http.Response, error) {
return client.Do(req) return client.Do(req)
}, },
gp.MinWorkerOpt(minWorker), gp.WithMinWorker(minWorker),
gp.MaxRequestBufOpt(maxRequestBuf), gp.WithMaxRequestBuf(maxRequestBuf),
gp.MaxRequestTempBufOpt(maxRequestTempBuf), gp.WithMaxRequestTempBuf(maxRequestTempBuf),
gp.MaxTickCountOpt(conf.MaxTickCount), gp.WithMaxIdelTime(conf.MaxIdleTime),
gp.TickWaitTimeOpt(conf.TickWaitTime),
), ),
} }
} }

@ -0,0 +1,157 @@
// 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 types
import (
"github.com/RoaringBitmap/roaring"
"github.com/RoaringBitmap/roaring/roaring64"
"github.com/alimy/tryst/types"
)
type roaringBitmap struct {
Map *roaring.Bitmap
}
type roaring64Bitmap struct {
Map *roaring64.Bitmap
}
func (m *roaringBitmap) MarshalBinary() ([]byte, error) {
if m == nil {
return []byte{}, nil
}
return m.Map.MarshalBinary()
}
func (m *roaringBitmap) UnmarshalBinary(data []byte) (res *roaringBitmap, err error) {
res = &roaringBitmap{
Map: roaring.New(),
}
err = res.Map.UnmarshalBinary(data)
return
}
func (m *roaring64Bitmap) MarshalBinary() ([]byte, error) {
if m == nil {
return []byte{}, nil
}
return m.Map.MarshalBinary()
}
func (m *roaring64Bitmap) UnmarshalBinary(data []byte) (res *roaring64Bitmap, err error) {
res = &roaring64Bitmap{
Map: roaring64.New(),
}
err = res.Map.UnmarshalBinary(data)
return
}
// Bitmap alias type of types.Binary[*roaringBitmap]
type Bitmap = types.Binary[*roaringBitmap]
// NullBitmap alias type of types.NullBinary[*roaringBitmap]
type NullBitmap = types.NullBinary[*roaringBitmap]
// Bitmap64 alias type of types.Binary[*roaring64Bitmap]
type Bitmap64 = types.Binary[*roaring64Bitmap]
// NullBitmap64 alias type of types.NullBinary[*roaring64Bitmap]
type NullBitmap64 = types.NullBinary[*roaring64Bitmap]
// NewBitmap create a Bitmap instance
func NewBitmap() (res *Bitmap) {
return &types.Binary[*roaringBitmap]{
Data: &roaringBitmap{
Map: roaring.New(),
},
}
}
// NewNullBitmap create a NullBitmap instance
func NewNullBitmap() *NullBitmap {
return &types.NullBinary[*roaringBitmap]{
Data: &roaringBitmap{
Map: roaring.New(),
},
}
}
// NewBitmap64 create a Bitmap64 instance
func NewBitmap64() *Bitmap64 {
return &types.Binary[*roaring64Bitmap]{
Data: &roaring64Bitmap{
Map: roaring64.New(),
},
}
}
// NewNullBitmap64 create a NullBitmap64 instance
func NewNullBitmap64() *NullBitmap64 {
return &types.NullBinary[*roaring64Bitmap]{
Data: &roaring64Bitmap{
Map: roaring64.New(),
},
}
}
// MustBitmap create a Bitmap instance
func MustBitmap(data ...[]byte) (res *Bitmap) {
res = &types.Binary[*roaringBitmap]{
Data: &roaringBitmap{
Map: roaring.New(),
},
}
if len(data) > 0 && len(data[0]) > 0 {
if err := res.Data.Map.UnmarshalBinary(data[0]); err != nil {
panic(err)
}
}
return
}
// MustNullBitmap create a NullBitmap instance
func MustNullBitmap(data ...[]byte) (res *NullBitmap) {
res = &types.NullBinary[*roaringBitmap]{
Data: &roaringBitmap{
Map: roaring.New(),
},
}
if len(data) > 0 && len(data[0]) > 0 {
if err := res.Data.Map.UnmarshalBinary(data[0]); err != nil {
panic(err)
}
}
return
}
// MustBitmap64 create a Bitmap64 instance
func MustBitmap64(data ...[]byte) (res *Bitmap64) {
res = &types.Binary[*roaring64Bitmap]{
Data: &roaring64Bitmap{
Map: roaring64.New(),
},
}
if len(data) > 0 && len(data[0]) > 0 {
if err := res.Data.Map.UnmarshalBinary(data[0]); err != nil {
panic(err)
}
}
return
}
// MustNullBitmap64 create a NullBitmap64 instance
func MustNullBitmap64(data ...[]byte) (res *NullBitmap64) {
res = &types.NullBinary[*roaring64Bitmap]{
Data: &roaring64Bitmap{
Map: roaring64.New(),
},
}
if len(data) > 0 && len(data[0]) > 0 {
if err := res.Data.Map.UnmarshalBinary(data[0]); err != nil {
panic(err)
}
}
return
}

@ -0,0 +1,36 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package types_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/rocboss/paopao-ce/pkg/types"
)
var _ = Describe("Bitmap", Ordered, func() {
It("sql scaner for Bitmap", func() {
bitmap := types.NewBitmap()
bitmap.Data.Map.Add(128)
value, err := bitmap.Value()
Expect(err).To(BeNil())
var bm types.Bitmap
bm.Scan(value)
Expect(bm.Data.Map.Contains(128)).To(BeTrue())
})
It("sql scaner for Bitmap64", func() {
bitmap := types.NewBitmap64()
bitmap.Data.Map.Add(128)
value, err := bitmap.Value()
Expect(err).To(BeNil())
var bm types.Bitmap64
bm.Scan(value)
Expect(bm.Data.Map.Contains(128)).To(BeTrue())
})
})

@ -0,0 +1,24 @@
// Copyright 2024 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 types
import (
"io"
)
type readerWrap struct {
r io.Reader
}
func (rw *readerWrap) Read(p []byte) (n int, err error) {
return rw.r.Read(p)
}
// PureReader wrap a pure io.Reader object
func PureReader(r io.Reader) io.Reader {
return &readerWrap{
r: r,
}
}

@ -0,0 +1,32 @@
// Copyright 2024 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 types
import (
"golang.org/x/crypto/bcrypt"
)
type PasswordProvider interface {
Generate(password []byte) ([]byte, error)
Compare(hashedPassword, password []byte) error
}
func NewBcryptPasswordProvider(cost int) PasswordProvider {
return &bcryptPasswordProvider{
cost: cost,
}
}
type bcryptPasswordProvider struct {
cost int
}
func (p *bcryptPasswordProvider) Generate(password []byte) ([]byte, error) {
return bcrypt.GenerateFromPassword(password, p.cost)
}
func (p *bcryptPasswordProvider) Compare(hashedPassword, password []byte) error {
return bcrypt.CompareHashAndPassword(hashedPassword, password)
}

@ -9,7 +9,7 @@ import (
) )
const ( const (
series = "v0.5" series = "v0.6-dev"
) )
var ( var (

@ -0,0 +1,37 @@
# syntax=docker/dockerfile:1
FROM getmeili/meilisearch:v1.5 as meilisearch
FROM redis:7.2-alpine3.18
ENV TZ=Asia/Shanghai
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates
RUN apk update --quiet \
&& apk -q --no-cache --no-progress add \
ca-certificates \
libgcc \
curl \
s6 \
&& update-ca-certificates
WORKDIR /app
COPY ./docker ./docker
# add meilisearch and meilitool to the `/bin` so you can run it from anywhere
# and it's easy to find.
COPY --from=meilisearch /bin/meilisearch /bin/meilisearch
COPY --from=meilisearch /bin/meilitool /bin/meilitool
# To stay compatible with the older version of the container (pre v0.27.0) we're
# going to symlink the meilisearch binary in the path to `/meilisearch`
RUN ln -s /bin/meilisearch /meilisearch
ENV MEILI_HTTP_ADDR 0.0.0.0:7700
ENV MEILI_SERVER_PROVIDER docker
ENV MEILI_DB_PATH=/app/meili_data
ENV MEILI_MASTER_KEY=paopao-meilisearch
RUN ./docker/build/finalize.sh
# Configure Docker Container
VOLUME ["/app/meili_data", "/app/custom"]
EXPOSE 7700/tcp 6379 8008
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD ps -ef | grep paopao || exit 1
ENTRYPOINT ["/app/docker/start.sh"]
CMD ["/bin/s6-svscan", "/app/docker/s6/"]

@ -1,4 +1,5 @@
FROM golang:1.21-alpine # syntax=docker/dockerfile:1
FROM golang:1.21-alpine3.18
RUN apk --no-cache --no-progress add --virtual \ RUN apk --no-cache --no-progress add --virtual \
build-deps \ build-deps \
build-base \ build-base \

@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1
FROM alpine:3.18 FROM alpine:3.18
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates

@ -1,6 +1,7 @@
### Dockerfile builer pre-build images ### Dockerfile builer pre-build images
```sh ```sh
docker build -t bitbus/paopao-ce-backend-builder:latest -f Dockerfile-backend-builder . docker buildx build -t bitbus/paopao-ce-backend-builder:latest -f Dockerfile.backend-builder .
docker build -t bitbus/paopao-ce-backend-runner:latest -f Dockerfile-backend-runner . docker buildx build -t bitbus/paopao-ce-backend-runner:latest -f Dockerfile.backend-runner .
cd ../../ && docker buildx build -t bitbus/paopao-ce-allinone-runner:latest -f scripts/docker/Dockerfile.allinone-runner .
``` ```

@ -0,0 +1,2 @@
ALTER TABLE `p_topic_user` DROP COLUMN `is_pin`;
DROP INDEX IF EXISTS `idx_topic_user_uid_ispin`;

@ -0,0 +1,2 @@
ALTER TABLE `p_topic_user` ADD COLUMN `is_pin` TINYINT NOT NULL DEFAULT 0 COMMENT '是否钉住 0 为未钉住、1 为已钉住';
CREATE INDEX `idx_topic_user_uid_ispin` ON `p_topic_user` (`user_id`, `is_pin`) USING BTREE;

@ -0,0 +1,2 @@
ALTER TABLE p_topic_user DROP COLUMN is_pin;
DROP INDEX IF EXISTS idx_topic_user_uid_ispin;

@ -0,0 +1,2 @@
ALTER TABLE p_topic_user ADD COLUMN is_pin SMALLINT NOT NULL DEFAULT 0; -- 是否钉住 0 为未钉住、1 为已钉住
CREATE INDEX idx_topic_user_uid_ispin ON p_topic_user USING btree ( user_id, is_pin );

@ -1,7 +1,5 @@
PRAGMA foreign_keys=off; PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE p_post_content RENAME TO _p_post_content; ALTER TABLE p_post_content RENAME TO _p_post_content;
ALTER TABLE p_comment_content RENAME TO _p_comment_content; ALTER TABLE p_comment_content RENAME TO _p_comment_content;
@ -53,46 +51,6 @@ CREATE TABLE p_comment_reply
PRIMARY KEY ("id") PRIMARY KEY ("id")
); );
-- ----------------------------
-- Indexes structure for table p_post_content
-- ----------------------------
CREATE INDEX "idx_post_content_post_id"
ON "p_post_content" (
"post_id" ASC
);
CREATE INDEX "idx_post_content_user_id"
ON "p_post_content" (
"user_id" ASC
);
-- ----------------------------
-- Indexes structure for table p_comment_reply
-- ----------------------------
CREATE INDEX "idx_comment_reply_comment_id"
ON "p_comment_reply" (
"comment_id" ASC
);
-- ----------------------------
-- Indexes structure for table p_comment_content
-- ----------------------------
CREATE INDEX "idx_comment_content_comment_id"
ON "p_comment_content" (
"comment_id" ASC
);
CREATE INDEX "idx_comment_content_sort"
ON "p_comment_content" (
"sort" ASC
);
CREATE INDEX "idx_comment_content_type"
ON "p_comment_content" (
"type" ASC
);
CREATE INDEX "idx_comment_content_user_id"
ON "p_comment_content" (
"user_id" ASC
);
INSERT INTO p_post_content (id, post_id, user_id, content, `type`, sort, created_on, modified_on, deleted_on, is_del) INSERT INTO p_post_content (id, post_id, user_id, content, `type`, sort, created_on, modified_on, deleted_on, is_del)
SELECT id, post_id, user_id, content, `type`, sort, created_on, modified_on, deleted_on, is_del SELECT id, post_id, user_id, content, `type`, sort, created_on, modified_on, deleted_on, is_del
FROM _p_post_content; FROM _p_post_content;
@ -112,10 +70,12 @@ DROP TABLE _p_comment_reply;
-- ---------------------------- -- ----------------------------
-- Indexes structure for table p_post_content -- Indexes structure for table p_post_content
-- ---------------------------- -- ----------------------------
DROP INDEX IF EXISTS "idx_post_content_post_id";
CREATE INDEX "idx_post_content_post_id" CREATE INDEX "idx_post_content_post_id"
ON "p_post_content" ( ON "p_post_content" (
"post_id" ASC "post_id" ASC
); );
DROP INDEX IF EXISTS "idx_post_content_user_id";
CREATE INDEX "idx_post_content_user_id" CREATE INDEX "idx_post_content_user_id"
ON "p_post_content" ( ON "p_post_content" (
"user_id" ASC "user_id" ASC
@ -124,6 +84,7 @@ ON "p_post_content" (
-- ---------------------------- -- ----------------------------
-- Indexes structure for table p_comment_reply -- Indexes structure for table p_comment_reply
-- ---------------------------- -- ----------------------------
DROP INDEX IF EXISTS "idx_comment_reply_comment_id";
CREATE INDEX "idx_comment_reply_comment_id" CREATE INDEX "idx_comment_reply_comment_id"
ON "p_comment_reply" ( ON "p_comment_reply" (
"comment_id" ASC "comment_id" ASC
@ -132,23 +93,25 @@ ON "p_comment_reply" (
-- ---------------------------- -- ----------------------------
-- Indexes structure for table p_comment_content -- Indexes structure for table p_comment_content
-- ---------------------------- -- ----------------------------
DROP INDEX IF EXISTS "idx_comment_content_comment_id";
CREATE INDEX "idx_comment_content_comment_id" CREATE INDEX "idx_comment_content_comment_id"
ON "p_comment_content" ( ON "p_comment_content" (
"comment_id" ASC "comment_id" ASC
); );
DROP INDEX IF EXISTS "idx_comment_content_sort";
CREATE INDEX "idx_comment_content_sort" CREATE INDEX "idx_comment_content_sort"
ON "p_comment_content" ( ON "p_comment_content" (
"sort" ASC "sort" ASC
); );
DROP INDEX IF EXISTS "idx_comment_content_type";
CREATE INDEX "idx_comment_content_type" CREATE INDEX "idx_comment_content_type"
ON "p_comment_content" ( ON "p_comment_content" (
"type" ASC "type" ASC
); );
DROP INDEX IF EXISTS "idx_comment_content_user_id";
CREATE INDEX "idx_comment_content_user_id" CREATE INDEX "idx_comment_content_user_id"
ON "p_comment_content" ( ON "p_comment_content" (
"user_id" ASC "user_id" ASC
); );
COMMIT;
PRAGMA foreign_keys=on; PRAGMA foreign_keys=on;

@ -0,0 +1,2 @@
ALTER TABLE "p_topic_user" DROP COLUMN "is_pin";
DROP INDEX IF EXISTS "idx_topic_user_uid_ispin";

@ -0,0 +1,6 @@
ALTER TABLE "p_topic_user" ADD COLUMN "is_pin" integer NOT NULL DEFAULT 0;
CREATE INDEX "main"."idx_topic_user_uid_ispin"
ON "p_topic_user" (
"user_id" ASC,
"is_pin" ASC
);

@ -324,6 +324,7 @@ CREATE TABLE `p_topic_user` (
`remark` VARCHAR ( 512 ) COMMENT '备注', `remark` VARCHAR ( 512 ) COMMENT '备注',
`quote_num` BIGINT COMMENT '引用数', `quote_num` BIGINT COMMENT '引用数',
`is_top` TINYINT NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶', `is_top` TINYINT NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶',
`is_pin` TINYINT NOT NULL DEFAULT '0' COMMENT '是否钉住 0 为未钉住、1 为已钉住',
`created_on` BIGINT NOT NULL DEFAULT '0' COMMENT '创建时间', `created_on` BIGINT NOT NULL DEFAULT '0' COMMENT '创建时间',
`modified_on` BIGINT NOT NULL DEFAULT '0' COMMENT '修改时间', `modified_on` BIGINT NOT NULL DEFAULT '0' COMMENT '修改时间',
`deleted_on` BIGINT NOT NULL DEFAULT '0' COMMENT '删除时间', `deleted_on` BIGINT NOT NULL DEFAULT '0' COMMENT '删除时间',
@ -331,7 +332,8 @@ CREATE TABLE `p_topic_user` (
`reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a', `reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a',
`reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b', `reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b',
PRIMARY KEY ( `id` ) USING BTREE, PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE,
KEY `idx_topic_user_uid_ispin` ( `user_id`, `is_pin`) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题'; ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题';
-- ---------------------------- -- ----------------------------

@ -265,6 +265,7 @@ CREATE TABLE p_topic_user (
remark VARCHAR ( 512 ),-- 备注 remark VARCHAR ( 512 ),-- 备注
quote_num BIGINT,-- 引用数 quote_num BIGINT,-- 引用数
is_top SMALLINT NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶 is_top SMALLINT NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
is_pin SMALLINT NOT NULL DEFAULT 0,-- 是否钉住 0 为未钉住、1 为已钉住
created_on BIGINT NOT NULL DEFAULT 0,-- 创建时间 created_on BIGINT NOT NULL DEFAULT 0,-- 创建时间
modified_on BIGINT NOT NULL DEFAULT 0,-- 修改时间 modified_on BIGINT NOT NULL DEFAULT 0,-- 修改时间
deleted_on BIGINT NOT NULL DEFAULT 0,-- 删除时间 deleted_on BIGINT NOT NULL DEFAULT 0,-- 删除时间
@ -273,6 +274,7 @@ CREATE TABLE p_topic_user (
reserve_b VARCHAR ( 255 ) -- 保留字段b reserve_b VARCHAR ( 255 ) -- 保留字段b
); );
CREATE UNIQUE INDEX idx_topic_user_uid_tid ON p_topic_user USING btree ( topic_id, user_id ); CREATE UNIQUE INDEX idx_topic_user_uid_tid ON p_topic_user USING btree ( topic_id, user_id );
CREATE INDEX idx_topic_user_uid_ispin ON p_topic_user USING btree ( user_id, is_pin );
CREATE SEQUENCE IF NOT EXISTS user_id_seq AS BIGINT MINVALUE 100058 NO MAXVALUE; CREATE SEQUENCE IF NOT EXISTS user_id_seq AS BIGINT MINVALUE 100058 NO MAXVALUE;
DROP TABLE IF EXISTS p_user; DROP TABLE IF EXISTS p_user;

@ -341,6 +341,7 @@ CREATE TABLE "p_topic_user" (
"remark" text ( 512 ),-- 备注 "remark" text ( 512 ),-- 备注
"quote_num" integer,-- 引用数 "quote_num" integer,-- 引用数
"is_top" integer NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶 "is_top" integer NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
"is_pin" integer NOT NULL DEFAULT 0,-- 是否钉住 0 为未钉住、1 为已钉住
"created_on" integer NOT NULL DEFAULT 0,-- 创建时间 "created_on" integer NOT NULL DEFAULT 0,-- 创建时间
"modified_on" integer NOT NULL DEFAULT 0,-- 修改时间 "modified_on" integer NOT NULL DEFAULT 0,-- 修改时间
"deleted_on" integer NOT NULL DEFAULT 0,-- 删除时间 "deleted_on" integer NOT NULL DEFAULT 0,-- 删除时间
@ -680,6 +681,11 @@ ON "p_topic_user" (
"topic_id", "topic_id",
"user_id" "user_id"
); );
CREATE INDEX "main"."idx_topic_user_uid_ispin"
ON "p_topic_user" (
"user_id" ASC,
"is_pin" ASC
);
-- ---------------------------- -- ----------------------------
-- Indexes structure for table p_user -- Indexes structure for table p_user

@ -7,7 +7,7 @@ VITE_USE_WEB_PROFILE=true # 是否使用后端web服务提供的WebPro
# 模块开启 # 模块开启
VITE_ENABLE_ANOUNCEMENT=false VITE_ENABLE_ANOUNCEMENT=false
VITE_ENABLE_WALLET=false VITE_ENABLE_WALLET=false
VITE_ENABLE_TRENDS_BAR=true VITE_ENABLE_TRENDS_BAR=false
# 功能开启 # 功能开启
VITE_ALLOW_TWEET_ATTACHMENT=true VITE_ALLOW_TWEET_ATTACHMENT=true
@ -37,6 +37,6 @@ VITE_COPYRIGHT_RIGHT_LINK="https://www.paopao.info"
# 图片推文参数 # 图片推文参数
VITE_DEFAULT_TWEET_IMAGE_404="https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/404.png" VITE_DEFAULT_TWEET_IMAGE_404="https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/404.png"
# AliOSS 图片thumbnail参数 # AliOSS 图片thumbnail参数
VITE_TWEET_IMAGE_THUMBNAIL="?x-oss-process=image/resize,m_fill,w_300,h_300,limit_0/auto-orient,1/format,png" VITE_TWEET_IMAGE_THUMBNAIL="?x-oss-process=image/resize,m_fill,w_300,h_300,limit_0/auto-orient,1/format,webp"
# MinIOn 图片thumbnail参数 # MinIOn 图片thumbnail参数
# VITE_TWEET_IMAGE_THUMBNAIL="?x-oss-process=image/resize,m_fill,w_300,h_300,limit_0/auto-orient,1/format,png" # VITE_TWEET_IMAGE_THUMBNAIL="?x-oss-process=image/resize,m_fill,w_300,h_300,limit_0/auto-orient,1/format,png"

@ -1 +0,0 @@
import{_ as s}from"./main-nav.vue_vue_type_style_index_0_lang-0ad26703.js";import{u as i}from"./vue-router-e5a2430e.js";import{G as a,e as c,a2 as u}from"./naive-ui-eecf2ec3.js";import{d as l,f as d,k as t,w as o,e as f,A as x}from"./@vue-a481fc63.js";import{_ as g}from"./index-1e276b8f.js";import"./vuex-44de225f.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./@vicons-f0266f88.js";import"./seemly-76b7b838.js";import"./vueuc-7c8d4b48.js";import"./@css-render-7124a1a5.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-4a70c6fc.js";import"./moment-2ab8298d.js";/* empty css */const v=l({__name:"404",setup(h){const e=i(),_=()=>{e.push({path:"/"})};return(k,w)=>{const n=s,p=c,r=u,m=a;return f(),d("div",null,[t(n,{title:"404"}),t(m,{class:"main-content-wrap wrap404",bordered:""},{default:o(()=>[t(r,{status:"404",title:"404 资源不存在",description:"再看看其他的吧"},{footer:o(()=>[t(p,{onClick:_},{default:o(()=>[x("回主页")]),_:1})]),_:1})]),_:1})])}}});const O=g(v,[["__scopeId","data-v-e62daa85"]]);export{O as default};

@ -0,0 +1 @@
import{_ as i}from"./main-nav.vue_vue_type_style_index_0_lang-DmAlkpQJ.js";import{u as s}from"./vue-router-zwGLnBy5.js";import{G as a,e as c,a2 as u}from"./naive-ui-DNcWoFGl.js";import{d as l,f as d,k as t,w as o,e as f,A as x}from"./@vue-CQsYufSu.js";import{_ as g}from"./index-v3l9hw1O.js";import"./vuex-DNAxYlmG.js";import"./vooks-BQzJqMzq.js";import"./evtd-CI_DDEu_.js";import"./@vicons-C3A8jsfr.js";import"./seemly-B7f2tHrf.js";import"./vueuc-CbQ6ZCvR.js";import"./@css-render-CQdyXCYJ.js";import"./vdirs-DL8EOfHr.js";import"./@juggle-C8OzoCMD.js";import"./css-render-Ct37b3-v.js";import"./@emotion-WldOFDRm.js";import"./lodash-es-i05dkx59.js";import"./treemate-HRdUPn5m.js";import"./async-validator-DKvM95Vc.js";import"./date-fns-x7VUUoCw.js";import"./axios-Bo0ATomq.js";import"./moment-BqTRGcJI.js";/* empty css */const h=l({__name:"404",setup(k){const n=s(),e=()=>{n.push({path:"/"})};return(w,v)=>{const r=i,p=c,_=u,m=a;return f(),d("div",null,[t(r,{title:"404"}),t(m,{class:"main-content-wrap wrap404",bordered:""},{default:o(()=>[t(_,{status:"404",title:"404 资源不存在",description:"再看看其他的吧"},{footer:o(()=>[t(p,{onClick:e},{default:o(()=>[x("回主页")]),_:1})]),_:1})]),_:1})])}}}),O=g(h,[["__scopeId","data-v-e62daa85"]]);export{O as default};

@ -1,3 +0,0 @@
import{i as d}from"./@vue-a481fc63.js";function C(i){let r=".",s="__",m="--",f;if(i){let e=i.blockPrefix;e&&(r=e),e=i.elementPrefix,e&&(s=e),e=i.modifierPrefix,e&&(m=e)}const b={install(e){f=e.c;const l=e.context;l.bem={},l.bem.b=null,l.bem.els=null}};function y(e){let l,n;return{before(t){l=t.bem.b,n=t.bem.els,t.bem.els=null},after(t){t.bem.b=l,t.bem.els=n},$({context:t,props:u}){return e=typeof e=="string"?e:e({context:t,props:u}),t.bem.b=e,`${(u==null?void 0:u.bPrefix)||r}${t.bem.b}`}}}function v(e){let l;return{before(n){l=n.bem.els},after(n){n.bem.els=l},$({context:n,props:t}){return e=typeof e=="string"?e:e({context:n,props:t}),n.bem.els=e.split(",").map(u=>u.trim()),n.bem.els.map(u=>`${(t==null?void 0:t.bPrefix)||r}${n.bem.b}${s}${u}`).join(", ")}}}function P(e){return{$({context:l,props:n}){e=typeof e=="string"?e:e({context:l,props:n});const t=e.split(",").map(o=>o.trim());function u(o){return t.map(x=>`&${(n==null?void 0:n.bPrefix)||r}${l.bem.b}${o!==void 0?`${s}${o}`:""}${m}${x}`).join(", ")}const c=l.bem.els;return c!==null?u(c[0]):u()}}}function _(e){return{$({context:l,props:n}){e=typeof e=="string"?e:e({context:l,props:n});const t=l.bem.els;return`&:not(${(n==null?void 0:n.bPrefix)||r}${l.bem.b}${t!==null&&t.length>0?`${s}${t[0]}`:""}${m}${e})`}}}return Object.assign(b,{cB:(...e)=>f(y(e[0]),e[1],e[2]),cE:(...e)=>f(v(e[0]),e[1],e[2]),cM:(...e)=>f(P(e[0]),e[1],e[2]),cNotM:(...e)=>f(_(e[0]),e[1],e[2])}),b}const $=Symbol("@css-render/vue3-ssr");function M(i,r){return`<style cssr-id="${i}">
${r}
</style>`}function S(i,r){const s=d($,null);if(s===null){console.error("[css-render/vue3-ssr]: no ssr context found.");return}const{styles:m,ids:f}=s;f.has(i)||m!==null&&(f.add(i),m.push(M(i,r)))}const j=typeof document<"u";function N(){if(j)return;const i=d($,null);if(i!==null)return{adapter:S,context:i}}export{C as p,N as u};

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

Loading…
Cancel
Save