Merge branch 'x/gorm' into x/sqlx

r/paopao-ce-xtra
Michael Li 2 years ago
commit 4db28908cf
No known key found for this signature in database

@ -342,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代码|

@ -9,7 +9,7 @@
* [ ] add extend base ORM code for implement data logic base sqlx/sqlc * [ ] 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
* [ ] remove `Deprecated:OldWeb` feature * [x] remove `Deprecated:OldWeb` feature
#### v0.2.0 #### v0.2.0

@ -42,7 +42,7 @@
* `Frontend:EmbedWeb` 开启内嵌于后端Web API服务中的前端服务(目前状态: 内测) * `Frontend:EmbedWeb` 开启内嵌于后端Web API服务中的前端服务(目前状态: 内测)
* [ ] 提按文档 * [ ] 提按文档
* [x] 服务初始化逻辑 * [x] 服务初始化逻辑
* `Deprecated:OldWeb` 开启旧的Web服务(目前状态: 内测) * `Deprecated:OldWeb` 开启旧的Web服务(目前状态: 已弃,不可用)
* [ ] 提按文档 * [ ] 提按文档
* [x] 服务初始化逻辑 * [x] 服务初始化逻辑

@ -24,7 +24,6 @@ var (
MysqlSetting *MySQLSettingS MysqlSetting *MySQLSettingS
PostgresSetting *PostgresSettingS PostgresSetting *PostgresSettingS
Sqlite3Setting *Sqlite3SettingS Sqlite3Setting *Sqlite3SettingS
ServerSetting *HttpServerSettingS
WebServerSetting *HttpServerSettingS WebServerSetting *HttpServerSettingS
AdminServerSetting *HttpServerSettingS AdminServerSetting *HttpServerSettingS
SpaceXServerSetting *HttpServerSettingS SpaceXServerSetting *HttpServerSettingS
@ -68,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,
@ -155,8 +153,5 @@ func GetOssDomain() string {
} }
func RunMode() string { func RunMode() string {
if !cfg.If("Deprecated:OldWeb") {
return ServerSetting.RunMode
}
return AppSetting.RunMode return AppSetting.RunMode
} }

@ -5,12 +5,6 @@ App: # APP基础设置项
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服务
@ -19,31 +13,37 @@ WebServer: # Web服务
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

@ -20,31 +20,75 @@ import (
var ( var (
ts core.TweetSearchService ts core.TweetSearchService
ds core.DataService ds core.DataService
dsa core.DataServantA
oss core.ObjectStorageService oss core.ObjectStorageService
onceTs, onceDs, onceOss sync.Once _onceInitial sync.Once
) )
func DataService() core.DataService { func DataService() core.DataService {
onceDs.Do(func() { lazyInitial()
var v core.VersionInfo return ds
}
func DataServantA() core.DataServantA {
lazyInitial()
return dsa
}
func ObjectStorageService() core.ObjectStorageService {
lazyInitial()
return oss
}
func TweetSearchService() core.TweetSearchService {
lazyInitial()
return ts
}
func newAuthorizationManageService() (ams core.AuthorizationManageService) {
if cfg.If("Gorm") { if cfg.If("Gorm") {
ds, v = jinzhu.NewDataService() ams = jinzhu.NewAuthorizationManageService()
} else if cfg.If("Sqlx") { } else if cfg.If("Sqlx") {
ds, v = sakila.NewDataService() ams = sakila.NewAuthorizationManageService()
} else if cfg.If("Sqlc") && (cfg.If("Postgres") || cfg.If("PostgreSQL")) { } else if cfg.If("Sqlc") && (cfg.If("Postgres") || cfg.If("PostgreSQL")) {
ds, v = slonik.NewDataService() ams = slonik.NewAuthorizationManageService()
} else { } else {
// default use gorm as orm for sql database ams = jinzhu.NewAuthorizationManageService()
ds, v = jinzhu.NewDataService()
} }
logrus.Infof("use %s as data service with version %s", v.Name(), v.Version()) return
}
// lazyInitial do some package lazy initialize for performance
func lazyInitial() {
_onceInitial.Do(func() {
initDsX()
initOSS()
initTsX()
}) })
return ds
} }
func ObjectStorageService() core.ObjectStorageService { func initDsX() {
onceOss.Do(func() { var dsVer, dsaVer core.VersionInfo
if cfg.If("Gorm") {
ds, dsVer = jinzhu.NewDataService()
dsa, dsaVer = jinzhu.NewDataServantA()
} else if cfg.If("Sqlx") {
ds, dsVer = sakila.NewDataService()
dsa, dsaVer = sakila.NewDataServantA()
} else if cfg.If("Sqlc") && (cfg.If("Postgres") || cfg.If("PostgreSQL")) {
ds, dsVer = slonik.NewDataService()
dsa, dsaVer = slonik.NewDataServantA()
} else {
// default use gorm as orm for sql database
ds, dsVer = jinzhu.NewDataService()
dsa, dsaVer = jinzhu.NewDataServantA()
}
logrus.Infof("use %s as core.DataService with version %s", dsVer.Name(), dsVer.Version())
logrus.Infof("use %s as core.ServantA with version %s", dsaVer.Name(), dsaVer.Version())
}
func initOSS() {
var v core.VersionInfo var v core.VersionInfo
if cfg.If("AliOSS") { if cfg.If("AliOSS") {
oss, v = storage.MustAliossService() oss, v = storage.MustAliossService()
@ -67,38 +111,21 @@ func ObjectStorageService() core.ObjectStorageService {
return return
} }
logrus.Infof("use %s as object storage by version %s", v.Name(), v.Version()) logrus.Infof("use %s as object storage by version %s", v.Name(), v.Version())
})
return oss
} }
func TweetSearchService() core.TweetSearchService { func initTsX() {
onceTs.Do(func() {
var v core.VersionInfo var v core.VersionInfo
ams := newAuthorizationManageService() ams := newAuthorizationManageService()
if cfg.If("Zinc") { cfg.On(cfg.Actions{
"Zinc": func() {
ts, v = search.NewZincTweetSearchService(ams) ts, v = search.NewZincTweetSearchService(ams)
} else if cfg.If("Meili") { },
"Meili": func() {
ts, v = search.NewMeiliTweetSearchService(ams) ts, v = search.NewMeiliTweetSearchService(ams)
} else { },
// default use Zinc as tweet search service }, func() {
ts, v = search.NewZincTweetSearchService(ams) ts, v = search.NewZincTweetSearchService(ams)
} })
logrus.Infof("use %s as tweet search serice by version %s", v.Name(), v.Version()) logrus.Infof("use %s as tweet search serice by version %s", v.Name(), v.Version())
ts = search.NewBridgeTweetSearchService(ts) ts = search.NewBridgeTweetSearchService(ts)
})
return ts
}
func newAuthorizationManageService() (s core.AuthorizationManageService) {
if cfg.If("Gorm") {
s = jinzhu.NewAuthorizationManageService()
} else if cfg.If("Sqlx") {
s = sakila.NewAuthorizationManageService()
} else if cfg.If("Sqlc") && (cfg.If("Postgres") || cfg.If("PostgreSQL")) {
s = slonik.NewAuthorizationManageService()
} else {
s = jinzhu.NewAuthorizationManageService()
}
return
} }

@ -21,13 +21,16 @@ import (
) )
var ( var (
_ core.DataService = (*dataServant)(nil) _ core.DataService = (*dataSrv)(nil)
_ core.VersionInfo = (*dataServant)(nil) _ core.VersionInfo = (*dataSrv)(nil)
_ core.DataServantA = (*dataSrvA)(nil)
_ core.VersionInfo = (*dataSrvA)(nil)
_onceInitial sync.Once _onceInitial sync.Once
) )
type dataServant struct { type dataSrv struct {
core.IndexPostsService core.IndexPostsService
core.WalletService core.WalletService
core.MessageService core.MessageService
@ -43,6 +46,13 @@ type dataServant struct {
core.AttachmentCheckService core.AttachmentCheckService
} }
type dataSrvA struct {
core.TopicServantA
core.TweetServantA
core.TweetManageServantA
core.TweetHelpServantA
}
func NewDataService() (core.DataService, core.VersionInfo) { func NewDataService() (core.DataService, core.VersionInfo) {
lazyInitial() lazyInitial()
@ -81,7 +91,7 @@ func NewDataService() (core.DataService, core.VersionInfo) {
} }
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 := &dataSrv{
IndexPostsService: cis, IndexPostsService: cis,
WalletService: newWalletService(db), WalletService: newWalletService(db),
MessageService: newMessageService(db), MessageService: newMessageService(db),
@ -99,18 +109,40 @@ func NewDataService() (core.DataService, core.VersionInfo) {
return ds, ds return ds, ds
} }
func NewDataServantA() (core.DataServantA, core.VersionInfo) {
lazyInitial()
db := conf.MustGormDB()
ds := &dataSrvA{
TopicServantA: newTopicServantA(db),
TweetServantA: newTweetServantA(db),
TweetManageServantA: newTweetManageServantA(db),
TweetHelpServantA: newTweetHelpServantA(db),
}
return ds, ds
}
func NewAuthorizationManageService() core.AuthorizationManageService { func NewAuthorizationManageService() core.AuthorizationManageService {
return newAuthorizationManageService(conf.MustGormDB()) return newAuthorizationManageService(conf.MustGormDB())
} }
func (s *dataServant) Name() string { func (s *dataSrv) Name() string {
return "Gorm" return "Gorm"
} }
func (s *dataServant) Version() *semver.Version { func (s *dataSrv) Version() *semver.Version {
return semver.MustParse("v0.2.0") return semver.MustParse("v0.2.0")
} }
func (s *dataSrvA) Name() string {
return "Gorm"
}
func (s *dataSrvA) Version() *semver.Version {
return semver.MustParse("v0.1.0")
}
// lazyInitial do some package lazy initialize for performance // lazyInitial do some package lazy initialize for performance
func lazyInitial() { func lazyInitial() {
_onceInitial.Do(func() { _onceInitial.Do(func() {

@ -43,7 +43,6 @@ type tweetSrvA struct {
} }
type tweetManageSrvA struct { type tweetManageSrvA struct {
cacheIndex core.CacheIndexService
db *gorm.DB db *gorm.DB
} }
@ -70,6 +69,24 @@ func newTweetHelpService(db *gorm.DB) core.TweetHelpService {
} }
} }
func newTweetServantA(db *gorm.DB) core.TweetServantA {
return &tweetSrvA{
db: db,
}
}
func newTweetManageServantA(db *gorm.DB) core.TweetManageServantA {
return &tweetManageSrvA{
db: db,
}
}
func newTweetHelpServantA(db *gorm.DB) core.TweetHelpServantA {
return &tweetHelpSrvA{
db: db,
}
}
// MergePosts post数据整合 // MergePosts post数据整合
func (s *tweetHelpSrv) MergePosts(posts []*core.Post) ([]*core.PostFormated, error) { func (s *tweetHelpSrv) MergePosts(posts []*core.Post) ([]*core.PostFormated, error) {
postIds := make([]int64, 0, len(posts)) postIds := make([]int64, 0, len(posts))

@ -93,6 +93,11 @@ func NewDataService() (core.DataService, core.VersionInfo) {
return ds, ds return ds, ds
} }
func NewDataServantA() (core.DataServantA, core.VersionInfo) {
logrus.Fatal("not support now")
return nil, nil
}
func NewAuthorizationManageService() core.AuthorizationManageService { func NewAuthorizationManageService() core.AuthorizationManageService {
lazyInitial() lazyInitial()
return newAuthorizationManageService(_db) return newAuthorizationManageService(_db)

@ -17,6 +17,11 @@ func NewDataService() (core.DataService, core.VersionInfo) {
return nil, nil return nil, nil
} }
func NewDataServantA() (core.DataServantA, core.VersionInfo) {
logrus.Fatal("not support now")
return nil, nil
}
func NewAuthorizationManageService() core.AuthorizationManageService { func NewAuthorizationManageService() core.AuthorizationManageService {
logrus.Fatal("not support now") logrus.Fatal("not support now")
return nil return nil

@ -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 Initialize() {
// migrate database if needed // migrate database if needed
migration.Run() migration.Run()
// initialize service
broker.Initialize()
api.Initialize()
} }

@ -14,7 +14,9 @@ import (
"github.com/alimy/mir/v3" "github.com/alimy/mir/v3"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"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/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,6 +26,7 @@ type BaseServant types.Empty
type DaoServant struct { type DaoServant struct {
Redis *redis.Client Redis *redis.Client
Dsa core.DataServantA
Ds core.DataService Ds core.DataService
Ts core.TweetSearchService Ts core.TweetSearchService
} }
@ -113,6 +116,15 @@ func RenderAny(c *gin.Context, data any, err mir.Error) {
} }
} }
func NewDaoServant() *DaoServant {
return &DaoServant{
Redis: conf.MustRedis(),
Dsa: dao.DataServantA(),
Ds: dao.DataService(),
Ts: dao.TweetSearchService(),
}
}
func (s *DaoServant) GetTweetBy(id int64) (*core.PostFormated, error) { func (s *DaoServant) GetTweetBy(id int64) (*core.PostFormated, error) {
post, err := s.Ds.GetPostByID(id) post, err := s.Ds.GetPostByID(id)
if err != nil { if err != nil {

@ -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, "无管理权限")
)

@ -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/cs"
)
func CreateAttachment(obj *cs.Attachment) (int64, error) {
return ds.CreateAttachment(obj)
}

@ -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,52 +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/redis/go-redis/v9"
"github.com/rocboss/paopao-ce/internal/conf"
"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
redisClient *redis.Client
DisablePhoneVerify bool
)
func Initialize() {
ds = dao.DataService()
ts = dao.TweetSearchService()
oss = dao.ObjectStorageService()
redisClient = conf.MustRedis()
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,348 +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 (
"time"
"github.com/rocboss/paopao-ce/internal/core"
"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/utils"
)
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: utils.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: utils.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,123 +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"
"time"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/errcode"
)
// MAX_WHISPER_NUM_DAILY 当日单用户私信总数限制TODO 配置化、积分兑换等)
const MAX_WHISPER_NUM_DAILY = 20
type ReadMessageReq struct {
ID int64 `json:"id" binding:"required"`
}
type WhisperReq struct {
UserID int64 `json:"user_id" binding:"required"`
Content string `json:"content" binding:"required"`
}
// CreateWhisper 创建私信
func CreateWhisper(c *gin.Context, msg *core.Message) (*core.Message, error) {
whisperKey := fmt.Sprintf("WhisperTimes:%d", msg.SenderUserID)
// 今日频次限制
if res, _ := redisClient.Get(c, whisperKey).Result(); convert.StrTo(res).MustInt() >= MAX_WHISPER_NUM_DAILY {
return nil, errcode.TooManyWhisperNum
}
// 创建私信
msg, err := ds.CreateMessage(msg)
if err != nil {
return nil, err
}
// 写入当日(自然日)计数缓存
redisClient.Incr(c, whisperKey).Result()
currentTime := time.Now()
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
redisClient.Expire(c, whisperKey, endTime.Sub(currentTime))
return msg, err
}
func GetUnreadCount(userID int64) (int64, error) {
return ds.GetUnreadCount(userID)
}
func ReadMessage(id, userID int64) error {
// 获取message
message, err := ds.GetMessageByID(id)
if err != nil {
return err
}
if message.ReceiverUserID != userID {
return errcode.NoPermission
}
// 已读消息
return ds.ReadMessage(message)
}
func GetMessages(userID int64, offset, limit int) ([]*core.MessageFormated, int64, error) {
conditions := &core.ConditionsT{
"receiver_user_id": userID,
"ORDER": "id DESC",
}
messages, err := ds.GetMessages(conditions, offset, limit)
for _, mf := range messages {
if mf.SenderUserID > 0 {
user, err := ds.GetUserByID(mf.SenderUserID)
if err == nil {
mf.SenderUser = user.Format()
}
}
// 好友申请消息不需要获取其他信息
if mf.Type == core.MsgTypeRequestingFriend {
continue
}
if mf.PostID > 0 {
post, err := GetPost(mf.PostID)
if err == nil {
mf.Post = post
if mf.CommentID > 0 {
comment, err := GetPostComment(mf.CommentID)
if err == nil {
mf.Comment = comment
if mf.ReplyID > 0 {
reply, err := GetPostCommentReply(mf.ReplyID)
if err == nil {
mf.Reply = reply
}
}
}
}
}
}
}
if err != nil {
return nil, 0, err
}
// 获取总量
totalRows, _ := ds.GetMessageCount(conditions)
return messages, totalRows, nil
}

@ -1,549 +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 (
"errors"
"fmt"
"math"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/rocboss/paopao-ce/pkg/utils"
"github.com/sirupsen/logrus"
)
type TagType = cs.TagType
type PostListReq struct {
Conditions *core.ConditionsT
Offset int
Limit int
}
type PostTagsReq struct {
Type TagType `json:"type" form:"type" binding:"required"`
Num int `json:"num" form:"num" binding:"required"`
}
type PostCreationReq struct {
Contents []*PostContentItem `json:"contents" binding:"required"`
Tags []string `json:"tags" binding:"required"`
Users []string `json:"users" binding:"required"`
AttachmentPrice int64 `json:"attachment_price"`
Visibility core.PostVisibleT `json:"visibility"`
}
type PostDelReq struct {
ID int64 `json:"id" binding:"required"`
}
type PostLockReq struct {
ID int64 `json:"id" binding:"required"`
}
type PostStickReq struct {
ID int64 `json:"id" binding:"required"`
}
type PostVisibilityReq struct {
ID int64 `json:"id" binding:"required"`
Visibility core.PostVisibleT `json:"visibility"`
}
type PostStarReq struct {
ID int64 `json:"id" binding:"required"`
}
type PostCollectionReq struct {
ID int64 `json:"id" binding:"required"`
}
type PostContentItem struct {
Content string `json:"content" binding:"required"`
Type core.PostContentT `json:"type" binding:"required"`
Sort int64 `json:"sort" binding:"required"`
}
// Check 检查PostContentItem属性
func (p *PostContentItem) Check() error {
// 检查附件是否是本站资源
if p.Type == core.ContentTypeImage || p.Type == core.ContentTypeVideo || p.Type == core.ContentTypeAttachment {
if err := ds.CheckAttachment(p.Content); err != nil {
return err
}
}
// 检查链接是否合法
if p.Type == core.ContentTypeLink {
if strings.Index(p.Content, "http://") != 0 && strings.Index(p.Content, "https://") != 0 {
return fmt.Errorf("链接不合法")
}
}
return nil
}
func tagsFrom(originTags []string) []string {
tags := make([]string, 0, len(originTags))
for _, tag := range originTags {
// TODO: 优化tag有效性检测
if tag = strings.TrimSpace(tag); len(tag) > 0 {
tags = append(tags, tag)
}
}
return tags
}
// CreatePost 创建文章
// TODO: 推文+推文内容需要在一个事务中添加,后续优化
func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (_ *core.PostFormated, err error) {
var mediaContents []string
defer func() {
if err != nil {
deleteOssObjects(mediaContents)
}
}()
if mediaContents, err = persistMediaContents(param.Contents); err != nil {
return
}
ip := c.ClientIP()
tags := tagsFrom(param.Tags)
post := &core.Post{
UserID: userID,
Tags: strings.Join(tags, ","),
IP: ip,
IPLoc: utils.GetIPLoc(ip),
AttachmentPrice: param.AttachmentPrice,
Visibility: param.Visibility,
}
post, err = ds.CreatePost(post)
if err != nil {
return nil, err
}
for _, item := range param.Contents {
if err := item.Check(); err != nil {
// 属性非法
logrus.Infof("contents check err: %v", err)
continue
}
if item.Type == core.ContentTypeAttachment && param.AttachmentPrice > 0 {
item.Type = core.ContentTypeChargeAttachment
}
postContent := &core.PostContent{
PostID: post.ID,
UserID: userID,
Content: item.Content,
Type: item.Type,
Sort: item.Sort,
}
if _, err = ds.CreatePostContent(postContent); err != nil {
return nil, err
}
}
// 私密推文不创建标签与用户提醒
if post.Visibility != core.PostVisitPrivate {
// 创建标签
ds.UpsertTags(userID, tags)
// 创建用户消息提醒
for _, u := range param.Users {
user, err := ds.GetUserByUsername(u)
if err != nil || user.ID == userID {
continue
}
// 创建消息提醒
// TODO: 优化消息提醒处理机制
go ds.CreateMessage(&core.Message{
SenderUserID: userID,
ReceiverUserID: user.ID,
Type: core.MsgTypePost,
Brief: "在新发布的泡泡动态中@了你",
PostID: post.ID,
})
}
}
// 推送Search
PushPostToSearch(post)
formatedPosts, err := ds.RevampPosts([]*core.PostFormated{post.Format()})
if err != nil {
return nil, err
}
return formatedPosts[0], nil
}
func DeletePost(user *core.User, id int64) *errcode.Error {
if user == nil {
return errcode.NoPermission
}
post, err := ds.GetPostByID(id)
if err != nil {
return errcode.GetPostFailed
}
if post.UserID != user.ID && !user.IsAdmin {
return errcode.NoPermission
}
mediaContents, err := ds.DeletePost(post)
if err != nil {
logrus.Errorf("service.DeletePost delete post failed: %s", err)
return errcode.DeletePostFailed
}
// 删除推文的媒体内容
deleteOssObjects(mediaContents)
// 删除索引
DeleteSearchPost(post)
return nil
}
// deleteOssObjects 删除推文的媒体内容, 宽松处理错误(就是不处理), 后续完善
func deleteOssObjects(mediaContents []string) {
mediaContentsSize := len(mediaContents)
if mediaContentsSize > 1 {
objectKeys := make([]string, 0, mediaContentsSize)
for _, cUrl := range mediaContents {
objectKeys = append(objectKeys, oss.ObjectKey(cUrl))
}
// TODO: 优化处理尽量使用channel传递objectKeys使用可控数量的Goroutine集中处理object删除动作后续完善
go oss.DeleteObjects(objectKeys)
} else if mediaContentsSize == 1 {
oss.DeleteObject(oss.ObjectKey(mediaContents[0]))
}
}
func LockPost(id int64) error {
post, _ := ds.GetPostByID(id)
err := ds.LockPost(post)
if err != nil {
return err
}
return nil
}
func StickPost(id int64) error {
post, _ := ds.GetPostByID(id)
err := ds.StickPost(post)
if err != nil {
return err
}
return nil
}
func VisiblePost(user *core.User, postId int64, visibility core.PostVisibleT) *errcode.Error {
if visibility >= core.PostVisitInvalid {
return errcode.InvalidParams
}
post, err := ds.GetPostByID(postId)
if err != nil {
return errcode.GetPostFailed
}
if err := checkPermision(user, post.UserID); err != nil {
return err
}
if err = ds.VisiblePost(post, visibility); err != nil {
logrus.Warnf("update post failure: %v", err)
return errcode.VisblePostFailed
}
// 推送Search
post.Visibility = visibility
PushPostToSearch(post)
return nil
}
func GetPostStar(postID, userID int64) (*core.PostStar, error) {
return ds.GetUserPostStar(postID, userID)
}
func CreatePostStar(postID, userID int64) (*core.PostStar, error) {
// 加载Post
post, err := ds.GetPostByID(postID)
if err != nil {
return nil, err
}
// 私密post不可操作
if post.Visibility == core.PostVisitPrivate {
return nil, errors.New("no permision")
}
star, err := ds.CreatePostStar(postID, userID)
if err != nil {
return nil, err
}
// 更新Post点赞数
post.UpvoteCount++
ds.UpdatePost(post)
// 更新索引
PushPostToSearch(post)
return star, nil
}
func DeletePostStar(star *core.PostStar) error {
err := ds.DeletePostStar(star)
if err != nil {
return err
}
// 加载Post
post, err := ds.GetPostByID(star.PostID)
if err != nil {
return err
}
// 私密post不可操作
if post.Visibility == core.PostVisitPrivate {
return errors.New("no permision")
}
// 更新Post点赞数
post.UpvoteCount--
ds.UpdatePost(post)
// 更新索引
PushPostToSearch(post)
return nil
}
func GetPostCollection(postID, userID int64) (*core.PostCollection, error) {
return ds.GetUserPostCollection(postID, userID)
}
func CreatePostCollection(postID, userID int64) (*core.PostCollection, error) {
// 加载Post
post, err := ds.GetPostByID(postID)
if err != nil {
return nil, err
}
// 私密post不可操作
if post.Visibility == core.PostVisitPrivate {
return nil, errors.New("no permision")
}
collection, err := ds.CreatePostCollection(postID, userID)
if err != nil {
return nil, err
}
// 更新Post点赞数
post.CollectionCount++
ds.UpdatePost(post)
// 更新索引
PushPostToSearch(post)
return collection, nil
}
func DeletePostCollection(collection *core.PostCollection) error {
err := ds.DeletePostCollection(collection)
if err != nil {
return err
}
// 加载Post
post, err := ds.GetPostByID(collection.PostID)
if err != nil {
return err
}
// 私密post不可操作
if post.Visibility == core.PostVisitPrivate {
return errors.New("no permision")
}
// 更新Post点赞数
post.CollectionCount--
ds.UpdatePost(post)
// 更新索引
PushPostToSearch(post)
return nil
}
func GetPost(id int64) (*core.PostFormated, error) {
post, err := ds.GetPostByID(id)
if err != nil {
return nil, err
}
postContents, err := ds.GetPostContentsByIDs([]int64{post.ID})
if err != nil {
return nil, err
}
users, err := ds.GetUsersByIDs([]int64{post.UserID})
if err != nil {
return nil, err
}
// 数据整合
postFormated := post.Format()
for _, user := range users {
postFormated.User = user.Format()
}
for _, content := range postContents {
if content.PostID == post.ID {
postFormated.Contents = append(postFormated.Contents, content.Format())
}
}
return postFormated, nil
}
func GetPostContentByID(id int64) (*core.PostContent, error) {
return ds.GetPostContentByID(id)
}
func GetIndexPosts(user *core.User, offset int, limit int) (*core.IndexTweetList, error) {
return ds.IndexPosts(user, offset, limit)
}
func GetPostList(req *PostListReq) ([]*core.Post, []*core.PostFormated, error) {
posts, err := ds.GetPosts(req.Conditions, req.Offset, req.Limit)
if err != nil {
return nil, nil, err
}
postFormated, err := ds.MergePosts(posts)
return posts, postFormated, err
}
func GetPostCount(conditions *core.ConditionsT) (int64, error) {
return ds.GetPostCount(conditions)
}
func GetPostListFromSearch(user *core.User, q *core.QueryReq, offset, limit int) ([]*core.PostFormated, int64, error) {
resp, err := ts.Search(user, q, offset, limit)
if err != nil {
return nil, 0, err
}
posts, err := ds.RevampPosts(resp.Items)
if err != nil {
return nil, 0, err
}
return posts, resp.Total, nil
}
func GetPostListFromSearchByQuery(user *core.User, query string, offset, limit int) ([]*core.PostFormated, int64, error) {
q := &core.QueryReq{
Query: query,
Type: "search",
}
return GetPostListFromSearch(user, q, offset, limit)
}
func PushPostToSearch(post *core.Post) {
postFormated := post.Format()
postFormated.User = &core.UserFormated{
ID: post.UserID,
}
contents, _ := ds.GetPostContentsByIDs([]int64{post.ID})
for _, content := range contents {
postFormated.Contents = append(postFormated.Contents, content.Format())
}
contentFormated := ""
for _, content := range postFormated.Contents {
if content.Type == core.ContentTypeText || content.Type == core.ContentTypeTitle {
contentFormated = contentFormated + content.Content + "\n"
}
}
docs := []core.TsDocItem{{
Post: post,
Content: contentFormated,
}}
ts.AddDocuments(docs, fmt.Sprintf("%d", post.ID))
}
func DeleteSearchPost(post *core.Post) error {
return ts.DeleteDocuments([]string{fmt.Sprintf("%d", post.ID)})
}
func PushPostsToSearch(c *gin.Context) {
if ok, _ := redisClient.SetNX(c, "JOB_PUSH_TO_SEARCH", 1, time.Hour).Result(); ok {
defer redisClient.Del(c, "JOB_PUSH_TO_SEARCH")
splitNum := 1000
totalRows, _ := GetPostCount(&core.ConditionsT{
"visibility IN ?": []core.PostVisibleT{core.PostVisitPublic, core.PostVisitFriend},
})
pages := math.Ceil(float64(totalRows) / float64(splitNum))
nums := int(pages)
for i := 0; i < nums; i++ {
posts, postsFormated, err := GetPostList(&PostListReq{
Conditions: &core.ConditionsT{},
Offset: i * splitNum,
Limit: splitNum,
})
if err != nil || len(posts) != len(postsFormated) {
continue
}
for i, pf := range postsFormated {
contentFormated := ""
for _, content := range pf.Contents {
if content.Type == core.ContentTypeText || content.Type == core.ContentTypeTitle {
contentFormated = contentFormated + content.Content + "\n"
}
}
docs := []core.TsDocItem{{
Post: posts[i],
Content: contentFormated,
}}
ts.AddDocuments(docs, fmt.Sprintf("%d", posts[i].ID))
}
}
}
}
func GetPostTags(param *PostTagsReq) (cs.TagList, error) {
num := param.Num
if num > conf.AppSetting.MaxPageSize {
num = conf.AppSetting.MaxPageSize
}
tags, err := ds.ListTags(param.Type, num, 0)
if err != nil {
return nil, err
}
return tags, nil
}
func CheckPostAttachmentIsPaid(postID, userID int64) bool {
bill, err := ds.GetPostAttatchmentBill(postID, userID)
return err == nil && bill.Model != nil && bill.ID > 0
}

@ -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/utils"
"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": utils.EncodeMD5(signRaw + secretKey),
})
}
return utils.EncodeMD5(signRaw + secretKey)
}

@ -1,487 +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/core"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/rocboss/paopao-ce/pkg/utils"
"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 := redisClient.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
}
// 清空登录计数
redisClient.Del(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID))
return user, nil
}
// 登录错误计数
_, err = redisClient.Incr(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID)).Result()
if err == nil {
redisClient.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, utils.EncodeMD5(utils.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 = utils.EncodeMD5(utils.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
}
// 写入计数缓存
redisClient.Incr(ctx, "PaoPaoSmsCaptcha:"+phone).Result()
currentTime := time.Now()
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
redisClient.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.TagsByKeyword(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 (
"time"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/gin-gonic/gin"
"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, _ := redisClient.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 redisClient.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)
}

@ -1,52 +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 api
import (
"github.com/alimy/cfg"
"github.com/redis/go-redis/v9"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/dao"
"github.com/sirupsen/logrus"
"github.com/smartwalle/alipay/v3"
)
var (
redisClient *redis.Client
alipayClient *alipay.Client
objectStorage core.ObjectStorageService
)
func Initialize() {
redisClient = conf.MustRedis()
objectStorage = dao.ObjectStorageService()
if cfg.If("Alipay") {
alipayClient = mustAlipayClient()
}
}
func mustAlipayClient() *alipay.Client {
s := conf.AlipaySetting
// 将 key 的验证调整到初始化阶段
client, err := alipay.New(s.AppID, s.PrivateKey, s.InProduction)
if err != nil {
logrus.Fatalf("alipay.New err: %s", err)
}
// 加载应用公钥证书
if err = client.LoadAppPublicCertFromFile(s.AppPublicCertFile); err != nil {
logrus.Fatalf("client.LoadAppPublicCertFromFile err: %s\n", err)
}
// 加载支付宝根证书
if err = client.LoadAliPayRootCertFromFile(s.RootCertFile); err != nil {
logrus.Fatalf("client.LoadAliPayRootCertFromFile err: %s\n", err)
}
// 加载支付宝公钥证书
if err = client.LoadAliPayPublicCertFromFile(s.PublicCertFile); err != nil {
logrus.Fatalf("client.LoadAliPayPublicCertFromFile err: %s\n", err)
}
return client
}

@ -1,257 +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 api
import (
"image"
"github.com/disintegration/imaging"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/sirupsen/logrus"
)
var uploadAttachmentTypeMap = map[string]cs.AttachmentType{
"public/image": cs.AttachmentTypeImage,
"public/avatar": cs.AttachmentTypeImage,
"public/video": cs.AttachmentTypeVideo,
"attachment": cs.AttachmentTypeOther,
}
func GeneratePath(s string) string {
n := len(s)
if n <= 2 {
return s
}
return GeneratePath(s[:n-2]) + "/" + s[n-2:]
}
func GetFileExt(s string) (string, error) {
switch s {
case "image/png":
return ".png", nil
case "image/jpg":
return ".jpg", nil
case "image/jpeg":
return ".jpeg", nil
case "image/gif":
return ".gif", nil
case "video/mp4":
return ".mp4", nil
case "video/quicktime":
return ".mov", nil
case "application/zip":
return ".zip", nil
default:
return "", errcode.FileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型")
}
}
func GetImageSize(img image.Rectangle) (int, int) {
b := img.Bounds()
width := b.Max.X
height := b.Max.Y
return width, height
}
func fileCheck(uploadType string, size int64) error {
if uploadType != "public/video" &&
uploadType != "public/image" &&
uploadType != "public/avatar" &&
uploadType != "attachment" {
return errcode.InvalidParams
}
if size > 1024*1024*100 {
return errcode.FileInvalidSize.WithDetails("最大允许100MB")
}
return nil
}
func UploadAttachment(c *gin.Context) {
response := app.NewResponse(c)
uploadType := c.Request.FormValue("type")
file, fileHeader, err := c.Request.FormFile("file")
if err != nil {
logrus.Errorf("api.UploadAttachment err: %v", err)
response.ToErrorResponse(errcode.FileUploadFailed)
return
}
defer file.Close()
if err = fileCheck(uploadType, fileHeader.Size); err != nil {
cErr, _ := err.(*errcode.Error)
response.ToErrorResponse(cErr)
return
}
contentType := fileHeader.Header.Get("Content-Type")
fileExt, err := GetFileExt(fileHeader.Header.Get("Content-Type"))
if err != nil {
logrus.Errorf("GetFileExt err: %v", err)
response.ToErrorResponse(err.(*errcode.Error))
return
}
// 生成随机路径
randomPath := uuid.Must(uuid.NewV4()).String()
ossSavePath := uploadType + "/" + GeneratePath(randomPath[:8]) + "/" + randomPath[9:] + fileExt
objectUrl, err := objectStorage.PutObject(ossSavePath, file, fileHeader.Size, contentType, false)
if err != nil {
logrus.Errorf("putObject err: %v", err)
response.ToErrorResponse(errcode.FileUploadFailed)
return
}
// 构造附件Model
attachment := &cs.Attachment{
FileSize: fileHeader.Size,
Content: objectUrl,
}
if userID, exists := c.Get("UID"); exists {
attachment.UserID = userID.(int64)
}
attachment.Type = uploadAttachmentTypeMap[uploadType]
if attachment.Type == cs.AttachmentTypeImage {
var src image.Image
src, err = imaging.Decode(file)
if err == nil {
attachment.ImgWidth, attachment.ImgHeight = GetImageSize(src.Bounds())
}
}
attachment.ID, err = broker.CreateAttachment(attachment)
if err != nil {
logrus.Errorf("service.CreateAttachment err: %v", err)
response.ToErrorResponse(errcode.FileUploadFailed)
}
response.ToResponse(attachment)
}
func DownloadAttachmentPrecheck(c *gin.Context) {
response := app.NewResponse(c)
contentID := convert.StrTo(c.Query("id")).MustInt64()
// 加载content
content, err := broker.GetPostContentByID(contentID)
if err != nil {
logrus.Errorf("service.GetPostContentByID err: %v", err)
response.ToErrorResponse(errcode.InvalidDownloadReq)
}
user, _ := c.Get("USER")
if content.Type == core.ContentTypeChargeAttachment {
// 加载post
post, err := broker.GetPost(content.PostID)
if err != nil {
logrus.Errorf("service.GetPost err: %v", err)
response.ToResponse(gin.H{
"paid": false,
})
return
}
// 发布者或管理员免费下载
if post.UserID == user.(*core.User).ID || user.(*core.User).IsAdmin {
response.ToResponse(gin.H{
"paid": true,
})
return
}
// 检测是否有购买记录
response.ToResponse(gin.H{
"paid": broker.CheckPostAttachmentIsPaid(post.ID, user.(*core.User).ID),
})
return
}
response.ToResponse(gin.H{
"paid": false,
})
}
func DownloadAttachment(c *gin.Context) {
response := app.NewResponse(c)
contentID := convert.StrTo(c.Query("id")).MustInt64()
// 加载content
content, err := broker.GetPostContentByID(contentID)
if err != nil {
logrus.Errorf("service.GetPostContentByID err: %v", err)
response.ToErrorResponse(errcode.InvalidDownloadReq)
}
// 收费附件
if content.Type == core.ContentTypeChargeAttachment {
user, _ := c.Get("USER")
// 加载post
post, err := broker.GetPost(content.PostID)
if err != nil {
logrus.Errorf("service.GetPost err: %v", err)
response.ToResponse(gin.H{
"paid": false,
})
return
}
paidFlag := false
// 发布者或管理员免费下载
if post.UserID == user.(*core.User).ID || user.(*core.User).IsAdmin {
paidFlag = true
}
// 检测是否有购买记录
if broker.CheckPostAttachmentIsPaid(post.ID, user.(*core.User).ID) {
paidFlag = true
}
if !paidFlag {
// 未购买,则尝试购买
err := broker.BuyPostAttachment(&core.Post{
Model: &core.Model{
ID: post.ID,
},
UserID: post.UserID,
AttachmentPrice: post.AttachmentPrice,
}, user.(*core.User))
if err != nil {
logrus.Errorf("service.BuyPostAttachment err: %v", err)
if err == errcode.InsuffientDownloadMoney {
response.ToErrorResponse(errcode.InsuffientDownloadMoney)
} else {
response.ToErrorResponse(errcode.DownloadExecFail)
}
return
}
}
}
objectKey := objectStorage.ObjectKey(content.Content)
signedURL, err := objectStorage.SignURL(objectKey, 60)
if err != nil {
logrus.Errorf("client.SignURL err: %v", err)
response.ToErrorResponse(errcode.DownloadReqError)
return
}
response.ToResponse(gin.H{
"signed_url": signedURL,
})
}

@ -1,146 +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 api
import (
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/sirupsen/logrus"
)
func GetPostComments(c *gin.Context) {
postID := convert.StrTo(c.Query("id")).MustInt64()
response := app.NewResponse(c)
contents, totalRows, err := broker.GetPostComments(postID, "id ASC", 0, 0)
if err != nil {
logrus.Errorf("service.GetPostComments err: %v\n", err)
response.ToErrorResponse(errcode.GetCommentsFailed)
return
}
response.ToResponseList(contents, totalRows)
}
func CreatePostComment(c *gin.Context) {
param := broker.CommentCreationReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
userID, _ := c.Get("UID")
comment, err := broker.CreatePostComment(c, userID.(int64), param)
if err != nil {
if err == errcode.MaxCommentCount {
response.ToErrorResponse(errcode.MaxCommentCount)
} else {
logrus.Errorf("service.CreatePostComment err: %v\n", err)
response.ToErrorResponse(errcode.CreateCommentFailed)
}
return
}
response.ToResponse(comment)
}
func DeletePostComment(c *gin.Context) {
param := broker.CommentDelReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, _ := c.Get("USER")
comment, err := broker.GetPostComment(param.ID)
if err != nil {
logrus.Errorf("service.GetPostComment err: %v\n", err)
response.ToErrorResponse(errcode.GetCommentFailed)
return
}
if user.(*core.User).ID != comment.UserID && !user.(*core.User).IsAdmin {
response.ToErrorResponse(errcode.NoPermission)
return
}
// 执行删除
err = broker.DeletePostComment(comment)
if err != nil {
logrus.Errorf("service.DeletePostComment err: %v\n", err)
response.ToErrorResponse(errcode.DeleteCommentFailed)
return
}
response.ToResponse(nil)
}
func CreatePostCommentReply(c *gin.Context) {
param := broker.CommentReplyCreationReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, _ := c.Get("USER")
comment, err := broker.CreatePostCommentReply(c, param.CommentID, param.Content, user.(*core.User).ID, param.AtUserID)
if err != nil {
logrus.Errorf("service.CreatePostCommentReply err: %v\n", err)
response.ToErrorResponse(errcode.CreateReplyFailed)
return
}
response.ToResponse(comment)
}
func DeletePostCommentReply(c *gin.Context) {
param := broker.ReplyDelReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, _ := c.Get("USER")
reply, err := broker.GetPostCommentReply(param.ID)
if err != nil {
logrus.Errorf("service.GetPostCommentReply err: %v\n", err)
response.ToErrorResponse(errcode.GetReplyFailed)
return
}
if user.(*core.User).ID != reply.UserID && !user.(*core.User).IsAdmin {
response.ToErrorResponse(errcode.NoPermission)
return
}
// 执行删除
err = broker.DeletePostCommentReply(reply)
if err != nil {
logrus.Errorf("service.DeletePostCommentReply err: %v\n", err)
response.ToErrorResponse(errcode.DeleteCommentFailed)
return
}
response.ToResponse(nil)
}

@ -1,107 +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 api
import (
"bytes"
"encoding/base64"
"image/color"
"image/png"
"time"
"github.com/afocus/captcha"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/servants/web/assets"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/debug"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/rocboss/paopao-ce/pkg/utils"
"github.com/sirupsen/logrus"
)
const MAX_PHONE_CAPTCHA = 10
func Version(c *gin.Context) {
response := app.NewResponse(c)
response.ToResponse(gin.H{
"BuildInfo": debug.ReadBuildInfo(),
})
}
func SyncSearchIndex(c *gin.Context) {
response := app.NewResponse(c)
user, _ := c.Get("USER")
if user.(*core.User).IsAdmin {
go broker.PushPostsToSearch(c)
}
response.ToResponse(nil)
}
func GetCaptcha(c *gin.Context) {
cap := captcha.New()
if err := cap.AddFontFromBytes(assets.ComicBytes); err != nil {
panic(err.Error())
}
cap.SetSize(160, 64)
cap.SetDisturbance(captcha.MEDIUM)
cap.SetFrontColor(color.RGBA{0, 0, 0, 255})
cap.SetBkgColor(color.RGBA{218, 240, 228, 255})
img, password := cap.Create(6, captcha.NUM)
emptyBuff := bytes.NewBuffer(nil)
_ = png.Encode(emptyBuff, img)
key := utils.EncodeMD5(uuid.Must(uuid.NewV4()).String())
// 五分钟有效期
redisClient.SetEx(c, "PaoPaoCaptcha:"+key, password, time.Minute*5)
response := app.NewResponse(c)
response.ToResponse(gin.H{
"id": key,
"b64s": "data:image/png;base64," + base64.StdEncoding.EncodeToString(emptyBuff.Bytes()),
})
}
func PostCaptcha(c *gin.Context) {
param := broker.PhoneCaptchaReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
// 验证图片验证码
if res, err := redisClient.Get(c.Request.Context(), "PaoPaoCaptcha:"+param.ImgCaptchaID).Result(); err != nil || res != param.ImgCaptcha {
response.ToErrorResponse(errcode.ErrorCaptchaPassword)
return
}
redisClient.Del(c.Request.Context(), "PaoPaoCaptcha:"+param.ImgCaptchaID).Result()
// 今日频次限制
if res, _ := redisClient.Get(c.Request.Context(), "PaoPaoSmsCaptcha:"+param.Phone).Result(); convert.StrTo(res).MustInt() >= MAX_PHONE_CAPTCHA {
response.ToErrorResponse(errcode.TooManyPhoneCaptchaSend)
return
}
err := broker.SendPhoneCaptcha(c, param.Phone)
if err != nil {
logrus.Errorf("app.SendPhoneCaptcha errs: %v", errs)
response.ToErrorResponse(errcode.GetPhoneCaptchaError)
return
}
response.ToResponse(nil)
}

@ -1,105 +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 api
import (
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/sirupsen/logrus"
)
func GetUnreadMsgCount(c *gin.Context) {
response := app.NewResponse(c)
user := &core.User{}
if u, exists := c.Get("USER"); exists {
user = u.(*core.User)
}
count, _ := broker.GetUnreadCount(user.ID)
response.ToResponse(gin.H{
"count": count,
})
}
func GetMessages(c *gin.Context) {
response := app.NewResponse(c)
userID, _ := c.Get("UID")
messages, totalRows, err := broker.GetMessages(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
if err != nil {
logrus.Errorf("service.GetMessages err: %v\n", err)
response.ToErrorResponse(errcode.GetMessagesFailed)
return
}
response.ToResponseList(messages, totalRows)
}
func ReadMessage(c *gin.Context) {
param := broker.ReadMessageReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
userID, _ := c.Get("UID")
err := broker.ReadMessage(param.ID, userID.(int64))
if err != nil {
logrus.Errorf("service.ReadMessage err: %v\n", err)
response.ToErrorResponse(errcode.ReadMessageFailed)
return
}
response.ToResponse(nil)
}
func SendUserWhisper(c *gin.Context) {
param := broker.WhisperReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
userID, _ := c.Get("UID")
// 不允许发送私信给自己
if userID.(int64) == param.UserID {
response.ToErrorResponse(errcode.NoWhisperToSelf)
return
}
_, err := broker.CreateWhisper(c, &core.Message{
SenderUserID: userID.(int64),
ReceiverUserID: param.UserID,
Type: core.MsgTypeWhisper,
Brief: "给你发送新私信了",
Content: param.Content,
})
if err != nil {
logrus.Errorf("service.CreateWhisper err: %v\n", err)
if err == errcode.TooManyWhisperNum {
response.ToErrorResponse(errcode.TooManyWhisperNum)
} else {
response.ToErrorResponse(errcode.SendWhisperFailed)
}
return
}
response.ToResponse(nil)
}

@ -1,335 +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 api
import (
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/sirupsen/logrus"
)
func GetPostList(c *gin.Context) {
response := app.NewResponse(c)
q := &core.QueryReq{
Query: c.Query("query"),
Type: "search",
}
if c.Query("type") == "tag" {
q.Type = "tag"
}
user, _ := userFrom(c)
offset, limit := app.GetPageOffset(c)
if q.Query == "" && q.Type == "search" {
resp, err := broker.GetIndexPosts(user, offset, limit)
if err != nil {
logrus.Errorf("service.GetPostList err: %v\n", err)
response.ToErrorResponse(errcode.GetPostsFailed)
return
}
response.ToResponseList(resp.Tweets, resp.Total)
} else {
posts, totalRows, err := broker.GetPostListFromSearch(user, q, offset, limit)
if err != nil {
logrus.Errorf("service.GetPostListFromSearch err: %v\n", err)
response.ToErrorResponse(errcode.GetPostsFailed)
return
}
response.ToResponseList(posts, totalRows)
}
}
func GetPost(c *gin.Context) {
postID := convert.StrTo(c.Query("id")).MustInt64()
response := app.NewResponse(c)
postFormated, err := broker.GetPost(postID)
if err != nil {
logrus.Errorf("service.GetPost err: %v\n", err)
response.ToErrorResponse(errcode.GetPostFailed)
return
}
response.ToResponse(postFormated)
}
func CreatePost(c *gin.Context) {
param := broker.PostCreationReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
userID, _ := c.Get("UID")
post, err := broker.CreatePost(c, userID.(int64), param)
if err != nil {
logrus.Errorf("service.CreatePost err: %v\n", err)
response.ToErrorResponse(errcode.CreatePostFailed)
return
}
response.ToResponse(post)
}
func DeletePost(c *gin.Context) {
param := broker.PostDelReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, exist := userFrom(c)
if !exist {
response.ToErrorResponse(errcode.NoPermission)
return
}
err := broker.DeletePost(user, param.ID)
if err != nil {
logrus.Errorf("service.DeletePost err: %v\n", err)
response.ToErrorResponse(err)
return
}
response.ToResponse(nil)
}
func GetPostStar(c *gin.Context) {
postID := convert.StrTo(c.Query("id")).MustInt64()
response := app.NewResponse(c)
userID, _ := c.Get("UID")
_, err := broker.GetPostStar(postID, userID.(int64))
if err != nil {
response.ToResponse(gin.H{
"status": false,
})
return
}
response.ToResponse(gin.H{
"status": true,
})
}
func PostStar(c *gin.Context) {
param := broker.PostStarReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
userID, _ := c.Get("UID")
status := false
star, err := broker.GetPostStar(param.ID, userID.(int64))
if err != nil {
// 创建Star
_, err = broker.CreatePostStar(param.ID, userID.(int64))
status = true
} else {
// 取消Star
err = broker.DeletePostStar(star)
}
if err != nil {
response.ToErrorResponse(errcode.NoPermission)
return
}
response.ToResponse(gin.H{
"status": status,
})
}
func GetPostCollection(c *gin.Context) {
postID := convert.StrTo(c.Query("id")).MustInt64()
response := app.NewResponse(c)
userID, _ := c.Get("UID")
_, err := broker.GetPostCollection(postID, userID.(int64))
if err != nil {
response.ToResponse(gin.H{
"status": false,
})
return
}
response.ToResponse(gin.H{
"status": true,
})
}
func PostCollection(c *gin.Context) {
param := broker.PostCollectionReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
userID, _ := c.Get("UID")
status := false
collection, err := broker.GetPostCollection(param.ID, userID.(int64))
if err != nil {
// 创建collection
_, err = broker.CreatePostCollection(param.ID, userID.(int64))
status = true
} else {
// 取消Star
err = broker.DeletePostCollection(collection)
}
if err != nil {
response.ToErrorResponse(errcode.NoPermission)
return
}
response.ToResponse(gin.H{
"status": status,
})
}
func LockPost(c *gin.Context) {
param := broker.PostLockReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, _ := c.Get("USER")
// 获取Post
postFormated, err := broker.GetPost(param.ID)
if err != nil {
logrus.Errorf("service.GetPost err: %v\n", err)
response.ToErrorResponse(errcode.GetPostFailed)
return
}
if postFormated.UserID != user.(*core.User).ID && !user.(*core.User).IsAdmin {
response.ToErrorResponse(errcode.NoPermission)
return
}
err = broker.LockPost(param.ID)
if err != nil {
logrus.Errorf("service.LockPost err: %v\n", err)
response.ToErrorResponse(errcode.LockPostFailed)
return
}
response.ToResponse(gin.H{
"lock_status": 1 - postFormated.IsLock,
})
}
func StickPost(c *gin.Context) {
param := broker.PostStickReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, _ := c.Get("USER")
// 获取Post
postFormated, err := broker.GetPost(param.ID)
if err != nil {
logrus.Errorf("service.GetPost err: %v\n", err)
response.ToErrorResponse(errcode.GetPostFailed)
return
}
if !user.(*core.User).IsAdmin {
response.ToErrorResponse(errcode.NoPermission)
return
}
err = broker.StickPost(param.ID)
if err != nil {
logrus.Errorf("service.StickPost err: %v\n", err)
response.ToErrorResponse(errcode.LockPostFailed)
return
}
response.ToResponse(gin.H{
"top_status": 1 - postFormated.IsTop,
})
}
func VisiblePost(c *gin.Context) {
param := broker.PostVisibilityReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, _ := userFrom(c)
if err := broker.VisiblePost(user, param.ID, param.Visibility); err != nil {
logrus.Errorf("service.VisiblePost err: %v\n", err)
response.ToErrorResponse(err)
return
}
response.ToResponse(gin.H{
"visibility": param.Visibility,
})
}
func GetPostTags(c *gin.Context) {
param := broker.PostTagsReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
tags, err := broker.GetPostTags(&param)
if err != nil {
logrus.Errorf("service.GetPostTags err: %v\n", err)
response.ToErrorResponse(errcode.GetPostTagsFailed)
return
}
response.ToResponse(gin.H{
"topics": tags,
})
}

@ -1,650 +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 api
import (
"fmt"
"net/http"
"unicode/utf8"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/servants/web/broker"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/convert"
"github.com/rocboss/paopao-ce/pkg/errcode"
"github.com/sirupsen/logrus"
"github.com/smartwalle/alipay/v3"
)
// Login 用户登录
func Login(c *gin.Context) {
param := broker.AuthRequest{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, err := broker.DoLogin(c, &param)
if err != nil {
logrus.Errorf("service.DoLogin err: %v", err)
response.ToErrorResponse(err.(*errcode.Error))
return
}
token, err := app.GenerateToken(user)
if err != nil {
logrus.Errorf("app.GenerateToken err: %v", err)
response.ToErrorResponse(errcode.UnauthorizedTokenGenerate)
return
}
response.ToResponse(gin.H{
"token": token,
})
}
// Register 用户注册
func Register(c *gin.Context) {
param := broker.RegisterRequest{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
// 用户名检查
err := broker.ValidUsername(param.Username)
if err != nil {
logrus.Errorf("service.Register err: %v", err)
response.ToErrorResponse(err.(*errcode.Error))
return
}
// 密码检查
err = broker.CheckPassword(param.Password)
if err != nil {
logrus.Errorf("service.Register err: %v", err)
response.ToErrorResponse(err.(*errcode.Error))
return
}
user, err := broker.Register(
param.Username,
param.Password,
)
if err != nil {
logrus.Errorf("service.Register err: %v", err)
response.ToErrorResponse(errcode.UserRegisterFailed)
return
}
response.ToResponse(gin.H{
"id": user.ID,
"username": user.Username,
})
}
func RequestingFriend(c *gin.Context) {
param := broker.RequestingFriendReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, ok := userFrom(c)
if !ok {
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
}
if user.ID == param.UserId {
response.ToErrorResponse(errcode.NoRequestingFriendToSelf)
return
}
if err := broker.RequestingFriend(user, &param); err != nil {
logrus.Errorf("service.RequestingFriend err: %v", err)
response.ToErrorResponse(errcode.SendRequestingFriendFailed)
return
}
response.ToResponse(nil)
}
func AddFriend(c *gin.Context) {
param := broker.AddFriendReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, ok := userFrom(c)
if !ok {
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
}
if user.ID == param.UserId {
response.ToErrorResponse(errcode.NoActionToSelf)
return
}
if err := broker.AddFriend(user, &param); err != nil {
logrus.Errorf("service.AddFriend err: %v", err)
response.ToErrorResponse(errcode.AddFriendFailed)
return
}
response.ToResponse(nil)
}
func RejectFriend(c *gin.Context) {
param := broker.RejectFriendReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, ok := userFrom(c)
if !ok {
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
}
if user.ID == param.UserId {
response.ToErrorResponse(errcode.NoActionToSelf)
return
}
if err := broker.RejectFriend(user, &param); err != nil {
logrus.Errorf("service.RejectFriend err: %v", err)
response.ToErrorResponse(errcode.RejectFriendFailed)
return
}
response.ToResponse(nil)
}
func DeleteFriend(c *gin.Context) {
param := broker.DeleteFriendReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, ok := userFrom(c)
if !ok {
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
}
if user.ID == param.UserId {
response.ToErrorResponse(errcode.NoActionToSelf)
return
}
if err := broker.DeleteFriend(user, &param); err != nil {
logrus.Errorf("service.DeleteFriend err: %v", err)
response.ToErrorResponse(errcode.DeleteFriendFailed)
return
}
response.ToResponse(nil)
}
func GetContacts(c *gin.Context) {
response := app.NewResponse(c)
user, ok := userFrom(c)
if !ok {
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
}
offset, limit := app.GetPageOffset(c)
resp, err := broker.GetContacts(user, offset, limit)
if err != nil {
logrus.Errorf("service.DeleteFriend err: %v", err)
response.ToErrorResponse(errcode.GetContactsFailed)
return
}
response.ToResponseList(resp.Contacts, resp.Total)
}
// 获取用户基本信息
func GetUserInfo(c *gin.Context) {
param := broker.AuthRequest{}
response := app.NewResponse(c)
if username, exists := c.Get("USERNAME"); exists {
param.Username = username.(string)
}
user, err := broker.GetUserInfo(&param)
if err != nil {
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
return
}
phone := ""
if user.Phone != "" && len(user.Phone) == 11 {
phone = user.Phone[0:3] + "****" + user.Phone[7:]
}
response.ToResponse(gin.H{
"id": user.ID,
"nickname": user.Nickname,
"username": user.Username,
"status": user.Status,
"avatar": user.Avatar,
"balance": user.Balance,
"phone": phone,
"is_admin": user.IsAdmin,
})
}
// 修改密码
func ChangeUserPassword(c *gin.Context) {
param := broker.ChangePasswordReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user := &core.User{}
if u, exists := c.Get("USER"); exists {
user = u.(*core.User)
}
// 密码检查
err := broker.CheckPassword(param.Password)
if err != nil {
logrus.Errorf("service.Register err: %v", err)
response.ToErrorResponse(err.(*errcode.Error))
return
}
// 旧密码校验
if !broker.ValidPassword(user.Password, param.OldPassword, user.Salt) {
response.ToErrorResponse(errcode.ErrorOldPassword)
return
}
// 更新入库
user.Password, user.Salt = broker.EncryptPasswordAndSalt(param.Password)
broker.UpdateUserInfo(user)
response.ToResponse(nil)
}
// 修改昵称
func ChangeNickname(c *gin.Context) {
param := broker.ChangeNicknameReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user := &core.User{}
if u, exists := c.Get("USER"); exists {
user = u.(*core.User)
}
if utf8.RuneCountInString(param.Nickname) < 2 || utf8.RuneCountInString(param.Nickname) > 12 {
response.ToErrorResponse(errcode.NicknameLengthLimit)
return
}
// 执行绑定
user.Nickname = param.Nickname
broker.UpdateUserInfo(user)
response.ToResponse(nil)
}
// 修改头像
func ChangeAvatar(c *gin.Context) {
param := broker.ChangeAvatarReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user, exist := userFrom(c)
if !exist {
response.ToErrorResponse(errcode.UnauthorizedTokenError)
return
}
if err := broker.ChangeUserAvatar(user, param.Avatar); err != nil {
response.ToErrorResponse(err)
return
}
response.ToResponse(nil)
}
// 用户绑定手机号
func BindUserPhone(c *gin.Context) {
param := broker.UserPhoneBindReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
user := &core.User{}
if u, exists := c.Get("USER"); exists {
user = u.(*core.User)
}
// 手机重复性检查
if broker.CheckPhoneExist(user.ID, param.Phone) {
response.ToErrorResponse(errcode.ExistedUserPhone)
return
}
if err := broker.CheckPhoneCaptcha(param.Phone, param.Captcha); err != nil {
logrus.Errorf("service.CheckPhoneCaptcha err: %v\n", err)
response.ToErrorResponse(err)
return
}
// 执行绑定
user.Phone = param.Phone
if err := broker.UpdateUserInfo(user); err != nil {
response.ToErrorResponse(err)
return
}
response.ToResponse(nil)
}
// 修改用户状态
func ChangeUserStatus(c *gin.Context) {
param := broker.ChangeUserStatusReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
if param.Status != core.UserStatusNormal && param.Status != core.UserStatusClosed {
response.ToErrorResponse(errcode.InvalidParams)
return
}
user, err := broker.GetUserByID(param.ID)
if err != nil {
logrus.Errorf("service.GetUserByID err: %v\n", err)
response.ToErrorResponse(errcode.NoExistUsername)
return
}
// 执行更新
user.Status = param.Status
broker.UpdateUserInfo(user)
response.ToResponse(nil)
}
func GetUserProfile(c *gin.Context) {
response := app.NewResponse(c)
username := c.Query("username")
user, _ := userFrom(c)
resp, err := broker.GetUserByUsername(user, username)
if err != nil {
logrus.Errorf("service.GetUserByUsername err: %v\n", err)
response.ToErrorResponse(errcode.NoExistUsername)
return
}
response.ToResponse(resp)
}
func GetUserPosts(c *gin.Context) {
response := app.NewResponse(c)
username := c.Query("username")
user, exists := userFrom(c)
other, err := broker.GetUserByUsername(user, username)
if err != nil {
logrus.Errorf("service.GetUserByUsername err: %v\n", err)
response.ToErrorResponse(errcode.NoExistUsername)
return
}
visibilities := []core.PostVisibleT{core.PostVisitPublic}
if exists {
if user.ID == other.ID || user.IsAdmin {
visibilities = append(visibilities, core.PostVisitPrivate, core.PostVisitFriend)
} else if other.IsFriend {
visibilities = append(visibilities, core.PostVisitFriend)
}
}
conditions := core.ConditionsT{
"user_id": other.ID,
"visibility IN ?": visibilities,
"ORDER": "latest_replied_on DESC",
}
_, posts, err := broker.GetPostList(&broker.PostListReq{
Conditions: &conditions,
Offset: (app.GetPage(c) - 1) * app.GetPageSize(c),
Limit: app.GetPageSize(c),
})
if err != nil {
logrus.Errorf("service.GetPostList err: %v\n", err)
response.ToErrorResponse(errcode.GetPostsFailed)
return
}
totalRows, _ := broker.GetPostCount(&conditions)
response.ToResponseList(posts, totalRows)
}
func GetUserCollections(c *gin.Context) {
response := app.NewResponse(c)
userID, _ := c.Get("UID")
posts, totalRows, err := broker.GetUserCollections(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
if err != nil {
logrus.Errorf("service.GetUserCollections err: %v\n", err)
response.ToErrorResponse(errcode.GetCollectionsFailed)
return
}
response.ToResponseList(posts, totalRows)
}
func GetUserStars(c *gin.Context) {
response := app.NewResponse(c)
userID, _ := c.Get("UID")
posts, totalRows, err := broker.GetUserStars(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
if err != nil {
logrus.Errorf("service.GetUserStars err: %v\n", err)
response.ToErrorResponse(errcode.GetCollectionsFailed)
return
}
response.ToResponseList(posts, totalRows)
}
func GetSuggestUsers(c *gin.Context) {
keyword := c.Query("k")
response := app.NewResponse(c)
usernames, err := broker.GetSuggestUsers(keyword)
if err != nil {
logrus.Errorf("service.GetSuggestUsers err: %v\n", err)
response.ToErrorResponse(errcode.GetCollectionsFailed)
return
}
response.ToResponse(gin.H{
"suggest": usernames,
})
}
func GetSuggestTags(c *gin.Context) {
keyword := c.Query("k")
response := app.NewResponse(c)
tags, err := broker.GetSuggestTags(keyword)
if err != nil {
logrus.Errorf("service.GetSuggestTags err: %v\n", err)
response.ToErrorResponse(errcode.GetCollectionsFailed)
return
}
response.ToResponse(gin.H{
"suggest": tags,
})
}
func GetUserRechargeLink(c *gin.Context) {
param := broker.RechargeReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
// 下单
userID, _ := c.Get("UID")
recharge, err := broker.CreateRecharge(userID.(int64), param.Amount)
if err != nil {
logrus.Errorf("service.CreateRecharge err: %v\n", err)
response.ToErrorResponse(errcode.RechargeReqFail)
return
}
p := alipay.TradePreCreate{}
p.OutTradeNo = fmt.Sprintf("%d", recharge.ID)
p.Subject = "PaoPao用户钱包充值"
p.TotalAmount = fmt.Sprintf("%.2f", float64(recharge.Amount)/100.0)
p.NotifyURL = "https://" + c.Request.Host + "/v1/alipay/notify"
rsp, err := alipayClient.TradePreCreate(p)
if err != nil {
logrus.Errorf("client.TradePreCreate err: %v\n", err)
response.ToErrorResponse(errcode.RechargeReqFail)
return
}
if rsp.Content.Code != alipay.CodeSuccess {
response.ToErrorResponse(errcode.RechargeReqFail)
return
}
response.ToResponse(gin.H{
"id": recharge.ID,
"pay": rsp.Content.QRCode,
})
}
func GetUserRechargeResult(c *gin.Context) {
response := app.NewResponse(c)
id := c.Query("id")
userID, _ := c.Get("UID")
recharge, err := broker.GetRechargeByID(convert.StrTo(id).MustInt64())
if err != nil {
response.ToErrorResponse(errcode.GetRechargeFailed)
return
}
if recharge.UserID != userID.(int64) {
response.ToErrorResponse(errcode.GetRechargeFailed)
return
}
response.ToResponse(gin.H{
"id": recharge.ID,
"status": recharge.TradeStatus,
})
}
func AlipayNotify(c *gin.Context) {
response := app.NewResponse(c)
c.Request.ParseForm()
_, err := alipayClient.GetTradeNotification(c.Request)
if err != nil {
logrus.Errorf("alipayClient.GetTradeNotification err: %v\n", err)
logrus.Infoln(c.Request.Form)
response.ToErrorResponse(errcode.RechargeNotifyError)
return
}
id := c.Request.Form.Get("out_trade_no")
tradeNo := c.Request.Form.Get("trade_no")
tradeStatus := c.Request.Form.Get("trade_status")
if tradeStatus == "TRADE_SUCCESS" {
// 交易支付成功
err = broker.FinishRecharge(c, convert.StrTo(id).MustInt64(), tradeNo)
if err != nil {
logrus.Errorf("service.FinishRecharge err: %v\n", err)
response.ToErrorResponse(errcode.RechargeNotifyError)
return
}
}
response.Ctx.String(http.StatusOK, "success")
}
func GetUserWalletBills(c *gin.Context) {
response := app.NewResponse(c)
userID, _ := c.Get("UID")
bills, totalRows, err := broker.GetUserWalletBills(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
if err != nil {
logrus.Errorf("service.GetUserWalletBills err: %v\n", err)
response.ToErrorResponse(errcode.GetCollectionsFailed)
return
}
response.ToResponseList(bills, totalRows)
}
func userFrom(c *gin.Context) (*core.User, bool) {
if u, exists := c.Get("USER"); exists {
user, ok := u.(*core.User)
return user, ok
}
return nil, false
}
func userIdFrom(c *gin.Context) (int64, bool) {
if u, exists := c.Get("UID"); exists {
uid, ok := u.(int64)
return uid, ok
}
return -1, false
}

@ -1,17 +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.
//go:build !docs
// +build !docs
package routers
import (
"github.com/gin-gonic/gin"
)
// registerDocs stub function for register docs asset route
func registerDocs(e *gin.Engine) {
// empty
}

@ -1,21 +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.
//go:build docs
// +build docs
package routers
import (
"github.com/alimy/cfg"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/docs/openapi"
)
// registerDocs register docs asset route
func registerDocs(e *gin.Engine) {
cfg.Be("Docs:OpenAPI", func() {
e.StaticFS("/docs/openapi", openapi.NewFileSystem())
})
}

@ -1,351 +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 routers
import (
"net/http"
"path/filepath"
"github.com/alimy/cfg"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/servants/chain"
"github.com/rocboss/paopao-ce/internal/servants/web/routers/api"
"github.com/sirupsen/logrus"
)
func NewRouter() *gin.Engine {
e := gin.New()
e.HandleMethodNotAllowed = true
e.Use(gin.Logger())
e.Use(gin.Recovery())
// 跨域配置
corsConfig := cors.DefaultConfig()
corsConfig.AllowAllOrigins = true
corsConfig.AddAllowHeaders("Authorization")
e.Use(cors.New(corsConfig))
// 按需注册 docs、静态资源、LocalOSS 路由
{
registerDocs(e)
registerStatick(e)
cfg.Be("LocalOSS", func() {
routeLocalOSS(e)
})
}
// v1 group api
r := e.Group("/v1")
// 获取version
r.GET("/", api.Version)
// 用户登录
r.POST("/auth/login", api.Login)
// 用户注册
r.POST("/auth/register", api.Register)
// 获取验证码
r.GET("/captcha", api.GetCaptcha)
// 发送验证码
r.POST("/captcha", api.PostCaptcha)
// 无鉴权路由组
noAuthApi := r.Group("/")
{
// 获取动态详情
noAuthApi.GET("/post", api.GetPost)
// 获取动态评论
noAuthApi.GET("/post/comments", api.GetPostComments)
// 获取话题列表
noAuthApi.GET("/tags", api.GetPostTags)
}
// 宽松鉴权路由组
looseApi := r.Group("/").Use(chain.JwtLoose())
{
// 获取广场流
looseApi.GET("/posts", api.GetPostList)
// 获取用户动态列表
looseApi.GET("/user/posts", api.GetUserPosts)
// 获取用户基本信息
looseApi.GET("/user/profile", api.GetUserProfile)
}
// 鉴权路由组
authApi := r.Group("/").Use(chain.JWT())
privApi := r.Group("/").Use(chain.JWT()).Use(chain.Priv())
adminApi := r.Group("/").Use(chain.JWT()).Use(chain.Admin())
// 核心路由注册
routeCore(authApi, privApi, adminApi)
// 支付宝路由注册
cfg.Be("Alipay", func() {
routeAlipay(r, authApi)
})
// Relationship相关路由注册
routeRelationship(authApi)
// 默认404
e.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"msg": "Not Found",
})
})
// 默认405
e.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"code": 405,
"msg": "Method Not Allowed",
})
})
return e
}
func RegisterRoute(e *gin.Engine) {
// 按需注册 Web前端静态资源
cfg.Be("Frontend:EmbedWeb", func() {
registerStatick(e)
})
// 按需注册 LocalOSS 路由
cfg.Be("LocalOSS", func() {
routeLocalOSS(e)
})
// v1 group api
r := e.Group("/v1")
// 获取version
r.GET("/", api.Version)
// 用户登录
r.POST("/auth/login", api.Login)
// 用户注册
r.POST("/auth/register", api.Register)
// 获取验证码
r.GET("/captcha", api.GetCaptcha)
// 发送验证码
r.POST("/captcha", api.PostCaptcha)
// 无鉴权路由组
noAuthApi := r.Group("/")
{
// 获取动态详情
noAuthApi.GET("/post", api.GetPost)
// 获取动态评论
noAuthApi.GET("/post/comments", api.GetPostComments)
// 获取话题列表
noAuthApi.GET("/tags", api.GetPostTags)
}
// 宽松鉴权路由组
looseApi := r.Group("/").Use(chain.JwtLoose())
{
// 获取广场流
looseApi.GET("/posts", api.GetPostList)
// 获取用户动态列表
looseApi.GET("/user/posts", api.GetUserPosts)
// 获取用户基本信息
looseApi.GET("/user/profile", api.GetUserProfile)
}
// 鉴权路由组
authApi := r.Group("/").Use(chain.JWT())
privApi := r.Group("/").Use(chain.JWT()).Use(chain.Priv())
adminApi := r.Group("/").Use(chain.JWT()).Use(chain.Admin())
// 核心路由注册
routeCore(authApi, privApi, adminApi)
// 支付宝路由注册
cfg.Be("Alipay", func() {
routeAlipay(r, authApi)
})
// Relationship相关路由注册
routeRelationship(authApi)
}
func routeCore(authApi gin.IRoutes, privApi gin.IRoutes, adminApi gin.IRoutes) {
// 同步索引
authApi.GET("/sync/index", api.SyncSearchIndex)
// 获取当前用户信息
authApi.GET("/user/info", api.GetUserInfo)
// 获取当前用户未读消息数量
authApi.GET("/user/msgcount/unread", api.GetUnreadMsgCount)
// 获取消息列表
authApi.GET("/user/messages", api.GetMessages)
// 标记消息已读
authApi.POST("/user/message/read", api.ReadMessage)
// 发送用户私信
authApi.POST("/user/whisper", api.SendUserWhisper)
// 获取用户收藏列表
authApi.GET("/user/collections", api.GetUserCollections)
// 获取用户点赞列表
authApi.GET("/user/stars", api.GetUserStars)
// 绑定用户手机号
authApi.POST("/user/phone", api.BindUserPhone)
// 修改密码
authApi.POST("/user/password", api.ChangeUserPassword)
// 修改昵称
authApi.POST("/user/nickname", api.ChangeNickname)
// 修改头像
authApi.POST("/user/avatar", api.ChangeAvatar)
// 检索用户
authApi.GET("/suggest/users", api.GetSuggestUsers)
// 检索标签
authApi.GET("/suggest/tags", api.GetSuggestTags)
// 上传资源
privApi.POST("/attachment", api.UploadAttachment)
// 下载资源预检
privApi.GET("/attachment/precheck", api.DownloadAttachmentPrecheck)
// 下载资源
privApi.GET("/attachment", api.DownloadAttachment)
// 发布动态
privApi.POST("/post", api.CreatePost)
// 删除动态
privApi.DELETE("/post", api.DeletePost)
// 获取动态点赞状态
authApi.GET("/post/star", api.GetPostStar)
// 动态点赞操作
privApi.POST("/post/star", api.PostStar)
// 获取动态收藏状态
authApi.GET("/post/collection", api.GetPostCollection)
// 动态收藏操作
privApi.POST("/post/collection", api.PostCollection)
// 锁定动态
privApi.POST("/post/lock", api.LockPost)
// 置顶动态
privApi.POST("/post/stick", api.StickPost)
// 修改动态可见度
privApi.POST("/post/visibility", api.VisiblePost)
// 发布动态评论
privApi.POST("/post/comment", api.CreatePostComment)
// 删除动态评论
privApi.DELETE("/post/comment", api.DeletePostComment)
// 发布评论回复
privApi.POST("/post/comment/reply", api.CreatePostCommentReply)
// 删除评论回复
privApi.DELETE("/post/comment/reply", api.DeletePostCommentReply)
// 管理·禁言/解封用户
adminApi.POST("/admin/user/status", api.ChangeUserStatus)
}
// routeLocalOSS register LocalOSS route if needed
func routeLocalOSS(e *gin.Engine) {
savePath, err := filepath.Abs(conf.LocalOSSSetting.SavePath)
if err != nil {
logrus.Fatalf("get localOSS save path err: %v", err)
}
e.Static("/oss", savePath)
logrus.Infof("register LocalOSS route in /oss on save path: %s", savePath)
}
// routeAlipay register Alipay feature releated route if needed
func routeAlipay(public gin.IRoutes, authApi gin.IRoutes) {
// 支付宝回调
public.POST("/alipay/notify", api.AlipayNotify)
// 用户充值
authApi.POST("/user/recharge", api.GetUserRechargeLink)
// 获取钱包余额
authApi.GET("/user/recharge", api.GetUserRechargeResult)
// 获取用户账单
authApi.GET("/user/wallet/bills", api.GetUserWalletBills)
}
// routeRelationship register Relationship releated routes
func routeRelationship(authApi gin.IRoutes) {
cfg.In(cfg.Actions{
"Friendship": func() {
routeFriendship(authApi)
},
"Followship": func() {
routeFollowship(authApi)
},
}, func() {
// 暂时默认使用好友模式
// TODO: 后期提供一种无关系模式(既不是好友模式也不是关注者模式)作为默认的关系模式
routeFriendship(authApi)
})
}
// routeFriendship register Friendship feature releated routes
func routeFriendship(authApi gin.IRoutes) {
// 请求添加朋友
authApi.POST("/friend/requesting", api.RequestingFriend)
// 同意添加好友
authApi.POST("/friend/add", api.AddFriend)
// 拒绝添加好友
authApi.POST("/friend/reject", api.RejectFriend)
// 删除好友
authApi.POST("/friend/delete", api.DeleteFriend)
// 获取好友列表
authApi.GET("/user/contacts", api.GetContacts)
}
// routeFollowship register Followship feature releated routes
func routeFollowship(authApi gin.IRoutes) {
// TODO: register followship routes
}

@ -1,17 +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.
//go:build !embed
// +build !embed
package routers
import (
"github.com/gin-gonic/gin"
)
// registerStatick stub function for register static asset route
func registerStatick(e *gin.Engine) {
// empty
}

@ -1,31 +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.
//go:build embed
// +build embed
package routers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/web"
)
// registerStatick register static assets route
func registerStatick(e *gin.Engine) {
routeStatic(e, "/", "/index.html", "/favicon.ico", "/assets/*filepath")
}
func routeStatic(e *gin.Engine, paths ...string) {
staticHandler := http.FileServer(web.NewFileSystem())
handler := func(c *gin.Context) {
staticHandler.ServeHTTP(c.Writer, c.Request)
}
for _, path := range paths {
e.GET(path, handler)
e.HEAD(path, handler)
}
}

@ -26,11 +26,7 @@ var (
func RouteWeb(e *gin.Engine) { func RouteWeb(e *gin.Engine) {
lazyInitial() lazyInitial()
oss := dao.ObjectStorageService() oss := dao.ObjectStorageService()
ds := &base.DaoServant{ ds := base.NewDaoServant()
Redis: conf.MustRedis(),
Ds: dao.DataService(),
Ts: dao.TweetSearchService(),
}
// aways register servants // aways register servants
api.RegisterAdminServant(e, newAdminSrv(ds), newAdminBinding(), newAdminRender()) api.RegisterAdminServant(e, newAdminSrv(ds), newAdminBinding(), newAdminRender())
api.RegisterCoreServant(e, newCoreSrv(ds, oss), newCoreBinding(), newCoreRender()) api.RegisterCoreServant(e, newCoreSrv(ds, oss), newCoreBinding(), newCoreRender())

@ -72,9 +72,6 @@ func newService() (ss []Service) {
"Docs": func() { "Docs": func() {
ss = append(ss, newDocsService()) ss = append(ss, newDocsService())
}, },
"Deprecated:OldWeb": func() {
ss = append(ss, newOldWebService())
},
}) })
return return
} }

@ -1,63 +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 service
import (
"fmt"
"net/http"
"github.com/Masterminds/semver/v3"
"github.com/fatih/color"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/servants/web/routers"
)
var (
_ Service = (*oldWebService)(nil)
)
type oldWebService struct {
*baseHttpService
}
func (s *oldWebService) Name() string {
return "OldWebService"
}
func (s *oldWebService) Version() *semver.Version {
return semver.MustParse("v0.1.0")
}
func (s *oldWebService) OnInit() error {
s.registerRoute(s, routers.RegisterRoute)
return nil
}
func (s *oldWebService) String() string {
return fmt.Sprintf("listen on %s\n", color.GreenString("http://%s:%s", conf.ServerSetting.HttpIp, conf.ServerSetting.HttpPort))
}
func newOldWebService() Service {
addr := conf.ServerSetting.HttpIp + ":" + conf.ServerSetting.HttpPort
server := httpServers.from(addr, func() *httpServer {
engine := newWebEngine()
return &httpServer{
baseServer: newBaseServe(),
e: engine,
server: &http.Server{
Addr: addr,
Handler: engine,
ReadTimeout: conf.ServerSetting.GetReadTimeout(),
WriteTimeout: conf.ServerSetting.GetWriteTimeout(),
MaxHeaderBytes: 1 << 20,
},
}
})
return &oldWebService{
baseHttpService: &baseHttpService{
server: server,
},
}
}

@ -9,7 +9,7 @@ import (
"os" "os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/pkg/errcode" "github.com/rocboss/paopao-ce/pkg/xerror"
) )
type Response struct { type Response struct {
@ -56,12 +56,12 @@ func (r *Response) ToResponseList(list any, totalRows int64) {
}) })
} }
func (r *Response) ToErrorResponse(err *errcode.Error) { func (r *Response) ToErrorResponse(err *xerror.Error) {
response := gin.H{"code": err.Code(), "msg": err.Msg()} response := gin.H{"code": err.StatusCode(), "msg": err.Msg()}
details := err.Details() details := err.Details()
if len(details) > 0 { if len(details) > 0 {
response["details"] = details response["details"] = details
} }
r.Ctx.JSON(err.StatusCode(), response) r.Ctx.JSON(xerror.HttpStatusCode(err), response)
} }

@ -1,30 +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 errcode
var (
Success = NewError(0, "成功")
ServerError = NewError(10000, "服务内部错误")
InvalidParams = NewError(10001, "入参错误")
NotFound = NewError(10002, "找不到")
UnauthorizedAuthNotExist = NewError(10003, "账户不存在")
UnauthorizedAuthFailed = NewError(10004, "账户密码错误")
UnauthorizedTokenError = NewError(10005, "鉴权失败Token 错误或丢失")
UnauthorizedTokenTimeout = NewError(10006, "鉴权失败Token 超时")
UnauthorizedTokenGenerate = NewError(10007, "鉴权失败Token 生成失败")
TooManyRequests = NewError(10008, "请求过多")
GatewayMethodsLimit = NewError(10109, "网关仅接受GET或POST请求")
GatewayLostSign = NewError(10110, "网关请求缺少签名")
GatewayLostAppKey = NewError(10111, "网关请求缺少APP KEY")
GatewayAppKeyInvalid = NewError(10112, "网关请求无效APP KEY")
GatewayAppKeyClosed = NewError(10113, "网关请求APP KEY已停用")
GatewayParamSignError = NewError(10114, "网关请求参数签名错误")
GatewayTooManyRequests = NewError(10115, "网关请求频次超限")
FileUploadFailed = NewError(10200, "文件上传失败")
FileInvalidExt = NewError(10201, "文件类型不合法")
FileInvalidSize = NewError(10202, "文件大小超限")
)

@ -1,79 +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 errcode
import (
"fmt"
"net/http"
)
type Error struct {
code int
msg string
details []string
}
var codes = map[int]string{}
func NewError(code int, msg string) *Error {
if _, ok := codes[code]; ok {
panic(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code))
}
codes[code] = msg
return &Error{code: code, msg: msg}
}
func (e *Error) Error() string {
return fmt.Sprintf("错误码: %d, 错误信息: %s", e.Code(), e.Msg())
}
func (e *Error) Code() int {
return e.code
}
func (e *Error) Msg() string {
return e.msg
}
func (e *Error) Msgf(args []any) string {
return fmt.Sprintf(e.msg, args...)
}
func (e *Error) Details() []string {
return e.details
}
func (e *Error) WithDetails(details ...string) *Error {
newError := *e
newError.details = []string{}
newError.details = append(newError.details, details...)
return &newError
}
func (e *Error) StatusCode() int {
switch e.Code() {
case Success.Code():
return http.StatusOK
case ServerError.Code():
return http.StatusInternalServerError
case InvalidParams.Code():
return http.StatusBadRequest
case UnauthorizedAuthNotExist.Code():
fallthrough
case UnauthorizedAuthFailed.Code():
fallthrough
case UnauthorizedTokenError.Code():
fallthrough
case UnauthorizedTokenGenerate.Code():
fallthrough
case UnauthorizedTokenTimeout.Code():
return http.StatusUnauthorized
case TooManyRequests.Code():
return http.StatusTooManyRequests
}
return http.StatusInternalServerError
}

@ -1,73 +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 errcode
var (
UsernameHasExisted = NewError(20001, "用户名已存在")
UsernameLengthLimit = NewError(20002, "用户名长度3~12")
UsernameCharLimit = NewError(20003, "用户名只能包含字母、数字")
PasswordLengthLimit = NewError(20004, "密码长度6~16")
UserRegisterFailed = NewError(20005, "用户注册失败")
UserHasBeenBanned = NewError(20006, "该账户已被封停")
NoPermission = NewError(20007, "无权限执行该请求")
UserHasBindOTP = NewError(20008, "当前用户已绑定二次验证")
UserOTPInvalid = NewError(20009, "二次验证码验证失败")
UserNoBindOTP = NewError(20010, "当前用户未绑定二次验证")
ErrorOldPassword = NewError(20011, "当前用户密码验证失败")
ErrorCaptchaPassword = NewError(20012, "图形验证码验证失败")
AccountNoPhoneBind = NewError(20013, "拒绝操作: 账户未绑定手机号")
TooManyLoginError = NewError(20014, "登录失败次数过多,请稍后再试")
GetPhoneCaptchaError = NewError(20015, "短信验证码获取失败")
TooManyPhoneCaptchaSend = NewError(20016, "短信验证码获取次数已达今日上限")
ExistedUserPhone = NewError(20017, "该手机号已被绑定")
ErrorPhoneCaptcha = NewError(20018, "手机验证码不正确")
MaxPhoneCaptchaUseTimes = NewError(20019, "手机验证码已达最大使用次数")
NicknameLengthLimit = NewError(20020, "昵称长度2~12")
NoExistUsername = NewError(20021, "用户不存在")
NoAdminPermission = NewError(20022, "无管理权限")
GetPostsFailed = NewError(30001, "获取动态列表失败")
CreatePostFailed = NewError(30002, "动态发布失败")
GetPostFailed = NewError(30003, "获取动态详情失败")
DeletePostFailed = NewError(30004, "动态删除失败")
LockPostFailed = NewError(30005, "动态锁定失败")
GetPostTagsFailed = NewError(30006, "获取话题列表失败")
InvalidDownloadReq = NewError(30007, "附件下载请求不合法")
DownloadReqError = NewError(30008, "附件下载请求失败")
InsuffientDownloadMoney = NewError(30009, "附件下载失败:账户资金不足")
DownloadExecFail = NewError(30010, "附件下载失败:扣费失败")
StickPostFailed = NewError(30011, "动态置顶失败")
VisblePostFailed = NewError(30012, "更新可见性失败")
GetCommentsFailed = NewError(40001, "获取评论列表失败")
CreateCommentFailed = NewError(40002, "评论发布失败")
GetCommentFailed = NewError(40003, "获取评论详情失败")
DeleteCommentFailed = NewError(40004, "评论删除失败")
CreateReplyFailed = NewError(40005, "评论回复失败")
GetReplyFailed = NewError(40006, "获取评论详情失败")
MaxCommentCount = NewError(40007, "评论数已达最大限制")
GetMessagesFailed = NewError(50001, "获取消息列表失败")
ReadMessageFailed = NewError(50002, "标记消息已读失败")
SendWhisperFailed = NewError(50003, "私信发送失败")
NoWhisperToSelf = NewError(50004, "不允许给自己发送私信")
TooManyWhisperNum = NewError(50005, "今日私信次数已达上限")
GetCollectionsFailed = NewError(60001, "获取收藏列表失败")
GetStarsFailed = NewError(60002, "获取点赞列表失败")
RechargeReqFail = NewError(70001, "充值请求失败")
RechargeNotifyError = NewError(70002, "充值回调失败")
GetRechargeFailed = NewError(70003, "充值详情获取失败")
NoRequestingFriendToSelf = NewError(80001, "不允许添加自己为好友")
NotExistFriendId = NewError(80002, "好友id不存在")
SendRequestingFriendFailed = NewError(80003, "申请添加朋友请求发送失败")
AddFriendFailed = NewError(80004, "添加好友失败")
RejectFriendFailed = NewError(80005, "拒绝好友失败")
DeleteFriendFailed = NewError(80006, "删除好友失败")
GetContactsFailed = NewError(80007, "获取联系人列表失败")
NoActionToSelf = NewError(80008, "不允许对自己操作")
)

@ -15,7 +15,7 @@ import (
var ( var (
_ mir.Error = (*Error)(nil) _ mir.Error = (*Error)(nil)
codes = map[int]string{} // codes = map[int]string{}
) )
type Error struct { type Error struct {
@ -47,10 +47,10 @@ func (v ValidErrors) Errors() []string {
} }
func NewError(code int, msg string) *Error { func NewError(code int, msg string) *Error {
if _, ok := codes[code]; ok { // if _, ok := codes[code]; ok {
panic(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code)) // panic(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code))
} // }
codes[code] = msg // codes[code] = msg
return &Error{code: code, msg: msg} return &Error{code: code, msg: msg}
} }

Loading…
Cancel
Save