mirror of https://github.com/rocboss/paopao-ce
Merge pull request #289 from rocboss/t/v0.3.0
v0.3.0 prepare release code review requestpull/290/head v0.3.0-rc.1
commit
021d840221
@ -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));
|
@ -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,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"
|
@ -1,6 +1,6 @@
|
||||
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
|
||||
| ----- | ----- | ----- | ----- | ----- | ----- |
|
||||
| 005| 北野 | 2022-11-21 | 2023-01-04 | v1.1 | 提议 |
|
||||
| 22112109 | 北野 | 2022-11-21 | 2023-01-04 | v1.1 | 提议 |
|
||||
|
||||
### 引入go-mir优化后端架构设计
|
||||
引入[github.com/alimy/mir/v3](https://github.com/alimy/mir)优化后端的架构设计,使得后端代码更具扩展型。
|
@ -1,6 +1,6 @@
|
||||
| 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 |
|
||||
| ----- | ----- | ----- | ----- | ----- | ----- |
|
||||
| 016| 北野 | 2023-02-15 | 2023-02-16 | v1.1 | 提议 |
|
||||
| 23021510| 北野 | 2023-02-15 | 2023-02-16 | v1.1 | 提议 |
|
||||
|
||||
### 概述
|
||||
Pyroscope 是一个开源的持续性能剖析平台。它能够帮你:
|
@ -1,6 +1,6 @@
|
||||
## Draft
|
||||
* [001-关于paopao-ce的设计定位](001-关于paopao-ce的设计定位.md "关于paopao-ce的设计定位")
|
||||
* [002-关于Friendship功能项的设计](002-关于Friendship功能项的设计.md "关于Friendship功能项的设计")
|
||||
* [003-关于Followship功能项的设计](003-关于Followship功能项的设计.md "关于Followship功能项的设计")
|
||||
* [005-引入go-mir优化后端架构设计](005-引入go-mir优化后端架构设计.md "引入go-mir优化后端架构设计")
|
||||
* [006-关于paopao-ce的结构设计](006-关于paopao-ce的结构设计.md "关于paopao-ce的结构设计")
|
||||
* [22110411-关于paopao-ce的设计定位](22110411-关于paopao-ce的设计定位.md "关于paopao-ce的设计定位")
|
||||
* [22110410-关于Friendship功能项的设计](22110410-关于Friendship功能项的设计.md "关于Friendship功能项的设计")
|
||||
* [22110409-关于Followship功能项的设计](22110409-关于Followship功能项的设计.md "关于Followship功能项的设计")
|
||||
* [22112109-引入go-mir优化后端架构设计](22112109-引入go-mir优化后端架构设计.md "引入go-mir优化后端架构设计")
|
||||
* [22112309-关于paopao-ce的结构设计](22112309-关于paopao-ce的结构设计.md "关于paopao-ce的结构设计")
|
||||
|
@ -1,136 +1,139 @@
|
||||
module github.com/rocboss/paopao-ce
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
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/alimy/cfg v0.3.0
|
||||
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/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/ethereum/go-ethereum v1.10.16
|
||||
github.com/fatih/color v1.14.1
|
||||
github.com/fbsobreira/gotron-sdk v0.0.0-20211102183839-58a64f4da5f4
|
||||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/go-redis/redis/v8 v8.11.4
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/getsentry/sentry-go v0.20.0
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/goccy/go-json v0.9.7
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
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/meilisearch/meilisearch-go v0.21.0
|
||||
github.com/minio/minio-go/v7 v7.0.45
|
||||
github.com/pyroscope-io/client v0.6.0
|
||||
github.com/meilisearch/meilisearch-go v0.24.0
|
||||
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/smartwalle/alipay/v3 v3.1.7
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.35
|
||||
github.com/smartwalle/alipay/v3 v3.2.1
|
||||
github.com/sourcegraph/conc v0.3.0
|
||||
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
|
||||
google.golang.org/grpc v1.50.1
|
||||
google.golang.org/protobuf v1.28.1
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
google.golang.org/grpc v1.54.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/resty.v1 v1.12.0
|
||||
gorm.io/driver/mysql v1.3.4
|
||||
gorm.io/driver/postgres v1.3.7
|
||||
gorm.io/driver/sqlite v1.3.4
|
||||
gorm.io/gorm v1.23.4
|
||||
gorm.io/plugin/dbresolver v1.1.0
|
||||
gorm.io/plugin/soft_delete v1.1.0
|
||||
modernc.org/sqlite v1.17.3
|
||||
gorm.io/driver/mysql v1.5.0
|
||||
gorm.io/driver/postgres v1.5.0
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
gorm.io/gorm v1.25.0
|
||||
gorm.io/plugin/dbresolver v1.4.1
|
||||
gorm.io/plugin/soft_delete v1.2.1
|
||||
modernc.org/sqlite v1.22.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // 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/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // 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/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // 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/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/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/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/pgproto3/v2 v2.3.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.11.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||
github.com/klauspost/compress v1.16.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/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/mattn/go-colorable v0.1.13 // 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/sha256-simd v1.0.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/reflect2 v1.0.2 // 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.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pyroscope-io/godeltaprof v0.1.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // 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/smartwalle/crypto4go v1.0.2 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.0 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // 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/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/fasthttp v1.37.1-0.20220607072126-8a320890c08d // indirect
|
||||
github.com/valyala/fasthttp v1.40.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // 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
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
modernc.org/libc v1.16.7 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
|
@ -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!")
|
||||
})
|
||||
})
|
||||
}
|
@ -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
|
@ -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()
|
||||
}
|
@ -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"`
|
||||
}
|
@ -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, "无管理权限")
|
||||
)
|
@ -1,13 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-ce/internal/core"
|
||||
)
|
||||
|
||||
func CreateAttachment(attachment *core.Attachment) (*core.Attachment, error) {
|
||||
return ds.CreateAttachment(attachment)
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultAvatars = []string{
|
||||
"https://assets.paopao.info/public/avatar/default/zoe.png",
|
||||
"https://assets.paopao.info/public/avatar/default/william.png",
|
||||
"https://assets.paopao.info/public/avatar/default/walter.png",
|
||||
"https://assets.paopao.info/public/avatar/default/thomas.png",
|
||||
"https://assets.paopao.info/public/avatar/default/taylor.png",
|
||||
"https://assets.paopao.info/public/avatar/default/sophia.png",
|
||||
"https://assets.paopao.info/public/avatar/default/sam.png",
|
||||
"https://assets.paopao.info/public/avatar/default/ryan.png",
|
||||
"https://assets.paopao.info/public/avatar/default/ruby.png",
|
||||
"https://assets.paopao.info/public/avatar/default/quinn.png",
|
||||
"https://assets.paopao.info/public/avatar/default/paul.png",
|
||||
"https://assets.paopao.info/public/avatar/default/owen.png",
|
||||
"https://assets.paopao.info/public/avatar/default/olivia.png",
|
||||
"https://assets.paopao.info/public/avatar/default/norman.png",
|
||||
"https://assets.paopao.info/public/avatar/default/nora.png",
|
||||
"https://assets.paopao.info/public/avatar/default/natalie.png",
|
||||
"https://assets.paopao.info/public/avatar/default/naomi.png",
|
||||
"https://assets.paopao.info/public/avatar/default/miley.png",
|
||||
"https://assets.paopao.info/public/avatar/default/mike.png",
|
||||
"https://assets.paopao.info/public/avatar/default/lucas.png",
|
||||
"https://assets.paopao.info/public/avatar/default/kylie.png",
|
||||
"https://assets.paopao.info/public/avatar/default/julia.png",
|
||||
"https://assets.paopao.info/public/avatar/default/joshua.png",
|
||||
"https://assets.paopao.info/public/avatar/default/john.png",
|
||||
"https://assets.paopao.info/public/avatar/default/jane.png",
|
||||
"https://assets.paopao.info/public/avatar/default/jackson.png",
|
||||
"https://assets.paopao.info/public/avatar/default/ivy.png",
|
||||
"https://assets.paopao.info/public/avatar/default/isaac.png",
|
||||
"https://assets.paopao.info/public/avatar/default/henry.png",
|
||||
"https://assets.paopao.info/public/avatar/default/harry.png",
|
||||
"https://assets.paopao.info/public/avatar/default/harold.png",
|
||||
"https://assets.paopao.info/public/avatar/default/hanna.png",
|
||||
"https://assets.paopao.info/public/avatar/default/grace.png",
|
||||
"https://assets.paopao.info/public/avatar/default/george.png",
|
||||
"https://assets.paopao.info/public/avatar/default/freddy.png",
|
||||
"https://assets.paopao.info/public/avatar/default/frank.png",
|
||||
"https://assets.paopao.info/public/avatar/default/finn.png",
|
||||
"https://assets.paopao.info/public/avatar/default/emma.png",
|
||||
"https://assets.paopao.info/public/avatar/default/emily.png",
|
||||
"https://assets.paopao.info/public/avatar/default/edward.png",
|
||||
"https://assets.paopao.info/public/avatar/default/clara.png",
|
||||
"https://assets.paopao.info/public/avatar/default/claire.png",
|
||||
"https://assets.paopao.info/public/avatar/default/chloe.png",
|
||||
"https://assets.paopao.info/public/avatar/default/audrey.png",
|
||||
"https://assets.paopao.info/public/avatar/default/arthur.png",
|
||||
"https://assets.paopao.info/public/avatar/default/anna.png",
|
||||
"https://assets.paopao.info/public/avatar/default/andy.png",
|
||||
"https://assets.paopao.info/public/avatar/default/alfred.png",
|
||||
"https://assets.paopao.info/public/avatar/default/alexa.png",
|
||||
"https://assets.paopao.info/public/avatar/default/abigail.png",
|
||||
}
|
||||
|
||||
func GetRandomAvatar() string {
|
||||
rand.Seed(time.Now().UnixMicro())
|
||||
return defaultAvatars[rand.Intn(len(defaultAvatars))]
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"github.com/alimy/cfg"
|
||||
"github.com/rocboss/paopao-ce/internal/core"
|
||||
"github.com/rocboss/paopao-ce/internal/dao"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
ds core.DataService
|
||||
ts core.TweetSearchService
|
||||
oss core.ObjectStorageService
|
||||
DisablePhoneVerify bool
|
||||
)
|
||||
|
||||
func Initialize() {
|
||||
ds = dao.DataService()
|
||||
ts = dao.TweetSearchService()
|
||||
oss = dao.ObjectStorageService()
|
||||
DisablePhoneVerify = !cfg.If("Sms")
|
||||
}
|
||||
|
||||
// persistMediaContents 获取媒体内容并持久化
|
||||
func persistMediaContents(contents []*PostContentItem) (items []string, err error) {
|
||||
items = make([]string, 0, len(contents))
|
||||
for _, item := range contents {
|
||||
switch item.Type {
|
||||
case core.ContentTypeImage,
|
||||
core.ContentTypeVideo,
|
||||
core.ContentTypeAudio,
|
||||
core.ContentTypeAttachment,
|
||||
core.ContentTypeChargeAttachment:
|
||||
items = append(items, item.Content)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err = oss.PersistObject(oss.ObjectKey(item.Content)); err != nil {
|
||||
logrus.Errorf("service.persistMediaContents failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,347 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-ce/internal/core"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-ce/internal/conf"
|
||||
"github.com/rocboss/paopao-ce/pkg/errcode"
|
||||
"github.com/rocboss/paopao-ce/pkg/util"
|
||||
)
|
||||
|
||||
type CommentCreationReq struct {
|
||||
PostID int64 `json:"post_id" binding:"required"`
|
||||
Contents []*PostContentItem `json:"contents" binding:"required"`
|
||||
Users []string `json:"users" binding:"required"`
|
||||
}
|
||||
type CommentReplyCreationReq struct {
|
||||
CommentID int64 `json:"comment_id" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
AtUserID int64 `json:"at_user_id"`
|
||||
}
|
||||
|
||||
type CommentDelReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
type ReplyDelReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
func GetPostComments(postID int64, sort string, offset, limit int) ([]*core.CommentFormated, int64, error) {
|
||||
conditions := &core.ConditionsT{
|
||||
"post_id": postID,
|
||||
"ORDER": sort,
|
||||
}
|
||||
comments, err := ds.GetComments(conditions, offset, limit)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
userIDs := []int64{}
|
||||
commentIDs := []int64{}
|
||||
for _, comment := range comments {
|
||||
userIDs = append(userIDs, comment.UserID)
|
||||
commentIDs = append(commentIDs, comment.ID)
|
||||
}
|
||||
|
||||
users, err := ds.GetUsersByIDs(userIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
contents, err := ds.GetCommentContentsByIDs(commentIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
replies, err := ds.GetCommentRepliesByID(commentIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
commentsFormated := []*core.CommentFormated{}
|
||||
for _, comment := range comments {
|
||||
commentFormated := comment.Format()
|
||||
for _, content := range contents {
|
||||
if content.CommentID == comment.ID {
|
||||
commentFormated.Contents = append(commentFormated.Contents, content)
|
||||
}
|
||||
}
|
||||
for _, reply := range replies {
|
||||
if reply.CommentID == commentFormated.ID {
|
||||
commentFormated.Replies = append(commentFormated.Replies, reply)
|
||||
}
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.ID == comment.UserID {
|
||||
commentFormated.User = user.Format()
|
||||
}
|
||||
}
|
||||
|
||||
commentsFormated = append(commentsFormated, commentFormated)
|
||||
}
|
||||
|
||||
// 获取总量
|
||||
totalRows, _ := ds.GetCommentCount(conditions)
|
||||
|
||||
return commentsFormated, totalRows, nil
|
||||
}
|
||||
|
||||
func CreatePostComment(ctx *gin.Context, userID int64, param CommentCreationReq) (comment *core.Comment, err error) {
|
||||
var mediaContents []string
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
deleteOssObjects(mediaContents)
|
||||
}
|
||||
}()
|
||||
|
||||
if mediaContents, err = persistMediaContents(param.Contents); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 加载Post
|
||||
post, err := ds.GetPostByID(param.PostID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if post.CommentCount >= conf.AppSetting.MaxCommentCount {
|
||||
return nil, errcode.MaxCommentCount
|
||||
}
|
||||
ip := ctx.ClientIP()
|
||||
comment = &core.Comment{
|
||||
PostID: post.ID,
|
||||
UserID: userID,
|
||||
IP: ip,
|
||||
IPLoc: util.GetIPLoc(ip),
|
||||
}
|
||||
comment, err = ds.CreateComment(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range param.Contents {
|
||||
// 检查附件是否是本站资源
|
||||
if item.Type == core.ContentTypeImage || item.Type == core.ContentTypeVideo || item.Type == core.ContentTypeAttachment {
|
||||
if err := ds.CheckAttachment(item.Content); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
postContent := &core.CommentContent{
|
||||
CommentID: comment.ID,
|
||||
UserID: userID,
|
||||
Content: item.Content,
|
||||
Type: item.Type,
|
||||
Sort: item.Sort,
|
||||
}
|
||||
ds.CreateCommentContent(postContent)
|
||||
}
|
||||
|
||||
// 更新Post回复数
|
||||
post.CommentCount++
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
ds.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
PushPostToSearch(post)
|
||||
|
||||
// 创建用户消息提醒
|
||||
postMaster, err := ds.GetUserByID(post.UserID)
|
||||
if err == nil && postMaster.ID != userID {
|
||||
go ds.CreateMessage(&core.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: postMaster.ID,
|
||||
Type: core.MsgtypeComment,
|
||||
Brief: "在泡泡中评论了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
})
|
||||
}
|
||||
for _, u := range param.Users {
|
||||
user, err := ds.GetUserByUsername(u)
|
||||
if err != nil || user.ID == userID || user.ID == postMaster.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建消息提醒
|
||||
go ds.CreateMessage(&core.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: user.ID,
|
||||
Type: core.MsgtypeComment,
|
||||
Brief: "在泡泡评论中@了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func GetPostComment(id int64) (*core.Comment, error) {
|
||||
return ds.GetCommentByID(id)
|
||||
}
|
||||
|
||||
func DeletePostComment(comment *core.Comment) error {
|
||||
// 加载post
|
||||
post, err := ds.GetPostByID(comment.PostID)
|
||||
if err == nil {
|
||||
// 更新post回复数
|
||||
post.CommentCount--
|
||||
ds.UpdatePost(post)
|
||||
}
|
||||
|
||||
return ds.DeleteComment(comment)
|
||||
}
|
||||
|
||||
func createPostPreHandler(commentID int64, userID, atUserID int64) (*core.Post, *core.Comment, int64,
|
||||
error) {
|
||||
// 加载Comment
|
||||
comment, err := ds.GetCommentByID(commentID)
|
||||
if err != nil {
|
||||
return nil, nil, atUserID, err
|
||||
}
|
||||
|
||||
// 加载comment的post
|
||||
post, err := ds.GetPostByID(comment.PostID)
|
||||
if err != nil {
|
||||
return nil, nil, atUserID, err
|
||||
}
|
||||
|
||||
if post.CommentCount >= conf.AppSetting.MaxCommentCount {
|
||||
return nil, nil, atUserID, errcode.MaxCommentCount
|
||||
}
|
||||
|
||||
if userID == atUserID {
|
||||
atUserID = 0
|
||||
}
|
||||
|
||||
if atUserID > 0 {
|
||||
// 检测目前用户是否存在
|
||||
users, _ := ds.GetUsersByIDs([]int64{atUserID})
|
||||
if len(users) == 0 {
|
||||
atUserID = 0
|
||||
}
|
||||
}
|
||||
|
||||
return post, comment, atUserID, nil
|
||||
}
|
||||
|
||||
func CreatePostCommentReply(ctx *gin.Context, commentID int64, content string, userID, atUserID int64) (*core.CommentReply, error) {
|
||||
var (
|
||||
post *core.Post
|
||||
comment *core.Comment
|
||||
err error
|
||||
)
|
||||
if post, comment, atUserID, err = createPostPreHandler(commentID, userID, atUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建评论
|
||||
ip := ctx.ClientIP()
|
||||
reply := &core.CommentReply{
|
||||
CommentID: commentID,
|
||||
UserID: userID,
|
||||
Content: content,
|
||||
AtUserID: atUserID,
|
||||
IP: ip,
|
||||
IPLoc: util.GetIPLoc(ip),
|
||||
}
|
||||
|
||||
reply, err = ds.CreateCommentReply(reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新Post回复数
|
||||
post.CommentCount++
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
ds.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
PushPostToSearch(post)
|
||||
|
||||
// 创建用户消息提醒
|
||||
commentMaster, err := ds.GetUserByID(comment.UserID)
|
||||
if err == nil && commentMaster.ID != userID {
|
||||
go ds.CreateMessage(&core.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: commentMaster.ID,
|
||||
Type: core.MsgTypeReply,
|
||||
Brief: "在泡泡评论下回复了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
ReplyID: reply.ID,
|
||||
})
|
||||
}
|
||||
postMaster, err := ds.GetUserByID(post.UserID)
|
||||
if err == nil && postMaster.ID != userID && commentMaster.ID != postMaster.ID {
|
||||
go ds.CreateMessage(&core.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: postMaster.ID,
|
||||
Type: core.MsgTypeReply,
|
||||
Brief: "在泡泡评论下发布了新回复",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
ReplyID: reply.ID,
|
||||
})
|
||||
}
|
||||
if atUserID > 0 {
|
||||
user, err := ds.GetUserByID(atUserID)
|
||||
if err == nil && user.ID != userID && commentMaster.ID != user.ID && postMaster.ID != user.ID {
|
||||
// 创建消息提醒
|
||||
go ds.CreateMessage(&core.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: user.ID,
|
||||
Type: core.MsgTypeReply,
|
||||
Brief: "在泡泡评论的回复中@了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
ReplyID: reply.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func GetPostCommentReply(id int64) (*core.CommentReply, error) {
|
||||
return ds.GetCommentReplyByID(id)
|
||||
}
|
||||
|
||||
func DeletePostCommentReply(reply *core.CommentReply) error {
|
||||
err := ds.DeleteCommentReply(reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 加载Comment
|
||||
comment, err := ds.GetCommentByID(reply.CommentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 加载comment的post
|
||||
post, err := ds.GetPostByID(comment.PostID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新Post回复数
|
||||
post.CommentCount--
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
ds.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
PushPostToSearch(post)
|
||||
|
||||
return nil
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/rocboss/paopao-ce/internal/conf"
|
||||
"github.com/rocboss/paopao-ce/pkg/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetParamSign(param map[string]any, secretKey string) string {
|
||||
signRaw := ""
|
||||
|
||||
rawStrs := []string{}
|
||||
for k, v := range param {
|
||||
if k != "sign" {
|
||||
rawStrs = append(rawStrs, k+"="+fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(rawStrs)
|
||||
for _, v := range rawStrs {
|
||||
signRaw += v
|
||||
}
|
||||
|
||||
if conf.ServerSetting.RunMode == "debug" {
|
||||
logrus.Info(map[string]string{
|
||||
"signRaw": signRaw,
|
||||
"sysSign": util.EncodeMD5(signRaw + secretKey),
|
||||
})
|
||||
}
|
||||
|
||||
return util.EncodeMD5(signRaw + secretKey)
|
||||
}
|
@ -1,488 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rocboss/paopao-ce/internal/conf"
|
||||
"github.com/rocboss/paopao-ce/internal/core"
|
||||
"github.com/rocboss/paopao-ce/pkg/convert"
|
||||
"github.com/rocboss/paopao-ce/pkg/errcode"
|
||||
"github.com/rocboss/paopao-ce/pkg/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const _MaxCaptchaTimes = 2
|
||||
|
||||
type PhoneCaptchaReq struct {
|
||||
Phone string `json:"phone" form:"phone" binding:"required"`
|
||||
ImgCaptcha string `json:"img_captcha" form:"img_captcha" binding:"required"`
|
||||
ImgCaptchaID string `json:"img_captcha_id" form:"img_captcha_id" binding:"required"`
|
||||
}
|
||||
|
||||
type UserPhoneBindReq struct {
|
||||
Phone string `json:"phone" form:"phone" binding:"required"`
|
||||
Captcha string `json:"captcha" form:"captcha" binding:"required"`
|
||||
}
|
||||
|
||||
type AuthRequest struct {
|
||||
Username string `json:"username" form:"username" binding:"required"`
|
||||
Password string `json:"password" form:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username" form:"username" binding:"required"`
|
||||
Password string `json:"password" form:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type ChangePasswordReq struct {
|
||||
Password string `json:"password" form:"password" binding:"required"`
|
||||
OldPassword string `json:"old_password" form:"old_password" binding:"required"`
|
||||
}
|
||||
|
||||
type ChangeNicknameReq struct {
|
||||
Nickname string `json:"nickname" form:"nickname" binding:"required"`
|
||||
}
|
||||
|
||||
type ChangeAvatarReq struct {
|
||||
Avatar string `json:"avatar" form:"avatar" binding:"required"`
|
||||
}
|
||||
|
||||
type ChangeUserStatusReq struct {
|
||||
ID int64 `json:"id" form:"id" binding:"required"`
|
||||
Status int `json:"status" form:"status" binding:"required"`
|
||||
}
|
||||
|
||||
type RequestingFriendReq struct {
|
||||
UserId int64 `json:"user_id" binding:"required"`
|
||||
Greetings string `json:"greetings" binding:"required"`
|
||||
}
|
||||
|
||||
type AddFriendReq struct {
|
||||
UserId int64 `json:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
type RejectFriendReq struct {
|
||||
UserId int64 `json:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
type DeleteFriendReq struct {
|
||||
UserId int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
type UserProfileResp struct {
|
||||
ID int64 `json:"id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Username string `json:"username"`
|
||||
Status int `json:"status"`
|
||||
Avatar string `json:"avatar"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
IsFriend bool `json:"is_friend"`
|
||||
}
|
||||
|
||||
const (
|
||||
_LoginErrKey = "PaoPaoUserLoginErr"
|
||||
_MaxLoginErrTimes = 10
|
||||
)
|
||||
|
||||
// DoLogin 用户认证
|
||||
func DoLogin(ctx *gin.Context, param *AuthRequest) (*core.User, error) {
|
||||
user, err := ds.GetUserByUsername(param.Username)
|
||||
if err != nil {
|
||||
return nil, errcode.UnauthorizedAuthNotExist
|
||||
}
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
if errTimes, err := conf.Redis.Get(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID)).Result(); err == nil {
|
||||
if convert.StrTo(errTimes).MustInt() >= _MaxLoginErrTimes {
|
||||
return nil, errcode.TooManyLoginError
|
||||
}
|
||||
}
|
||||
|
||||
// 对比密码是否正确
|
||||
if ValidPassword(user.Password, param.Password, user.Salt) {
|
||||
|
||||
if user.Status == core.UserStatusClosed {
|
||||
return nil, errcode.UserHasBeenBanned
|
||||
}
|
||||
|
||||
// 清空登录计数
|
||||
conf.Redis.Del(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID))
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// 登录错误计数
|
||||
_, err = conf.Redis.Incr(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID)).Result()
|
||||
if err == nil {
|
||||
conf.Redis.Expire(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID), time.Hour).Result()
|
||||
}
|
||||
|
||||
return nil, errcode.UnauthorizedAuthFailed
|
||||
}
|
||||
|
||||
return nil, errcode.UnauthorizedAuthNotExist
|
||||
}
|
||||
|
||||
// ValidPassword 检查密码是否一致
|
||||
func ValidPassword(dbPassword, password, salt string) bool {
|
||||
return strings.Compare(dbPassword, util.EncodeMD5(util.EncodeMD5(password)+salt)) == 0
|
||||
}
|
||||
|
||||
// CheckStatus 检测用户权限
|
||||
func CheckStatus(user *core.User) bool {
|
||||
return user.Status == core.UserStatusNormal
|
||||
}
|
||||
|
||||
// ValidUsername 验证用户
|
||||
func ValidUsername(username string) error {
|
||||
// 检测用户是否合规
|
||||
if utf8.RuneCountInString(username) < 3 || utf8.RuneCountInString(username) > 12 {
|
||||
return errcode.UsernameLengthLimit
|
||||
}
|
||||
|
||||
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(username) {
|
||||
return errcode.UsernameCharLimit
|
||||
}
|
||||
|
||||
// 重复检查
|
||||
user, _ := ds.GetUserByUsername(username)
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
return errcode.UsernameHasExisted
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPassword 密码检查
|
||||
func CheckPassword(password string) error {
|
||||
// 检测用户是否合规
|
||||
if utf8.RuneCountInString(password) < 6 || utf8.RuneCountInString(password) > 16 {
|
||||
return errcode.PasswordLengthLimit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPhoneCaptcha 验证手机验证码
|
||||
func CheckPhoneCaptcha(phone, captcha string) *errcode.Error {
|
||||
// 如果禁止phone verify 则允许通过任意验证码
|
||||
if DisablePhoneVerify {
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := ds.GetLatestPhoneCaptcha(phone)
|
||||
if err != nil {
|
||||
return errcode.ErrorPhoneCaptcha
|
||||
}
|
||||
|
||||
if c.Captcha != captcha {
|
||||
return errcode.ErrorPhoneCaptcha
|
||||
}
|
||||
|
||||
if c.ExpiredOn < time.Now().Unix() {
|
||||
return errcode.ErrorPhoneCaptcha
|
||||
}
|
||||
|
||||
if c.UseTimes >= _MaxCaptchaTimes {
|
||||
return errcode.MaxPhoneCaptchaUseTimes
|
||||
}
|
||||
|
||||
// 更新检测次数
|
||||
ds.UsePhoneCaptcha(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPhoneExist 检测手机号是否存在
|
||||
func CheckPhoneExist(uid int64, phone string) bool {
|
||||
u, err := ds.GetUserByPhone(phone)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.Model == nil || u.ID == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.ID == uid {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// EncryptPasswordAndSalt 密码加密&生成salt
|
||||
func EncryptPasswordAndSalt(password string) (string, string) {
|
||||
salt := uuid.Must(uuid.NewV4()).String()[:8]
|
||||
password = util.EncodeMD5(util.EncodeMD5(password) + salt)
|
||||
|
||||
return password, salt
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func Register(username, password string) (*core.User, error) {
|
||||
password, salt := EncryptPasswordAndSalt(password)
|
||||
|
||||
user := &core.User{
|
||||
Nickname: username,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Avatar: GetRandomAvatar(),
|
||||
Salt: salt,
|
||||
Status: core.UserStatusNormal,
|
||||
}
|
||||
|
||||
user, err := ds.CreateUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func RequestingFriend(user *core.User, param *RequestingFriendReq) error {
|
||||
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
||||
return errcode.NotExistFriendId
|
||||
}
|
||||
return ds.RequestingFriend(user.ID, param.UserId, param.Greetings)
|
||||
}
|
||||
|
||||
func AddFriend(user *core.User, param *AddFriendReq) error {
|
||||
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
||||
return errcode.NotExistFriendId
|
||||
}
|
||||
return ds.AddFriend(user.ID, param.UserId)
|
||||
}
|
||||
|
||||
func RejectFriend(user *core.User, param *RejectFriendReq) error {
|
||||
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
||||
return errcode.NotExistFriendId
|
||||
}
|
||||
return ds.RejectFriend(user.ID, param.UserId)
|
||||
}
|
||||
|
||||
func DeleteFriend(user *core.User, param *DeleteFriendReq) error {
|
||||
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
||||
return errcode.NotExistFriendId
|
||||
}
|
||||
return ds.DeleteFriend(user.ID, param.UserId)
|
||||
}
|
||||
|
||||
func GetContacts(user *core.User, offset int, limit int) (*core.ContactList, error) {
|
||||
return ds.GetContacts(user.ID, offset, limit)
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func GetUserInfo(param *AuthRequest) (*core.User, error) {
|
||||
user, err := ds.GetUserByUsername(param.Username)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, errcode.UnauthorizedAuthNotExist
|
||||
}
|
||||
|
||||
func GetUserByID(id int64) (*core.User, error) {
|
||||
user, err := ds.GetUserByID(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, errcode.NoExistUsername
|
||||
}
|
||||
|
||||
func GetUserByUsername(user *core.User, username string) (*UserProfileResp, error) {
|
||||
other, err := ds.GetUserByUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp *UserProfileResp
|
||||
if other.Model != nil && other.ID > 0 {
|
||||
resp = &UserProfileResp{
|
||||
ID: other.ID,
|
||||
Nickname: other.Nickname,
|
||||
Username: other.Username,
|
||||
Status: other.Status,
|
||||
Avatar: other.Avatar,
|
||||
IsAdmin: other.IsAdmin,
|
||||
IsFriend: !(user == nil || user.ID == other.ID),
|
||||
}
|
||||
} else {
|
||||
return nil, errcode.NoExistUsername
|
||||
}
|
||||
|
||||
if user != nil && user.ID != other.ID {
|
||||
resp.IsFriend = ds.IsFriend(user.ID, other.ID)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdateUserInfo 更新用户信息
|
||||
func UpdateUserInfo(user *core.User) *errcode.Error {
|
||||
if err := ds.UpdateUser(user); err != nil {
|
||||
return errcode.ServerError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ChangeUserAvatar(user *core.User, avatar string) (err *errcode.Error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
deleteOssObjects([]string{avatar})
|
||||
}
|
||||
}()
|
||||
|
||||
if err := ds.CheckAttachment(avatar); err != nil {
|
||||
return errcode.InvalidParams
|
||||
}
|
||||
|
||||
if err := oss.PersistObject(oss.ObjectKey(avatar)); err != nil {
|
||||
logrus.Errorf("service.ChangeUserAvatar persist object failed: %s", err)
|
||||
return errcode.ServerError
|
||||
}
|
||||
|
||||
user.Avatar = avatar
|
||||
err = UpdateUserInfo(user)
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserCollections 获取用户收藏列表
|
||||
func GetUserCollections(userID int64, offset, limit int) ([]*core.PostFormated, int64, error) {
|
||||
collections, err := ds.GetUserPostCollections(userID, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalRows, err := ds.GetUserPostCollectionCount(userID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var posts []*core.Post
|
||||
for _, collection := range collections {
|
||||
posts = append(posts, collection.Post)
|
||||
}
|
||||
postsFormated, err := ds.MergePosts(posts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return postsFormated, totalRows, nil
|
||||
}
|
||||
|
||||
// GetUserStars 获取用户点赞列表
|
||||
func GetUserStars(userID int64, offset, limit int) ([]*core.PostFormated, int64, error) {
|
||||
stars, err := ds.GetUserPostStars(userID, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalRows, err := ds.GetUserPostStarCount(userID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var posts []*core.Post
|
||||
for _, star := range stars {
|
||||
posts = append(posts, star.Post)
|
||||
}
|
||||
postsFormated, err := ds.MergePosts(posts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return postsFormated, totalRows, nil
|
||||
}
|
||||
|
||||
// GetUserWalletBills 获取用户账单列表
|
||||
func GetUserWalletBills(userID int64, offset, limit int) ([]*core.WalletStatement, int64, error) {
|
||||
bills, err := ds.GetUserWalletBills(userID, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalRows, err := ds.GetUserWalletBillCount(userID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return bills, totalRows, nil
|
||||
}
|
||||
|
||||
// SendPhoneCaptcha 发送短信验证码
|
||||
func SendPhoneCaptcha(ctx *gin.Context, phone string) error {
|
||||
|
||||
err := ds.SendPhoneCaptcha(phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入计数缓存
|
||||
conf.Redis.Incr(ctx, "PaoPaoSmsCaptcha:"+phone).Result()
|
||||
|
||||
currentTime := time.Now()
|
||||
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
|
||||
|
||||
conf.Redis.Expire(ctx, "PaoPaoSmsCaptcha:"+phone, endTime.Sub(currentTime))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSuggestUsers 根据关键词获取用户推荐
|
||||
func GetSuggestUsers(keyword string) ([]string, error) {
|
||||
users, err := ds.GetUsersByKeyword(keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usernames := []string{}
|
||||
for _, user := range users {
|
||||
usernames = append(usernames, user.Username)
|
||||
}
|
||||
|
||||
return usernames, nil
|
||||
}
|
||||
|
||||
// GetSuggestTags 根据关键词获取标签推荐
|
||||
func GetSuggestTags(keyword string) ([]string, error) {
|
||||
tags, err := ds.GetTagsByKeyword(keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts := []string{}
|
||||
for _, t := range tags {
|
||||
ts = append(ts, t.Tag)
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
func IsFriend(userId, friendId int64) bool {
|
||||
return ds.IsFriend(userId, friendId)
|
||||
}
|
||||
|
||||
// checkPermision 检查是否拥有者或管理员
|
||||
func checkPermision(user *core.User, targetUserId int64) *errcode.Error {
|
||||
if user == nil || (user.ID != targetUserId && !user.IsAdmin) {
|
||||
return errcode.NoPermission
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2022 ROC. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-ce/internal/core"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-ce/internal/conf"
|
||||
"github.com/rocboss/paopao-ce/pkg/errcode"
|
||||
)
|
||||
|
||||
type RechargeReq struct {
|
||||
Amount int64 `json:"amount" form:"amount" binding:"required"`
|
||||
}
|
||||
|
||||
func GetRechargeByID(id int64) (*core.WalletRecharge, error) {
|
||||
return ds.GetRechargeByID(id)
|
||||
}
|
||||
|
||||
func CreateRecharge(userID, amount int64) (*core.WalletRecharge, error) {
|
||||
return ds.CreateRecharge(userID, amount)
|
||||
}
|
||||
|
||||
func FinishRecharge(ctx *gin.Context, id int64, tradeNo string) error {
|
||||
if ok, _ := conf.Redis.SetNX(ctx, "PaoPaoRecharge:"+tradeNo, 1, time.Second*5).Result(); ok {
|
||||
recharge, err := ds.GetRechargeByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recharge.TradeStatus != "TRADE_SUCCESS" {
|
||||
|
||||
// 标记为已付款
|
||||
err := ds.HandleRechargeSuccess(recharge, tradeNo)
|
||||
defer conf.Redis.Del(ctx, "PaoPaoRecharge:"+tradeNo)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuyPostAttachment(post *core.Post, user *core.User) error {
|
||||
if user.Balance < post.AttachmentPrice {
|
||||
return errcode.InsuffientDownloadMoney
|
||||
}
|
||||
|
||||
// 执行购买
|
||||
return ds.HandlePostAttachmentBought(post, user)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue