merge from dev branch

pull/288/head
Michael Li 2 years ago
commit d34068a8c3
No known key found for this signature in database

@ -9,6 +9,7 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
target-branch: "dev"
reviewers: reviewers:
- "rocboss" - "rocboss"
- "alimy" - "alimy"

@ -9,6 +9,7 @@ on:
- 'feature/**' - 'feature/**'
- 'r/**' - 'r/**'
- 'x/**' - 'x/**'
- 't/**'
paths: paths:
- '**.go' - '**.go'
- 'go.mod' - 'go.mod'
@ -46,7 +47,7 @@ jobs:
name: Test name: Test
strategy: strategy:
matrix: matrix:
go-version: [ 1.18.x, 1.19.x ] go-version: [ 1.20.x ]
platform: [ ubuntu-latest, macos-latest ] platform: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
@ -65,7 +66,7 @@ jobs:
name: TestOnWindows name: TestOnWindows
strategy: strategy:
matrix: matrix:
go-version: [ 1.18.x, 1.19.x ] go-version: [ 1.20.x ]
platform: [ windows-latest ] platform: [ windows-latest ]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:

4
.gitignore vendored

@ -1,5 +1,6 @@
.DS_Store
.idea .idea
.vscode __debug_bin
!*.example !*.example
/config.yaml /config.yaml
*.log *.log
@ -7,3 +8,4 @@ paopao-ce*
/release /release
/data /data
/custom /custom
/.custom

@ -0,0 +1,4 @@
.DS_Store
*.log
__debug_bin
settings.json

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "paopao-ce [debug]",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/.vscode/__debug_bin",
"preLaunchTask": "go: build (debug)",
"cwd": "${workspaceFolder}"
}
]
}

@ -0,0 +1,4 @@
let launch = require('./launch.json');
launch.configurations.sort((a, b) => a.name.localeCompare(b.name));
let fs = require('fs');
fs.writeFileSync('launch.json', JSON.stringify(launch, null, 4));

21
.vscode/tasks.json vendored

@ -0,0 +1,21 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "go: build (debug)",
"type": "shell",
"command": "go",
"args": [
"build",
"-gcflags=all=-N -l",
"-tags",
"'embed go_json'",
"-o",
"${workspaceFolder}/.vscode/__debug_bin"
],
"options": {
"cwd": "${workspaceFolder}"
}
}
]
}

@ -0,0 +1,159 @@
# Changelog
All notable changes to paopao-ce are documented in this file.
## 0.3.0+dev ([`dev`](https://github.com/rocboss/paopao-ce/tree/dev))
### Added
- add custom comment sort strategy support [#243](https://github.com/rocboss/paopao-ce/pull/243)
- add `RedisCacheIndex` feature [#250](https://github.com/rocboss/paopao-ce/pull/250)
- add `Sentry` feature [#258](https://github.com/rocboss/paopao-ce/pull/258)
- add simple tweet share feature(just copy tweet link to clipboard now) support [#264](https://github.com/rocboss/paopao-ce/pull/264)
- add default tweet max length configure in web/.env support. [&a1160ca](https://github.com/rocboss/paopao-ce/commit/a1160ca79380445157146d9eae1710543c153cce 'commit a1160ca')
Set the value of `VITE_DEFAULT_TWEET_MAX_LENGTH` in file web/.env to change the tweet max default length.
- add custom whether provide user register configure in web/.env support. [#267](https://github.com/rocboss/paopao-ce/pull/267)
Set the value of `VITE_ALLOW_USER_REGISTER` in file web/.env to custom whether provide user register feature.
```
# file: web/.env or web/.env.local
...
# 局部参数
VITE_ALLOW_USER_REGISTER=true
...
```
and disallow user register in backend(add `Web:DisallowUserRegister` feature in `config.yaml`):
```yaml
# file config.yaml
...
Features:
Default: ["Base", "Postgres", "Zinc", "LocalOSS", "LoggerZinc", "BigCacheIndex", "Friendship", "Service", "Web:DisallowUserRegister"]
...
```
- add topic follow feature support [#273](https://github.com/rocboss/paopao-ce/pull/273)
mirgration database first(sql ddl file in `scripts/migration/**/*_topic_follow.up.sql`):
```sql
CREATE TABLE `p_topic_user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`topic_id` BIGINT UNSIGNED NOT NULL COMMENT '标签ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '创建者ID',
`alias_name` VARCHAR ( 255 ) COMMENT '别名',
`remark` VARCHAR ( 512 ) COMMENT '备注',
`quote_num` BIGINT UNSIGNED COMMENT '引用数',
`is_top` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶',
`created_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
`modified_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间',
`deleted_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除时间',
`is_del` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除',
`reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a',
`reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b',
PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题';
```
- add tweet comment thumbs up/down feature support [#275](https://github.com/rocboss/paopao-ce/pull/275)
mirgration database first(sql ddl file in `scripts/migration/**/*_comment_thumbs.up.sql`):
- add load more comments feature support [&60b217b](https://github.com/rocboss/paopao-ce/commit/60b217bcd950c69ba45cebcaa17efdf8048d5a4f 'commit 60b217b')
### Fixed
- fixed sql ddl p_contact's column `is_delete` define error (change to `is_del`) in scripts/paopao-mysql.sql [&afd8fe1](https://github.com/rocboss/paopao-ce/commit/afd8fe18d2dce08a4af846c2f822379d99a3d3b3 'commit afd8fe1')
- fixed cache index not expire in delete/add tweet error [#266](https://github.com/rocboss/paopao-ce/pull/266)
- fixed tweet's owner not allow star/collection action when tweet is private error [#274](https://github.com/rocboss/paopao-ce/pull/274)
- fixed user not list owner's collectioned private tweet error [#274](https://github.com/rocboss/paopao-ce/pull/274)
- fixed comments thumbs up/down state incorrect error [#283](https://github.com/rocboss/paopao-ce/pull/283)
### Changed
- use [github.com/rueian/rueidis](https://github.com/rueian/rueidis) as Redis client [#249](https://github.com/rocboss/paopao-ce/pull/249)
the **Old** redis client configure field
```yaml
...
Redis:
Host: redis:6379
Password:
DB:
```
the **New** redis client configure field
```yaml
...
Redis:
InitAddress:
- redis:6379
Username:
Password:
SelectDB:
ConnWriteTimeout: 60 # 连接写超时时间 多少秒 默认 60秒
```
- optimize web frontend dark theme [&b082a8f](https://github.com/rocboss/paopao-ce/commit/b082a8fa5e43dd6dacf459df93fa7e243dd901ea 'commit b082a8f')
- change web frontend main content layout default size to 544px [&b082a8f](https://github.com/rocboss/paopao-ce/commit/b082a8fa5e43dd6dacf459df93fa7e243dd901ea 'commit b082a8f')
- optimize web frontend in mobile environment use Drawer to display menu [#265](https://github.com/rocboss/paopao-ce/pull/265)
- optimize Dockerfile use pre-build builder/runner image to prevent network latency problem (`bitbus/paopao-ce-backend-builder` `bitbus/paopao-ce-backend-runner`) [#265](https://github.com/rocboss/paopao-ce/pull/265)
- optimize web ui in mobile environment [#280](https://github.com/rocboss/paopao-ce/pull/280)
### Removed
- remove `Deprecated:OldWeb` feature [#256](https://github.com/rocboss/paopao-ce/pull/256)
## 0.2.5
### Changed
- fixed sql ddl error for contact table [#281](https://github.com/rocboss/paopao-ce/pull/281)
## 0.2.4
### Added
- add PWA support for web frontend [#242](https://github.com/rocboss/paopao-ce/pull/242)
## 0.2.3
### Added
- add PostgreSQL DDL file [#229](https://github.com/rocboss/paopao-ce/pull/229)
### Changed
- optimize MySQL DDL file [#229](https://github.com/rocboss/paopao-ce/pull/229)
- optimize Sqlite3 DDL file [#229](https://github.com/rocboss/paopao-ce/pull/229)
## 0.2.2
### Fixed
- fixed add star to tweet error [#222](https://github.com/rocboss/paopao-ce/pull/222)
## 0.2.1
### Changed
- optimize docker-compose.yaml use bitbus/paopao-ce:latest as release image [#217](https://github.com/rocboss/paopao-ce/pull/217)
### Fixed
- fixed sql ddl in scripts/paopao-mysql.sql and scripts/paopao-sqlite3.sql [#217](https://github.com/rocboss/paopao-ce/pull/217)
## 0.2.0
### Added
- add `Friendship` feature [#192](https://github.com/rocboss/paopao-ce/pull/192)
- add `Lightship` feature [#198](https://github.com/rocboss/paopao-ce/pull/198)
- add `Pyroscope` feature [#199](https://github.com/rocboss/paopao-ce/pull/199)
- add new `Web` service [#196](https://github.com/rocboss/paopao-ce/pull/196)
- add `Frontend:Web` feature [#196](https://github.com/rocboss/paopao-ce/pull/196)
- add `Deprecated:OldWeb` feature [#196](https://github.com/rocboss/paopao-ce/pull/196)
### Changes
- support run multiple service in single paopao-ce instance [#196](https://github.com/rocboss/paopao-ce/pull/196)
- use [go-mir](https://github.com/alimy/mir) optimize paopao-ce source code architecture [#196](https://github.com/rocboss/paopao-ce/pull/196)
### Fixed
- some other features optimize and bug fix
---
**Older change logs can be found on [GitHub](https://github.com/rocboss/paopao-ce/releases?after=v0.2.0).**

@ -1,5 +1,7 @@
# syntax=docker/dockerfile:experimental
# build frontend # build frontend
FROM node:18-alpine as frontend FROM node:19-alpine as frontend
ARG API_HOST ARG API_HOST
ARG USE_API_HOST=yes ARG USE_API_HOST=yes
ARG EMBED_UI=yes ARG EMBED_UI=yes
@ -11,15 +13,11 @@ RUN [ $EMBED_UI != yes ] || [ $USE_DIST != no ] || (yarn && yarn build)
RUN [ $EMBED_UI = yes ] || mkdir dist || echo "" RUN [ $EMBED_UI = yes ] || mkdir dist || echo ""
# build backend # build backend
FROM golang:1.18-alpine AS backend FROM bitbus/paopao-ce-backend-builder:latest AS backend
ARG API_HOST ARG API_HOST
ARG USE_API_HOST=yes ARG USE_API_HOST=yes
ARG EMBED_UI=yes ARG EMBED_UI=yes
ARG USE_DIST=no ARG USE_DIST=no
RUN apk --no-cache --no-progress add --virtual \
build-deps \
build-base \
git
WORKDIR /paopao-ce WORKDIR /paopao-ce
COPY . . COPY . .
@ -28,19 +26,18 @@ ENV GOPROXY=https://goproxy.cn
RUN [ $EMBED_UI != yes ] || make build TAGS='embed go_json' RUN [ $EMBED_UI != yes ] || make build TAGS='embed go_json'
RUN [ $EMBED_UI = yes ] || make build TAGS='go_json' RUN [ $EMBED_UI = yes ] || make build TAGS='go_json'
FROM alpine:3.16 FROM bitbus/paopao-ce-backend-runner:latest
ARG API_HOST ARG API_HOST
ARG USE_API_HOST=yes ARG USE_API_HOST=yes
ARG EMBED_UI=yes ARG EMBED_UI=yes
ARG USE_DIST=no ARG USE_DIST=no
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates
WORKDIR /app/paopao-ce WORKDIR /app/paopao-ce
COPY --from=backend /paopao-ce/release/paopao-ce . COPY --from=backend /paopao-ce/release/paopao-ce .
COPY configs ./configs COPY --from=backend /paopao-ce/config.yaml.sample config.yaml
VOLUME ["/app/paopao-ce/configs"] VOLUME ["/app/paopao-ce/custom"]
EXPOSE 8008 EXPOSE 8008
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD ps -ef | grep paopao-ce || exit 1 HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD ps -ef | grep paopao-ce || exit 1
ENTRYPOINT ["/app/paopao-ce/paopao-ce"] ENTRYPOINT ["/app/paopao-ce/paopao-ce"]

@ -4,27 +4,28 @@ TARGET = paopao-ce
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
TARGET := $(TARGET).exe TARGET := $(TARGET).exe
endif endif
TARGET_BIN = $(basename $(TARGET))
ifeq (n$(CGO_ENABLED),n) ifeq (n$(CGO_ENABLED),n)
CGO_ENABLED := 1 CGO_ENABLED := 0
endif endif
RELEASE_ROOT = release RELEASE_ROOT = release
RELEASE_FILES = LICENSE README.md config.yaml.sample scripts configs RELEASE_FILES = LICENSE README.md CHANGELOG.md config.yaml.sample docker-compose.yaml scripts docs
RELEASE_LINUX_AMD64 = $(RELEASE_ROOT)/linux-amd64/$(TARGET) RELEASE_LINUX_AMD64 = $(RELEASE_ROOT)/linux-amd64/$(TARGET)
RELEASE_DARWIN_AMD64 = $(RELEASE_ROOT)/darwin-amd64/$(TARGET) RELEASE_DARWIN_AMD64 = $(RELEASE_ROOT)/darwin-amd64/$(TARGET)
RELEASE_DARWIN_ARM64 = $(RELEASE_ROOT)/darwin-arm64/$(TARGET) RELEASE_DARWIN_ARM64 = $(RELEASE_ROOT)/darwin-arm64/$(TARGET)
RELEASE_WINDOWS_AMD64 = $(RELEASE_ROOT)/windows-amd64/$(TARGET) RELEASE_WINDOWS_AMD64 = $(RELEASE_ROOT)/windows-amd64/$(TARGET)
BUILD_VERSION := $(shell git describe --tags | cut -f 1 -d "-") BUILD_VERSION := $(shell git describe --tags --always | cut -f1 -f2 -d "-")
BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S') BUILD_DATE := $(shell date +'%Y-%m-%d %H:%M:%S')
SHA_SHORT := $(shell git rev-parse --short HEAD) SHA_SHORT := $(shell git rev-parse --short HEAD)
TAGS = "" TAGS = ""
MOD_NAME = github.com/rocboss/paopao-ce MOD_NAME = github.com/rocboss/paopao-ce
LDFLAGS = -X "${MOD_NAME}/pkg/debug.version=${BUILD_VERSION}" \ LDFLAGS = -X "${MOD_NAME}/pkg/version.version=${BUILD_VERSION}" \
-X "${MOD_NAME}/pkg/debug.buildDate=${BUILD_DATE}" \ -X "${MOD_NAME}/pkg/version.buildDate=${BUILD_DATE}" \
-X "${MOD_NAME}/pkg/debug.commitID=${SHA_SHORT}" -w -s -X "${MOD_NAME}/pkg/version.commitID=${SHA_SHORT}" -w -s
all: fmt build all: fmt build
@ -50,23 +51,23 @@ release: linux-amd64 darwin-amd64 darwin-arm64 windows-x64
.PHONY: linux-amd64 .PHONY: linux-amd64
linux-amd64: linux-amd64:
@echo Build paopao-ce [linux-amd64] CGO_ENABLED=$(CGO_ENABLED) @echo Build paopao-ce [linux-amd64] CGO_ENABLED=$(CGO_ENABLED) TAGS="'$(TAGS)'"
@CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_LINUX_AMD64)/$(TARGET) @CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_LINUX_AMD64)/$(TARGET_BIN)
.PHONY: darwin-amd64 .PHONY: darwin-amd64
darwin-amd64: darwin-amd64:
@echo Build paopao-ce [darwin-amd64] CGO_ENABLED=$(CGO_ENABLED) @echo Build paopao-ce [darwin-amd64] CGO_ENABLED=$(CGO_ENABLED) TAGS="'$(TAGS)'"
@CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_DARWIN_AMD64)/$(TARGET) @CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_DARWIN_AMD64)/$(TARGET_BIN)
.PHONY: darwin-arm64 .PHONY: darwin-arm64
darwin-arm64: darwin-arm64:
@echo Build paopao-ce [darwin-arm64] CGO_ENABLED=$(CGO_ENABLED) @echo Build paopao-ce [darwin-arm64] CGO_ENABLED=$(CGO_ENABLED) TAGS="'$(TAGS)'"
@CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=arm64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_DARWIN_ARM64)/$(TARGET) @CGO_ENABLED=$(CGO_ENABLED) GOOS=darwin GOARCH=arm64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_DARWIN_ARM64)/$(TARGET_BIN)
.PHONY: windows-x64 .PHONY: windows-x64
windows-x64: windows-x64:
@echo Build paopao-ce [windows-x64] CGO_ENABLED=$(CGO_ENABLED) @echo Build paopao-ce [windows-x64] CGO_ENABLED=$(CGO_ENABLED) TAGS="'$(TAGS)'"
@CGO_ENABLED=$(CGO_ENABLED) GOOS=windows GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_WINDOWS_AMD64)/$(basename $(TARGET)).exe @CGO_ENABLED=$(CGO_ENABLED) GOOS=windows GOARCH=amd64 go build -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $(RELEASE_WINDOWS_AMD64)/$(TARGET_BIN).exe
.PHONY: generate .PHONY: generate
generate: gen-mir gen-grpc generate: gen-mir gen-grpc
@ -109,9 +110,9 @@ 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.11.0 @go install github.com/bufbuild/buf/cmd/buf@v1.15.1
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking@v1.11.0 @go install github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking@v1.15.1
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-lint@v1.11.0 @go install github.com/bufbuild/buf/cmd/protoc-gen-buf-lint@v1.15.1
@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

@ -65,7 +65,7 @@ PaoPao主要由以下优秀的开源项目/工具构建
### 环境要求 ### 环境要求
* Go (1.18+) * Go (1.20+)
* Node.js (14+) * Node.js (14+)
* MySQL (5.7+) * MySQL (5.7+)
* Redis * Redis
@ -194,7 +194,10 @@ PaoPao主要由以下优秀的开源项目/工具构建
docker build -t your/paopao-ce:tag --build-arg EMBED_UI=no . docker build -t your/paopao-ce:tag --build-arg EMBED_UI=no .
# 运行 # 运行
docker run -d -p 8008:8008 -v ${PWD}/config.yaml.sample:/app/paopao-ce/config.yaml your/paopao-ce:tag mkdir custom && docker run -d -p 8008:8008 -v ${PWD}/custom:/app/paopao-ce/custom -v ${PWD}/config.yaml.sample:/app/paopao-ce/config.yaml your/paopao-ce:tag
# 或者直接运行构建好的docker image
mkdir custom && docker run -d -p 8008:8008 -v ${PWD}/custom:/app/paopao-ce/custom -v ${PWD}/config.yaml.sample:/app/paopao-ce/config.yaml bitbus/paopao-ce:latest
``` ```
* 前端: * 前端:
@ -227,8 +230,7 @@ docker compose up --build
# file: docker-compose.yaml # file: docker-compose.yaml
... ...
backend: backend:
build: image: bitbus/paopao-ce:latest
context: .
restart: always restart: always
depends_on: depends_on:
- db - db
@ -340,7 +342,6 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r
|`Docs` | 子服务 | WIP | 开启开发者文档服务| |`Docs` | 子服务 | WIP | 开启开发者文档服务|
|`Frontend:Web` | 子服务 | 稳定 | 开启独立前端服务| |`Frontend:Web` | 子服务 | 稳定 | 开启独立前端服务|
|`Frontend:EmbedWeb` | 子服务 | 稳定 | 开启内嵌于后端Web API服务中的前端服务| |`Frontend:EmbedWeb` | 子服务 | 稳定 | 开启内嵌于后端Web API服务中的前端服务|
|`Deprecated:Web` | 子服务 | 稳定 | 开启旧的Web服务|
|`Gorm` | 数据库 | 稳定(默认) | 使用[gorm](https://github.com/go-gorm/gorm)作为数据库的ORM默认使用 `Gorm` + `MySQL`组合| |`Gorm` | 数据库 | 稳定(默认) | 使用[gorm](https://github.com/go-gorm/gorm)作为数据库的ORM默认使用 `Gorm` + `MySQL`组合|
|`Sqlx`| 数据库 | WIP | 使用[sqlx](https://github.com/jmoiron/sqlx)作为数据库的ORM| |`Sqlx`| 数据库 | WIP | 使用[sqlx](https://github.com/jmoiron/sqlx)作为数据库的ORM|
|`Sqlc`| 数据库 | WIP | 使用[sqlc](https://github.com/kyleconroy/sqlc)自动生成ORM代码| |`Sqlc`| 数据库 | WIP | 使用[sqlc](https://github.com/kyleconroy/sqlc)自动生成ORM代码|
@ -358,9 +359,11 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r
|`Redis` | 缓存 | 稳定 | Redis缓存功能 | |`Redis` | 缓存 | 稳定 | Redis缓存功能 |
|`SimpleCacheIndex` | 缓存 | Deprecated | 提供简单的 广场推文列表 的缓存功能 | |`SimpleCacheIndex` | 缓存 | Deprecated | 提供简单的 广场推文列表 的缓存功能 |
|`BigCacheIndex` | 缓存 | 稳定(推荐) | 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 | |`BigCacheIndex` | 缓存 | 稳定(推荐) | 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 |
|`RedisCacheIndex` | 缓存 | 内测(推荐) | 使用Redis缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面 |
|`Zinc` | 搜索 | 稳定(推荐) | 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务 | |`Zinc` | 搜索 | 稳定(推荐) | 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务 |
|`Meili` | 搜索 | 稳定(推荐) | 基于[Meilisearch](https://github.com/meilisearch/meilisearch)搜索引擎提供推文搜索服务 | |`Meili` | 搜索 | 稳定(推荐) | 基于[Meilisearch](https://github.com/meilisearch/meilisearch)搜索引擎提供推文搜索服务 |
|`Bleve` | 搜索 | WIP | 基于[Bleve](https://github.com/blevesearch/bleve)搜索引擎提供推文搜索服务 | |`Bleve` | 搜索 | WIP | 基于[Bleve](https://github.com/blevesearch/bleve)搜索引擎提供推文搜索服务 |
|[`Sentry`](docs/proposal/23040412-关于使用sentry用于错误追踪与性能检测的设计.md) | 监控 | 内测 | 使用Sentry进行错误跟踪与性能监控 |
|`LoggerFile` | 日志 | 稳定 | 使用文件写日志 | |`LoggerFile` | 日志 | 稳定 | 使用文件写日志 |
|`LoggerZinc` | 日志 | 稳定(推荐) | 使用[Zinc](https://github.com/zinclabs/zinc)写日志 | |`LoggerZinc` | 日志 | 稳定(推荐) | 使用[Zinc](https://github.com/zinclabs/zinc)写日志 |
|`LoggerMeili` | 日志 | 内测 | 使用[Meilisearch](https://github.com/meilisearch/meilisearch)写日志 | |`LoggerMeili` | 日志 | 内测 | 使用[Meilisearch](https://github.com/meilisearch/meilisearch)写日志 |
@ -370,7 +373,9 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r
|`Alipay` | 支付 | 稳定 | 开启基于[支付宝开放平台](https://open.alipay.com/)的钱包功能 | |`Alipay` | 支付 | 稳定 | 开启基于[支付宝开放平台](https://open.alipay.com/)的钱包功能 |
|`Sms` | 短信验证 | 稳定 | 开启短信验证码功能,用于手机绑定验证手机是否注册者的;功能如果没有开启,手机绑定时任意短信验证码都可以绑定手机 | |`Sms` | 短信验证 | 稳定 | 开启短信验证码功能,用于手机绑定验证手机是否注册者的;功能如果没有开启,手机绑定时任意短信验证码都可以绑定手机 |
|`Docs:OpenAPI` | 开发文档 | 稳定 | 开启openapi文档功能提供web api文档说明(visit http://127.0.0.1:8008/docs/openapi) | |`Docs:OpenAPI` | 开发文档 | 稳定 | 开启openapi文档功能提供web api文档说明(visit http://127.0.0.1:8008/docs/openapi) |
|[`Pyroscope`](docs/proposal/016-关于使用pyroscope用于性能调试的设计.md)| 性能优化 | 内测 | 开启Pyroscope功能用于性能调试 |
|`PhoneBind` | 其他 | 稳定 | 手机绑定功能 | |`PhoneBind` | 其他 | 稳定 | 手机绑定功能 |
|`Web:DisallowUserRegister` | 功能特性 | 稳定 | 不允许用户注册 |
> 功能项状态详情参考 [features-status](features-status.md). > 功能项状态详情参考 [features-status](features-status.md).
@ -492,6 +497,32 @@ MinIO: # MinIO 存储配置
... ...
``` ```
#### [Pyroscope](https://github.com/pyroscope-io/pyroscope) 性能剖析
* Pyroscope运行
```sh
mkdir -p data/minio/data
# 使用Docker运行
docker run -it -p 4040:4040 pyroscope/pyroscope:latest server
# 使用docker compose运行 需要删除docker-compose.yaml中关于pyroscope的注释
docker compose up -d pyroscope
# visit http://loclahost:4040
```
* 修改Pyroscope配置
```yaml
# features中加上 MinIO
Features:
Default: ["Meili", "LoggerMeili", "Base", "Sqlite3", "BigCacheIndex", "Pyroscope"]
...
Pyroscope: # Pyroscope配置
AppName: "paopao-ce"
Endpoint: "http://localhost:4040" # Pyroscope server address
AuthToken: # Pyroscope authentication token
Logger: none # Pyroscope logger (standard | logrus | none)
...
```
### 源代码分支管理 ### 源代码分支管理
**主代码库`github.com/rocboss/paopao-ce`** **主代码库`github.com/rocboss/paopao-ce`**
```bash ```bash
@ -513,19 +544,23 @@ x/sqlx
| 名称 | 说明 | 备注| | 名称 | 说明 | 备注|
| ----- | ----- | ----- | | ----- | ----- | ----- |
| [`main`](https://github.com/rocboss/paopao-ce) | 主分支 |分支`main`是主分支也是paopao-ce的稳定版本发布分支只有经过内部测试没有重大bug出现的稳定代码才会推进到这个分支该分支主要由`beta`分支代码演进而来,原则上**只接受bug修复PR**。`rc版本/稳定版本` 发布都应该在`main`主分支中进行。| | [`main`](https://github.com/rocboss/paopao-ce) | 主分支 |分支`main`是主分支也是paopao-ce的稳定版本发布分支只有经过内部测试没有重大bug出现的稳定代码才会推进到这个分支该分支主要由`beta`分支代码演进而来,原则上**只接受bug修复PR**。`rc版本/稳定版本` 发布都应该在`main`主分支中进行。|
| [`beta`](https://github.com/rocboss/paopao-ce/tree/beta) | 公测分支 |分支`beta`是公测分支,代码推进到`main`主分支的候选分支;该分支主要由`dev`分支代码演进而来,**接受bug修复以及新功能优化的PR**原则上不接受新功能PR。`alpha/beta版本` 发布都应该在`beta`公测分支下进行。| | [`beta`](https://github.com/rocboss/paopao-ce/tree/beta) | 公测分支 |分支`beta`是公测分支,代码推进到`main`主分支的候选分支;该分支主要由`alpha`分支代码演进而来,**接受bug修复以及新功能优化的PR**原则上不接受新功能PR。`beta版本` 发布都应该在`beta`公测分支下进行。|
| [`dev`](https://github.com/rocboss/paopao-ce/tree/dev) | 开发分支 | 分支`dev`是开发分支,**不定期频繁更新**,接受 *新功能PR、代码优化PR、bug修复PR***新功能PR** 都应该首先提交给`dev`分支进行合并bug修复/代码优化 后 **冻结新功能** 将代码演进合并到`beta`分支。| | [`alpha`](https://github.com/rocboss/paopao-ce/tree/alpha) | 内测分支 |分支`alpha`是内测分支,代码推进到`beta`分支的候选分支;该分支主要由`dev`分支代码演进而来,**接受bug修复以及新功能相关的PR**接受新功能PR。分支代码演进到一个里程碑式的阶段后**冻结所有新功能**,合并代码到`beta`公测分支进行下一阶段的持续演进。`alpha版本` 发布都应该在`alpha`内测分支下进行。|
| [`dev`](https://github.com/rocboss/paopao-ce/tree/dev) | 开发分支 | 分支`dev`是开发分支,**不定期频繁更新**,接受 *新功能PR、代码优化PR、bug修复PR***新功能PR** 都应该首先提交给`dev`分支进行合并bug修复/新功能开发/代码优化 **阶段性冻结** 后将代码演进合并到`alpha`分支。|
| `feature/*` | 子功能分支 |`feature/*`是新功能子分支,一般新功能子分支都是 *从`dev`开发分支fork出来的*;子功能分支 **只专注于该新功能** 代码的开发/优化,待开发接近内测阶段 *提交新功能PR给`dev`分支进行review/merge*,待新功能代码演进到`beta`分支后,原则上是可以删除该分支,但也可以保留到稳定版本发布。**该分支专注于新功能的开发只接受新功能的bug修复/优化PR**。| | `feature/*` | 子功能分支 |`feature/*`是新功能子分支,一般新功能子分支都是 *从`dev`开发分支fork出来的*;子功能分支 **只专注于该新功能** 代码的开发/优化,待开发接近内测阶段 *提交新功能PR给`dev`分支进行review/merge*,待新功能代码演进到`beta`分支后,原则上是可以删除该分支,但也可以保留到稳定版本发布。**该分支专注于新功能的开发只接受新功能的bug修复/优化PR**。|
| `jc/*` |维护者的开发分支|`jc/*`是代码库维护者的开发分支一般包含一些局部优化或者bug修复代码有时可以直接将代码merge到`dev/beta`分支原则上不允许直接merge代码到`main`主分支。| | `jc/*` |维护者的开发分支|`jc/*`是代码库维护者的开发分支一般包含一些局部优化或者bug修复代码有时可以直接将代码merge到`dev/beta`分支原则上不允许直接merge代码到`main`主分支。|
| `x/*` |实验分支|`x/*`是技术实验分支某些技术的引入需要经过具体的代码实现与真实场景的测评考量评估后如果某项技术适合引入到paopao-ce就fork出一个`feature/*`分支作为新功能引入到paopao-ce。一般一些比较激进的技术从`dev`分支fork出一个新的`x/*`分支各种尝试、考量、评估后或丢弃、或引入到paopao-ce。| | `x/*` |实验分支|`x/*`是技术实验分支某些技术的引入需要经过具体的代码实现与真实场景的测评考量评估后如果某项技术适合引入到paopao-ce就fork出一个`feature/*`分支作为新功能引入到paopao-ce。一般一些比较激进的技术从`dev`分支fork出一个新的`x/*`分支各种尝试、考量、评估后或丢弃、或引入到paopao-ce。|
| `t/*` | 临时分支 |`t/*`是临时发版本分支,一般 `beta` 分支演进到正式版本发布前的最后某个beta版本比如v0.2.0-beta)就从beta分支fork出一个 `t/*` 分支用于向 `main` 分支提交 PR 用于Review待 PR Reviewed 合并到 `main` 分支后,可以删除这个临时创建的分支。这样设计主要是考虑到有时合并到 `main` 分支时需要Review的时间可能会长一些而dev分支的代码又急需推进到beta分支以发布下一个alpha版本用于内测相当于为下一个测试版本发布腾地方。|
| `r/*` |发行版本分支|`r/*`是不同发行版本分支,不同发行版本各有不同的侧重点,可以根据需要选择适合的发行版本。| | `r/*` |发行版本分支|`r/*`是不同发行版本分支,不同发行版本各有不同的侧重点,可以根据需要选择适合的发行版本。|
**发行版本分支说明** **发行版本分支说明**
| 名称 | 说明 | 维护者 | 备注 | | 名称 | 说明 | 维护者 | 备注 |
| ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- |
|[`paopao-ce`](https://github.com/rocboss/paopao-ce/tree/dev)|paopao-ce 主发行版本|[ROC](https://github.com/rocboss 'ROC')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/dev/internal/dao/jinzhu) 使用[gorm](https://github.com/go-gorm/gorm)作为数据逻辑层的ORM框架适配MySQL/PostgreSQL/Sqlite3数据库。| |[`paopao-ce`](https://github.com/rocboss/paopao-ce/tree/dev)|paopao-ce 主发行版本|[ROC](https://github.com/rocboss 'ROC')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/dev/internal/dao/jinzhu) 使用[gorm](https://github.com/go-gorm/gorm)作为数据逻辑层的ORM框架适配MySQL/PostgreSQL/Sqlite3数据库。|
|[`r/paopao-ce`](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce)|paopao-ce 主分支预览版本|[ROC](https://github.com/rocboss 'ROC')<br/>[北野](https://github.com/alimy 'Michael Li')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/dev/internal/dao/jinzhu) 使用[gorm](https://github.com/go-gorm/gorm)作为数据逻辑层的ORM框架适配MySQL/PostgreSQL/Sqlite3数据库。代码较`main`分支新,是主发行版本的前瞻预览版本。|
|[`r/paopao-ce-plus`](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-plus)|paopao-ce-plus 发行版本|[北野](https://github.com/alimy 'Michael Li')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-plus/internal/dao/sakila) 使用[sqlx](https://github.com/jmoiron/sqlx)作为数据逻辑层的ORM框架专注于为MySQL/PostgreSQL/Sqlite3使用更优化的查询语句以提升数据检索效率。建议熟悉[sqlx](https://github.com/jmoiron/sqlx)的开发人员可以基于此版本来做 二次开发。| |[`r/paopao-ce-plus`](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-plus)|paopao-ce-plus 发行版本|[北野](https://github.com/alimy 'Michael Li')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-plus/internal/dao/sakila) 使用[sqlx](https://github.com/jmoiron/sqlx)作为数据逻辑层的ORM框架专注于为MySQL/PostgreSQL/Sqlite3使用更优化的查询语句以提升数据检索效率。建议熟悉[sqlx](https://github.com/jmoiron/sqlx)的开发人员可以基于此版本来做 二次开发。|
|[`r/paopao-ce-pro`](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-pro)|paopao-ce-pro 发行版本|[北野](https://github.com/alimy 'Michael Li')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-pro/internal/dao/slonik) 使用[sqlc](https://github.com/kyleconroy/sqlc)作为sql语句生成器自动生成ORM代码专门针对特定数据库MySQL/PostgreSQL进行查询优化熟悉[sqlc](https://github.com/kyleconroy/sqlc)的开发人员可以基于此版本来做 二次开发。(另:分支目前只使用[pgx-v5](https://github.com/jackc/pgx)适配了PostgreSQL数据库后续或许会适配MySQL/TiDB数据库。)| |[`r/paopao-ce-pro`](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-pro)|paopao-ce-pro 发行版本|[北野](https://github.com/alimy 'Michael Li')|该分支 [数据逻辑层](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-pro/internal/dao/slonik) 使用[sqlc](https://github.com/kyleconroy/sqlc)作为sql语句生成器自动生成ORM代码专门针对特定数据库MySQL/PostgreSQL进行查询优化熟悉[sqlc](https://github.com/kyleconroy/sqlc)的开发人员可以基于此版本来做 二次开发。(另:分支目前只使用[pgx-v5](https://github.com/jackc/pgx)适配了PostgreSQL数据库后续或许会适配MySQL/TiDB数据库。)|
|[`r/paopao-ce-xtra`](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-xtra)|paopao-ce-xtra 发行版本|[北野](https://github.com/alimy 'Michael Li')|该分支 是r/paopao-ce、r/paopao-ce-plus、r/paopao-ce-pro的合集|
**代码分支演进图** **代码分支演进图**
![](docs/proposal/.assets/000-01.png) ![](docs/proposal/.assets/000-01.png)

@ -1,16 +1,28 @@
## Roadmap for paopao-ce ## Roadmap for paopao-ce
[paopao-ce](https://github.com/rocboss/paopao-ce/tree/dev)/[paopao-plus](https://github.com/rocboss/paopao-ce/tree/r/paopao-plus)/[paopao-pro](https://github.com/rocboss/paopao-ce/tree/r/paopao-pro) features develop or optimize and bug fix roadmap. [paopao-ce](https://github.com/rocboss/paopao-ce/tree/dev)/[paopao-ce-plus](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-plus)/[paopao-ce-pro](https://github.com/rocboss/paopao-ce/tree/r/paopao-ce-pro) features develop or optimize and bug fix roadmap.
### paopao-ce roadmap ## paopao-ce roadmap
#### v0.3.0 #### dev+
* [ ] add `Followship` feature * [ ] add `Followship` feature
* [ ] add `Auth:Bcrypt` feature
* [ ] add `Auth:MD5` feature (just for compatible)
* [x] add extend base ORM code for implement data logic base sqlx/sqlc
* [ ] optimize media tweet submit logic * [ ] optimize media tweet submit logic
* [ ] optimize search logic service * [ ] optimize search logic service
#### v0.3.0
* [x] remove `Deprecated:OldWeb` feature
* [x] add user topic follow feature support
* [x] add tweet link share support
* [ ] add comment thumbsUp/thumbsDown support
* [x] add `RedisCacheIndex` feature
* [x] add `Sentry` feature
#### v0.2.0 #### v0.2.0
* [x] add `Friendship` feature * [x] add `Friendship` feature
* [x] add `Lightship` feature * [x] add `Lightship` feature
* [ ] add extend base ORM code for implement data logic base sqlx/sqlc * [ ] add extend base ORM code for implement data logic base sqlx/sqlc
* [x] add `Pyroscope` feature
* [x] add new `Web` service * [x] add new `Web` service
* [x] add `Frontend:Web` feature * [x] add `Frontend:Web` feature
* [x] add `Deprecated:OldWeb` feature * [x] add `Deprecated:OldWeb` feature
@ -33,14 +45,26 @@
* [ ] add reactions support * [ ] add reactions support
* [ ] add tweet thread like twitter support * [ ] add tweet thread like twitter support
* [ ] add short link support * [ ] add short link support
* [ ] optimize current message push logic service use `ims` module
* [ ] optimize topics service * [ ] optimize topics service
* [ ] optimize current message push logic service use `ims` module
* [ ] optimize backend data logic service(optimize database CRUD operate) * [ ] optimize backend data logic service(optimize database CRUD operate)
### paopao-plus roadmap ## paopao-ce-plus roadmap
#### v0.3.0 #### paopao-ce-plus/v0.4.0
* [ ] adapt for paopao-ce v0.4.0
#### paopao-ce-plus/v0.3.0
* [ ] adapt for paopao-ce v0.3.0 * [ ] adapt for paopao-ce v0.3.0
### paopao-pro roadmap #### paopao-ce-plus/v0.2.0
#### v0.3.0 * [ ] adapt for paopao-ce v0.2.0
## paopao-ce-pro roadmap
#### paopao-ce-pro/v0.4.0
* [ ] adapt for paopao-ce v0.4.0
#### paopao-ce-pro/v0.3.0
* [ ] adapt for paopao-ce v0.3.0 * [ ] adapt for paopao-ce v0.3.0
#### paopao-ce-pro/v0.2.0
* [ ] adapt for paopao-ce v0.2.0

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
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 v3.0.1 // - mir v3.1.1
package v1 package v1
@ -16,6 +16,8 @@ type Loose interface {
// Chain provide handlers chain for gin // Chain provide handlers chain for gin
Chain() gin.HandlersChain Chain() gin.HandlersChain
TweetComments(*web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error)
TopicList(*web.TopicListReq) (*web.TopicListResp, mir.Error)
GetUserProfile(*web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) GetUserProfile(*web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error)
GetUserTweets(*web.GetUserTweetsReq) (*web.GetUserTweetsResp, mir.Error) GetUserTweets(*web.GetUserTweetsReq) (*web.GetUserTweetsResp, mir.Error)
Timeline(*web.TimelineReq) (*web.TimelineResp, mir.Error) Timeline(*web.TimelineReq) (*web.TimelineResp, mir.Error)
@ -24,6 +26,8 @@ type Loose interface {
} }
type LooseBinding interface { type LooseBinding interface {
BindTweetComments(*gin.Context) (*web.TweetCommentsReq, mir.Error)
BindTopicList(*gin.Context) (*web.TopicListReq, mir.Error)
BindGetUserProfile(*gin.Context) (*web.GetUserProfileReq, mir.Error) BindGetUserProfile(*gin.Context) (*web.GetUserProfileReq, mir.Error)
BindGetUserTweets(*gin.Context) (*web.GetUserTweetsReq, mir.Error) BindGetUserTweets(*gin.Context) (*web.GetUserTweetsReq, mir.Error)
BindTimeline(*gin.Context) (*web.TimelineReq, mir.Error) BindTimeline(*gin.Context) (*web.TimelineReq, mir.Error)
@ -32,6 +36,8 @@ type LooseBinding interface {
} }
type LooseRender interface { type LooseRender interface {
RenderTweetComments(*gin.Context, *web.TweetCommentsResp, mir.Error)
RenderTopicList(*gin.Context, *web.TopicListResp, mir.Error)
RenderGetUserProfile(*gin.Context, *web.GetUserProfileResp, mir.Error) RenderGetUserProfile(*gin.Context, *web.GetUserProfileResp, mir.Error)
RenderGetUserTweets(*gin.Context, *web.GetUserTweetsResp, mir.Error) RenderGetUserTweets(*gin.Context, *web.GetUserTweetsResp, mir.Error)
RenderTimeline(*gin.Context, *web.TimelineResp, mir.Error) RenderTimeline(*gin.Context, *web.TimelineResp, mir.Error)
@ -47,6 +53,38 @@ func RegisterLooseServant(e *gin.Engine, s Loose, b LooseBinding, r LooseRender)
router.Use(middlewares...) router.Use(middlewares...)
// register routes info to router // register routes info to router
router.Handle("GET", "/post/comments", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindTweetComments(c)
if err != nil {
r.RenderTweetComments(c, nil, err)
return
}
resp, err := s.TweetComments(req)
r.RenderTweetComments(c, resp, err)
})
router.Handle("GET", "/tags", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindTopicList(c)
if err != nil {
r.RenderTopicList(c, nil, err)
return
}
resp, err := s.TopicList(req)
r.RenderTopicList(c, resp, err)
})
router.Handle("GET", "/user/profile", func(c *gin.Context) { router.Handle("GET", "/user/profile", func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
@ -105,6 +143,14 @@ func (UnimplementedLooseServant) Chain() gin.HandlersChain {
return nil return nil
} }
func (UnimplementedLooseServant) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedLooseServant) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedLooseServant) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) { func (UnimplementedLooseServant) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
} }
@ -124,6 +170,14 @@ type UnimplementedLooseRender struct {
RenderAny func(*gin.Context, any, mir.Error) RenderAny func(*gin.Context, any, mir.Error)
} }
func (r *UnimplementedLooseRender) RenderTweetComments(c *gin.Context, data *web.TweetCommentsResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedLooseRender) RenderTopicList(c *gin.Context, data *web.TopicListResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedLooseRender) RenderGetUserProfile(c *gin.Context, data *web.GetUserProfileResp, err mir.Error) { func (r *UnimplementedLooseRender) RenderGetUserProfile(c *gin.Context, data *web.GetUserProfileResp, err mir.Error) {
r.RenderAny(c, data, err) r.RenderAny(c, data, err)
} }
@ -143,6 +197,18 @@ type UnimplementedLooseBinding struct {
BindAny func(*gin.Context, any) mir.Error BindAny func(*gin.Context, any) mir.Error
} }
func (b *UnimplementedLooseBinding) BindTweetComments(c *gin.Context) (*web.TweetCommentsReq, mir.Error) {
obj := new(web.TweetCommentsReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedLooseBinding) BindTopicList(c *gin.Context) (*web.TopicListReq, mir.Error) {
obj := new(web.TopicListReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedLooseBinding) BindGetUserProfile(c *gin.Context) (*web.GetUserProfileReq, mir.Error) { func (b *UnimplementedLooseBinding) BindGetUserProfile(c *gin.Context) (*web.GetUserProfileReq, mir.Error) {
obj := new(web.GetUserProfileReq) obj := new(web.GetUserProfileReq)
err := b.BindAny(c, obj) err := b.BindAny(c, obj)

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v3.0.1 // - mir v3.1.1
package v1 package v1
@ -16,6 +16,13 @@ type Priv interface {
// Chain provide handlers chain for gin // Chain provide handlers chain for gin
Chain() gin.HandlersChain Chain() gin.HandlersChain
UnfollowTopic(*web.UnfollowTopicReq) mir.Error
FollowTopic(*web.FollowTopicReq) mir.Error
StickTopic(*web.StickTopicReq) (*web.StickTopicResp, mir.Error)
ThumbsDownTweetReply(*web.TweetReplyThumbsReq) mir.Error
ThumbsUpTweetReply(*web.TweetReplyThumbsReq) mir.Error
ThumbsDownTweetComment(*web.TweetCommentThumbsReq) mir.Error
ThumbsUpTweetComment(*web.TweetCommentThumbsReq) mir.Error
DeleteCommentReply(*web.DeleteCommentReplyReq) mir.Error DeleteCommentReply(*web.DeleteCommentReplyReq) mir.Error
CreateCommentReply(*web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error) CreateCommentReply(*web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error)
DeleteComment(*web.DeleteCommentReq) mir.Error DeleteComment(*web.DeleteCommentReq) mir.Error
@ -35,6 +42,13 @@ type Priv interface {
} }
type PrivBinding interface { type PrivBinding interface {
BindUnfollowTopic(*gin.Context) (*web.UnfollowTopicReq, mir.Error)
BindFollowTopic(*gin.Context) (*web.FollowTopicReq, mir.Error)
BindStickTopic(*gin.Context) (*web.StickTopicReq, mir.Error)
BindThumbsDownTweetReply(*gin.Context) (*web.TweetReplyThumbsReq, mir.Error)
BindThumbsUpTweetReply(*gin.Context) (*web.TweetReplyThumbsReq, mir.Error)
BindThumbsDownTweetComment(*gin.Context) (*web.TweetCommentThumbsReq, mir.Error)
BindThumbsUpTweetComment(*gin.Context) (*web.TweetCommentThumbsReq, mir.Error)
BindDeleteCommentReply(*gin.Context) (*web.DeleteCommentReplyReq, mir.Error) BindDeleteCommentReply(*gin.Context) (*web.DeleteCommentReplyReq, mir.Error)
BindCreateCommentReply(*gin.Context) (*web.CreateCommentReplyReq, mir.Error) BindCreateCommentReply(*gin.Context) (*web.CreateCommentReplyReq, mir.Error)
BindDeleteComment(*gin.Context) (*web.DeleteCommentReq, mir.Error) BindDeleteComment(*gin.Context) (*web.DeleteCommentReq, mir.Error)
@ -54,6 +68,13 @@ type PrivBinding interface {
} }
type PrivRender interface { type PrivRender interface {
RenderUnfollowTopic(*gin.Context, mir.Error)
RenderFollowTopic(*gin.Context, mir.Error)
RenderStickTopic(*gin.Context, *web.StickTopicResp, mir.Error)
RenderThumbsDownTweetReply(*gin.Context, mir.Error)
RenderThumbsUpTweetReply(*gin.Context, mir.Error)
RenderThumbsDownTweetComment(*gin.Context, mir.Error)
RenderThumbsUpTweetComment(*gin.Context, mir.Error)
RenderDeleteCommentReply(*gin.Context, mir.Error) RenderDeleteCommentReply(*gin.Context, mir.Error)
RenderCreateCommentReply(*gin.Context, *web.CreateCommentReplyResp, mir.Error) RenderCreateCommentReply(*gin.Context, *web.CreateCommentReplyResp, mir.Error)
RenderDeleteComment(*gin.Context, mir.Error) RenderDeleteComment(*gin.Context, mir.Error)
@ -80,6 +101,112 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) {
router.Use(middlewares...) router.Use(middlewares...)
// register routes info to router // register routes info to router
router.Handle("POST", "/topic/unfollow", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindUnfollowTopic(c)
if err != nil {
r.RenderUnfollowTopic(c, err)
return
}
r.RenderUnfollowTopic(c, s.UnfollowTopic(req))
})
router.Handle("POST", "/topic/follow", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindFollowTopic(c)
if err != nil {
r.RenderFollowTopic(c, err)
return
}
r.RenderFollowTopic(c, s.FollowTopic(req))
})
router.Handle("POST", "/topic/stick", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindStickTopic(c)
if err != nil {
r.RenderStickTopic(c, nil, err)
return
}
resp, err := s.StickTopic(req)
r.RenderStickTopic(c, resp, err)
})
router.Handle("POST", "/tweet/reply/thumbsdown", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindThumbsDownTweetReply(c)
if err != nil {
r.RenderThumbsDownTweetReply(c, err)
return
}
r.RenderThumbsDownTweetReply(c, s.ThumbsDownTweetReply(req))
})
router.Handle("POST", "/tweet/reply/thumbsup", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindThumbsUpTweetReply(c)
if err != nil {
r.RenderThumbsUpTweetReply(c, err)
return
}
r.RenderThumbsUpTweetReply(c, s.ThumbsUpTweetReply(req))
})
router.Handle("POST", "/tweet/comment/thumbsdown", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindThumbsDownTweetComment(c)
if err != nil {
r.RenderThumbsDownTweetComment(c, err)
return
}
r.RenderThumbsDownTweetComment(c, s.ThumbsDownTweetComment(req))
})
router.Handle("POST", "/tweet/comment/thumbsup", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindThumbsUpTweetComment(c)
if err != nil {
r.RenderThumbsUpTweetComment(c, err)
return
}
r.RenderThumbsUpTweetComment(c, s.ThumbsUpTweetComment(req))
})
router.Handle("DELETE", "/post/comment/reply", func(c *gin.Context) { router.Handle("DELETE", "/post/comment/reply", func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
@ -206,7 +333,7 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) {
r.RenderCollectionTweet(c, resp, err) r.RenderCollectionTweet(c, resp, err)
}) })
router.Handle("POST", "/post/start", func(c *gin.Context) { router.Handle("POST", "/post/star", func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
return return
@ -311,6 +438,34 @@ func (UnimplementedPrivServant) Chain() gin.HandlersChain {
return nil return nil
} }
func (UnimplementedPrivServant) UnfollowTopic(req *web.UnfollowTopicReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) FollowTopic(req *web.FollowTopicReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) ThumbsDownTweetReply(req *web.TweetReplyThumbsReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) ThumbsUpTweetReply(req *web.TweetReplyThumbsReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) ThumbsDownTweetComment(req *web.TweetCommentThumbsReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) ThumbsUpTweetComment(req *web.TweetCommentThumbsReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) DeleteCommentReply(req *web.DeleteCommentReplyReq) mir.Error { func (UnimplementedPrivServant) DeleteCommentReply(req *web.DeleteCommentReplyReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
} }
@ -374,6 +529,34 @@ type UnimplementedPrivRender struct {
RenderAny func(*gin.Context, any, mir.Error) RenderAny func(*gin.Context, any, mir.Error)
} }
func (r *UnimplementedPrivRender) RenderUnfollowTopic(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderFollowTopic(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderStickTopic(c *gin.Context, data *web.StickTopicResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedPrivRender) RenderThumbsDownTweetReply(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderThumbsUpTweetReply(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderThumbsDownTweetComment(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderThumbsUpTweetComment(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderDeleteCommentReply(c *gin.Context, err mir.Error) { func (r *UnimplementedPrivRender) RenderDeleteCommentReply(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err) r.RenderAny(c, nil, err)
} }
@ -437,6 +620,48 @@ type UnimplementedPrivBinding struct {
BindAny func(*gin.Context, any) mir.Error BindAny func(*gin.Context, any) mir.Error
} }
func (b *UnimplementedPrivBinding) BindUnfollowTopic(c *gin.Context) (*web.UnfollowTopicReq, mir.Error) {
obj := new(web.UnfollowTopicReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindFollowTopic(c *gin.Context) (*web.FollowTopicReq, mir.Error) {
obj := new(web.FollowTopicReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindStickTopic(c *gin.Context) (*web.StickTopicReq, mir.Error) {
obj := new(web.StickTopicReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindThumbsDownTweetReply(c *gin.Context) (*web.TweetReplyThumbsReq, mir.Error) {
obj := new(web.TweetReplyThumbsReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindThumbsUpTweetReply(c *gin.Context) (*web.TweetReplyThumbsReq, mir.Error) {
obj := new(web.TweetReplyThumbsReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindThumbsDownTweetComment(c *gin.Context) (*web.TweetCommentThumbsReq, mir.Error) {
obj := new(web.TweetCommentThumbsReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindThumbsUpTweetComment(c *gin.Context) (*web.TweetCommentThumbsReq, mir.Error) {
obj := new(web.TweetCommentThumbsReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindDeleteCommentReply(c *gin.Context) (*web.DeleteCommentReplyReq, mir.Error) { func (b *UnimplementedPrivBinding) BindDeleteCommentReply(c *gin.Context) (*web.DeleteCommentReplyReq, mir.Error) {
obj := new(web.DeleteCommentReplyReq) obj := new(web.DeleteCommentReplyReq)
err := b.BindAny(c, obj) err := b.BindAny(c, obj)

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v3.0.1 // - mir v3.1.1
package v1 package v1
@ -13,8 +13,6 @@ import (
) )
type Pub interface { type Pub interface {
TopicList(*web.TopicListReq) (*web.TopicListResp, mir.Error)
TweetComments(*web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error)
TweetDetail(*web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) TweetDetail(*web.TweetDetailReq) (*web.TweetDetailResp, mir.Error)
SendCaptcha(*web.SendCaptchaReq) mir.Error SendCaptcha(*web.SendCaptchaReq) mir.Error
GetCaptcha() (*web.GetCaptchaResp, mir.Error) GetCaptcha() (*web.GetCaptchaResp, mir.Error)
@ -26,8 +24,6 @@ type Pub interface {
} }
type PubBinding interface { type PubBinding interface {
BindTopicList(*gin.Context) (*web.TopicListReq, mir.Error)
BindTweetComments(*gin.Context) (*web.TweetCommentsReq, mir.Error)
BindTweetDetail(*gin.Context) (*web.TweetDetailReq, mir.Error) BindTweetDetail(*gin.Context) (*web.TweetDetailReq, mir.Error)
BindSendCaptcha(*gin.Context) (*web.SendCaptchaReq, mir.Error) BindSendCaptcha(*gin.Context) (*web.SendCaptchaReq, mir.Error)
BindRegister(*gin.Context) (*web.RegisterReq, mir.Error) BindRegister(*gin.Context) (*web.RegisterReq, mir.Error)
@ -37,8 +33,6 @@ type PubBinding interface {
} }
type PubRender interface { type PubRender interface {
RenderTopicList(*gin.Context, *web.TopicListResp, mir.Error)
RenderTweetComments(*gin.Context, *web.TweetCommentsResp, mir.Error)
RenderTweetDetail(*gin.Context, *web.TweetDetailResp, mir.Error) RenderTweetDetail(*gin.Context, *web.TweetDetailResp, mir.Error)
RenderSendCaptcha(*gin.Context, mir.Error) RenderSendCaptcha(*gin.Context, mir.Error)
RenderGetCaptcha(*gin.Context, *web.GetCaptchaResp, mir.Error) RenderGetCaptcha(*gin.Context, *web.GetCaptchaResp, mir.Error)
@ -54,38 +48,6 @@ func RegisterPubServant(e *gin.Engine, s Pub, b PubBinding, r PubRender) {
router := e.Group("v1") router := e.Group("v1")
// register routes info to router // register routes info to router
router.Handle("GET", "/tags", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindTopicList(c)
if err != nil {
r.RenderTopicList(c, nil, err)
return
}
resp, err := s.TopicList(req)
r.RenderTopicList(c, resp, err)
})
router.Handle("GET", "/post/comments", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindTweetComments(c)
if err != nil {
r.RenderTweetComments(c, nil, err)
return
}
resp, err := s.TweetComments(req)
r.RenderTweetComments(c, resp, err)
})
router.Handle("GET", "/post", func(c *gin.Context) { router.Handle("GET", "/post", func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
@ -177,14 +139,6 @@ func RegisterPubServant(e *gin.Engine, s Pub, b PubBinding, r PubRender) {
type UnimplementedPubServant struct { type UnimplementedPubServant struct {
} }
func (UnimplementedPubServant) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPubServant) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPubServant) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) { func (UnimplementedPubServant) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
} }
@ -216,14 +170,6 @@ type UnimplementedPubRender struct {
RenderAny func(*gin.Context, any, mir.Error) RenderAny func(*gin.Context, any, mir.Error)
} }
func (r *UnimplementedPubRender) RenderTopicList(c *gin.Context, data *web.TopicListResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedPubRender) RenderTweetComments(c *gin.Context, data *web.TweetCommentsResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedPubRender) RenderTweetDetail(c *gin.Context, data *web.TweetDetailResp, err mir.Error) { func (r *UnimplementedPubRender) RenderTweetDetail(c *gin.Context, data *web.TweetDetailResp, err mir.Error) {
r.RenderAny(c, data, err) r.RenderAny(c, data, err)
} }
@ -255,18 +201,6 @@ type UnimplementedPubBinding struct {
BindAny func(*gin.Context, any) mir.Error BindAny func(*gin.Context, any) mir.Error
} }
func (b *UnimplementedPubBinding) BindTopicList(c *gin.Context) (*web.TopicListReq, mir.Error) {
obj := new(web.TopicListReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPubBinding) BindTweetComments(c *gin.Context) (*web.TweetCommentsReq, mir.Error) {
obj := new(web.TweetCommentsReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPubBinding) BindTweetDetail(c *gin.Context) (*web.TweetDetailReq, mir.Error) { func (b *UnimplementedPubBinding) BindTweetDetail(c *gin.Context) (*web.TweetDetailReq, mir.Error) {
obj := new(web.TweetDetailReq) obj := new(web.TweetDetailReq)
err := b.BindAny(c, obj) err := b.BindAny(c, obj)

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT. // Code generated by go-mir. DO NOT EDIT.
// versions: // versions:
// - mir v3.0.1 // - mir v3.1.1
package v1 package v1

@ -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.28.1 // protoc-gen-go v1.30.0
// protoc (unknown) // protoc (unknown)
// source: v1/auth.proto // source: v1/auth.proto

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.2.0 // - protoc-gen-go-grpc v1.3.0
// - protoc (unknown) // - protoc (unknown)
// source: v1/auth.proto // source: v1/auth.proto
@ -18,6 +18,12 @@ import (
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion7
const (
Authenticate_PreLogin_FullMethodName = "/auth.Authenticate/preLogin"
Authenticate_Login_FullMethodName = "/auth.Authenticate/login"
Authenticate_Logout_FullMethodName = "/auth.Authenticate/logout"
)
// AuthenticateClient is the client API for Authenticate service. // AuthenticateClient is the client API for Authenticate service.
// //
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
@ -37,7 +43,7 @@ func NewAuthenticateClient(cc grpc.ClientConnInterface) AuthenticateClient {
func (c *authenticateClient) PreLogin(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) { func (c *authenticateClient) PreLogin(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) {
out := new(ActionReply) out := new(ActionReply)
err := c.cc.Invoke(ctx, "/auth.Authenticate/preLogin", in, out, opts...) err := c.cc.Invoke(ctx, Authenticate_PreLogin_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -46,7 +52,7 @@ func (c *authenticateClient) PreLogin(ctx context.Context, in *User, opts ...grp
func (c *authenticateClient) Login(ctx context.Context, in *User, opts ...grpc.CallOption) (*LoginReply, error) { func (c *authenticateClient) Login(ctx context.Context, in *User, opts ...grpc.CallOption) (*LoginReply, error) {
out := new(LoginReply) out := new(LoginReply)
err := c.cc.Invoke(ctx, "/auth.Authenticate/login", in, out, opts...) err := c.cc.Invoke(ctx, Authenticate_Login_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -55,7 +61,7 @@ func (c *authenticateClient) Login(ctx context.Context, in *User, opts ...grpc.C
func (c *authenticateClient) Logout(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) { func (c *authenticateClient) Logout(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) {
out := new(ActionReply) out := new(ActionReply)
err := c.cc.Invoke(ctx, "/auth.Authenticate/logout", in, out, opts...) err := c.cc.Invoke(ctx, Authenticate_Logout_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -108,7 +114,7 @@ func _Authenticate_PreLogin_Handler(srv interface{}, ctx context.Context, dec fu
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/auth.Authenticate/preLogin", FullMethod: Authenticate_PreLogin_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthenticateServer).PreLogin(ctx, req.(*User)) return srv.(AuthenticateServer).PreLogin(ctx, req.(*User))
@ -126,7 +132,7 @@ func _Authenticate_Login_Handler(srv interface{}, ctx context.Context, dec func(
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/auth.Authenticate/login", FullMethod: Authenticate_Login_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthenticateServer).Login(ctx, req.(*User)) return srv.(AuthenticateServer).Login(ctx, req.(*User))
@ -144,7 +150,7 @@ func _Authenticate_Logout_Handler(srv interface{}, ctx context.Context, dec func
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/auth.Authenticate/logout", FullMethod: Authenticate_Logout_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthenticateServer).Logout(ctx, req.(*User)) return srv.(AuthenticateServer).Logout(ctx, req.(*User))

@ -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 -f2 -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:${VERSION}" \
--tag "$IMAGE:latest" \
. -f Dockerfile
# push to image rep
# if [ -n "$1" ]; then
# docker push "$IMAGE:${VERSION}"
# docker push "$IMAGE:latest"
# fi

@ -0,0 +1,11 @@
#!/bin/sh
# eg.1 : sh build-image.sh
# eg.2, set tags: sh build-image.sh 'embed go_json'
TAGS='embed go_json'
if [ -n "$1" ]; then
TAGS="$1"
fi
make release CGO_ENABLED=0 TAGS="$TAGS"

@ -8,23 +8,23 @@ App: # APP基础设置项
Server: # 服务设置 Server: # 服务设置
RunMode: debug RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8008 HttpPort: 8010
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
Features: Features:
Default: ["Base", "MySQL", "Option", "Zinc", "LocalOSS", "LoggerFile", "Friendship", "Deprecated"] Default: ["Web", "Frontend:EmbedWeb", "Zinc", "LocalOSS", "MySQL", "BigCacheIndex", "LoggerZinc", "Friendship"]
Develop: ["Base", "MySQL", "BigCacheIndex", "Meili", "Sms", "AliOSS", "LoggerMeili", "OSS:Retention"] Develop: ["Base", "MySQL", "BigCacheIndex", "Meili", "Sms", "AliOSS", "LoggerMeili", "OSS:Retention"]
Demo: ["Base", "MySQL", "Option", "Zinc", "Sms", "MinIO", "LoggerZinc", "Migration"] Demo: ["Base", "MySQL", "Option", "Zinc", "Sms", "MinIO", "LoggerZinc", "Migration"]
Slim: ["Base", "Sqlite3", "LocalOSS", "LoggerFile", "OSS:TempDir"] Slim: ["Base", "Sqlite3", "LocalOSS", "LoggerFile", "OSS:TempDir"]
Base: ["Redis", "PhoneBind"] Base: ["Redis", "PhoneBind"]
Docs: ["Docs:OpenAPI"] Docs: ["Docs:OpenAPI"]
Deprecated: ["Deprecated:OldWeb"] Deprecated: ["Deprecated:OldWeb"]
Service: ["Web", "Admin", "SpaceX", "Bot", "LocalOSS", "Mobile", "Frontend:Web", "Fronetend:EmbedWeb", "Docs"] Service: ["Web", "Admin", "SpaceX", "Bot", "LocalOSS", "Mobile", "Frontend:Web", "Frontend:EmbedWeb", "Docs"]
Option: ["SimpleCacheIndex"] Option: ["SimpleCacheIndex"]
Sms: "SmsJuhe" Sms: "SmsJuhe"
WebServer: # Web服务 WebServer: # Web服务
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8010 HttpPort: 8008
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
AdminServer: # Admin后台运维服务 AdminServer: # Admin后台运维服务
@ -84,7 +84,7 @@ BigCacheIndex: # 使用BigCache缓存泡泡广场消息流
Logger: # 日志通用配置 Logger: # 日志通用配置
Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace
LoggerFile: # 使用File写日志 LoggerFile: # 使用File写日志
SavePath: data/paopao-ce/logs SavePath: custom/data/paopao-ce/logs
FileName: app FileName: app
FileExt: .log FileExt: .log
LoggerZinc: # 使用Zinc写日志 LoggerZinc: # 使用Zinc写日志
@ -154,7 +154,7 @@ S3: # Amazon S3 存储配置
Bucket: paopao Bucket: paopao
Domain: Domain:
LocalOSS: # 本地文件OSS存储配置 LocalOSS: # 本地文件OSS存储配置
SavePath: data/paopao-ce/oss SavePath: custom/data/paopao-ce/oss
Secure: False Secure: False
Bucket: paopao Bucket: paopao
Domain: 127.0.0.1:8008 Domain: 127.0.0.1:8008
@ -179,9 +179,8 @@ Postgres: # PostgreSQL数据库
SSLMode: disable SSLMode: disable
TimeZone: Asia/Shanghai TimeZone: Asia/Shanghai
Sqlite3: # Sqlite3数据库 Sqlite3: # Sqlite3数据库
Path: data/sqlite3/paopao-ce.db Path: custom/data/sqlite3/paopao-ce.db
Redis: Redis:
Host: redis:6379 InitAddress:
Password: - redis:6379
DB:

@ -4,4 +4,4 @@ PaoPao部署站点信息。
| 名称 | 网址 | 站长 | 备注 | | 名称 | 网址 | 站长 | 备注 |
| ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- |
|泡泡|[www.paopao.info](https://www.paopao.info)|[ROC](https://www.paopao.info/#/user?username=roc 'roc(@paopao.info)')|PaoPao官方站点| |泡泡|[www.paopao.info](https://www.paopao.info)|[ROC](https://www.paopao.info/#/user?username=roc 'roc(@paopao.info)')|PaoPao官方站点|
|布里塔|[bulita.net](https://bulita.net)|[chendong](https://www.paopao.info/#/user?username=chendong 'chendong(@paopao.info)')|布里塔 - 招聘求职转发| |布里塔|[bulita.cn](https://bulita.cn)|[chendong](https://www.paopao.info/#/user?username=chendong 'chendong(@paopao.info)')|招聘求职等信息|

@ -13,7 +13,7 @@ services:
- ./scripts/paopao-mysql.sql:/docker-entrypoint-initdb.d/paopao.sql - ./scripts/paopao-mysql.sql:/docker-entrypoint-initdb.d/paopao.sql
- ./custom/data/mysql/data:/var/lib/mysql - ./custom/data/mysql/data:/var/lib/mysql
ports: ports:
- 127.0.0.1::3306 - 3306:3306
networks: networks:
- paopao-network - paopao-network
@ -32,11 +32,22 @@ services:
# networks: # networks:
# - paopao-network # - paopao-network
# redis:
# image: redis:7.0-alpine
# restart: always
# ports:
# - 6379:6379
# networks:
# - paopao-network
redis: redis:
image: redis:7.0-alpine image: redis/redis-stack:7.0.6-RC8
restart: always restart: always
ports: ports:
- 6379:6379 - 6379:6379
- 8001:8001
environment:
REDISEARCH_ARGS: "MAXSEARCHRESULTS 5"
networks: networks:
- paopao-network - paopao-network
@ -56,7 +67,7 @@ services:
- paopao-network - paopao-network
# meili: # meili:
# image: getmeili/meilisearch:v1.0 # image: getmeili/meilisearch:v1.1
# restart: always # restart: always
# ports: # ports:
# - 7700:7700 # - 7700:7700
@ -67,12 +78,13 @@ services:
# networks: # networks:
# - paopao-network # - paopao-network
# # a ui for managing your meilisearch instances # pyroscope:
# uirecord: # image: pyroscope/pyroscope:latest
# image: bitriory/uirecord:latest
# restart: always # restart: always
# ports: # ports:
# - 7701:3000 # - 4040:4040
# command:
# - 'server'
# networks: # networks:
# - paopao-network # - paopao-network
@ -90,8 +102,7 @@ services:
- paopao-network - paopao-network
backend: backend:
build: image: bitbus/paopao-ce:nightly
context: .
restart: always restart: always
depends_on: depends_on:
- db - db
@ -100,7 +111,7 @@ services:
# modify below to reflect your custom configure # modify below to reflect your custom configure
volumes: volumes:
- ./config.yaml.sample:/app/paopao-ce/config.yaml - ./config.yaml.sample:/app/paopao-ce/config.yaml
- ./custom/data/paopao-ce/data:/app/paopao-ce/data - ./custom:/app/paopao-ce/custom
ports: ports:
- 8008:8008 - 8008:8008
networks: networks:

@ -94,6 +94,7 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r
|`Sms` | 短信验证 | 稳定 | 开启短信验证码功能,用于手机绑定验证手机是否注册者的;功能如果没有开启,手机绑定时任意短信验证码都可以绑定手机 | |`Sms` | 短信验证 | 稳定 | 开启短信验证码功能,用于手机绑定验证手机是否注册者的;功能如果没有开启,手机绑定时任意短信验证码都可以绑定手机 |
|`Docs:OpenAPI` | 开发文档 | 稳定 | 开启openapi文档功能提供web api文档说明(visit http://127.0.0.1:8008/docs/openapi) | |`Docs:OpenAPI` | 开发文档 | 稳定 | 开启openapi文档功能提供web api文档说明(visit http://127.0.0.1:8008/docs/openapi) |
|`PhoneBind` | 其他 | 稳定 | 手机绑定功能 | |`PhoneBind` | 其他 | 稳定 | 手机绑定功能 |
|`Web:DisallowUserRegister` | 功能特性 | 稳定 | 不允许用户注册 |
> 功能项状态详情参考 [features-status](../../../features-status.md). > 功能项状态详情参考 [features-status](../../../features-status.md).

@ -0,0 +1,30 @@
Features:
Default: ["Sqlite3", "Zinc", "LocalOSS", "LoggerFile", "BigCacheIndex", "Friendship", "Frontend:EmbedWeb", "Web"]
BigCacheIndex: # 使用BigCache缓存泡泡广场消息流
MaxIndexPage: 1024 # 最大缓存页数必须是2^n, 代表最大同时缓存多少页数据
HardMaxCacheSize: 256 # 最大缓存大小(MB)0表示无限制
WebServer: # Web服务
HttpIp: 0.0.0.0
HttpPort: 8008
ReadTimeout: 60
WriteTimeout: 60
JWT: # 鉴权加密
Secret: 18a6413dc4fe394c66345ebe501b2f26
Issuer: paopao-api
Expire: 86400
Zinc: # Zinc搜索配置
Host: 127.0.0.1:4080
Index: paopao-data
User: admin
Password: admin
Secure: False
LocalOSS: # 本地文件OSS存储配置
SavePath: custom/data/oss
Secure: True
Bucket: paopao
Domain: api.alimy.me
Sqlite3: # Sqlite3数据库
Path: custom/data/sqlite3/paopao-ce.db
Redis:
InitAddress:
- 127.0.0.1:6379

@ -3,3 +3,6 @@
* [0000-讨论样式模版](./0000-讨论样式模版.md "讨论样式模版") * [0000-讨论样式模版](./0000-讨论样式模版.md "讨论样式模版")
* [0001-FAQs](./0001-FAQs.md "FAQs") * [0001-FAQs](./0001-FAQs.md "FAQs")
### paopao.info
关于paopao-ce的交流、反馈也可以直接在我们官网[paopao.info](https://www.paopao.info)进行,发布动态时记得加上标签`#paopao-ce`或类似主题相关的标签,方便话题查找。欢迎大家在[paopao.info](https://www.paopao.info)畅快愉悦的交流一起让paopao-ce的用户体验更好、更便捷。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 000 | 北野 | 2022-11-04 |2022-11-04 | v1.0| 提议 | | YYMMDDHH | 北野 | 2022-11-04 |2022-11-04 | v1.0| 提议 |
## <我是标题> ## <我是标题>
---- 这里写简要介绍 ---- ---- 这里写简要介绍 ----

@ -1,28 +0,0 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- |
| 003| 北野 | 2022-11-04 | 2022-11-04 | v0.0 | 提议 |
### 关于Followship功能项的设计
---- 这里写简要介绍 ----
### 场景
---- 这里描述在什么使用场景下会需要本提按 ----
### 需求
实现类似于Twitter的追随者关注模式的推文传播模型使得广场页面可以浏览已关注用户的推文。
TODO-TL;DR...
### 方案
TODO
### 疑问
1. 如何开启这个功能?
TODO
### 更新记录
#### v0.0(2022-11-04) - 北野
* 初始文档, 先占个位置

@ -0,0 +1,60 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- |
| 22110409| 北野 | 2022-11-04 | 2022-11-21 | v0.1 | 提议 |
### 关于Followship功能项的设计
Followship是实现类似Twitter Timeline模式**关注者模型**的时间线信息流广场推文列表的生成将主要与推文时间、用户的关注者相关。Twitter的推文消息流是非常智能的用户体验也非常好这得益于其背后的智能推荐算法以及完善的关注者模型体系当然还有很多其他机制共同作用下的结果。本提按作为一个总纲为paopao-ce引入类似的机制这将是一个持续完善的缓慢过程一切都是为了用户体验用户就是上帝用户需要什么paopao-ce就努力提供什么
### 场景
* 站点用户基数达到一定规模,推文总量很多、推文更新很快,需要一种机制更智能的生成广场推文列表改善用户体验;
* 单纯的想为站点引入关注者模型体系;
### 需求
#### 基本需求
* 实现类似于Twitter的**追随者关注模式**的推文传播模型,使得广场页面可以浏览已关注用户的推文;
* 提供 `关注/取消关注` 用户的机制;
* 浏览 `用户关注者` 列表的机制;
* 浏览 `用户追随者` 列表的机制;
* 用户主页显示 `关注者/追随者` 计数,并作为入口进入对应浏览 `关注者/追随者` 列表的页面;
* 广场推文页面 依据 **关注者模型**体系生成个性化推文列表;
#### 可选需求
* 建立 **发现机制(Discover)** 为用户 *智能推荐* 用户可能会**感兴趣、值得关注**的用户;
* 同上,广场推文页面可以附带一些**被推荐的推文**,这些推文不属于关注用户的推文,但是与关注用户相关,比如是用户关注者的关注者、热度用户等;
### 方案
#### 方案一 (仅使用后端关系数据库MySQL/Sqite3/PostgreSQL满足基本需求)
* 提供 `关注/取消关注` 用户的机制 - 前端/后端
* 浏览 `用户关注者` 列表的机制 - 前端/后端
* 浏览 `用户追随者` 列表的机制 - 前端/后端
* 用户主页显示 `关注者/追随者` 计数,并作为入口进入对应浏览 `关注者/追随者` 列表的页面 - 前端/后端
* 广场推文页面 依据 **关注者模型**体系生成个性化推文列表 - 后端
* 发送推文时,默认可见性为 `公开`, 公开的语义将是`关注者可见`, 因此用户如果没有登入或者没有关注任何用户,广场推文列表将只有自己的私密推文或者是空的 - 前端/后端
#### 方案二
在方案一的基础上通过引入 *图数据库* 实现可选需求。待研究、设计~
### 疑问
1. Friendship与Followship这两功能项是否可以共存还是互斥呢
理论上这两个功能项是可以共存的如果设置成共存就需要同时提供这两个机制的UI交互同时广场推文列表的生成机制也需要做相应适配可能会比较复杂。前期开发将把这两个功能设置为互斥的即**用户关系模型**只能设置为其中之一,待两个功能项都趋于稳定后,再测试这两个功能项共存后的用户体验,如果效果还不错就引入共存机制。
1. 如何开启这个功能?
在配置文件config.yaml中的`Features`中添加`Followship`功能项开启该功能:
```yaml
...
# features中加上 Followship
Features:
Default: ["Meili", "LoggerMeili", "Base", "Sqlite3", "BigCacheIndex", "MinIO", "Followship"]
Base: ["Redis", "PhoneBind"]
...
```
### 更新记录
#### v0.0(2022-11-04) - 北野
* 初始文档, 先占个位置;
#### v0.1(2022-11-21) - 北野
* 添加初始内容;

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 002| 北野 | 2022-11-04 | 2023-01-04 | v1.0 | 提议 | | 22110410 | 北野 | 2022-11-04 | 2023-01-04 | v1.0 | 提议 |
### Friendship功能项的设计概要 ### Friendship功能项的设计概要
Friendship功能提供好友间分享推文信息的机制更好的帮助用户建立自己的推文分享小圈子。Friendship本质上想优化的是泡泡广场页面推文列表的生成机制开启功能后推文列表只能获取 `公开/私密/好友` 的推文,每个用户都有属于自己的个性化推文列表。在提供个性化推文列表生成机制的同时,好友体系的建立也顺便帮助用户建立自己的个性化有限范围内的灵魂社交小圈子,只有相互间拥有个性化认同感的用户才能互为好友。 Friendship功能提供好友间分享推文信息的机制更好的帮助用户建立自己的推文分享小圈子。Friendship本质上想优化的是泡泡广场页面推文列表的生成机制开启功能后推文列表只能获取 `公开/私密/好友` 的推文,每个用户都有属于自己的个性化推文列表。在提供个性化推文列表生成机制的同时,好友体系的建立也顺便帮助用户建立自己的个性化有限范围内的灵魂社交小圈子,只有相互间拥有个性化认同感的用户才能互为好友。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 001| 北野 | 2022-11-04 | 2023-01-13 | v1.1 | 提议 | | 22110411 | 北野 | 2022-11-04 | 2023-01-13 | v1.1 | 提议 |
## 概述 ## 概述
paopao-ce是一个清新文艺的微社区提供类似Twiter/微博的推文分享服务。paopao-ce的运营形态有点类似WordPress只不过WordPress是使用PHP语言开发的博客平台提供的是博客服务而paopao-ce提供的是类似Twitter的推文分享服务。paopao-ce 让 **个人或小组织** 可以快速、方便的部署一个提供**推文分享服务**的小站点,在有限范围内形成一个友善的社交小圈子微社区。 paopao-ce是一个清新文艺的微社区提供类似Twiter/微博的推文分享服务。paopao-ce的运营形态有点类似WordPress只不过WordPress是使用PHP语言开发的博客平台提供的是博客服务而paopao-ce提供的是类似Twitter的推文分享服务。paopao-ce 让 **个人或小组织** 可以快速、方便的部署一个提供**推文分享服务**的小站点,在有限范围内形成一个友善的社交小圈子微社区。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 005| 北野 | 2022-11-21 | 2023-01-04 | v1.1 | 提议 | | 22112109 | 北野 | 2022-11-21 | 2023-01-04 | v1.1 | 提议 |
### 引入go-mir优化后端架构设计 ### 引入go-mir优化后端架构设计
引入[github.com/alimy/mir/v3](https://github.com/alimy/mir)优化后端的架构设计,使得后端代码更具扩展型。 引入[github.com/alimy/mir/v3](https://github.com/alimy/mir)优化后端的架构设计,使得后端代码更具扩展型。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 006| 北野 | 2022-11-23 | 2022-01-01 | v1.0 | 提议 | | 22112309 | 北野 | 2022-11-23 | 2022-01-01 | v1.0 | 提议 |
### 关于paopao-ce的结构设计 ### 关于paopao-ce的结构设计
本文档主要讨论paopao-ce目前的代码结构简要清晰的描述一个**API请求**从 **接受解析->逻辑处理->结果响应**的大概路径帮助开发人员快速了解paopao-ce代码基的基本面更好的融入paopao-ce的开发中做出PR贡献。 本文档主要讨论paopao-ce目前的代码结构简要清晰的描述一个**API请求**从 **接受解析->逻辑处理->结果响应**的大概路径帮助开发人员快速了解paopao-ce代码基的基本面更好的融入paopao-ce的开发中做出PR贡献。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 011| 北野 | 2022-12-14 | 2022-01-09 | v1.1 | 提议 | | 22121409 | 北野 | 2022-12-14 | 2022-01-09 | v1.1 | 提议 |
### 关于Lightship功能项的设计 ### 关于Lightship功能项的设计
Lightship(开放模式)功能提供完全公开的推文分享服务,有别于[Friendship](002-关于Friendship功能项的设计.md "关于Friendship功能项的设计")、[Followship](003-关于Followship功能项的设计.md "关于Followship功能项的设计")使用Lightship用户模式部署paopao-ce用户发布的所有推文都是公开可访问的广场推文列表展示的是全站所有公开推文的Timeline Tweets。 Lightship(开放模式)功能提供完全公开的推文分享服务,有别于[Friendship](002-关于Friendship功能项的设计.md "关于Friendship功能项的设计")、[Followship](003-关于Followship功能项的设计.md "关于Followship功能项的设计")使用Lightship用户模式部署paopao-ce用户发布的所有推文都是公开可访问的广场推文列表展示的是全站所有公开推文的Timeline Tweets。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 013| 北野 | 2023-01-13 | 2023-01-13 | v0.0 | 提议 | | 23011309 | 北野 | 2023-01-13 | 2023-01-13 | v0.0 | 提议 |
### 概述 ### 概述
目前paopao-ce前端/后端 都使用中文提供业务服务暂时还没有提供国际化i18n机制提供国际化的业务服务。本提按提议提过在前端/后端中引入i8n机制以提供国际化业务服务。 本提按建立在[012-优化前端运行时配置获取机制的设计](012-优化前端运行时配置获取机制的设计.md)基础之上。 目前paopao-ce前端/后端 都使用中文提供业务服务暂时还没有提供国际化i18n机制提供国际化的业务服务。本提按提议提过在前端/后端中引入i8n机制以提供国际化业务服务。 本提按建立在[012-优化前端运行时配置获取机制的设计](012-优化前端运行时配置获取机制的设计.md)基础之上。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 012| 北野 | 2023-01-13 | 2023-01-13 | v0.0 | 提议 | | 23011310 | 北野 | 2023-01-13 | 2023-01-13 | v0.0 | 提议 |
### 概述 ### 概述
目前的Web前端运行时配置是通过编译时配置[.env](../../web/.env)进行静态配置虽然能满足简单的功能需求但是非常不灵活。本提按提议一种由paopao-ce后端服务控制的前端运行时配置获取机制让前端更灵活的依据运行时配置提供产品服务。 目前的Web前端运行时配置是通过编译时配置[.env](../../web/.env)进行静态配置虽然能满足简单的功能需求但是非常不灵活。本提按提议一种由paopao-ce后端服务控制的前端运行时配置获取机制让前端更灵活的依据运行时配置提供产品服务。

@ -1,6 +1,6 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| 014| 北野 | 2023-02-09 | 2023-02-09 | v0.0 | 提议 | | 23020910| 北野 | 2023-02-09 | 2023-02-09 | v0.0 | 提议 |
### 概述 ### 概述
TODO TODO

@ -0,0 +1,25 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- |
| 23021310| 北野 | 2023-02-13 | 2023-02-13 | v0.0 | 提议 |
### 概述
TODO
### 需求
TODO
### 方案
TODO
### 疑问
1. 为什么要引入bcrypt
TODO
### 参考文档
* [bcrypt](https://github.com/golang/crypto/blob/master/bcrypt/bcrypt.go)
* [bcrypt module](https://pkg.go.dev/golang.org/x/crypto@v0.6.0/bcrypt)
### 更新记录
#### v0.0(2023-02-13) - 北野
* 初始文档, 先占个位置

@ -0,0 +1,53 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- |
| 23021510| 北野 | 2023-02-15 | 2023-02-16 | v1.1 | 提议 |
### 概述
Pyroscope 是一个开源的持续性能剖析平台。它能够帮你:
* 找出源代码中的性能问题和瓶颈
* 解决 CPU 利用率高的问题
* 理解应用程序的调用树call tree
* 追踪随一段时间内变化的情况
### 需求
* 开发环境下启用Pyroscope但是部署环境下禁用。
### 方案
#### 设计要点
* 使用//go:build pyroscope 可选编译app集成Pyroscope功能
* config.yaml中添加`Pyroscope` 功能来启用Pyroscope
#### 设计细节
* 参考实现(PR):
[add Pyroscope support #199](https://github.com/rocboss/paopao-ce/pull/199)
### 疑问
1. 为什么要引入Pyroscope
用于开发环境下对paopao-ce进行性能优化。
2. 如何开启这个功能?
* 构建时将 `pyroscope` 添加到TAGS中:
```sh
make run TAGS='pyroscope'
```
* 在配置文件config.yaml中的`Features`中添加`Pyroscope`功能项开启该功能:
```yaml
...
# features中加上 Friendship
Features:
Default: ["Meili", "LoggerMeili", "Base", "Sqlite3", "BigCacheIndex", "MinIO", "Pyroscope"]
Base: ["Redis", "PhoneBind"]
...
```
### 参考文档
* [pyroscope](https://github.com/pyroscope-io/pyroscope)
* [pyroscope client](https://github.com/pyroscope-io/client)
### 更新记录
#### v1.0(2023-02-15) - 北野
* 初始文档
#### v1.1(2023-02-16) - 北野
* 添加参考实现

@ -0,0 +1,49 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- |
| 23040412| 北野 | 2023-04-04 | 2023-04-04 | v1.0 | 提议 |
### 概述
[Sentry](https://github.com/getsentry/sentry) Sentry is a developer-first error tracking and performance monitoring platform that helps developers see what actually matters, solve quicker, and learn continuously about their applications.
### 需求
* 通过配置文件开启Sentry功能
### 方案
#### 设计要点
* config.yaml中添加`Sentry` 功能来启用Sentry功能
#### 设计细节
* 参考实现(PR):
[add Sentry feature support #258](https://github.com/rocboss/paopao-ce/pull/258)
### 疑问
1. 为什么要引入Sentry
添加一种对paopao-ce的错误追踪与性能检测机制。
2. 如何开启这个功能?
* 在配置文件config.yaml中的`Features`中添加`Sentry`功能项开启该功能:
```yaml
...
# features中加上 Sentry
Features:
Default: ["Meili", "LoggerMeili", "Base", "Sqlite3", "BigCacheIndex", "MinIO", "Sentry"]
Base: ["Redis", "PhoneBind"]
Sentry:
Sentry: # Sentry配置
Dsn: "http://4ea0af5cd88d4512b7e52070506c80ec@localhost:9000/2"
Debug: True
AttachStacktrace: True
TracesSampleRate: 1.0
AttachLogrus: True # logrus是否附加到Sentry
AttachGin: True # gin是否附加到Sentry
...
```
### 参考文档
* [sentry](https://github.com/getsentry/sentry)
* [self-hosted](https://develop.sentry.dev/self-hosted/)
### 更新记录
#### v1.0(2023-04-04) - 北野
* 初始文档

@ -1,6 +1,6 @@
## Draft ## Draft
* [001-关于paopao-ce的设计定位](001-关于paopao-ce的设计定位.md "关于paopao-ce的设计定位") * [22110411-关于paopao-ce的设计定位](22110411-关于paopao-ce的设计定位.md "关于paopao-ce的设计定位")
* [002-关于Friendship功能项的设计](002-关于Friendship功能项的设计.md "关于Friendship功能项的设计") * [22110410-关于Friendship功能项的设计](22110410-关于Friendship功能项的设计.md "关于Friendship功能项的设计")
* [003-关于Followship功能项的设计](003-关于Followship功能项的设计.md "关于Followship功能项的设计") * [22110409-关于Followship功能项的设计](22110409-关于Followship功能项的设计.md "关于Followship功能项的设计")
* [005-引入go-mir优化后端架构设计](005-引入go-mir优化后端架构设计.md "引入go-mir优化后端架构设计") * [22112109-引入go-mir优化后端架构设计](22112109-引入go-mir优化后端架构设计.md "引入go-mir优化后端架构设计")
* [006-关于paopao-ce的结构设计](006-关于paopao-ce的结构设计.md "关于paopao-ce的结构设计") * [22112309-关于paopao-ce的结构设计](22112309-关于paopao-ce的结构设计.md "关于paopao-ce的结构设计")

@ -1,8 +1,8 @@
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
| ----- | ----- | ----- | ----- | ----- | ----- | | ----- | ----- | ----- | ----- | ----- | ----- |
| <编号000> | <作者> | <发表时间> | <变更时间> | <版本号v1.0> | <提议/提案/决议/冻结> | | <编号YYMMDDHH> | <作者> | <发表时间> | <变更时间> | <版本号v1.0> | <提议/提案/决议/冻结> |
* 编号: 填写提案编号,三位数比如001 * 编号: 填写提案编号,六位数格式为YYMMDDHH年(后两位)+月+日+时
* 作者: 填写发表者。 * 作者: 填写发表者。
* 发表时间: 填写首次发表时间,之后保持不变。 * 发表时间: 填写首次发表时间,之后保持不变。
* 变更时间: 填写变更时间,首次发表时,变更时间和发表时间一样。 * 变更时间: 填写变更时间,首次发表时,变更时间和发表时间一样。

@ -42,7 +42,7 @@
* `Frontend:EmbedWeb` 开启内嵌于后端Web API服务中的前端服务(目前状态: 内测) * `Frontend:EmbedWeb` 开启内嵌于后端Web API服务中的前端服务(目前状态: 内测)
* [ ] 提按文档 * [ ] 提按文档
* [x] 服务初始化逻辑 * [x] 服务初始化逻辑
* `Deprecated:OldWeb` 开启旧的Web服务(目前状态: 内测) * `Deprecated:OldWeb` 开启旧的Web服务(目前状态: 已弃,不可用)
* [ ] 提按文档 * [ ] 提按文档
* [x] 服务初始化逻辑 * [x] 服务初始化逻辑
@ -105,6 +105,10 @@
* [ ] 提按文档 * [ ] 提按文档
* [x] 接口定义 * [x] 接口定义
* [x] 业务逻辑实现 * [x] 业务逻辑实现
* `RedisCacheIndex` 使用Redis缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面(目前状态: 内测阶段,推荐使用)
* [ ] 提按文档
* [x] 接口定义
* [x] 业务逻辑实现
#### 搜索: #### 搜索:
* `Zinc` 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务(目前状态: 稳定,推荐使用) * `Zinc` 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务(目前状态: 稳定,推荐使用)
@ -119,6 +123,7 @@
* [ ] 提按文档 * [ ] 提按文档
* [ ] 接口定义 * [ ] 接口定义
* [ ] 业务逻辑实现 * [ ] 业务逻辑实现
#### 日志: #### 日志:
* `LoggerFile` 使用文件写日志(目前状态: 稳定); * `LoggerFile` 使用文件写日志(目前状态: 稳定);
* [ ] 提按文档 * [ ] 提按文档
@ -133,6 +138,12 @@
* [x] 接口定义 * [x] 接口定义
* [x] 业务逻辑实现 * [x] 业务逻辑实现
#### 监控:
* `Sentry` 使用Sentry进行错误跟踪与性能监控(目前状态: 内测);
* [x] [提按文档](docs/proposal/23040412-关于使用sentry用于错误追踪与性能检测的设计.md)
* [x] 接口定义
* [x] 业务逻辑实现
#### 关系模式: #### 关系模式:
* `Friendship` 弱关系好友模式,类似微信朋友圈(目前状态: 内测); * `Friendship` 弱关系好友模式,类似微信朋友圈(目前状态: 内测);
* [x] [提按文档](docs/proposal/002-关于Friendship功能项的设计.md) * [x] [提按文档](docs/proposal/002-关于Friendship功能项的设计.md)
@ -164,13 +175,24 @@
* [x] 业务逻辑实现 * [x] 业务逻辑实现
### 开发文档: ### 开发文档:
* `Docs:OpenAPI` 开启openapi文档功能提供web api文档说明(visit http://127.0.0.1:8008/docs/openapi) * `Docs:OpenAPI` 开启openapi文档功能提供web api文档说明(visit http://127.0.0.1:8008/docs/openapi);
* [ ] 提按文档 * [ ] 提按文档
* [x] 接口定义 * [x] 接口定义
* [x] 业务逻辑实现 * [x] 业务逻辑实现
### 性能优化
* [`Pyroscope`](docs/proposal/016-关于使用pyroscope用于性能调试的设计.md) 开启Pyroscope功能用于性能调试(目前状态: 内测);
* [x] 提按文档
* [x] 业务逻辑实现
### 其他: ### 其他:
* `PhoneBind` 手机绑定功能; * `PhoneBind` 手机绑定功能;
* [ ] 提按文档 * [ ] 提按文档
* [x] 接口定义 * [x] 接口定义
* [x] 业务逻辑实现 * [x] 业务逻辑实现
### 功能特性:
* `Web:DisallowUserRegister` 不允许用户注册;
* [ ] 提按文档
* [x] 接口定义
* [x] 业务逻辑实现

168
go.mod

@ -1,133 +1,139 @@
module github.com/rocboss/paopao-ce module github.com/rocboss/paopao-ce
go 1.18 go 1.20
require ( require (
github.com/Masterminds/semver/v3 v3.1.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
github.com/alimy/cfg v0.3.0 github.com/alimy/cfg v0.3.0
github.com/alimy/mir/v3 v3.0.1 github.com/alimy/mir/v3 v3.1.1
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
github.com/allegro/bigcache/v3 v3.0.2 github.com/allegro/bigcache/v3 v3.0.2
github.com/bytedance/sonic v1.5.0 github.com/bytedance/sonic v1.8.8
github.com/cockroachdb/errors v1.9.1
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/ethereum/go-ethereum v1.10.16 github.com/fatih/color v1.15.0
github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.20.0
github.com/fbsobreira/gotron-sdk v0.0.0-20211102183839-58a64f4da5f4 github.com/gin-contrib/cors v1.4.0
github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.9.0
github.com/gin-gonic/gin v1.8.1
github.com/go-redis/redis/v8 v8.11.4
github.com/go-resty/resty/v2 v2.7.0 github.com/go-resty/resty/v2 v2.7.0
github.com/goccy/go-json v0.9.7 github.com/goccy/go-json v0.10.2
github.com/gofrs/uuid v4.0.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.4.3 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-migrate/migrate/v4 v4.15.2 github.com/golang-migrate/migrate/v4 v4.15.2
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.12+incompatible github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.3+incompatible
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/meilisearch/meilisearch-go v0.23.0 github.com/meilisearch/meilisearch-go v0.24.0
github.com/minio/minio-go/v7 v7.0.45 github.com/minio/minio-go/v7 v7.0.52
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.6
github.com/pyroscope-io/client v0.7.0
github.com/rueian/rueidis v0.0.100
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/smartwalle/alipay/v3 v3.1.7 github.com/smartwalle/alipay/v3 v3.2.1
github.com/spf13/viper v1.14.0 github.com/sourcegraph/conc v0.3.0
github.com/tencentyun/cos-go-sdk-v5 v0.7.35 github.com/spf13/viper v1.15.0
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc
google.golang.org/grpc v1.50.1 go.uber.org/automaxprocs v1.5.2
google.golang.org/protobuf v1.28.1 google.golang.org/grpc v1.54.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0 google.golang.org/protobuf v1.30.0
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.3.4 gorm.io/driver/mysql v1.5.0
gorm.io/driver/postgres v1.3.7 gorm.io/driver/postgres v1.5.0
gorm.io/driver/sqlite v1.3.4 gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.23.4 gorm.io/gorm v1.25.0
gorm.io/plugin/dbresolver v1.1.0 gorm.io/plugin/dbresolver v1.4.1
gorm.io/plugin/soft_delete v1.1.0 gorm.io/plugin/soft_delete v1.2.1
modernc.org/sqlite v1.17.3 modernc.org/sqlite v1.22.1
) )
require ( require (
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/btcsuite/btcd v0.22.0-beta // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 // indirect
github.com/clbanning/mxj v1.8.4 // indirect github.com/clbanning/mxj v1.8.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/cockroachdb/redact v1.1.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/validator/v10 v10.10.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/go-playground/validator/v10 v10.11.2 // 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/gogo/protobuf v1.3.2 // 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.2 // indirect github.com/golang/protobuf v1.5.3 // 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/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // 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/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.12.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgx/v5 v5.3.0 // indirect
github.com/jackc/pgtype v1.11.0 // indirect
github.com/jackc/pgx/v4 v4.16.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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.9 // indirect github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/lib/pq v1.10.2 // indirect github.com/lib/pq v1.10.2 // indirect
github.com/magiconair/properties v1.8.6 // 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.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect github.com/mattn/go-sqlite3 v1.14.16 // 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.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mozillazg/go-httpheader v0.3.1 // indirect github.com/mozillazg/go-httpheader v0.3.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/pyroscope-io/godeltaprof v0.1.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rs/xid v1.4.0 // indirect github.com/rs/xid v1.4.0 // indirect
github.com/smartwalle/crypto4go v1.0.2 // indirect github.com/smartwalle/ncrypto v1.0.0 // indirect
github.com/spf13/afero v1.9.2 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect github.com/subosito/gotenv v1.4.2 // 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.7 // indirect github.com/ugorji/go/codec v1.2.9 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d // indirect github.com/valyala/fasthttp v1.40.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/crypto v0.7.0 // indirect
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.1.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.36.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.6 // indirect modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.16.7 // indirect modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.4.1 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.1.1 // indirect modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.1 // indirect modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.1 // indirect modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.0 // indirect modernc.org/token v1.0.1 // indirect
) )

654
go.sum

File diff suppressed because it is too large Load Diff

@ -6,60 +6,60 @@ package conf
import ( import (
"log" "log"
"sync"
"time" "time"
"github.com/alimy/cfg" "github.com/alimy/cfg"
) )
var ( var (
loggerSetting *LoggerSettingS loggerSetting *loggerConf
loggerFileSetting *LoggerFileSettingS loggerFileSetting *loggerFileConf
loggerZincSetting *LoggerZincSettingS loggerZincSetting *loggerZincConf
loggerMeiliSetting *LoggerMeiliSettingS loggerMeiliSetting *loggerMeiliConf
redisSetting *RedisSettingS sentrySetting *sentryConf
redisSetting *redisConf
DatabaseSetting *DatabaseSetingS PyroscopeSetting *pyroscopeConf
MysqlSetting *MySQLSettingS DatabaseSetting *databaseConf
PostgresSetting *PostgresSettingS MysqlSetting *mysqlConf
Sqlite3Setting *Sqlite3SettingS PostgresSetting *postgresConf
ServerSetting *HttpServerSettingS Sqlite3Setting *sqlite3Conf
WebServerSetting *HttpServerSettingS WebServerSetting *httpServerConf
AdminServerSetting *HttpServerSettingS AdminServerSetting *httpServerConf
SpaceXServerSetting *HttpServerSettingS SpaceXServerSetting *httpServerConf
BotServerSetting *HttpServerSettingS BotServerSetting *httpServerConf
LocalossServerSetting *HttpServerSettingS LocalossServerSetting *httpServerConf
FrontendWebSetting *HttpServerSettingS FrontendWebSetting *httpServerConf
DocsServerSetting *HttpServerSettingS DocsServerSetting *httpServerConf
MobileServerSetting *GRPCServerSettingS MobileServerSetting *grpcServerConf
AppSetting *AppSettingS AppSetting *appConf
CacheIndexSetting *CacheIndexSettingS CacheIndexSetting *cacheIndexConf
SimpleCacheIndexSetting *SimpleCacheIndexSettingS SimpleCacheIndexSetting *simpleCacheIndexConf
BigCacheIndexSetting *BigCacheIndexSettingS BigCacheIndexSetting *bigCacheIndexConf
SmsJuheSetting *SmsJuheSettings RedisCacheIndexSetting *redisCacheIndexConf
AlipaySetting *AlipaySettingS SmsJuheSetting *smsJuheConf
TweetSearchSetting *TweetSearchS AlipaySetting *alipayConf
ZincSetting *ZincSettingS TweetSearchSetting *tweetSearchConf
MeiliSetting *MeiliSettingS ZincSetting *zincConf
ObjectStorage *ObjectStorageS MeiliSetting *meiliConf
AliOSSSetting *AliOSSSettingS ObjectStorage *objectStorageConf
COSSetting *COSSettingS AliOSSSetting *aliOSSConf
HuaweiOBSSetting *HuaweiOBSSettingS COSSetting *cosConf
MinIOSetting *MinIOSettingS HuaweiOBSSetting *huaweiOBSConf
S3Setting *S3SettingS MinIOSetting *minioConf
LocalOSSSetting *LocalOSSSettingS S3Setting *s3Conf
JWTSetting *JWTSettingS LocalOSSSetting *localossConf
Mutex *sync.Mutex JWTSetting *jwtConf
) )
func setupSetting(suite []string, noDefault bool) error { func setupSetting(suite []string, noDefault bool) error {
setting, err := NewSetting() vp, err := newViper()
if err != nil { if err != nil {
return err return err
} }
// initialize features configure // initialize features configure
ss, kv := setting.featuresInfoFrom("Features") ss, kv := featuresInfoFrom(vp, "Features")
cfg.Initial(ss, kv) cfg.Initial(ss, kv)
if len(suite) > 0 { if len(suite) > 0 {
cfg.Use(suite, noDefault) cfg.Use(suite, noDefault)
@ -67,7 +67,6 @@ func setupSetting(suite []string, noDefault bool) error {
objects := map[string]any{ objects := map[string]any{
"App": &AppSetting, "App": &AppSetting,
"Server": &ServerSetting,
"WebServer": &WebServerSetting, "WebServer": &WebServerSetting,
"AdminServer": &AdminServerSetting, "AdminServer": &AdminServerSetting,
"SpaceXServer": &SpaceXServerSetting, "SpaceXServer": &SpaceXServerSetting,
@ -79,8 +78,11 @@ func setupSetting(suite []string, noDefault bool) error {
"CacheIndex": &CacheIndexSetting, "CacheIndex": &CacheIndexSetting,
"SimpleCacheIndex": &SimpleCacheIndexSetting, "SimpleCacheIndex": &SimpleCacheIndexSetting,
"BigCacheIndex": &BigCacheIndexSetting, "BigCacheIndex": &BigCacheIndexSetting,
"RedisCacheIndex": &RedisCacheIndexSetting,
"Alipay": &AlipaySetting, "Alipay": &AlipaySetting,
"SmsJuhe": &SmsJuheSetting, "SmsJuhe": &SmsJuheSetting,
"Pyroscope": &PyroscopeSetting,
"Sentry": &sentrySetting,
"Logger": &loggerSetting, "Logger": &loggerSetting,
"LoggerFile": &loggerFileSetting, "LoggerFile": &loggerFileSetting,
"LoggerZinc": &loggerZincSetting, "LoggerZinc": &loggerZincSetting,
@ -102,27 +104,30 @@ func setupSetting(suite []string, noDefault bool) error {
"LocalOSS": &LocalOSSSetting, "LocalOSS": &LocalOSSSetting,
"S3": &S3Setting, "S3": &S3Setting,
} }
if err = setting.Unmarshal(objects); err != nil { for k, v := range objects {
err := vp.UnmarshalKey(k, v)
if err != nil {
return err return err
} }
}
JWTSetting.Expire *= time.Second JWTSetting.Expire *= time.Second
SimpleCacheIndexSetting.CheckTickDuration *= time.Second SimpleCacheIndexSetting.CheckTickDuration *= time.Second
SimpleCacheIndexSetting.ExpireTickDuration *= time.Second SimpleCacheIndexSetting.ExpireTickDuration *= time.Second
BigCacheIndexSetting.ExpireInSecond *= time.Second BigCacheIndexSetting.ExpireInSecond *= time.Second
RedisCacheIndexSetting.ExpireInSecond *= time.Second
redisSetting.ConnWriteTimeout *= time.Second
Mutex = &sync.Mutex{}
return nil return nil
} }
func Initialize(suite []string, noDefault bool) { func Initial(suite []string, noDefault bool) {
err := setupSetting(suite, noDefault) err := setupSetting(suite, noDefault)
if err != nil { if err != nil {
log.Fatalf("init.setupSetting err: %v", err) log.Fatalf("init.setupSetting err: %v", err)
} }
setupLogger() setupLogger()
setupDBEngine() initSentry()
} }
func GetOssDomain() string { func GetOssDomain() string {
@ -154,8 +159,9 @@ func GetOssDomain() string {
} }
func RunMode() string { func RunMode() string {
if !cfg.If("Deprecated:OldWeb") {
return ServerSetting.RunMode
}
return AppSetting.RunMode return AppSetting.RunMode
} }
func UseSentryGin() bool {
return cfg.If("Sentry") && sentrySetting.AttachGin
}

@ -1,49 +1,50 @@
App: # APP基础设置项 App: # APP基础设置项
RunMode: debug RunMode: debug
AttachmentIncomeRate: 0.8 AttachmentIncomeRate: 0.8
MaxCommentCount: 10 MaxCommentCount: 1000
DefaultContextTimeout: 60 DefaultContextTimeout: 60
DefaultPageSize: 10 DefaultPageSize: 10
MaxPageSize: 100 MaxPageSize: 100
Server: # 服务设置
RunMode: debug
HttpIp: 0.0.0.0
HttpPort: 8008
ReadTimeout: 60
WriteTimeout: 60
Features: Features:
Default: [] Default: []
WebServer: # Web服务 WebServer: # Web服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8010 HttpPort: 8008
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
AdminServer: # Admin后台运维服务 AdminServer: # Admin后台运维服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8014 HttpPort: 8014
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
SpaceXServer: # SpaceX服务 SpaceXServer: # SpaceX服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8012 HttpPort: 8012
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
BotServer: # Bot服务 BotServer: # Bot服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8016 HttpPort: 8016
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
LocalossServer: # Localoss服务 LocalossServer: # Localoss服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8018 HttpPort: 8018
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
FrontendWebServer: # Web前端静态资源服务 FrontendWebServer: # Web前端静态资源服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8006 HttpPort: 8006
ReadTimeout: 60 ReadTimeout: 60
WriteTimeout: 60 WriteTimeout: 60
DocsServer: # 开发文档服务 DocsServer: # 开发文档服务
RunMode: debug
HttpIp: 0.0.0.0 HttpIp: 0.0.0.0
HttpPort: 8011 HttpPort: 8011
ReadTimeout: 60 ReadTimeout: 60
@ -70,12 +71,28 @@ SimpleCacheIndex: # 缓存泡泡广场消息流
ExpireTickDuration: 300 # 每多少秒后强制过期缓存, 设置为0禁止强制使缓存过期 ExpireTickDuration: 300 # 每多少秒后强制过期缓存, 设置为0禁止强制使缓存过期
BigCacheIndex: # 使用BigCache缓存泡泡广场消息流 BigCacheIndex: # 使用BigCache缓存泡泡广场消息流
MaxIndexPage: 1024 # 最大缓存页数必须是2^n, 代表最大同时缓存多少页数据 MaxIndexPage: 1024 # 最大缓存页数必须是2^n, 代表最大同时缓存多少页数据
HardMaxCacheSize: 256 # 最大缓存大小(MB)0表示无限制
Verbose: False # 是否打印cache操作的log
ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存
RedisCacheIndex: # 使用Redis缓存泡泡广场消息流
Verbose: False # 是否打印cache操作的log Verbose: False # 是否打印cache操作的log
ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存 ExpireInSecond: 300 # 多少秒(>0)后强制过期缓存
Pyroscope: # Pyroscope配置
AppName: "paopao-ce" # application name
Endpoint: "http://localhost:4040" # Pyroscope server address
AuthToken: # Pyroscope authentication token
Logger: none # Pyroscope logger (standard | logrus | none)
Sentry: # Sentry配置
Dsn: "http://4ea0af5cd88d4512b7e52070506c80ec@localhost:9000/2"
Debug: True
AttachStacktrace: True
TracesSampleRate: 1.0
AttachLogrus: True # logrus是否附加到Sentry
AttachGin: True # gin是否附加到Sentry
Logger: # 日志通用配置 Logger: # 日志通用配置
Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace
LoggerFile: # 使用File写日志 LoggerFile: # 使用File写日志
SavePath: data/paopao-ce/logs SavePath: custom/data/paopao-ce/logs
FileName: app FileName: app
FileExt: .log FileExt: .log
LoggerZinc: # 使用Zinc写日志 LoggerZinc: # 使用Zinc写日志
@ -145,7 +162,7 @@ S3: # Amazon S3 存储配置
Bucket: paopao Bucket: paopao
Domain: Domain:
LocalOSS: # 本地文件OSS存储配置 LocalOSS: # 本地文件OSS存储配置
SavePath: data/paopao-ce/oss SavePath: custom/data/paopao-ce/oss
Secure: False Secure: False
Bucket: paopao Bucket: paopao
Domain: 127.0.0.1:8008 Domain: 127.0.0.1:8008
@ -165,14 +182,18 @@ Postgres: # PostgreSQL数据库
User: paopao User: paopao
Password: paopao Password: paopao
DBName: paopao DBName: paopao
Schema: public
Host: localhost Host: localhost
Port: 5432 Port: 5432
SSLMode: disable SSLMode: disable
TimeZone: Asia/Shanghai ApplicationName:
Sqlite3: # Sqlite3数据库 Sqlite3: # Sqlite3数据库
Path: data/sqlite3/paopao-ce.db Path: custom/data/sqlite3/paopao-ce.db
Redis: Redis:
Host: 127.0.0.1:6379 InitAddress:
- redis:6379
Username:
Password: Password:
DB: SelectDB:
ConnWriteTimeout: 60 # 连接写超时时间 多少秒 默认 60秒

@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/alimy/cfg" "github.com/alimy/cfg"
"github.com/go-redis/redis/v8"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
@ -20,22 +19,21 @@ import (
) )
var ( var (
db *gorm.DB _gormDB *gorm.DB
Redis *redis.Client _onceGorm sync.Once
once sync.Once
) )
func MustGormDB() *gorm.DB { func MustGormDB() *gorm.DB {
once.Do(func() { _onceGorm.Do(func() {
var err error var err error
if db, err = newDBEngine(); err != nil { if _gormDB, err = newGormDB(); err != nil {
logrus.Fatalf("new gorm db failed: %s", err) logrus.Fatalf("new gorm db failed: %s", err)
} }
}) })
return db return _gormDB
} }
func newDBEngine() (*gorm.DB, error) { func newGormDB() (*gorm.DB, error) {
newLogger := logger.New( newLogger := logger.New(
logrus.StandardLogger(), // io writer日志输出的目标前缀和日志包含的内容 logrus.StandardLogger(), // io writer日志输出的目标前缀和日志包含的内容
logger.Config{ logger.Config{
@ -84,11 +82,3 @@ func newDBEngine() (*gorm.DB, error) {
return db, err return db, err
} }
func setupDBEngine() {
Redis = redis.NewClient(&redis.Options{
Addr: redisSetting.Host,
Password: redisSetting.Password,
DB: redisSetting.DB,
})
}

@ -6,8 +6,11 @@ package conf
import ( import (
"io" "io"
"time"
"github.com/alimy/cfg" "github.com/alimy/cfg"
"github.com/getsentry/sentry-go"
sentrylogrus "github.com/getsentry/sentry-go/logrus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
) )
@ -42,3 +45,16 @@ func setupLogger() {
}, },
}) })
} }
func setupSentryLogrus(opts sentry.ClientOptions) {
// Send only ERROR and higher level logs to Sentry
sentryLevels := []logrus.Level{logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel}
sentryHook, err := sentrylogrus.New(sentryLevels, opts)
if err != nil {
panic(err)
}
logrus.AddHook(sentryHook)
// Flushes before calling os.Exit(1) when using logger.Fatal
// (else all defers are not called, and Sentry does not have time to send the event)
logrus.RegisterExitHandler(func() { sentryHook.Flush(5 * time.Second) })
}

@ -0,0 +1,34 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package conf
import (
"log"
"sync"
"github.com/rueian/rueidis"
)
var (
_redisClient rueidis.Client
_onceRedis sync.Once
)
func MustRedisClient() rueidis.Client {
_onceRedis.Do(func() {
client, err := rueidis.NewClient(rueidis.ClientOption{
InitAddress: redisSetting.InitAddress,
Username: redisSetting.Username,
Password: redisSetting.Password,
SelectDB: redisSetting.SelectDB,
ConnWriteTimeout: redisSetting.ConnWriteTimeout,
})
if err != nil {
log.Fatalf("create a redis client failed: %s", err)
}
_redisClient = client
})
return _redisClient
}

@ -0,0 +1,35 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package conf
import (
"time"
"github.com/alimy/cfg"
"github.com/getsentry/sentry-go"
"github.com/rocboss/paopao-ce/pkg/version"
)
func initSentry() {
cfg.Be("Sentry", func() {
opts := sentry.ClientOptions{
Dsn: sentrySetting.Dsn,
Debug: sentrySetting.Debug,
AttachStacktrace: sentrySetting.AttachStacktrace,
TracesSampleRate: sentrySetting.TracesSampleRate,
}
_ = sentry.Init(opts)
if sentrySetting.AttachLogrus {
setupSentryLogrus(opts)
}
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetExtras(map[string]any{
"version": version.VersionInfo(),
"time": time.Now().Local(),
})
sentry.CaptureMessage("paopao-ce sentry works!")
})
})
}

@ -1,38 +1,52 @@
// Copyright 2022 ROC. All rights reserved. // Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package conf package conf
import ( import (
"embed" "bytes"
_ "embed"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/pyroscope-io/client/pyroscope"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
) )
//go:embed config.yaml //go:embed config.yaml
var files embed.FS var configBytes []byte
type Setting struct { type pyroscopeConf struct {
vp *viper.Viper AppName string
Endpoint string
AuthToken string
Logger string
}
type sentryConf struct {
Dsn string
Debug bool
AttachStacktrace bool
TracesSampleRate float64
AttachLogrus bool
AttachGin bool
} }
type LoggerSettingS struct { type loggerConf struct {
Level string Level string
} }
type LoggerFileSettingS struct { type loggerFileConf struct {
SavePath string SavePath string
FileName string FileName string
FileExt string FileExt string
} }
type LoggerZincSettingS struct { type loggerZincConf struct {
Host string Host string
Index string Index string
User string User string
@ -40,7 +54,7 @@ type LoggerZincSettingS struct {
Secure bool Secure bool
} }
type LoggerMeiliSettingS struct { type loggerMeiliConf struct {
Host string Host string
Index string Index string
ApiKey string ApiKey string
@ -49,7 +63,7 @@ type LoggerMeiliSettingS struct {
MinWorker int MinWorker int
} }
type HttpServerSettingS struct { type httpServerConf struct {
RunMode string RunMode string
HttpIp string HttpIp string
HttpPort string HttpPort string
@ -57,12 +71,12 @@ type HttpServerSettingS struct {
WriteTimeout time.Duration WriteTimeout time.Duration
} }
type GRPCServerSettingS struct { type grpcServerConf struct {
Host string Host string
Port string Port string
} }
type AppSettingS struct { type appConf struct {
RunMode string RunMode string
MaxCommentCount int64 MaxCommentCount int64
AttachmentIncomeRate float64 AttachmentIncomeRate float64
@ -71,24 +85,30 @@ type AppSettingS struct {
MaxPageSize int MaxPageSize int
} }
type CacheIndexSettingS struct { type cacheIndexConf struct {
MaxUpdateQPS int MaxUpdateQPS int
MinWorker int MinWorker int
} }
type SimpleCacheIndexSettingS struct { type simpleCacheIndexConf struct {
MaxIndexSize int MaxIndexSize int
CheckTickDuration time.Duration CheckTickDuration time.Duration
ExpireTickDuration time.Duration ExpireTickDuration time.Duration
} }
type BigCacheIndexSettingS struct { type bigCacheIndexConf struct {
MaxIndexPage int MaxIndexPage int
HardMaxCacheSize int
ExpireInSecond time.Duration ExpireInSecond time.Duration
Verbose bool Verbose bool
} }
type AlipaySettingS struct { type redisCacheIndexConf struct {
ExpireInSecond time.Duration
Verbose bool
}
type alipayConf struct {
AppID string AppID string
PrivateKey string PrivateKey string
RootCertFile string RootCertFile string
@ -97,19 +117,19 @@ type AlipaySettingS struct {
InProduction bool InProduction bool
} }
type SmsJuheSettings struct { type smsJuheConf struct {
Gateway string Gateway string
Key string Key string
TplID string TplID string
TplVal string TplVal string
} }
type TweetSearchS struct { type tweetSearchConf struct {
MaxUpdateQPS int MaxUpdateQPS int
MinWorker int MinWorker int
} }
type ZincSettingS struct { type zincConf struct {
Host string Host string
Index string Index string
User string User string
@ -117,19 +137,19 @@ type ZincSettingS struct {
Secure bool Secure bool
} }
type MeiliSettingS struct { type meiliConf struct {
Host string Host string
Index string Index string
ApiKey string ApiKey string
Secure bool Secure bool
} }
type DatabaseSetingS struct { type databaseConf struct {
TablePrefix string TablePrefix string
LogLevel string LogLevel string
} }
type MySQLSettingS struct { type mysqlConf struct {
UserName string UserName string
Password string Password string
Host string Host string
@ -140,18 +160,18 @@ type MySQLSettingS struct {
MaxOpenConns int MaxOpenConns int
} }
type PostgresSettingS map[string]string type postgresConf map[string]string
type Sqlite3SettingS struct { type sqlite3Conf struct {
Path string Path string
} }
type ObjectStorageS struct { type objectStorageConf struct {
RetainInDays int RetainInDays int
TempDir string TempDir string
} }
type MinIOSettingS struct { type minioConf struct {
AccessKey string AccessKey string
SecretKey string SecretKey string
Secure bool Secure bool
@ -160,7 +180,7 @@ type MinIOSettingS struct {
Domain string Domain string
} }
type S3SettingS struct { type s3Conf struct {
AccessKey string AccessKey string
SecretKey string SecretKey string
Secure bool Secure bool
@ -169,7 +189,7 @@ type S3SettingS struct {
Domain string Domain string
} }
type AliOSSSettingS struct { type aliOSSConf struct {
AccessKeyID string AccessKeyID string
AccessKeySecret string AccessKeySecret string
Endpoint string Endpoint string
@ -177,7 +197,7 @@ type AliOSSSettingS struct {
Domain string Domain string
} }
type COSSettingS struct { type cosConf struct {
SecretID string SecretID string
SecretKey string SecretKey string
Region string Region string
@ -185,7 +205,7 @@ type COSSettingS struct {
Domain string Domain string
} }
type HuaweiOBSSettingS struct { type huaweiOBSConf struct {
AccessKey string AccessKey string
SecretKey string SecretKey string
Endpoint string Endpoint string
@ -193,92 +213,36 @@ type HuaweiOBSSettingS struct {
Domain string Domain string
} }
type LocalOSSSettingS struct { type localossConf struct {
SavePath string SavePath string
Secure bool Secure bool
Bucket string Bucket string
Domain string Domain string
} }
type RedisSettingS struct { type redisConf struct {
Host string InitAddress []string
Username string
Password string Password string
DB int SelectDB int
ConnWriteTimeout time.Duration
} }
type JWTSettingS struct { type jwtConf struct {
Secret string Secret string
Issuer string Issuer string
Expire time.Duration Expire time.Duration
} }
func NewSetting() (*Setting, error) { func (s *httpServerConf) GetReadTimeout() time.Duration {
cfgFile, err := files.Open("config.yaml")
if err != nil {
return nil, err
}
defer cfgFile.Close()
vp := viper.New()
vp.SetConfigName("config")
vp.AddConfigPath(".")
vp.AddConfigPath("custom/")
vp.SetConfigType("yaml")
if err = vp.ReadConfig(cfgFile); err != nil {
return nil, err
}
if err = vp.MergeInConfig(); err != nil {
return nil, err
}
return &Setting{vp}, nil
}
func (s *Setting) ReadSection(k string, v any) error {
err := s.vp.UnmarshalKey(k, v)
if err != nil {
return err
}
return nil
}
func (s *Setting) Unmarshal(objects map[string]any) error {
for k, v := range objects {
err := s.vp.UnmarshalKey(k, v)
if err != nil {
return err
}
}
return nil
}
func (s *Setting) featuresInfoFrom(k string) (map[string][]string, map[string]string) {
sub := s.vp.Sub(k)
keys := sub.AllKeys()
suites := make(map[string][]string)
kv := make(map[string]string, len(keys))
for _, key := range sub.AllKeys() {
val := sub.Get(key)
switch v := val.(type) {
case string:
kv[key] = v
case []any:
suites[key] = sub.GetStringSlice(key)
}
}
return suites, kv
}
func (s *HttpServerSettingS) GetReadTimeout() time.Duration {
return s.ReadTimeout * time.Second return s.ReadTimeout * time.Second
} }
func (s *HttpServerSettingS) GetWriteTimeout() time.Duration { func (s *httpServerConf) GetWriteTimeout() time.Duration {
return s.WriteTimeout * time.Second return s.WriteTimeout * time.Second
} }
func (s *MySQLSettingS) Dsn() string { func (s *mysqlConf) Dsn() string {
return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local", return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local",
s.UserName, s.UserName,
s.Password, s.Password,
@ -289,17 +253,27 @@ func (s *MySQLSettingS) Dsn() string {
) )
} }
func (s PostgresSettingS) Dsn() string { func (s postgresConf) Dsn() string {
var params []string var params []string
for k, v := range s { for k, v := range s {
if len(v) > 0 { if len(v) == 0 {
params = append(params, strings.ToLower(k)+"="+v) continue
}
lk := strings.ToLower(k)
tv := strings.Trim(v, " ")
switch lk {
case "schema":
params = append(params, "search_path="+tv)
case "applicationname":
params = append(params, "application_name="+tv)
default:
params = append(params, lk+"="+tv)
} }
} }
return strings.Join(params, " ") return strings.Join(params, " ")
} }
func (s *Sqlite3SettingS) Dsn(driverName string) string { func (s *sqlite3Conf) Dsn(driverName string) string {
pragmas := "_foreign_keys=1&_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=8000" pragmas := "_foreign_keys=1&_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=8000"
if driverName == "sqlite" { if driverName == "sqlite" {
pragmas = "_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=busy_timeout(8000)&_pragma=journal_size_limit(100000000)" pragmas = "_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=busy_timeout(8000)&_pragma=journal_size_limit(100000000)"
@ -307,7 +281,7 @@ func (s *Sqlite3SettingS) Dsn(driverName string) string {
return fmt.Sprintf("file:%s?%s", s.Path, pragmas) return fmt.Sprintf("file:%s?%s", s.Path, pragmas)
} }
func (s *DatabaseSetingS) logLevel() logger.LogLevel { func (s *databaseConf) logLevel() logger.LogLevel {
switch strings.ToLower(s.LogLevel) { switch strings.ToLower(s.LogLevel) {
case "silent": case "silent":
return logger.Silent return logger.Silent
@ -322,7 +296,7 @@ func (s *DatabaseSetingS) logLevel() logger.LogLevel {
} }
} }
func (s *LoggerSettingS) logLevel() logrus.Level { func (s *loggerConf) logLevel() logrus.Level {
switch strings.ToLower(s.Level) { switch strings.ToLower(s.Level) {
case "panic": case "panic":
return logrus.PanicLevel return logrus.PanicLevel
@ -343,15 +317,15 @@ func (s *LoggerSettingS) logLevel() logrus.Level {
} }
} }
func (s *LoggerZincSettingS) Endpoint() string { func (s *loggerZincConf) Endpoint() string {
return endpoint(s.Host, s.Secure) return endpoint(s.Host, s.Secure)
} }
func (s *LoggerMeiliSettingS) Endpoint() string { func (s *loggerMeiliConf) Endpoint() string {
return endpoint(s.Host, s.Secure) return endpoint(s.Host, s.Secure)
} }
func (s *LoggerMeiliSettingS) minWork() int { func (s *loggerMeiliConf) minWork() int {
if s.MinWorker < 5 { if s.MinWorker < 5 {
return 5 return 5
} else if s.MinWorker > 100 { } else if s.MinWorker > 100 {
@ -360,7 +334,7 @@ func (s *LoggerMeiliSettingS) minWork() int {
return s.MinWorker return s.MinWorker
} }
func (s *LoggerMeiliSettingS) maxLogBuffer() int { func (s *loggerMeiliConf) maxLogBuffer() int {
if s.MaxLogBuffer < 10 { if s.MaxLogBuffer < 10 {
return 10 return 10
} else if s.MaxLogBuffer > 1000 { } else if s.MaxLogBuffer > 1000 {
@ -369,18 +343,28 @@ func (s *LoggerMeiliSettingS) maxLogBuffer() int {
return s.MaxLogBuffer return s.MaxLogBuffer
} }
func (s *ObjectStorageS) TempDirSlash() string { func (s *objectStorageConf) TempDirSlash() string {
return strings.Trim(s.TempDir, " /") + "/" return strings.Trim(s.TempDir, " /") + "/"
} }
func (s *ZincSettingS) Endpoint() string { func (s *zincConf) Endpoint() string {
return endpoint(s.Host, s.Secure) return endpoint(s.Host, s.Secure)
} }
func (s *MeiliSettingS) Endpoint() string { func (s *meiliConf) Endpoint() string {
return endpoint(s.Host, s.Secure) return endpoint(s.Host, s.Secure)
} }
func (s *pyroscopeConf) GetLogger() (logger pyroscope.Logger) {
switch strings.ToLower(s.Logger) {
case "standard":
logger = pyroscope.StandardLogger
case "logrus":
logger = logrus.StandardLogger()
}
return
}
func endpoint(host string, secure bool) string { func endpoint(host string, secure bool) string {
schema := "http" schema := "http"
if secure { if secure {
@ -388,3 +372,37 @@ func endpoint(host string, secure bool) string {
} }
return schema + "://" + host return schema + "://" + host
} }
func newViper() (*viper.Viper, error) {
vp := viper.New()
vp.SetConfigName("config")
vp.AddConfigPath(".")
vp.AddConfigPath("custom/")
vp.SetConfigType("yaml")
err := vp.ReadConfig(bytes.NewReader(configBytes))
if err != nil {
return nil, err
}
if err = vp.MergeInConfig(); err != nil {
return nil, err
}
return vp, nil
}
func featuresInfoFrom(vp *viper.Viper, k string) (map[string][]string, map[string]string) {
sub := vp.Sub(k)
keys := sub.AllKeys()
suites := make(map[string][]string)
kv := make(map[string]string, len(keys))
for _, key := range sub.AllKeys() {
val := sub.Get(key)
switch v := val.(type) {
case string:
kv[key] = v
case []any:
suites[key] = sub.GetStringSlice(key)
}
}
return suites, kv
}

@ -5,6 +5,8 @@
package core package core
import ( import (
"context"
"github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr"
) )
@ -56,3 +58,21 @@ type CacheIndexService interface {
SendAction(act IdxAct, post *dbr.Post) SendAction(act IdxAct, post *dbr.Post)
} }
// RedisCache memory cache by Redis
type RedisCache interface {
SetPushToSearchJob(ctx context.Context) error
DelPushToSearchJob(ctx context.Context) error
SetImgCaptcha(ctx context.Context, id string, value string) error
GetImgCaptcha(ctx context.Context, id string) (string, error)
DelImgCaptcha(ctx context.Context, id string) error
GetCountSmsCaptcha(ctx context.Context, phone string) (int64, error)
IncrCountSmsCaptcha(ctx context.Context, phone string) error
GetCountLoginErr(ctx context.Context, id int64) (int64, error)
DelCountLoginErr(ctx context.Context, id int64) error
IncrCountLoginErr(ctx context.Context, id int64) error
GetCountWhisper(ctx context.Context, uid int64) (int64, error)
IncrCountWhisper(ctx context.Context, uid int64) error
SetRechargeStatus(ctx context.Context, tradeNo string) error
DelRechargeStatus(ctx context.Context, tradeNo string) error
}

@ -5,6 +5,7 @@
package core package core
import ( import (
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr"
) )
@ -24,6 +25,7 @@ type CommentService interface {
GetCommentReplyByID(id int64) (*CommentReply, error) GetCommentReplyByID(id int64) (*CommentReply, error)
GetCommentContentsByIDs(ids []int64) ([]*CommentContent, error) GetCommentContentsByIDs(ids []int64) ([]*CommentContent, error)
GetCommentRepliesByID(ids []int64) ([]*CommentReplyFormated, error) GetCommentRepliesByID(ids []int64) ([]*CommentReplyFormated, error)
GetCommentThumbsMap(userId int64, tweetId int64) (cs.CommentThumbsMap, cs.CommentThumbsMap, error)
} }
// CommentManageService 评论管理服务 // CommentManageService 评论管理服务
@ -33,4 +35,8 @@ type CommentManageService interface {
CreateCommentReply(reply *CommentReply) (*CommentReply, error) CreateCommentReply(reply *CommentReply) (*CommentReply, error)
DeleteCommentReply(reply *CommentReply) error DeleteCommentReply(reply *CommentReply) error
CreateCommentContent(content *CommentContent) (*CommentContent, error) CreateCommentContent(content *CommentContent) (*CommentContent, error)
ThumbsUpComment(userId int64, tweetId, commentId int64) error
ThumbsDownComment(userId int64, tweetId, commentId int64) error
ThumbsUpReply(userId int64, tweetId, commentId, replyId int64) error
ThumbsDownReply(userId int64, tweetId, commentId, replyId int64) error
} }

@ -0,0 +1,19 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package cs
type CommentThumbs struct {
UserID int64 `json:"user_id"`
TweetID int64 `json:"tweet_id"`
CommentID int64 `json:"comment_id"`
ReplyID int64 `json:"reply_id"`
CommentType int8 `json:"comment_type"`
IsThumbsUp int8 `json:"is_thumbs_up"`
IsThumbsDown int8 `json:"is_thumbs_down"`
}
type CommentThumbsList []*CommentThumbs
type CommentThumbsMap map[int64]*CommentThumbs

@ -18,5 +18,11 @@ type TopicService interface {
CreateTag(tag *Tag) (*Tag, error) CreateTag(tag *Tag) (*Tag, error)
DeleteTag(tag *Tag) error DeleteTag(tag *Tag) error
GetTags(conditions *ConditionsT, offset, limit int) ([]*Tag, error) GetTags(conditions *ConditionsT, offset, limit int) ([]*Tag, error)
GetHotTags(userId int64, limit int, offset int) ([]*TagFormated, error)
GetNewestTags(userId int64, limit int, offset int) ([]*TagFormated, error)
GetFollowTags(userId int64, limit int, offset int) ([]*TagFormated, error)
GetTagsByKeyword(keyword string) ([]*Tag, error) GetTagsByKeyword(keyword string) ([]*Tag, error)
FollowTopic(userId int64, topicId int64) error
UnfollowTopic(userId int64, topicId int64) error
StickTopic(userId int64, topicId int64) (int8, error)
} }

@ -0,0 +1,229 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package cache
import (
"bytes"
"encoding/gob"
"fmt"
"strconv"
"strings"
"time"
"github.com/Masterminds/semver/v3"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/types"
"github.com/sirupsen/logrus"
)
const (
_cacheIndexKey = "paopao_index"
)
var (
_ core.CacheIndexService = (*cacheIndexSrv)(nil)
_ core.VersionInfo = (*cacheIndexSrv)(nil)
)
type postsEntry struct {
key string
tweets *core.IndexTweetList
}
type tweetsCache interface {
core.VersionInfo
getTweetsBytes(key string) ([]byte, error)
setTweetsBytes(key string, bs []byte) error
delTweets(keys []string) error
allKeys() ([]string, error)
}
type cacheIndexSrv struct {
ips core.IndexPostsService
ams core.AuthorizationManageService
name string
version *semver.Version
indexActionCh chan *core.IndexAction
cachePostsCh chan *postsEntry
cache tweetsCache
lastCacheResetTime time.Time
preventDuration time.Duration
}
func (s *cacheIndexSrv) IndexPosts(user *core.User, offset int, limit int) (*core.IndexTweetList, error) {
key := s.keyFrom(user, offset, limit)
posts, err := s.getPosts(key)
if err == nil {
logrus.Debugf("cacheIndexSrv.IndexPosts get index posts from cache by key: %s", key)
return posts, nil
}
if posts, err = s.ips.IndexPosts(user, offset, limit); err != nil {
return nil, err
}
logrus.Debugf("cacheIndexSrv.IndexPosts get index posts from database by key: %s", key)
s.cachePosts(key, posts)
return posts, nil
}
func (s *cacheIndexSrv) getPosts(key string) (*core.IndexTweetList, error) {
data, err := s.cache.getTweetsBytes(key)
if err != nil {
logrus.Debugf("cacheIndexSrv.getPosts get posts by key: %s from cache err: %v", key, err)
return nil, err
}
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
var resp core.IndexTweetList
if err := dec.Decode(&resp); err != nil {
logrus.Debugf("cacheIndexSrv.getPosts get posts from cache in decode err: %v", err)
return nil, err
}
return &resp, nil
}
func (s *cacheIndexSrv) cachePosts(key string, tweets *core.IndexTweetList) {
entry := &postsEntry{key: key, tweets: tweets}
select {
case s.cachePostsCh <- entry:
logrus.Debugf("cacheIndexSrv.cachePosts cachePosts by chan of key: %s", key)
default:
go func(ch chan<- *postsEntry, entry *postsEntry) {
logrus.Debugf("cacheIndexSrv.cachePosts cachePosts indexAction by goroutine of key: %s", key)
ch <- entry
}(s.cachePostsCh, entry)
}
}
func (s *cacheIndexSrv) setPosts(entry *postsEntry) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(entry.tweets); err != nil {
logrus.Debugf("cacheIndexSrv.setPosts setPosts encode post entry err: %v", err)
return
}
if err := s.cache.setTweetsBytes(entry.key, buf.Bytes()); err != nil {
logrus.Debugf("cacheIndexSrv.setPosts setPosts set cache err: %v", err)
}
logrus.Debugf("cacheIndexSrv.setPosts setPosts set cache by key: %s", entry.key)
}
func (s *cacheIndexSrv) keyFrom(user *core.User, offset int, limit int) string {
var userId int64 = -1
if user != nil {
userId = user.ID
}
return fmt.Sprintf("%s:%d:%d:%d", _cacheIndexKey, userId, offset, limit)
}
func (s *cacheIndexSrv) SendAction(act core.IdxAct, post *core.Post) {
action := core.NewIndexAction(act, post)
select {
case s.indexActionCh <- action:
logrus.Debugf("cacheIndexSrv.SendAction send indexAction by chan: %s", act)
default:
go func(ch chan<- *core.IndexAction, act *core.IndexAction) {
logrus.Debugf("cacheIndexSrv.SendAction send indexAction by goroutine: %s", action.Act)
ch <- act
}(s.indexActionCh, action)
}
}
func (s *cacheIndexSrv) startIndexPosts() {
for {
select {
case entry := <-s.cachePostsCh:
s.setPosts(entry)
case action := <-s.indexActionCh:
s.handleIndexAction(action)
}
}
}
func (s *cacheIndexSrv) handleIndexAction(action *core.IndexAction) {
act, post := action.Act, action.Post
// 创建/删除 私密推文特殊处理
switch act {
case core.IdxActCreatePost, core.IdxActDeletePost:
if post.Visibility == core.PostVisitPrivate {
s.deleteCacheByUserId(post.UserID, true)
return
}
}
// 如果在s.preventDuration时间内就清除所有缓存否则只清除自个儿的缓存
// TODO: 需要优化只清除受影响的缓存,后续完善
if time.Since(s.lastCacheResetTime) > s.preventDuration {
s.deleteCacheByUserId(post.UserID, false)
} else {
s.deleteCacheByUserId(post.UserID, true)
}
}
func (s *cacheIndexSrv) deleteCacheByUserId(id int64, oneself bool) {
var keys []string
userId := strconv.FormatInt(id, 10)
friendSet := core.FriendSet{}
if !oneself {
friendSet = s.ams.MyFriendSet(id)
}
friendSet[userId] = types.Empty{}
// 获取需要删除缓存的key目前是仅删除自个儿的缓存
allKeys, err := s.cache.allKeys()
if err != nil {
logrus.Debugf("cacheIndexSrv.deleteCacheByUserId userId: %s err:%s", userId, err)
}
for _, key := range allKeys {
keyParts := strings.Split(key, ":")
if len(keyParts) > 2 && keyParts[0] == _cacheIndexKey {
if _, ok := friendSet[keyParts[1]]; ok {
keys = append(keys, key)
}
}
}
// 执行删缓存
s.cache.delTweets(keys)
s.lastCacheResetTime = time.Now()
logrus.Debugf("cacheIndexSrv.deleteCacheByUserId userId:%s oneself:%t keys:%d", userId, oneself, len(keys))
}
func (s *cacheIndexSrv) Name() string {
return s.name
}
func (s *cacheIndexSrv) Version() *semver.Version {
return s.version
}
func newCacheIndexSrv(ips core.IndexPostsService, ams core.AuthorizationManageService, tc tweetsCache) *cacheIndexSrv {
cacheIndex := &cacheIndexSrv{
ips: ips,
ams: ams,
cache: tc,
name: tc.Name(),
version: tc.Version(),
preventDuration: 10 * time.Second,
}
// indexActionCh capacity custom configure by conf.yaml need in [10, 10000]
// or re-compile source to adjust min/max capacity
capacity := conf.CacheIndexSetting.MaxUpdateQPS
if capacity < 10 {
capacity = 10
} else if capacity > 10000 {
capacity = 10000
}
cacheIndex.indexActionCh = make(chan *core.IndexAction, capacity)
cacheIndex.cachePostsCh = make(chan *postsEntry, capacity)
// 启动索引更新器
go cacheIndex.startIndexPosts()
return cacheIndex
}

@ -5,189 +5,51 @@
package cache package cache
import ( import (
"bytes"
"encoding/gob"
"fmt"
"strconv"
"strings"
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/allegro/bigcache/v3" "github.com/allegro/bigcache/v3"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/types"
"github.com/sirupsen/logrus"
) )
var ( var (
_ core.CacheIndexService = (*bigCacheIndexServant)(nil) _ tweetsCache = (*bigCacheTweetsCache)(nil)
_ core.VersionInfo = (*bigCacheIndexServant)(nil)
) )
type postsEntry struct { type bigCacheTweetsCache struct {
key string name string
tweets *core.IndexTweetList version *semver.Version
} bc *bigcache.BigCache
type bigCacheIndexServant struct {
ips core.IndexPostsService
ams core.AuthorizationManageService
indexActionCh chan *core.IndexAction
cachePostsCh chan *postsEntry
cache *bigcache.BigCache
lastCacheResetTime time.Time
preventDuration time.Duration
}
func (s *bigCacheIndexServant) IndexPosts(user *core.User, offset int, limit int) (*core.IndexTweetList, error) {
key := s.keyFrom(user, offset, limit)
posts, err := s.getPosts(key)
if err == nil {
logrus.Debugf("bigCacheIndexServant.IndexPosts get index posts from cache by key: %s", key)
return posts, nil
}
if posts, err = s.ips.IndexPosts(user, offset, limit); err != nil {
return nil, err
}
logrus.Debugf("bigCacheIndexServant.IndexPosts get index posts from database by key: %s", key)
s.cachePosts(key, posts)
return posts, nil
}
func (s *bigCacheIndexServant) getPosts(key string) (*core.IndexTweetList, error) {
data, err := s.cache.Get(key)
if err != nil {
logrus.Debugf("bigCacheIndexServant.getPosts get posts by key: %s from cache err: %v", key, err)
return nil, err
}
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
var resp core.IndexTweetList
if err := dec.Decode(&resp); err != nil {
logrus.Debugf("bigCacheIndexServant.getPosts get posts from cache in decode err: %v", err)
return nil, err
}
return &resp, nil
} }
func (s *bigCacheIndexServant) cachePosts(key string, tweets *core.IndexTweetList) { func (s *bigCacheTweetsCache) getTweetsBytes(key string) ([]byte, error) {
entry := &postsEntry{key: key, tweets: tweets} return s.bc.Get(key)
select {
case s.cachePostsCh <- entry:
logrus.Debugf("bigCacheIndexServant.cachePosts cachePosts by chan of key: %s", key)
default:
go func(ch chan<- *postsEntry, entry *postsEntry) {
logrus.Debugf("bigCacheIndexServant.cachePosts cachePosts indexAction by goroutine of key: %s", key)
ch <- entry
}(s.cachePostsCh, entry)
}
} }
func (s *bigCacheIndexServant) setPosts(entry *postsEntry) { func (s *bigCacheTweetsCache) setTweetsBytes(key string, bs []byte) error {
var buf bytes.Buffer return s.bc.Set(key, bs)
enc := gob.NewEncoder(&buf)
if err := enc.Encode(entry.tweets); err != nil {
logrus.Debugf("bigCacheIndexServant.setPosts setPosts encode post entry err: %v", err)
return
}
if err := s.cache.Set(entry.key, buf.Bytes()); err != nil {
logrus.Debugf("bigCacheIndexServant.setPosts setPosts set cache err: %v", err)
}
logrus.Debugf("bigCacheIndexServant.setPosts setPosts set cache by key: %s", entry.key)
} }
func (s *bigCacheIndexServant) keyFrom(user *core.User, offset int, limit int) string { func (s *bigCacheTweetsCache) delTweets(keys []string) error {
var userId int64 = -1 for _, k := range keys {
if user != nil { s.bc.Delete(k)
userId = user.ID
}
return fmt.Sprintf("index:%d:%d:%d", userId, offset, limit)
}
func (s *bigCacheIndexServant) SendAction(act core.IdxAct, post *core.Post) {
action := core.NewIndexAction(act, post)
select {
case s.indexActionCh <- action:
logrus.Debugf("bigCacheIndexServant.SendAction send indexAction by chan: %s", act)
default:
go func(ch chan<- *core.IndexAction, act *core.IndexAction) {
logrus.Debugf("bigCacheIndexServant.SendAction send indexAction by goroutine: %s", action.Act)
ch <- act
}(s.indexActionCh, action)
}
}
func (s *bigCacheIndexServant) startIndexPosts() {
for {
select {
case entry := <-s.cachePostsCh:
s.setPosts(entry)
case action := <-s.indexActionCh:
s.handleIndexAction(action)
}
}
}
func (s *bigCacheIndexServant) handleIndexAction(action *core.IndexAction) {
act, post := action.Act, action.Post
// 创建/删除 私密推文特殊处理
switch act {
case core.IdxActCreatePost, core.IdxActDeletePost:
if post.Visibility == core.PostVisitPrivate {
s.deleteCacheByUserId(post.UserID, true)
return
}
}
// 如果在s.preventDuration时间内就清除所有缓存否则只清除自个儿的缓存
// TODO: 需要优化只清除受影响的缓存,后续完善
if time.Since(s.lastCacheResetTime) > s.preventDuration {
s.deleteCacheByUserId(post.UserID, false)
} else {
s.deleteCacheByUserId(post.UserID, true)
} }
return nil
} }
func (s *bigCacheIndexServant) deleteCacheByUserId(id int64, oneself bool) { func (s *bigCacheTweetsCache) allKeys() ([]string, error) {
var keys []string var keys []string
userId := strconv.FormatInt(id, 10) for it := s.bc.Iterator(); it.SetNext(); {
friendSet := core.FriendSet{}
if !oneself {
friendSet = s.ams.MyFriendSet(id)
}
friendSet[userId] = types.Empty{}
// 获取需要删除缓存的key目前是仅删除自个儿的缓存
for it := s.cache.Iterator(); it.SetNext(); {
entry, err := it.Value() entry, err := it.Value()
if err != nil { if err != nil {
logrus.Debugf("bigCacheIndexServant.deleteCacheByUserId userId: %s err:%s", userId, err) return nil, err
return
}
key := entry.Key()
keyParts := strings.Split(key, ":")
if len(keyParts) > 2 && keyParts[0] == "index" {
if _, ok := friendSet[keyParts[1]]; ok {
keys = append(keys, key)
}
}
} }
keys = append(keys, entry.Key())
// 执行删缓存
for _, k := range keys {
s.cache.Delete(k)
} }
s.lastCacheResetTime = time.Now() return keys, nil
logrus.Debugf("bigCacheIndexServant.deleteCacheByUserId userId:%s oneself:%t keys:%d", userId, oneself, len(keys))
} }
func (s *bigCacheIndexServant) Name() string { func (s *bigCacheTweetsCache) Name() string {
return "BigCacheIndex" return "BigCacheIndex"
} }
func (s *bigCacheIndexServant) Version() *semver.Version { func (s *bigCacheTweetsCache) Version() *semver.Version {
return semver.MustParse("v0.2.0") return semver.MustParse("v0.2.0")
} }

@ -13,40 +13,37 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func NewRedisCache() core.RedisCache {
return &redisCache{
c: conf.MustRedisClient(),
}
}
func NewBigCacheIndexService(ips core.IndexPostsService, ams core.AuthorizationManageService) (core.CacheIndexService, core.VersionInfo) { func NewBigCacheIndexService(ips core.IndexPostsService, ams core.AuthorizationManageService) (core.CacheIndexService, core.VersionInfo) {
s := conf.BigCacheIndexSetting s := conf.BigCacheIndexSetting
c := bigcache.DefaultConfig(s.ExpireInSecond)
c.Shards = s.MaxIndexPage
c.HardMaxCacheSize = s.HardMaxCacheSize
c.Verbose = s.Verbose
c.MaxEntrySize = 10000
c.Logger = logrus.StandardLogger()
config := bigcache.DefaultConfig(s.ExpireInSecond) bc, err := bigcache.NewBigCache(c)
config.Shards = s.MaxIndexPage
config.Verbose = s.Verbose
config.MaxEntrySize = 10000
config.Logger = logrus.StandardLogger()
cache, err := bigcache.NewBigCache(config)
if err != nil { if err != nil {
logrus.Fatalf("initial bigCahceIndex failure by err: %v", err) logrus.Fatalf("initial bigCahceIndex failure by err: %v", err)
} }
cacheIndex := newCacheIndexSrv(ips, ams, &bigCacheTweetsCache{
bc: bc,
})
return cacheIndex, cacheIndex
}
cacheIndex := &bigCacheIndexServant{ func NewRedisCacheIndexService(ips core.IndexPostsService, ams core.AuthorizationManageService) (core.CacheIndexService, core.VersionInfo) {
ips: ips, cacheIndex := newCacheIndexSrv(ips, ams, &redisCacheTweetsCache{
ams: ams, expireDuration: conf.RedisCacheIndexSetting.ExpireInSecond,
cache: cache, expireInSecond: int64(conf.RedisCacheIndexSetting.ExpireInSecond / time.Second),
preventDuration: 10 * time.Second, c: conf.MustRedisClient(),
} })
// indexActionCh capacity custom configure by conf.yaml need in [10, 10000]
// or re-compile source to adjust min/max capacity
capacity := conf.CacheIndexSetting.MaxUpdateQPS
if capacity < 10 {
capacity = 10
} else if capacity > 10000 {
capacity = 10000
}
cacheIndex.indexActionCh = make(chan *core.IndexAction, capacity)
cacheIndex.cachePostsCh = make(chan *postsEntry, capacity)
// 启动索引更新器
go cacheIndex.startIndexPosts()
return cacheIndex, cacheIndex return cacheIndex, cacheIndex
} }

@ -0,0 +1,153 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package cache
import (
"context"
"fmt"
"time"
"unsafe"
"github.com/Masterminds/semver/v3"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rueian/rueidis"
)
var (
_ core.RedisCache = (*redisCache)(nil)
_ tweetsCache = (*redisCacheTweetsCache)(nil)
)
const (
_cacheIndexKeyPattern = _cacheIndexKey + "*"
_pushToSearchJobKey = "paopao_push_to_search_job"
_countLoginErrKey = "paopao_count_login_err"
_imgCaptchaKey = "paopao_img_captcha:"
_smsCaptchaKey = "paopao_sms_captcha"
_countWhisperKey = "paopao_whisper_key"
_rechargeStatusKey = "paopao_recharge_status:"
)
type redisCache struct {
c rueidis.Client
}
type redisCacheTweetsCache struct {
expireDuration time.Duration
expireInSecond int64
c rueidis.Client
}
func (s *redisCacheTweetsCache) getTweetsBytes(key string) ([]byte, error) {
res, err := rueidis.MGetCache(s.c, context.Background(), s.expireDuration, []string{key})
if err != nil {
return nil, err
}
message := res[key]
return message.AsBytes()
}
func (s *redisCacheTweetsCache) setTweetsBytes(key string, bs []byte) error {
cmd := s.c.B().Set().Key(key).Value(rueidis.BinaryString(bs)).ExSeconds(s.expireInSecond).Build()
return s.c.Do(context.Background(), cmd).Error()
}
func (s *redisCacheTweetsCache) delTweets(keys []string) error {
cmd := s.c.B().Del().Key(keys...).Build()
return s.c.Do(context.Background(), cmd).Error()
}
func (s *redisCacheTweetsCache) allKeys() ([]string, error) {
cmd := s.c.B().Keys().Pattern(_cacheIndexKeyPattern).Build()
return s.c.Do(context.Background(), cmd).AsStrSlice()
}
func (s *redisCacheTweetsCache) Name() string {
return "RedisCacheIndex"
}
func (s *redisCacheTweetsCache) Version() *semver.Version {
return semver.MustParse("v0.1.0")
}
func (r *redisCache) SetPushToSearchJob(ctx context.Context) error {
return r.c.Do(ctx, r.c.B().Set().
Key(_pushToSearchJobKey).Value("1").
Nx().ExSeconds(3600).
Build()).Error()
}
func (r *redisCache) DelPushToSearchJob(ctx context.Context) error {
return r.c.Do(ctx, r.c.B().Del().Key(_pushToSearchJobKey).Build()).Error()
}
func (r *redisCache) SetImgCaptcha(ctx context.Context, id string, value string) error {
return r.c.Do(ctx, r.c.B().Set().
Key(_imgCaptchaKey+id).Value(value).
ExSeconds(300).
Build()).Error()
}
func (r *redisCache) GetImgCaptcha(ctx context.Context, id string) (string, error) {
res, err := r.c.Do(ctx, r.c.B().Get().Key(_imgCaptchaKey+id).Build()).AsBytes()
return unsafe.String(&res[0], len(res)), err
}
func (r *redisCache) DelImgCaptcha(ctx context.Context, id string) error {
return r.c.Do(ctx, r.c.B().Del().Key(_imgCaptchaKey+id).Build()).Error()
}
func (r *redisCache) GetCountSmsCaptcha(ctx context.Context, phone string) (int64, error) {
return r.c.Do(ctx, r.c.B().Get().Key(_smsCaptchaKey+phone).Build()).AsInt64()
}
func (r *redisCache) IncrCountSmsCaptcha(ctx context.Context, phone string) (err error) {
if err = r.c.Do(ctx, r.c.B().Incr().Key(_smsCaptchaKey+phone).Build()).Error(); err == nil {
currentTime := time.Now()
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
err = r.c.Do(ctx, r.c.B().Expire().Key(_smsCaptchaKey+phone).Seconds(int64(endTime.Sub(currentTime)/time.Second)).Build()).Error()
}
return
}
func (r *redisCache) GetCountLoginErr(ctx context.Context, id int64) (int64, error) {
return r.c.Do(ctx, r.c.B().Get().Key(fmt.Sprintf("%s:%d", _countLoginErrKey, id)).Build()).AsInt64()
}
func (r *redisCache) DelCountLoginErr(ctx context.Context, id int64) error {
return r.c.Do(ctx, r.c.B().Del().Key(fmt.Sprintf("%s:%d", _countLoginErrKey, id)).Build()).Error()
}
func (r *redisCache) IncrCountLoginErr(ctx context.Context, id int64) error {
err := r.c.Do(ctx, r.c.B().Incr().Key(fmt.Sprintf("%s:%d", _countLoginErrKey, id)).Build()).Error()
if err == nil {
err = r.c.Do(ctx, r.c.B().Expire().Key(fmt.Sprintf("%s:%d", _countLoginErrKey, id)).Seconds(3600).Build()).Error()
}
return err
}
func (r *redisCache) GetCountWhisper(ctx context.Context, uid int64) (int64, error) {
return r.c.Do(ctx, r.c.B().Get().Key(fmt.Sprintf("%s:%d", _countWhisperKey, uid)).Build()).AsInt64()
}
func (r *redisCache) IncrCountWhisper(ctx context.Context, uid int64) (err error) {
key := fmt.Sprintf("%s:%d", _countWhisperKey, uid)
if err = r.c.Do(ctx, r.c.B().Incr().Key(key).Build()).Error(); err == nil {
currentTime := time.Now()
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
err = r.c.Do(ctx, r.c.B().Expire().Key(key).Seconds(int64(endTime.Sub(currentTime)/time.Second)).Build()).Error()
}
return
}
func (r *redisCache) SetRechargeStatus(ctx context.Context, tradeNo string) error {
return r.c.Do(ctx, r.c.B().Set().
Key(_rechargeStatusKey+tradeNo).Value("1").
Nx().ExSeconds(5).Build()).Error()
}
func (r *redisCache) DelRechargeStatus(ctx context.Context, tradeNo string) error {
return r.c.Do(ctx, r.c.B().Del().Key(_rechargeStatusKey+tradeNo).Build()).Error()
}

@ -5,8 +5,12 @@
package jinzhu package jinzhu
import ( import (
"time"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr"
"github.com/rocboss/paopao-ce/pkg/types"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -35,6 +39,26 @@ func newCommentManageService(db *gorm.DB) core.CommentManageService {
} }
} }
func (s *commentServant) GetCommentThumbsMap(userId int64, tweetId int64) (cs.CommentThumbsMap, cs.CommentThumbsMap, error) {
if userId < 0 {
return nil, nil, nil
}
commentThumbsList := cs.CommentThumbsList{}
err := s.db.Model(&dbr.TweetCommentThumbs{}).Where("user_id=? AND tweet_id=?", userId, tweetId).Find(&commentThumbsList).Error
if err != nil {
return nil, nil, err
}
commentThumbs, replyThumbs := make(cs.CommentThumbsMap), make(cs.CommentThumbsMap)
for _, thumbs := range commentThumbsList {
if thumbs.CommentType == 0 {
commentThumbs[thumbs.CommentID] = thumbs
} else {
replyThumbs[thumbs.ReplyID] = thumbs
}
}
return commentThumbs, replyThumbs, nil
}
func (s *commentServant) GetComments(conditions *core.ConditionsT, offset, limit int) ([]*core.Comment, error) { func (s *commentServant) GetComments(conditions *core.ConditionsT, offset, limit int) ([]*core.Comment, error) {
return (&dbr.Comment{}).List(s.db, conditions, offset, limit) return (&dbr.Comment{}).List(s.db, conditions, offset, limit)
} }
@ -72,6 +96,7 @@ func (s *commentServant) GetCommentRepliesByID(ids []int64) ([]*core.CommentRepl
CommentReply := &dbr.CommentReply{} CommentReply := &dbr.CommentReply{}
replies, err := CommentReply.List(s.db, &dbr.ConditionsT{ replies, err := CommentReply.List(s.db, &dbr.ConditionsT{
"comment_id IN ?": ids, "comment_id IN ?": ids,
"ORDER": "id ASC",
}, 0, 0) }, 0, 0)
if err != nil { if err != nil {
@ -106,7 +131,22 @@ func (s *commentServant) GetCommentRepliesByID(ids []int64) ([]*core.CommentRepl
} }
func (s *commentManageServant) DeleteComment(comment *core.Comment) error { func (s *commentManageServant) DeleteComment(comment *core.Comment) error {
return comment.Delete(s.db) db := s.db.Begin()
defer db.Rollback()
err := comment.Delete(s.db)
if err != nil {
return err
}
err = db.Model(&dbr.TweetCommentThumbs{}).Where("user_id=? AND tweet_id=? AND comment_id=?", comment.UserID, comment.PostID, comment.ID).Updates(map[string]any{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
if err != nil {
return err
}
db.Commit()
return nil
} }
func (s *commentManageServant) CreateComment(comment *core.Comment) (*core.Comment, error) { func (s *commentManageServant) CreateComment(comment *core.Comment) (*core.Comment, error) {
@ -117,10 +157,244 @@ func (s *commentManageServant) CreateCommentReply(reply *core.CommentReply) (*co
return reply.Create(s.db) return reply.Create(s.db)
} }
func (s *commentManageServant) DeleteCommentReply(reply *core.CommentReply) error { func (s *commentManageServant) DeleteCommentReply(reply *core.CommentReply) (err error) {
return reply.Delete(s.db) db := s.db.Begin()
defer db.Rollback()
err = reply.Delete(s.db)
if err != nil {
return
}
err = db.Model(&dbr.TweetCommentThumbs{}).
Where("user_id=? AND comment_id=? AND reply_id=?", reply.UserID, reply.CommentID, reply.ID).Updates(map[string]any{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
if err != nil {
return
}
db.Commit()
return
} }
func (s *commentManageServant) CreateCommentContent(content *core.CommentContent) (*core.CommentContent, error) { func (s *commentManageServant) CreateCommentContent(content *core.CommentContent) (*core.CommentContent, error) {
return content.Create(s.db) return content.Create(s.db)
} }
func (s *commentManageServant) ThumbsUpComment(userId int64, tweetId, commentId int64) error {
db := s.db.Begin()
defer db.Rollback()
var (
thumbsUpCount int32 = 0
thumbsDownCount int32 = 0
)
commentThumbs := &dbr.TweetCommentThumbs{}
// 检查thumbs状态
err := s.db.Where("user_id=? AND tweet_id=? AND comment_id=? AND comment_type=0", userId, tweetId, commentId).Take(commentThumbs).Error
if err == nil {
switch {
case commentThumbs.IsThumbsUp == types.Yes && commentThumbs.IsThumbsDown == types.No:
thumbsUpCount, thumbsDownCount = -1, 0
case commentThumbs.IsThumbsUp == types.No && commentThumbs.IsThumbsDown == types.No:
thumbsUpCount, thumbsDownCount = 1, 0
default:
thumbsUpCount, thumbsDownCount = 1, -1
commentThumbs.IsThumbsDown = types.No
}
commentThumbs.IsThumbsUp = 1 - commentThumbs.IsThumbsUp
commentThumbs.ModifiedOn = time.Now().Unix()
} else {
commentThumbs = &dbr.TweetCommentThumbs{
UserID: userId,
TweetID: tweetId,
CommentID: commentId,
IsThumbsUp: types.Yes,
IsThumbsDown: types.No,
CommentType: 0,
Model: &dbr.Model{
CreatedOn: time.Now().Unix(),
},
}
thumbsUpCount, thumbsDownCount = 1, 0
}
// 更新thumbs状态
if err = s.db.Save(commentThumbs).Error; err != nil {
return err
}
// 更新thumbsUpCount
if err = s.updateCommentThumbsUpCount(&dbr.Comment{}, commentId, thumbsUpCount, thumbsDownCount); err != nil {
return err
}
db.Commit()
return nil
}
func (s *commentManageServant) ThumbsDownComment(userId int64, tweetId, commentId int64) error {
db := s.db.Begin()
defer db.Rollback()
var (
thumbsUpCount int32 = 0
thumbsDownCount int32 = 0
)
commentThumbs := &dbr.TweetCommentThumbs{}
// 检查thumbs状态
err := s.db.Where("user_id=? AND tweet_id=? AND comment_id=? AND comment_type=0", userId, tweetId, commentId).Take(commentThumbs).Error
if err == nil {
switch {
case commentThumbs.IsThumbsDown == types.Yes:
thumbsUpCount, thumbsDownCount = 0, -1
case commentThumbs.IsThumbsDown == types.No && commentThumbs.IsThumbsUp == types.No:
thumbsUpCount, thumbsDownCount = 0, 1
default:
thumbsUpCount, thumbsDownCount = -1, 1
commentThumbs.IsThumbsUp = types.No
}
commentThumbs.IsThumbsDown = 1 - commentThumbs.IsThumbsDown
commentThumbs.ModifiedOn = time.Now().Unix()
} else {
commentThumbs = &dbr.TweetCommentThumbs{
UserID: userId,
TweetID: tweetId,
CommentID: commentId,
IsThumbsUp: types.No,
IsThumbsDown: types.Yes,
CommentType: 0,
Model: &dbr.Model{
CreatedOn: time.Now().Unix(),
},
}
thumbsUpCount, thumbsDownCount = 0, 1
}
// 更新thumbs状态
if err = s.db.Save(commentThumbs).Error; err != nil {
return err
}
// 更新thumbsUpCount
if err = s.updateCommentThumbsUpCount(&dbr.Comment{}, commentId, thumbsUpCount, thumbsDownCount); err != nil {
return err
}
db.Commit()
return nil
}
func (s *commentManageServant) ThumbsUpReply(userId int64, tweetId, commentId, replyId int64) error {
db := s.db.Begin()
defer db.Rollback()
var (
thumbsUpCount int32 = 0
thumbsDownCount int32 = 0
)
commentThumbs := &dbr.TweetCommentThumbs{}
// 检查thumbs状态
err := s.db.Where("user_id=? AND tweet_id=? AND comment_id=? AND reply_id=? AND comment_type=1", userId, tweetId, commentId, replyId).Take(commentThumbs).Error
if err == nil {
switch {
case commentThumbs.IsThumbsUp == types.Yes:
thumbsUpCount, thumbsDownCount = -1, 0
case commentThumbs.IsThumbsUp == types.No && commentThumbs.IsThumbsDown == types.No:
thumbsUpCount, thumbsDownCount = 1, 0
default:
thumbsUpCount, thumbsDownCount = 1, -1
commentThumbs.IsThumbsDown = types.No
}
commentThumbs.IsThumbsUp = 1 - commentThumbs.IsThumbsUp
commentThumbs.ModifiedOn = time.Now().Unix()
} else {
commentThumbs = &dbr.TweetCommentThumbs{
UserID: userId,
TweetID: tweetId,
CommentID: commentId,
ReplyID: replyId,
IsThumbsUp: types.Yes,
IsThumbsDown: types.No,
CommentType: 1,
Model: &dbr.Model{
CreatedOn: time.Now().Unix(),
},
}
thumbsUpCount, thumbsDownCount = 1, 0
}
// 更新thumbs状态
if err = s.db.Save(commentThumbs).Error; err != nil {
return err
}
// 更新thumbsUpCount
if err = s.updateCommentThumbsUpCount(&dbr.CommentReply{}, replyId, thumbsUpCount, thumbsDownCount); err != nil {
return err
}
db.Commit()
return nil
}
func (s *commentManageServant) ThumbsDownReply(userId int64, tweetId, commentId, replyId int64) error {
db := s.db.Begin()
defer db.Rollback()
var (
thumbsUpCount int32 = 0
thumbsDownCount int32 = 0
)
commentThumbs := &dbr.TweetCommentThumbs{}
// 检查thumbs状态
err := s.db.Where("user_id=? AND tweet_id=? AND comment_id=? AND reply_id=? AND comment_type=1", userId, tweetId, commentId, replyId).Take(commentThumbs).Error
if err == nil {
switch {
case commentThumbs.IsThumbsDown == types.Yes:
thumbsUpCount, thumbsDownCount = 0, -1
case commentThumbs.IsThumbsUp == types.No && commentThumbs.IsThumbsDown == types.No:
thumbsUpCount, thumbsDownCount = 0, 1
default:
thumbsUpCount, thumbsDownCount = -1, 1
commentThumbs.IsThumbsUp = types.No
}
commentThumbs.IsThumbsDown = 1 - commentThumbs.IsThumbsDown
commentThumbs.ModifiedOn = time.Now().Unix()
} else {
commentThumbs = &dbr.TweetCommentThumbs{
UserID: userId,
TweetID: tweetId,
CommentID: commentId,
ReplyID: replyId,
IsThumbsUp: types.No,
IsThumbsDown: types.Yes,
CommentType: 1,
Model: &dbr.Model{
CreatedOn: time.Now().Unix(),
},
}
thumbsUpCount, thumbsDownCount = 0, 1
}
// 更新thumbs状态
if err = s.db.Save(commentThumbs).Error; err != nil {
return err
}
// 更新thumbsUpCount
if err = s.updateCommentThumbsUpCount(&dbr.CommentReply{}, replyId, thumbsUpCount, thumbsDownCount); err != nil {
return err
}
db.Commit()
return nil
}
func (s *commentManageServant) updateCommentThumbsUpCount(obj any, id int64, thumbsUpCount, thumbsDownCount int32) error {
updateColumns := make(map[string]any, 2)
if thumbsUpCount == 1 {
updateColumns["thumbs_up_count"] = gorm.Expr("thumbs_up_count + 1")
} else if thumbsUpCount == -1 {
updateColumns["thumbs_up_count"] = gorm.Expr("thumbs_up_count - 1")
}
if thumbsDownCount == 1 {
updateColumns["thumbs_down_count"] = gorm.Expr("thumbs_down_count + 1")
} else if thumbsDownCount == -1 {
updateColumns["thumbs_down_count"] = gorm.Expr("thumbs_down_count - 1")
}
if len(updateColumns) > 0 {
updateColumns["modified_on"] = time.Now().Unix()
return s.db.Model(obj).Where("id=?", id).UpdateColumns(updateColumns).Error
}
return nil
}

@ -7,6 +7,7 @@ package dbr
import ( import (
"time" "time"
"github.com/rocboss/paopao-ce/pkg/types"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -16,6 +17,8 @@ type Comment struct {
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
IP string `json:"ip"` IP string `json:"ip"`
IPLoc string `json:"ip_loc"` IPLoc string `json:"ip_loc"`
ThumbsUpCount int32 `json:"thumbs_up_count"`
ThumbsDownCount int32 `json:"-"`
} }
type CommentFormated struct { type CommentFormated struct {
@ -26,6 +29,9 @@ type CommentFormated struct {
Contents []*CommentContent `json:"contents"` Contents []*CommentContent `json:"contents"`
Replies []*CommentReplyFormated `json:"replies"` Replies []*CommentReplyFormated `json:"replies"`
IPLoc string `json:"ip_loc"` IPLoc string `json:"ip_loc"`
ThumbsUpCount int32 `json:"thumbs_up_count"`
IsThumbsUp int8 `json:"is_thumbs_up"`
IsThumbsDown int8 `json:"is_thumbs_down"`
CreatedOn int64 `json:"created_on"` CreatedOn int64 `json:"created_on"`
ModifiedOn int64 `json:"modified_on"` ModifiedOn int64 `json:"modified_on"`
} }
@ -42,6 +48,9 @@ func (c *Comment) Format() *CommentFormated {
Contents: []*CommentContent{}, Contents: []*CommentContent{},
Replies: []*CommentReplyFormated{}, Replies: []*CommentReplyFormated{},
IPLoc: c.IPLoc, IPLoc: c.IPLoc,
ThumbsUpCount: c.ThumbsUpCount,
IsThumbsUp: types.No,
IsThumbsDown: types.No,
CreatedOn: c.CreatedOn, CreatedOn: c.CreatedOn,
ModifiedOn: c.ModifiedOn, ModifiedOn: c.ModifiedOn,
} }

@ -7,6 +7,7 @@ package dbr
import ( import (
"time" "time"
"github.com/rocboss/paopao-ce/pkg/types"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -18,6 +19,8 @@ type CommentReply struct {
Content string `json:"content"` Content string `json:"content"`
IP string `json:"ip"` IP string `json:"ip"`
IPLoc string `json:"ip_loc"` IPLoc string `json:"ip_loc"`
ThumbsUpCount int32 `json:"thumbs_up_count"`
ThumbsDownCount int32 `json:"-"`
} }
type CommentReplyFormated struct { type CommentReplyFormated struct {
@ -29,6 +32,9 @@ type CommentReplyFormated struct {
AtUser *UserFormated `json:"at_user"` AtUser *UserFormated `json:"at_user"`
Content string `json:"content"` Content string `json:"content"`
IPLoc string `json:"ip_loc"` IPLoc string `json:"ip_loc"`
ThumbsUpCount int32 `json:"thumbs_up_count"`
IsThumbsUp int8 `json:"is_thumbs_up"`
IsThumbsDown int8 `json:"is_thumbs_down"`
CreatedOn int64 `json:"created_on"` CreatedOn int64 `json:"created_on"`
ModifiedOn int64 `json:"modified_on"` ModifiedOn int64 `json:"modified_on"`
} }
@ -47,6 +53,9 @@ func (c *CommentReply) Format() *CommentReplyFormated {
AtUser: &UserFormated{}, AtUser: &UserFormated{},
Content: c.Content, Content: c.Content,
IPLoc: c.IPLoc, IPLoc: c.IPLoc,
ThumbsUpCount: c.ThumbsUpCount,
IsThumbsUp: types.No,
IsThumbsDown: types.No,
CreatedOn: c.CreatedOn, CreatedOn: c.CreatedOn,
ModifiedOn: c.ModifiedOn, ModifiedOn: c.ModifiedOn,
} }

@ -0,0 +1,16 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package dbr
type TweetCommentThumbs struct {
*Model
UserID int64 `json:"user_id"`
TweetID int64 `json:"tweet_id"`
CommentID int64 `json:"comment_id"`
ReplyID int64 `json:"reply_id"`
CommentType int8 `json:"comment_type"`
IsThumbsUp int8 `json:"is_thumbs_up"`
IsThumbsDown int8 `json:"is_thumbs_down"`
}

@ -7,6 +7,7 @@ package dbr
import ( import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
const ( const (
@ -73,7 +74,7 @@ func (c *Contact) List(db *gorm.DB, conditions ConditionsT, offset, limit int) (
} }
} }
db.Joins("User").Order("`User`.`nickname` ASC") db.Joins("User").Order(clause.OrderByColumn{Column: clause.Column{Name: "nickname"}, Desc: false})
if err = db.Find(&contacts).Error; err != nil { if err = db.Find(&contacts).Error; err != nil {
return nil, err return nil, err
} }

@ -26,6 +26,7 @@ type Post struct {
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
CommentCount int64 `json:"comment_count"` CommentCount int64 `json:"comment_count"`
CollectionCount int64 `json:"collection_count"` CollectionCount int64 `json:"collection_count"`
ShareCount int64 `json:"share_count"`
UpvoteCount int64 `json:"upvote_count"` UpvoteCount int64 `json:"upvote_count"`
Visibility PostVisibleT `json:"visibility"` Visibility PostVisibleT `json:"visibility"`
IsTop int `json:"is_top"` IsTop int `json:"is_top"`
@ -45,6 +46,7 @@ type PostFormated struct {
Contents []*PostContentFormated `json:"contents"` Contents []*PostContentFormated `json:"contents"`
CommentCount int64 `json:"comment_count"` CommentCount int64 `json:"comment_count"`
CollectionCount int64 `json:"collection_count"` CollectionCount int64 `json:"collection_count"`
ShareCount int64 `json:"share_count"`
UpvoteCount int64 `json:"upvote_count"` UpvoteCount int64 `json:"upvote_count"`
Visibility PostVisibleT `json:"visibility"` Visibility PostVisibleT `json:"visibility"`
IsTop int `json:"is_top"` IsTop int `json:"is_top"`
@ -71,6 +73,7 @@ func (p *Post) Format() *PostFormated {
Contents: []*PostContentFormated{}, Contents: []*PostContentFormated{},
CommentCount: p.CommentCount, CommentCount: p.CommentCount,
CollectionCount: p.CollectionCount, CollectionCount: p.CollectionCount,
ShareCount: p.ShareCount,
UpvoteCount: p.UpvoteCount, UpvoteCount: p.UpvoteCount,
Visibility: p.Visibility, Visibility: p.Visibility,
IsTop: p.IsTop, IsTop: p.IsTop,

@ -8,6 +8,7 @@ import (
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
type PostCollection struct { type PostCollection struct {
@ -31,7 +32,7 @@ func (p *PostCollection) Get(db *gorm.DB) (*PostCollection, error) {
db = db.Where(tn+"user_id = ?", p.UserID) db = db.Where(tn+"user_id = ?", p.UserID)
} }
db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") db = db.Joins("Post").Where("visibility <> ? OR (visibility = ? AND ? = ?)", PostVisitPrivate, PostVisitPrivate, clause.Column{Table: "Post", Name: "user_id"}, p.UserID).Order(clause.OrderByColumn{Column: clause.Column{Table: "Post", Name: "id"}, Desc: true})
err := db.First(&star).Error err := db.First(&star).Error
if err != nil { if err != nil {
return &star, err return &star, err
@ -73,7 +74,7 @@ func (p *PostCollection) List(db *gorm.DB, conditions *ConditionsT, offset, limi
} }
} }
db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") db = db.Joins("Post").Where(`visibility <> ? OR (visibility = ? AND ? = ?)`, PostVisitPrivate, PostVisitPrivate, clause.Column{Table: "Post", Name: "user_id"}, p.UserID).Order(clause.OrderByColumn{Column: clause.Column{Table: "Post", Name: "id"}, Desc: true})
if err = db.Where(tn+"is_del = ?", 0).Find(&collections).Error; err != nil { if err = db.Where(tn+"is_del = ?", 0).Find(&collections).Error; err != nil {
return nil, err return nil, err
} }
@ -97,7 +98,7 @@ func (p *PostCollection) Count(db *gorm.DB, conditions *ConditionsT) (int64, err
} }
} }
db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate) db = db.Joins("Post").Where(`visibility <> ? OR (visibility = ? AND ? = ?)`, PostVisitPrivate, PostVisitPrivate, clause.Column{Table: "Post", Name: "user_id"}, p.UserID)
if err := db.Model(p).Count(&count).Error; err != nil { if err := db.Model(p).Count(&count).Error; err != nil {
return 0, err return 0, err
} }

@ -8,6 +8,7 @@ import (
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
type PostStar struct { type PostStar struct {
@ -31,7 +32,7 @@ func (p *PostStar) Get(db *gorm.DB) (*PostStar, error) {
db = db.Where(tn+"user_id = ?", p.UserID) db = db.Where(tn+"user_id = ?", p.UserID)
} }
db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") db = db.Joins("Post").Where("visibility <> ? OR (visibility = ? AND ? = ?)", PostVisitPrivate, PostVisitPrivate, clause.Column{Table: "Post", Name: "user_id"}, p.UserID).Order(clause.OrderByColumn{Column: clause.Column{Table: "Post", Name: "id"}, Desc: true})
if err := db.First(&star).Error; err != nil { if err := db.First(&star).Error; err != nil {
return nil, err return nil, err
} }
@ -71,7 +72,7 @@ func (p *PostStar) List(db *gorm.DB, conditions *ConditionsT, offset, limit int)
} }
} }
db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") db = db.Joins("Post").Where("visibility <> ? OR (visibility = ? AND ? = ?)", PostVisitPrivate, PostVisitPrivate, clause.Column{Table: "Post", Name: "user_id"}, p.UserID).Order(clause.OrderByColumn{Column: clause.Column{Table: "Post", Name: "id"}, Desc: true})
if err = db.Find(&stars).Error; err != nil { if err = db.Find(&stars).Error; err != nil {
return nil, err return nil, err
} }
@ -94,7 +95,7 @@ func (p *PostStar) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
} }
} }
db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate) db = db.Joins("Post").Where("visibility <> ? OR (visibility = ? AND ? = ?)", PostVisitPrivate, PostVisitPrivate, clause.Column{Table: "Post", Name: "user_id"}, p.UserID)
if err := db.Model(p).Count(&count).Error; err != nil { if err := db.Model(p).Count(&count).Error; err != nil {
return 0, err return 0, err
} }

@ -16,12 +16,27 @@ type Tag struct {
Tag string `json:"tag"` Tag string `json:"tag"`
QuoteNum int64 `json:"quote_num"` QuoteNum int64 `json:"quote_num"`
} }
type TopicUser struct {
*Model
UserID int64 `json:"user_id"`
TopicID int64 `json:"topic_id"`
AliasName string `json:"-"`
Remark string `json:"-"`
QuoteNum int64 `json:"quote_num"`
IsTop int8 `json:"is_top"`
ReserveA string `json:"-"`
ReserveB string `json:"-"`
}
type TagFormated struct { type TagFormated struct {
ID int64 `json:"id"` ID int64 `json:"id"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
User *UserFormated `json:"user"` User *UserFormated `json:"user"`
Tag string `json:"tag"` Tag string `json:"tag"`
QuoteNum int64 `json:"quote_num"` QuoteNum int64 `json:"quote_num"`
IsFollowing int8 `json:"is_following"`
IsTop int8 `json:"is_top"`
} }
func (t *Tag) Format() *TagFormated { func (t *Tag) Format() *TagFormated {
@ -35,6 +50,8 @@ func (t *Tag) Format() *TagFormated {
User: &UserFormated{}, User: &UserFormated{},
Tag: t.Tag, Tag: t.Tag,
QuoteNum: t.QuoteNum, QuoteNum: t.QuoteNum,
IsFollowing: 0,
IsTop: 0,
} }
} }

@ -49,6 +49,7 @@ func NewDataService() (core.DataService, core.VersionInfo) {
pvs := security.NewPhoneVerifyService() pvs := security.NewPhoneVerifyService()
ams := NewAuthorizationManageService() ams := NewAuthorizationManageService()
ths := newTweetHelpService(db) ths := newTweetHelpService(db)
ums := newUserManageService(db)
// initialize core.IndexPostsService // initialize core.IndexPostsService
if cfg.If("Friendship") { if cfg.If("Friendship") {
@ -63,23 +64,30 @@ func NewDataService() (core.DataService, core.VersionInfo) {
} }
// initialize core.CacheIndexService // initialize core.CacheIndexService
if cfg.If("SimpleCacheIndex") { cfg.On(cfg.Actions{
"SimpleCacheIndex": func() {
// simpleCache use special post index service // simpleCache use special post index service
ips = newSimpleIndexPostsService(db, ths) ips = newSimpleIndexPostsService(db, ths)
cis, v = cache.NewSimpleCacheIndexService(ips) cis, v = cache.NewSimpleCacheIndexService(ips)
} else if cfg.If("BigCacheIndex") { },
"BigCacheIndex": func() {
// TODO: make cache index post in different scence like friendship/followship/lightship // TODO: make cache index post in different scence like friendship/followship/lightship
cis, v = cache.NewBigCacheIndexService(ips, ams) cis, v = cache.NewBigCacheIndexService(ips, ams)
} else { },
"RedisCacheIndex": func() {
cis, v = cache.NewRedisCacheIndexService(ips, ams)
},
}, func() {
// defualt no cache
cis, v = cache.NewNoneCacheIndexService(ips) cis, v = cache.NewNoneCacheIndexService(ips)
} })
logrus.Infof("use %s as cache index service by version: %s", v.Name(), v.Version()) logrus.Infof("use %s as cache index service by version: %s", v.Name(), v.Version())
ds := &dataServant{ ds := &dataServant{
IndexPostsService: cis, IndexPostsService: cis,
WalletService: newWalletService(db), WalletService: newWalletService(db),
MessageService: newMessageService(db), MessageService: newMessageService(db),
TopicService: newTopicService(db), TopicService: newTopicService(db, ums),
TweetService: newTweetService(db), TweetService: newTweetService(db),
TweetManageService: newTweetManageService(db, cis), TweetManageService: newTweetManageService(db, cis),
TweetHelpService: newTweetHelpService(db), TweetHelpService: newTweetHelpService(db),

@ -5,6 +5,7 @@
package jinzhu package jinzhu
import ( import (
"errors"
"strings" "strings"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
@ -18,11 +19,22 @@ var (
type topicServant struct { type topicServant struct {
db *gorm.DB db *gorm.DB
ums core.UserManageService
tnTopicUser string
tnDotTopicUser string
} }
func newTopicService(db *gorm.DB) core.TopicService { type topicInfo struct {
TopicId int64
IsTop int8
}
func newTopicService(db *gorm.DB, ums core.UserManageService) core.TopicService {
return &topicServant{ return &topicServant{
db: db, db: db,
ums: ums,
tnTopicUser: db.NamingStrategy.TableName("TopicUser"),
tnDotTopicUser: db.NamingStrategy.TableName("TopicUser") + ".",
} }
} }
@ -38,6 +50,110 @@ func (s *topicServant) GetTags(conditions *core.ConditionsT, offset, limit int)
return (&dbr.Tag{}).List(s.db, conditions, offset, limit) return (&dbr.Tag{}).List(s.db, conditions, offset, limit)
} }
func (s *topicServant) GetHotTags(userId int64, limit int, offset int) ([]*core.TagFormated, error) {
tags, err := (&dbr.Tag{}).List(s.db, &core.ConditionsT{
"ORDER": "quote_num DESC",
}, offset, limit)
if err != nil {
return nil, err
}
return s.tagsFormat(userId, nil, tags)
}
func (s *topicServant) GetNewestTags(userId int64, limit int, offset int) ([]*core.TagFormated, error) {
tags, err := (&dbr.Tag{}).List(s.db, &core.ConditionsT{
"ORDER": "id DESC",
}, offset, limit)
if err != nil {
return nil, err
}
return s.tagsFormat(userId, nil, tags)
}
func (s *topicServant) GetFollowTags(userId int64, limit int, offset int) ([]*core.TagFormated, error) {
if userId < 0 {
return nil, nil
}
userTopics := []*topicInfo{}
err := s.db.Model(&dbr.TopicUser{}).
Where("user_id=?", userId).
Order("is_top DESC").
Limit(limit).
Offset(offset).
Find(&userTopics).Error
if err != nil {
return nil, err
}
userTopicsMap := make(map[int64]*topicInfo, len(userTopics))
topicIds := make([]int64, 0, len(userTopics))
topicIdsMap := make(map[int64]int, len(userTopics))
for idx, info := range userTopics {
userTopicsMap[info.TopicId] = info
topicIds = append(topicIds, info.TopicId)
topicIdsMap[info.TopicId] = idx
}
var tags []*core.Tag
err = s.db.Model(&dbr.Tag{}).Where("quote_num > 0 and id in ?", topicIds).Order("quote_num DESC").Find(&tags).Error
if err != nil {
return nil, err
}
formtedTags, err := s.tagsFormat(-1, userTopicsMap, tags)
if err != nil {
return nil, err
}
// 置顶排序后处理
// TODO: 垃圾办法最好是topic_user join tag 一次查询但是gorm的join真他喵的别扭F*K
res := make([]*core.TagFormated, len(topicIds), len(topicIds))
for _, tag := range formtedTags {
res[topicIdsMap[tag.ID]] = tag
}
return res, nil
}
func (s *topicServant) tagsFormat(userId int64, userTopicsMap map[int64]*topicInfo, tags []*core.Tag) ([]*core.TagFormated, error) {
// 获取创建者User IDs
userIds := []int64{}
tagIds := []int64{}
for _, tag := range tags {
userIds = append(userIds, tag.UserID)
tagIds = append(tagIds, tag.ID)
}
users, err := s.ums.GetUsersByIDs(userIds)
if err != nil {
return nil, err
}
tagsFormated := []*core.TagFormated{}
for _, tag := range tags {
tagFormated := tag.Format()
for _, user := range users {
if user.ID == tagFormated.UserID {
tagFormated.User = user.Format()
}
}
tagsFormated = append(tagsFormated, tagFormated)
}
// 填充话题follow信息
if userId > -1 && len(userTopicsMap) <= 0 {
userTopics := []*topicInfo{}
err = s.db.Model(&dbr.TopicUser{}).Where("is_del=0 and user_id=? and topic_id in ?", userId, tagIds).Find(&userTopics).Error
if err != nil {
return nil, err
}
userTopicsMap = make(map[int64]*topicInfo, len(userTopics))
for _, info := range userTopics {
userTopicsMap[info.TopicId] = info
}
}
if len(userTopicsMap) > 0 {
for _, tag := range tagsFormated {
if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop
}
}
}
return tagsFormated, nil
}
func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) { func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) {
tag := &dbr.Tag{} tag := &dbr.Tag{}
@ -53,3 +169,39 @@ func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) {
}, 0, 6) }, 0, 6)
} }
} }
func (s *topicServant) FollowTopic(userId int64, topicId int64) (err error) {
return s.db.Create(&dbr.TopicUser{
UserID: userId,
TopicID: topicId,
IsTop: 0,
}).Error
}
func (s *topicServant) UnfollowTopic(userId int64, topicId int64) error {
return s.db.Exec("DELETE FROM "+s.tnTopicUser+" WHERE user_id=? AND topic_id=?", userId, topicId).Error
}
func (s *topicServant) StickTopic(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_top", gorm.Expr("1-is_top")).Error
if err != nil {
return
}
status = -1
err = db.Model(m).Where("user_id=? and topic_id=?", userId, topicId).Select("is_top").Scan(&status).Error
if err != nil {
return
}
if status < 0 {
return -1, errors.New("topic not exist")
}
db.Commit()
return
}

@ -293,6 +293,7 @@ func (s *tweetManageServant) VisiblePost(post *core.Post, visibility core.PostVi
tags := strings.Split(post.Tags, ",") tags := strings.Split(post.Tags, ",")
for _, t := range tags { for _, t := range tags {
tag := &dbr.Tag{ tag := &dbr.Tag{
UserID: post.UserID,
Tag: t, Tag: t,
} }
// TODO: 暂时宽松不处理错误,这里或许可以有优化,后续完善 // TODO: 暂时宽松不处理错误,这里或许可以有优化,后续完善

@ -57,7 +57,7 @@ func NewZincTweetSearchService(ams core.AuthorizationManageService) (core.TweetS
ams: ams, ams: ams,
}, },
indexName: s.Index, indexName: s.Index,
client: zinc.NewClient(s), client: zinc.NewClient(s.Endpoint(), s.User, s.Password),
publicFilter: fmt.Sprintf("visibility:%d", core.PostVisitPublic), publicFilter: fmt.Sprintf("visibility:%d", core.PostVisitPublic),
privateFilter: fmt.Sprintf("visibility:%d AND user_id:%%d", core.PostVisitPrivate), privateFilter: fmt.Sprintf("visibility:%d AND user_id:%%d", core.PostVisitPrivate),
friendFilter: fmt.Sprintf("visibility:%d", core.PostVisitFriend), friendFilter: fmt.Sprintf("visibility:%d", core.PostVisitFriend),

@ -1,11 +1,11 @@
package security package security
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/cockroachdb/errors"
"github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/json" "github.com/rocboss/paopao-ce/pkg/json"

@ -5,7 +5,6 @@
package storage package storage
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -14,6 +13,7 @@ import (
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/cockroachdb/errors"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )

@ -6,15 +6,9 @@ package internal
import ( import (
"github.com/rocboss/paopao-ce/internal/migration" "github.com/rocboss/paopao-ce/internal/migration"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/internal/servants/web/routers/api"
) )
func Initialize() { func Initial() {
// migrate database if needed // migrate database if needed
migration.Run() migration.Run()
// initialize service
broker.Initialize()
api.Initialize()
} }

@ -9,6 +9,25 @@ import (
"github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/base"
) )
const (
TagTypeHot TagType = "hot"
TagTypeNew TagType = "new"
TagTypeFollow TagType = "follow"
TagTypeHotExtral TagType = "hot_extral"
)
type TweetCommentsReq struct {
SimpleInfo `form:"-" binding:"-"`
TweetId int64 `form:"id" binding:"required"`
SortStrategy string `form:"sort_strategy"`
Page int `form:"-" binding:"-"`
PageSize int `form:"-" binding:"-"`
}
type TweetCommentsResp base.PageResp
type TagType string
type TimelineReq struct { type TimelineReq struct {
BaseInfo `form:"-" binding:"-"` BaseInfo `form:"-" binding:"-"`
Query string `form:"query"` Query string `form:"query"`
@ -44,6 +63,24 @@ type GetUserProfileResp struct {
IsFriend bool `json:"is_friend"` IsFriend bool `json:"is_friend"`
} }
type TopicListReq struct {
SimpleInfo `form:"-" binding:"-"`
Type TagType `json:"type" form:"type" binding:"required"`
Num int `json:"num" form:"num" binding:"required"`
ExtralNum int `json:"extral_num" form:"extral_num"`
}
// TopicListResp 主题返回值
// TODO: 优化内容定义
type TopicListResp struct {
Topics []*core.TagFormated `json:"topics"`
ExtralTopics []*core.TagFormated `json:"extral_topics,omitempty"`
}
func (r *GetUserTweetsReq) SetPageInfo(page int, pageSize int) { func (r *GetUserTweetsReq) SetPageInfo(page int, pageSize int) {
r.Page, r.PageSize = page, pageSize r.Page, r.PageSize = page, pageSize
} }
func (r *TweetCommentsReq) SetPageInfo(page int, pageSize int) {
r.Page, r.PageSize = page, pageSize
}

@ -12,6 +12,19 @@ import (
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
) )
type TweetCommentThumbsReq struct {
SimpleInfo `json:"-" binding:"-"`
TweetId int64 `json:"tweet_id" binding:"required"`
CommentId int64 `json:"comment_id" binding:"required"`
}
type TweetReplyThumbsReq struct {
SimpleInfo `json:"-" binding:"-"`
TweetId int64 `json:"tweet_id" binding:"required"`
CommentId int64 `json:"comment_id" binding:"required"`
ReplyId int64 `json:"reply_id" binding:"required"`
}
type PostContentItem struct { type PostContentItem struct {
Content string `json:"content" binding:"required"` Content string `json:"content" binding:"required"`
Type core.PostContentT `json:"type" binding:"required"` Type core.PostContentT `json:"type" binding:"required"`
@ -146,6 +159,25 @@ type DownloadAttachmentResp struct {
SignedURL string `json:"signed_url"` SignedURL string `json:"signed_url"`
} }
type StickTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
type StickTopicResp struct {
StickStatus int8 `json:"top_status"`
}
type FollowTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
type UnfollowTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
// Check 检查PostContentItem属性 // Check 检查PostContentItem属性
func (p *PostContentItem) Check(acs core.AttachmentCheckService) error { func (p *PostContentItem) Check(acs core.AttachmentCheckService) error {
// 检查附件是否是本站资源 // 检查附件是否是本站资源

@ -6,42 +6,15 @@ package web
import ( import (
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/pkg/version"
"github.com/rocboss/paopao-ce/pkg/debug"
) )
const (
TagTypeHot TagType = "hot"
TagTypeNew TagType = "new"
)
type TagType string
type TweetDetailReq struct { type TweetDetailReq struct {
TweetId int64 `form:"id"` TweetId int64 `form:"id"`
} }
type TweetDetailResp core.PostFormated type TweetDetailResp core.PostFormated
type TweetCommentsReq struct {
TweetId int64 `form:"id"`
Page int `form:"-"`
PageSize int `form:"-"`
}
type TweetCommentsResp base.PageResp
type TopicListReq struct {
Type TagType `json:"type" form:"type" binding:"required"`
Num int `json:"num" form:"num" binding:"required"`
}
// TopicListResp 主题返回值
// TODO: 优化内容定义
type TopicListResp struct {
Topics []*core.TagFormated `json:"topics"`
}
type GetCaptchaResp struct { type GetCaptchaResp struct {
Id string `json:"id"` Id string `json:"id"`
Content string `json:"b64s"` Content string `json:"b64s"`
@ -54,7 +27,7 @@ type SendCaptchaReq struct {
} }
type VersionResp struct { type VersionResp struct {
BuildInfo *debug.BuildInfo `json:"build_info"` BuildInfo *version.BuildInfo `json:"build_info"`
} }
type LoginReq struct { type LoginReq struct {

@ -37,7 +37,7 @@ func newUserSrv() api.User {
func newUserBinding() api.UserBinding { func newUserBinding() api.UserBinding {
return &userBinding{ return &userBinding{
UnimplementedUserBinding: &api.UnimplementedUserBinding{ UnimplementedUserBinding: &api.UnimplementedUserBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
} }
} }

@ -9,12 +9,16 @@ import (
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
"time"
"github.com/alimy/mir/v3" "github.com/alimy/mir/v3"
"github.com/cockroachdb/errors"
"github.com/getsentry/sentry-go"
sentrygin "github.com/getsentry/sentry-go/gin"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8" "github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/dao"
"github.com/rocboss/paopao-ce/internal/dao/cache"
"github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/types" "github.com/rocboss/paopao-ce/pkg/types"
"github.com/rocboss/paopao-ce/pkg/xerror" "github.com/rocboss/paopao-ce/pkg/xerror"
@ -24,9 +28,9 @@ import (
type BaseServant types.Empty type BaseServant types.Empty
type DaoServant struct { type DaoServant struct {
Redis *redis.Client
Ds core.DataService Ds core.DataService
Ts core.TweetSearchService Ts core.TweetSearchService
Redis core.RedisCache
} }
type BaseBinding types.Empty type BaseBinding types.Empty
@ -39,6 +43,10 @@ type JsonResp struct {
Data any `json:"data,omitempty"` Data any `json:"data,omitempty"`
} }
type SentryHubSetter interface {
SetSentryHub(hub *sentry.Hub)
}
type UserSetter interface { type UserSetter interface {
SetUser(*core.User) SetUser(*core.User)
} }
@ -75,7 +83,7 @@ func UserNameFrom(c *gin.Context) (string, bool) {
return "", false return "", false
} }
func BindAny(c *gin.Context, obj any) mir.Error { func bindAny(c *gin.Context, obj any) mir.Error {
var errs xerror.ValidErrors var errs xerror.ValidErrors
err := c.ShouldBind(obj) err := c.ShouldBind(obj)
if err != nil { if err != nil {
@ -99,6 +107,40 @@ func BindAny(c *gin.Context, obj any) mir.Error {
return nil return nil
} }
func bindAnySentry(c *gin.Context, obj any) mir.Error {
hub := sentrygin.GetHubFromContext(c)
var errs xerror.ValidErrors
err := c.ShouldBind(obj)
if err != nil {
xerr := mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
if hub != nil {
hub.CaptureException(errors.Wrap(xerr, "bind object"))
}
return xerr
}
// setup sentry hub if needed
if setter, ok := obj.(SentryHubSetter); ok && hub != nil {
setter.SetSentryHub(hub)
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
return nil
}
func RenderAny(c *gin.Context, data any, err mir.Error) { func RenderAny(c *gin.Context, data any, err mir.Error) {
if err == nil { if err == nil {
c.JSON(http.StatusOK, &JsonResp{ c.JSON(http.StatusOK, &JsonResp{
@ -141,8 +183,8 @@ func (s *DaoServant) GetTweetBy(id int64) (*core.PostFormated, error) {
} }
func (s *DaoServant) PushPostsToSearch(c context.Context) { func (s *DaoServant) PushPostsToSearch(c context.Context) {
if ok, err := s.Redis.SetNX(c, "JOB_PUSH_TO_SEARCH", 1, time.Hour).Result(); ok { if err := s.Redis.SetPushToSearchJob(c); err == nil {
defer s.Redis.Del(c, "JOB_PUSH_TO_SEARCH") defer s.Redis.DelPushToSearchJob(c)
splitNum := 1000 splitNum := 1000
totalRows, _ := s.Ds.GetPostCount(&core.ConditionsT{ totalRows, _ := s.Ds.GetPostCount(&core.ConditionsT{
@ -170,7 +212,7 @@ func (s *DaoServant) PushPostsToSearch(c context.Context) {
} }
} }
} else { } else {
logrus.Warnf("set cache by key JOB_PUSH_TO_SEARCH failed: %s", err) logrus.Errorf("redis: set JOB_PUSH_TO_SEARCH error: %s", err)
} }
} }
@ -210,3 +252,18 @@ func (s *DaoServant) GetTweetList(conditions *core.ConditionsT, offset, limit in
postFormated, err := s.Ds.MergePosts(posts) postFormated, err := s.Ds.MergePosts(posts)
return posts, postFormated, err return posts, postFormated, err
} }
func NewDaoServant() *DaoServant {
return &DaoServant{
Redis: cache.NewRedisCache(),
Ds: dao.DataService(),
Ts: dao.TweetSearchService(),
}
}
func NewBindAnyFn() func(c *gin.Context, obj any) mir.Error {
if conf.UseSentryGin() {
return bindAnySentry
}
return bindAny
}

@ -37,7 +37,7 @@ func newUserSrv() api.User {
func newUserBinding() api.UserBinding { func newUserBinding() api.UserBinding {
return &userBinding{ return &userBinding{
UnimplementedUserBinding: &api.UnimplementedUserBinding{ UnimplementedUserBinding: &api.UnimplementedUserBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
} }
} }

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/errcode"
) )
func Admin() gin.HandlerFunc { func Admin() gin.HandlerFunc {
@ -23,7 +22,7 @@ func Admin() gin.HandlerFunc {
} }
response := app.NewResponse(c) response := app.NewResponse(c)
response.ToErrorResponse(errcode.NoAdminPermission) response.ToErrorResponse(_errNoAdminPermission)
c.Abort() c.Abort()
} }
} }

@ -11,7 +11,7 @@ import (
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/errcode" "github.com/rocboss/paopao-ce/pkg/xerror"
) )
func JWT() gin.HandlerFunc { func JWT() gin.HandlerFunc {
@ -19,7 +19,7 @@ func JWT() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var ( var (
token string token string
ecode = errcode.Success ecode = xerror.Success
) )
if s, exist := c.GetQuery("token"); exist { if s, exist := c.GetQuery("token"); exist {
token = s token = s
@ -29,7 +29,7 @@ func JWT() gin.HandlerFunc {
// 验证前端传过来的token格式不为空开头为Bearer // 验证前端传过来的token格式不为空开头为Bearer
if token == "" || !strings.HasPrefix(token, "Bearer ") { if token == "" || !strings.HasPrefix(token, "Bearer ") {
response := app.NewResponse(c) response := app.NewResponse(c)
response.ToErrorResponse(errcode.UnauthorizedTokenError) response.ToErrorResponse(xerror.UnauthorizedTokenError)
c.Abort() c.Abort()
return return
} }
@ -38,15 +38,15 @@ func JWT() gin.HandlerFunc {
token = token[7:] token = token[7:]
} }
if token == "" { if token == "" {
ecode = errcode.InvalidParams ecode = xerror.InvalidParams
} else { } else {
claims, err := app.ParseToken(token) claims, err := app.ParseToken(token)
if err != nil { if err != nil {
switch err.(*jwt.ValidationError).Errors { switch err.(*jwt.ValidationError).Errors {
case jwt.ValidationErrorExpired: case jwt.ValidationErrorExpired:
ecode = errcode.UnauthorizedTokenTimeout ecode = xerror.UnauthorizedTokenTimeout
default: default:
ecode = errcode.UnauthorizedTokenError ecode = xerror.UnauthorizedTokenError
} }
} else { } else {
c.Set("UID", claims.UID) c.Set("UID", claims.UID)
@ -57,17 +57,17 @@ func JWT() gin.HandlerFunc {
if err == nil { if err == nil {
c.Set("USER", user) c.Set("USER", user)
} else { } else {
ecode = errcode.UnauthorizedAuthNotExist ecode = xerror.UnauthorizedAuthNotExist
} }
// 强制下线机制 // 强制下线机制
if (conf.JWTSetting.Issuer + ":" + user.Salt) != claims.Issuer { if (conf.JWTSetting.Issuer + ":" + user.Salt) != claims.Issuer {
ecode = errcode.UnauthorizedTokenTimeout ecode = xerror.UnauthorizedTokenTimeout
} }
} }
} }
if ecode != errcode.Success { if ecode != xerror.Success {
response := app.NewResponse(c) response := app.NewResponse(c)
response.ToErrorResponse(ecode) response.ToErrorResponse(ecode)
c.Abort() c.Abort()

@ -9,7 +9,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/app" "github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/errcode"
) )
func Priv() gin.HandlerFunc { func Priv() gin.HandlerFunc {
@ -20,7 +19,7 @@ func Priv() gin.HandlerFunc {
if user.Status == core.UserStatusNormal { if user.Status == core.UserStatusNormal {
if user.Phone == "" { if user.Phone == "" {
response := app.NewResponse(c) response := app.NewResponse(c)
response.ToErrorResponse(errcode.AccountNoPhoneBind) response.ToErrorResponse(_errAccountNoPhoneBind)
c.Abort() c.Abort()
return return
} }
@ -30,7 +29,7 @@ func Priv() gin.HandlerFunc {
} }
} }
response := app.NewResponse(c) response := app.NewResponse(c)
response.ToErrorResponse(errcode.UserHasBeenBanned) response.ToErrorResponse(_errUserHasBeenBanned)
c.Abort() c.Abort()
} }
} else { } else {
@ -42,7 +41,7 @@ func Priv() gin.HandlerFunc {
} }
} }
response := app.NewResponse(c) response := app.NewResponse(c)
response.ToErrorResponse(errcode.UserHasBeenBanned) response.ToErrorResponse(_errUserHasBeenBanned)
c.Abort() c.Abort()
} }
} }

@ -0,0 +1,16 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package chain
import (
"github.com/rocboss/paopao-ce/pkg/xerror"
)
// nolint
var (
_errUserHasBeenBanned = xerror.NewError(20006, "该账户已被封停")
_errAccountNoPhoneBind = xerror.NewError(20013, "拒绝操作: 账户未绑定手机号")
_errNoAdminPermission = xerror.NewError(20022, "无管理权限")
)

@ -37,7 +37,7 @@ func newUserSrv() api.User {
func newUserBinding() api.UserBinding { func newUserBinding() api.UserBinding {
return &userBinding{ return &userBinding{
UnimplementedUserBinding: &api.UnimplementedUserBinding{ UnimplementedUserBinding: &api.UnimplementedUserBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
} }
} }

@ -37,7 +37,7 @@ func newUserSrv() api.User {
func newUserBinding() api.UserBinding { func newUserBinding() api.UserBinding {
return &userBinding{ return &userBinding{
UnimplementedUserBinding: &api.UnimplementedUserBinding{ UnimplementedUserBinding: &api.UnimplementedUserBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
} }
} }

@ -16,7 +16,7 @@ import (
// RegisterWebStatick register web static assets route // RegisterWebStatick register web static assets route
func RegisterWebStatick(e *gin.Engine) { func RegisterWebStatick(e *gin.Engine) {
routeWebStatic(e, "/", "/index.html", "/favicon.ico", "/assets/*filepath") routeWebStatic(e, "/", "/index.html", "/favicon.ico", "/logo.png", "/sw.js", "/manifest.json", "/assets/*filepath")
} }
func routeWebStatic(e *gin.Engine, paths ...string) { func routeWebStatic(e *gin.Engine, paths ...string) {

@ -59,7 +59,7 @@ func newAdminSrv(s *base.DaoServant) api.Admin {
func newAdminBinding() api.AdminBinding { func newAdminBinding() api.AdminBinding {
return &adminBinding{ return &adminBinding{
UnimplementedAdminBinding: &api.UnimplementedAdminBinding{ UnimplementedAdminBinding: &api.UnimplementedAdminBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
} }
} }

@ -6,7 +6,6 @@ package web
import ( import (
"fmt" "fmt"
"time"
"github.com/alimy/mir/v3" "github.com/alimy/mir/v3"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -80,7 +79,7 @@ func (b *alipayPubBinding) BindAlipayNotify(c *gin.Context) (*web.AlipayNotifyRe
func (s *alipayPubSrv) AlipayNotify(req *web.AlipayNotifyReq) mir.Error { func (s *alipayPubSrv) AlipayNotify(req *web.AlipayNotifyReq) mir.Error {
if req.TradeStatus == alipay.TradeStatusSuccess { if req.TradeStatus == alipay.TradeStatusSuccess {
if ok, _ := s.Redis.SetNX(req.Ctx, "PaoPaoRecharge:"+req.TradeNo, 1, time.Second*5).Result(); ok { if err := s.Redis.SetRechargeStatus(req.Ctx, req.TradeNo); err == nil {
recharge, err := s.Ds.GetRechargeByID(req.ID) recharge, err := s.Ds.GetRechargeByID(req.ID)
if err != nil { if err != nil {
logrus.Errorf("GetRechargeByID id:%d err: %s", req.ID, err) logrus.Errorf("GetRechargeByID id:%d err: %s", req.ID, err)
@ -89,7 +88,7 @@ func (s *alipayPubSrv) AlipayNotify(req *web.AlipayNotifyReq) mir.Error {
if recharge.TradeStatus != "TRADE_SUCCESS" { if recharge.TradeStatus != "TRADE_SUCCESS" {
// 标记为已付款 // 标记为已付款
err := s.Ds.HandleRechargeSuccess(recharge, req.TradeNo) err := s.Ds.HandleRechargeSuccess(recharge, req.TradeNo)
defer s.Redis.Del(req.Ctx, "PaoPaoRecharge:"+req.TradeNo) defer s.Redis.DelRechargeStatus(req.Ctx, req.TradeNo)
if err != nil { if err != nil {
logrus.Errorf("HandleRechargeSuccess id:%d err: %s", req.ID, err) logrus.Errorf("HandleRechargeSuccess id:%d err: %s", req.ID, err)
return _errRechargeNotifyError return _errRechargeNotifyError
@ -201,7 +200,7 @@ func newAlipayPubSrv(s *base.DaoServant) api.AlipayPub {
func newAlipayPubBinding(alipayClient *alipay.Client) api.AlipayPubBinding { func newAlipayPubBinding(alipayClient *alipay.Client) api.AlipayPubBinding {
return &alipayPubBinding{ return &alipayPubBinding{
UnimplementedAlipayPubBinding: &api.UnimplementedAlipayPubBinding{ UnimplementedAlipayPubBinding: &api.UnimplementedAlipayPubBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
alipayClient: alipayClient, alipayClient: alipayClient,
} }
@ -225,7 +224,7 @@ func newAlipayPrivSrv(s *base.DaoServant, client *alipay.Client) api.AlipayPriv
func newAlipayPrivBinding() api.AlipayPrivBinding { func newAlipayPrivBinding() api.AlipayPrivBinding {
return &alipayPrivBinding{ return &alipayPrivBinding{
UnimplementedAlipayPrivBinding: &api.UnimplementedAlipayPrivBinding{ UnimplementedAlipayPrivBinding: &api.UnimplementedAlipayPrivBinding{
BindAny: base.BindAny, BindAny: base.NewBindAnyFn(),
}, },
} }
} }

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

Loading…
Cancel
Save