mirror of https://github.com/rocboss/paopao-ce
parent
71d3009cab
commit
8eafd27dfb
@ -0,0 +1,185 @@
|
|||||||
|
// Code generated by go-mir. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - mir v4.2.0
|
||||||
|
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/alimy/mir/v4"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/v2/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _binding_ interface {
|
||||||
|
Bind(*gin.Context) mir.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type _render_ interface {
|
||||||
|
Render(*gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type _default_ interface {
|
||||||
|
Bind(*gin.Context, any) mir.Error
|
||||||
|
BindQuery(*gin.Context, any) mir.Error
|
||||||
|
Render(*gin.Context, any, mir.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Loose interface {
|
||||||
|
_default_
|
||||||
|
|
||||||
|
// Chain provide handlers chain for gin
|
||||||
|
Chain() gin.HandlersChain
|
||||||
|
|
||||||
|
TweetDetail(*web.TweetDetailReq) (*web.TweetDetailResp, mir.Error)
|
||||||
|
TweetComments(*web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error)
|
||||||
|
TopicList(*web.TopicListReq) (*web.TopicListResp, mir.Error)
|
||||||
|
GetUserProfile(*web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error)
|
||||||
|
GetUserTweets(*web.GetUserTweetsReq) (*web.GetUserTweetsResp, mir.Error)
|
||||||
|
Timeline(*web.TimelineReq) (*web.TimelineResp, mir.Error)
|
||||||
|
|
||||||
|
mustEmbedUnimplementedLooseServant()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterLooseServant register Loose servant to gin
|
||||||
|
func RegisterLooseServant(e *gin.Engine, s Loose) {
|
||||||
|
router := e.Group("v2")
|
||||||
|
// use chain for router
|
||||||
|
middlewares := s.Chain()
|
||||||
|
router.Use(middlewares...)
|
||||||
|
|
||||||
|
// register routes info to router
|
||||||
|
router.Handle("GET", "post", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.TweetDetailReq)
|
||||||
|
if err := s.BindQuery(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.TweetDetail(req)
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
router.Handle("GET", "post/comments", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.TweetCommentsReq)
|
||||||
|
if err := s.BindQuery(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.TweetComments(req)
|
||||||
|
if err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rv _render_ = resp
|
||||||
|
rv.Render(c)
|
||||||
|
})
|
||||||
|
router.Handle("GET", "tags", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.TopicListReq)
|
||||||
|
if err := s.BindQuery(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.TopicList(req)
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
router.Handle("GET", "user/profile", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.GetUserProfileReq)
|
||||||
|
if err := s.BindQuery(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.GetUserProfile(req)
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
router.Handle("GET", "user/posts", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.GetUserTweetsReq)
|
||||||
|
if err := s.BindQuery(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.GetUserTweets(req)
|
||||||
|
if err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rv _render_ = resp
|
||||||
|
rv.Render(c)
|
||||||
|
})
|
||||||
|
router.Handle("GET", "posts", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.TimelineReq)
|
||||||
|
if err := s.BindQuery(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.Timeline(req)
|
||||||
|
if err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rv _render_ = resp
|
||||||
|
rv.Render(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedLooseServant can be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedLooseServant struct{}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) Chain() gin.HandlersChain {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) GetUserTweets(req *web.GetUserTweetsReq) (*web.GetUserTweetsResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) Timeline(req *web.TimelineReq) (*web.TimelineResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedLooseServant) mustEmbedUnimplementedLooseServant() {}
|
@ -0,0 +1,118 @@
|
|||||||
|
// Code generated by go-mir. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - mir v4.2.0
|
||||||
|
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/alimy/mir/v4"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pub interface {
|
||||||
|
_default_
|
||||||
|
|
||||||
|
SendCaptcha(*web.SendCaptchaReq) mir.Error
|
||||||
|
GetCaptcha() (*web.GetCaptchaResp, mir.Error)
|
||||||
|
Register(*web.RegisterReq) (*web.RegisterResp, mir.Error)
|
||||||
|
Login(*web.LoginReq) (*web.LoginResp, mir.Error)
|
||||||
|
Version() (*web.VersionResp, mir.Error)
|
||||||
|
|
||||||
|
mustEmbedUnimplementedPubServant()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterPubServant register Pub servant to gin
|
||||||
|
func RegisterPubServant(e *gin.Engine, s Pub) {
|
||||||
|
router := e.Group("v2")
|
||||||
|
|
||||||
|
// register routes info to router
|
||||||
|
router.Handle("POST", "captcha", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.SendCaptchaReq)
|
||||||
|
if err := s.Bind(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Render(c, nil, s.SendCaptcha(req))
|
||||||
|
})
|
||||||
|
router.Handle("GET", "captcha", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.GetCaptcha()
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
router.Handle("POST", "auth/register", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.RegisterReq)
|
||||||
|
if err := s.Bind(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.Register(req)
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
router.Handle("POST", "auth/login", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
req := new(web.LoginReq)
|
||||||
|
if err := s.Bind(c, req); err != nil {
|
||||||
|
s.Render(c, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.Login(req)
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
router.Handle("GET", "/", func(c *gin.Context) {
|
||||||
|
select {
|
||||||
|
case <-c.Request.Context().Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.Version()
|
||||||
|
s.Render(c, resp, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedPubServant can be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedPubServant struct{}
|
||||||
|
|
||||||
|
func (UnimplementedPubServant) SendCaptcha(req *web.SendCaptchaReq) mir.Error {
|
||||||
|
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedPubServant) GetCaptcha() (*web.GetCaptchaResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedPubServant) Register(req *web.RegisterReq) (*web.RegisterResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedPubServant) Login(req *web.LoginReq) (*web.LoginResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedPubServant) Version() (*web.VersionResp, mir.Error) {
|
||||||
|
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedPubServant) mustEmbedUnimplementedPubServant() {}
|
@ -0,0 +1 @@
|
|||||||
|
web v2 model
|
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2025 ROC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rocboss/paopao-ce/internal/conf"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core/cs"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core/ms"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/joint"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TagTypeHot = cs.TagTypeHot
|
||||||
|
TagTypeNew = cs.TagTypeNew
|
||||||
|
TagTypeFollow = cs.TagTypeFollow
|
||||||
|
TagTypePin = cs.TagTypePin
|
||||||
|
TagTypeHotExtral = cs.TagTypeHotExtral
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserPostsStylePost = "post"
|
||||||
|
UserPostsStyleComment = "comment"
|
||||||
|
UserPostsStyleHighlight = "highlight"
|
||||||
|
UserPostsStyleMedia = "media"
|
||||||
|
UserPostsStyleStar = "star"
|
||||||
|
|
||||||
|
StyleTweetsNewest = "newest"
|
||||||
|
StyleTweetsHots = "hots"
|
||||||
|
StyleTweetsFollowing = "following"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TagType = cs.TagType
|
||||||
|
|
||||||
|
type CommentStyleType string
|
||||||
|
|
||||||
|
type TweetCommentsReq struct {
|
||||||
|
SimpleInfo
|
||||||
|
joint.PageInfo
|
||||||
|
TweetId int64 `form:"id" query:"id" binding:"required"`
|
||||||
|
Style CommentStyleType `form:"style" query:"style"`
|
||||||
|
Page int `form:"page" query:"page" binding:"-"`
|
||||||
|
PageSize int `form:"page_size" query:"page_size" binding:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TweetCommentsResp struct {
|
||||||
|
joint.CachePageResp
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimelineReq struct {
|
||||||
|
BaseInfo
|
||||||
|
joint.PageInfo
|
||||||
|
Query string `form:"query" query:"query"`
|
||||||
|
Visibility []core.PostVisibleT `form:"visibility" query:"visibility"`
|
||||||
|
Type string `form:"type" query:"type"`
|
||||||
|
Style string `form:"style" query:"query"`
|
||||||
|
Page int `form:"page" query:"page"`
|
||||||
|
PageSize int `form:"page_size" query:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimelineResp struct {
|
||||||
|
joint.CachePageResp
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTweetsReq struct {
|
||||||
|
BaseInfo `form:"-" binding:"-"`
|
||||||
|
joint.PageInfo
|
||||||
|
Username string `form:"username" query:"username" binding:"required"`
|
||||||
|
Style string `form:"style" query:"style"`
|
||||||
|
Page int `form:"page" query:"page"`
|
||||||
|
PageSize int `form:"page_size" query:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTweetsResp struct {
|
||||||
|
joint.CachePageResp
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserProfileReq struct {
|
||||||
|
BaseInfo
|
||||||
|
Username string `form:"username" query:"username" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserProfileResp 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"`
|
||||||
|
IsFollowing bool `json:"is_following"`
|
||||||
|
CreatedOn int64 `json:"created_on"`
|
||||||
|
Follows int64 `json:"follows"`
|
||||||
|
Followings int64 `json:"followings"`
|
||||||
|
TweetsCount int `json:"tweets_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopicListReq struct {
|
||||||
|
SimpleInfo
|
||||||
|
Type TagType `json:"type" form:"type" query:"type" binding:"required"`
|
||||||
|
Num int `json:"num" form:"num" query:"num" binding:"required"`
|
||||||
|
ExtralNum int `json:"extral_num" form:"extral_num" query:"extral_num"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopicListResp 主题返回值
|
||||||
|
// TODO: 优化内容定义
|
||||||
|
type TopicListResp struct {
|
||||||
|
Topics cs.TagList `json:"topics"`
|
||||||
|
ExtralTopics cs.TagList `json:"extral_topics,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TweetDetailReq struct {
|
||||||
|
BaseInfo
|
||||||
|
TweetId int64 `form:"id" query:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TweetDetailResp ms.PostFormated
|
||||||
|
|
||||||
|
func (r *GetUserTweetsReq) Ajust(page int, pageSize int) {
|
||||||
|
r.BuildPageInfo(r.Page, r.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TweetCommentsReq) Ajust(page int, pageSize int) {
|
||||||
|
r.BuildPageInfo(r.Page, r.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TimelineReq) Ajust() {
|
||||||
|
r.BuildPageInfo(r.Page, r.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CommentStyleType) ToInnerValue() (res cs.StyleCommentType) {
|
||||||
|
switch s {
|
||||||
|
case "hots":
|
||||||
|
res = cs.StyleCommentHots
|
||||||
|
case "newest":
|
||||||
|
res = cs.StyleCommentNewest
|
||||||
|
case "default":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
res = cs.StyleCommentDefault
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CommentStyleType) String() (res string) {
|
||||||
|
switch s {
|
||||||
|
case "default":
|
||||||
|
res = conf.InfixCommentDefault
|
||||||
|
case "hots":
|
||||||
|
res = conf.InfixCommentHots
|
||||||
|
case "newest":
|
||||||
|
res = conf.InfixCommentNewest
|
||||||
|
default:
|
||||||
|
res = "_"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2025 ROC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alimy/mir/v4"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core/ms"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/servants/base"
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/app"
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/xerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bindAny = base.NewBindAnyFn()
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseInfo struct {
|
||||||
|
User *ms.User `json:"-" form:"-" query:"limit" binding:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleInfo struct {
|
||||||
|
Uid int64 `json:"-" form:"-" query:"limit" binding:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasePageReq struct {
|
||||||
|
UserId int64
|
||||||
|
Page int
|
||||||
|
PageSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseInfo) SetUser(user *ms.User) {
|
||||||
|
b.User = user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleInfo) SetUserId(id int64) {
|
||||||
|
s.Uid = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func BasePageReqFrom(c *gin.Context) (*BasePageReq, mir.Error) {
|
||||||
|
uid, ok := base.UserIdFrom(c)
|
||||||
|
if !ok {
|
||||||
|
return nil, xerror.UnauthorizedTokenError
|
||||||
|
}
|
||||||
|
page, pageSize := app.GetPageInfo(c)
|
||||||
|
return &BasePageReq{
|
||||||
|
UserId: uid,
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BasePageReq) Bind(c *gin.Context) mir.Error {
|
||||||
|
uid, ok := base.UserIdFrom(c)
|
||||||
|
if !ok {
|
||||||
|
return xerror.UnauthorizedTokenError
|
||||||
|
}
|
||||||
|
r.UserId = uid
|
||||||
|
r.Page, r.PageSize = app.GetPageInfo(c)
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2025 ROC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/xerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint
|
||||||
|
var (
|
||||||
|
ErrUsernameHasExisted = xerror.NewError(20001, "用户名已存在")
|
||||||
|
ErrUsernameLengthLimit = xerror.NewError(20002, "用户名长度3~12")
|
||||||
|
ErrUsernameCharLimit = xerror.NewError(20003, "用户名只能包含字母、数字")
|
||||||
|
ErrPasswordLengthLimit = xerror.NewError(20004, "密码长度6~16")
|
||||||
|
ErrUserRegisterFailed = xerror.NewError(20005, "用户注册失败")
|
||||||
|
ErrUserHasBeenBanned = xerror.NewError(20006, "该账户已被封停")
|
||||||
|
ErrNoPermission = xerror.NewError(20007, "无权限执行该请求")
|
||||||
|
ErrUserHasBindOTP = xerror.NewError(20008, "当前用户已绑定二次验证")
|
||||||
|
ErrUserOTPInvalid = xerror.NewError(20009, "二次验证码验证失败")
|
||||||
|
ErrUserNoBindOTP = xerror.NewError(20010, "当前用户未绑定二次验证")
|
||||||
|
ErrErrorOldPassword = xerror.NewError(20011, "当前用户密码验证失败")
|
||||||
|
ErrErrorCaptchaPassword = xerror.NewError(20012, "图形验证码验证失败")
|
||||||
|
ErrAccountNoPhoneBind = xerror.NewError(20013, "拒绝操作: 账户未绑定手机号")
|
||||||
|
ErrTooManyLoginError = xerror.NewError(20014, "登录失败次数过多,请稍后再试")
|
||||||
|
ErrGetPhoneCaptchaError = xerror.NewError(20015, "短信验证码获取失败")
|
||||||
|
ErrTooManyPhoneCaptchaSend = xerror.NewError(20016, "短信验证码获取次数已达今日上限")
|
||||||
|
ErrExistedUserPhone = xerror.NewError(20017, "该手机号已被绑定")
|
||||||
|
ErrErrorPhoneCaptcha = xerror.NewError(20018, "手机验证码不正确")
|
||||||
|
ErrMaxPhoneCaptchaUseTimes = xerror.NewError(20019, "手机验证码已达最大使用次数")
|
||||||
|
ErrNicknameLengthLimit = xerror.NewError(20020, "昵称长度2~12")
|
||||||
|
ErrNoExistUsername = xerror.NewError(20021, "用户不存在")
|
||||||
|
ErrNoAdminPermission = xerror.NewError(20022, "无管理权限")
|
||||||
|
ErrDisallowUserRegister = xerror.NewError(20023, "系统不允许注册用户")
|
||||||
|
|
||||||
|
ErrGetPostsFailed = xerror.NewError(30001, "获取动态列表失败")
|
||||||
|
ErrCreatePostFailed = xerror.NewError(30002, "动态发布失败")
|
||||||
|
ErrGetPostFailed = xerror.NewError(30003, "获取动态详情失败")
|
||||||
|
ErrDeletePostFailed = xerror.NewError(30004, "动态删除失败")
|
||||||
|
ErrLockPostFailed = xerror.NewError(30005, "动态锁定失败")
|
||||||
|
ErrGetPostTagsFailed = xerror.NewError(30006, "获取话题列表失败")
|
||||||
|
ErrInvalidDownloadReq = xerror.NewError(30007, "附件下载请求不合法")
|
||||||
|
ErrDownloadReqError = xerror.NewError(30008, "附件下载请求失败")
|
||||||
|
ErrInsuffientDownloadMoney = xerror.NewError(30009, "附件下载失败:账户资金不足")
|
||||||
|
ErrDownloadExecFail = xerror.NewError(30010, "附件下载失败:扣费失败")
|
||||||
|
ErrStickPostFailed = xerror.NewError(30011, "动态置顶失败")
|
||||||
|
ErrVisblePostFailed = xerror.NewError(30012, "更新可见性失败")
|
||||||
|
ErrHighlightPostFailed = xerror.NewError(30013, "动态设为亮点失败")
|
||||||
|
ErrGetPostsUnknowStyle = xerror.NewError(30014, "使用未知样式参数获取动态列表")
|
||||||
|
ErrGetPostsNilUser = xerror.NewError(30015, "使用游客账户获取动态详情失败")
|
||||||
|
|
||||||
|
ErrGetCommentsFailed = xerror.NewError(40001, "获取评论列表失败")
|
||||||
|
ErrCreateCommentFailed = xerror.NewError(40002, "评论发布失败")
|
||||||
|
ErrGetCommentFailed = xerror.NewError(40003, "获取评论详情失败")
|
||||||
|
ErrDeleteCommentFailed = xerror.NewError(40004, "评论删除失败")
|
||||||
|
ErrCreateReplyFailed = xerror.NewError(40005, "评论回复失败")
|
||||||
|
ErrGetReplyFailed = xerror.NewError(40006, "获取评论详情失败")
|
||||||
|
ErrMaxCommentCount = xerror.NewError(40007, "评论数已达最大限制")
|
||||||
|
ErrGetCommentThumbs = xerror.NewError(40008, "获取评论点赞信息失败")
|
||||||
|
ErrHighlightCommentFailed = xerror.NewError(40009, "设置精选评论失败")
|
||||||
|
|
||||||
|
ErrGetMessagesFailed = xerror.NewError(50001, "获取消息列表失败")
|
||||||
|
ErrReadMessageFailed = xerror.NewError(50002, "标记消息已读失败")
|
||||||
|
ErrSendWhisperFailed = xerror.NewError(50003, "私信发送失败")
|
||||||
|
ErrNoWhisperToSelf = xerror.NewError(50004, "不允许给自己发送私信")
|
||||||
|
ErrTooManyWhisperNum = xerror.NewError(50005, "今日私信次数已达上限")
|
||||||
|
|
||||||
|
ErrGetCollectionsFailed = xerror.NewError(60001, "获取收藏列表失败")
|
||||||
|
ErrGetStarsFailed = xerror.NewError(60002, "获取点赞列表失败")
|
||||||
|
|
||||||
|
ErrRechargeReqFail = xerror.NewError(70001, "充值请求失败")
|
||||||
|
ErrRechargeNotifyError = xerror.NewError(70002, "充值回调失败")
|
||||||
|
ErrGetRechargeFailed = xerror.NewError(70003, "充值详情获取失败")
|
||||||
|
ErrUserWalletBillsFailed = xerror.NewError(70004, "用户钱包账单获取失败")
|
||||||
|
|
||||||
|
ErrNoRequestingFriendToSelf = xerror.NewError(80001, "不允许添加自己为好友")
|
||||||
|
ErrNotExistFriendId = xerror.NewError(80002, "好友id不存在")
|
||||||
|
ErrSendRequestingFriendFailed = xerror.NewError(80003, "申请添加朋友请求发送失败")
|
||||||
|
ErrAddFriendFailed = xerror.NewError(80004, "添加好友失败")
|
||||||
|
ErrRejectFriendFailed = xerror.NewError(80005, "拒绝好友失败")
|
||||||
|
ErrDeleteFriendFailed = xerror.NewError(80006, "删除好友失败")
|
||||||
|
ErrGetContactsFailed = xerror.NewError(80007, "获取联系人列表失败")
|
||||||
|
ErrNoActionToSelf = xerror.NewError(80008, "不允许对自己操作")
|
||||||
|
ErrFolloUserFailed = xerror.NewError(80100, "关注失败")
|
||||||
|
ErrUnfollowUserFailed = xerror.NewError(80101, "取消关注失败")
|
||||||
|
ErrListFollowsFailed = xerror.NewError(80102, "获取关注列表失败")
|
||||||
|
ErrListFollowingsFailed = xerror.NewError(80103, "获取粉丝列表列表失败")
|
||||||
|
ErrGetFollowCountFailed = xerror.NewError(80104, "获取关注计数信息失败")
|
||||||
|
ErrNotAllowFollowSelf = xerror.NewError(80105, "不能关注自己")
|
||||||
|
ErrNotAllowUnfollowSelf = xerror.NewError(80106, "不能取消关注自己")
|
||||||
|
|
||||||
|
ErrGetIndexTrendsFailed = xerror.NewError(802001, "获取动态条栏信息失败")
|
||||||
|
|
||||||
|
ErrFollowTopicFailed = xerror.NewError(90001, "关注话题失败")
|
||||||
|
ErrUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败")
|
||||||
|
ErrStickTopicFailed = xerror.NewError(90003, "更行话题置顶状态失败")
|
||||||
|
ErrPinTopicFailed = xerror.NewError(90005, "更行话题钉住状态失败")
|
||||||
|
ErrThumbsUpTweetComment = xerror.NewError(90101, "评论点赞失败")
|
||||||
|
ErrThumbsDownTweetComment = xerror.NewError(90102, "评论点踩失败")
|
||||||
|
ErrThumbsUpTweetReply = xerror.NewError(90103, "评论回复点赞失败")
|
||||||
|
ErrThumbsDownTweetReply = xerror.NewError(90104, "评论回复点踩失败")
|
||||||
|
|
||||||
|
ErrFileUploadFailed = xerror.NewError(10200, "文件上传失败")
|
||||||
|
ErrFileInvalidExt = xerror.NewError(10201, "文件类型不合法")
|
||||||
|
ErrFileInvalidSize = xerror.NewError(10202, "文件大小超限")
|
||||||
|
|
||||||
|
ErrNotImplemented = xerror.NewError(10501, "功能未实现")
|
||||||
|
)
|
@ -0,0 +1,562 @@
|
|||||||
|
// Copyright 2025 ROC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/alimy/mir/v4"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
api "github.com/rocboss/paopao-ce/auto/api/v2"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/conf"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core/cs"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core/ms"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/joint"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/v2/web"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/servants/base"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/servants/chain"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ api.Loose = (*looseSrv)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type looseSrv struct {
|
||||||
|
api.UnimplementedLooseServant
|
||||||
|
*base.DaoServant
|
||||||
|
ac core.AppCache
|
||||||
|
userTweetsExpire int64
|
||||||
|
idxTweetsExpire int64
|
||||||
|
tweetCommentsExpire int64
|
||||||
|
prefixUserTweets string
|
||||||
|
prefixIdxTweetsNewest string
|
||||||
|
prefixIdxTweetsHots string
|
||||||
|
prefixIdxTweetsFollowing string
|
||||||
|
prefixTweetComment string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) Chain() gin.HandlersChain {
|
||||||
|
return gin.HandlersChain{chain.JwtLoose()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) Timeline(req *web.TimelineReq) (*web.TimelineResp, mir.Error) {
|
||||||
|
if req.Query == "" && req.Type == "search" {
|
||||||
|
return s.getIndexTweets(req, req.Limit, req.Offset)
|
||||||
|
}
|
||||||
|
q := &core.QueryReq{
|
||||||
|
Query: req.Query,
|
||||||
|
Type: core.SearchType(req.Type),
|
||||||
|
}
|
||||||
|
res, err := s.Ts.Search(req.User, q, req.Offset, req.Limit)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Ts.Search err: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
posts, err := s.Ds.RevampPosts(res.Items)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Ds.RevampPosts err: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
userId := int64(-1)
|
||||||
|
if req.User != nil {
|
||||||
|
userId = req.User.ID
|
||||||
|
}
|
||||||
|
if err := s.PrepareTweets(userId, posts); err != nil {
|
||||||
|
logrus.Errorf("timeline occurs error[2]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
resp := joint.PageRespFrom(posts, req.Page, req.PageSize, res.Total)
|
||||||
|
return &web.TimelineResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
Data: resp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) getIndexTweets(req *web.TimelineReq, limit int, offset int) (res *web.TimelineResp, err mir.Error) {
|
||||||
|
// 尝试直接从缓存中获取数据
|
||||||
|
key, ok := "", false
|
||||||
|
if res, key, ok = s.indexTweetsFromCache(req, limit, offset); ok {
|
||||||
|
// logrus.Debugf("getIndexTweets from cache key:%s", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
posts []*ms.Post
|
||||||
|
total int64
|
||||||
|
xerr error
|
||||||
|
)
|
||||||
|
switch req.Style {
|
||||||
|
case web.StyleTweetsFollowing:
|
||||||
|
if req.User != nil {
|
||||||
|
posts, total, xerr = s.Ds.ListFollowingTweets(req.User.ID, limit, offset)
|
||||||
|
} else {
|
||||||
|
// return nil, web.ErrGetPostsNilUser
|
||||||
|
// 宽松处理,前端退出登录后马上获取动态列表,可能错误走到这里
|
||||||
|
posts, total, xerr = s.Ds.ListIndexNewestTweets(limit, offset)
|
||||||
|
}
|
||||||
|
case web.StyleTweetsNewest:
|
||||||
|
posts, total, xerr = s.Ds.ListIndexNewestTweets(limit, offset)
|
||||||
|
case web.StyleTweetsHots:
|
||||||
|
posts, total, xerr = s.Ds.ListIndexHotsTweets(limit, offset)
|
||||||
|
default:
|
||||||
|
return nil, web.ErrGetPostsUnknowStyle
|
||||||
|
}
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("getIndexTweets occurs error[1]: %s", xerr)
|
||||||
|
return nil, web.ErrGetPostFailed
|
||||||
|
}
|
||||||
|
postsFormated, verr := s.Ds.MergePosts(posts)
|
||||||
|
if verr != nil {
|
||||||
|
logrus.Errorf("getIndexTweets in merge posts occurs error: %s", verr)
|
||||||
|
return nil, web.ErrGetPostFailed
|
||||||
|
}
|
||||||
|
userId := int64(-1)
|
||||||
|
if req.User != nil {
|
||||||
|
userId = req.User.ID
|
||||||
|
}
|
||||||
|
if err := s.PrepareTweets(userId, postsFormated); err != nil {
|
||||||
|
logrus.Errorf("getIndexTweets occurs error[2]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, total)
|
||||||
|
// 缓存处理
|
||||||
|
base.OnCacheRespEvent(s.ac, key, resp, s.idxTweetsExpire)
|
||||||
|
return &web.TimelineResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
Data: resp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) indexTweetsFromCache(req *web.TimelineReq, limit int, offset int) (res *web.TimelineResp, key string, ok bool) {
|
||||||
|
username := "_"
|
||||||
|
if req.User != nil {
|
||||||
|
username = req.User.Username
|
||||||
|
}
|
||||||
|
switch req.Style {
|
||||||
|
case web.StyleTweetsFollowing:
|
||||||
|
key = fmt.Sprintf("%s%s:%d:%d", s.prefixIdxTweetsFollowing, username, offset, limit)
|
||||||
|
case web.StyleTweetsNewest:
|
||||||
|
key = fmt.Sprintf("%s%s:%d:%d", s.prefixIdxTweetsNewest, username, offset, limit)
|
||||||
|
case web.StyleTweetsHots:
|
||||||
|
key = fmt.Sprintf("%s%s:%d:%d", s.prefixIdxTweetsHots, username, offset, limit)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if data, err := s.ac.Get(key); err == nil {
|
||||||
|
ok, res = true, &web.TimelineResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
JsonResp: data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) tweetCommentsFromCache(req *web.TweetCommentsReq, limit int, offset int) (res *web.TweetCommentsResp, key string, ok bool) {
|
||||||
|
key = fmt.Sprintf("%s%d:%s:%d:%d", s.prefixTweetComment, req.TweetId, req.Style, limit, offset)
|
||||||
|
if data, err := s.ac.Get(key); err == nil {
|
||||||
|
ok, res = true, &web.TweetCommentsResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
JsonResp: data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) GetUserTweets(req *web.GetUserTweetsReq) (res *web.GetUserTweetsResp, err mir.Error) {
|
||||||
|
user, xerr := s.RelationTypFrom(req.User, req.Username)
|
||||||
|
if xerr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 尝试直接从缓存中获取数据
|
||||||
|
key, ok := "", false
|
||||||
|
if res, key, ok = s.userTweetsFromCache(req, user); ok {
|
||||||
|
// logrus.Debugf("GetUserTweets from cache key:%s", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 缓存获取未成功,只能查库了
|
||||||
|
switch req.Style {
|
||||||
|
case web.UserPostsStyleComment, web.UserPostsStyleMedia:
|
||||||
|
res, err = s.listUserTweets(req, user)
|
||||||
|
case web.UserPostsStyleHighlight:
|
||||||
|
res, err = s.getUserPostTweets(req, user, true)
|
||||||
|
case web.UserPostsStyleStar:
|
||||||
|
res, err = s.getUserStarTweets(req, user)
|
||||||
|
case web.UserPostsStylePost:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
res, err = s.getUserPostTweets(req, user, false)
|
||||||
|
}
|
||||||
|
// 缓存处理
|
||||||
|
if err == nil {
|
||||||
|
base.OnCacheRespEvent(s.ac, key, res.Data, s.userTweetsExpire)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) userTweetsFromCache(req *web.GetUserTweetsReq, user *cs.VistUser) (res *web.GetUserTweetsResp, key string, ok bool) {
|
||||||
|
switch req.Style {
|
||||||
|
case web.UserPostsStylePost, web.UserPostsStyleHighlight, web.UserPostsStyleMedia:
|
||||||
|
key = fmt.Sprintf("%s%d:%s:%s:%d:%d", s.prefixUserTweets, user.UserId, req.Style, user.RelTyp, req.Page, req.PageSize)
|
||||||
|
default:
|
||||||
|
meName := "_"
|
||||||
|
if user.RelTyp != cs.RelationGuest {
|
||||||
|
meName = req.User.Username
|
||||||
|
}
|
||||||
|
key = fmt.Sprintf("%s%d:%s:%s:%d:%d", s.prefixUserTweets, user.UserId, req.Style, meName, req.Page, req.PageSize)
|
||||||
|
}
|
||||||
|
if data, err := s.ac.Get(key); err == nil {
|
||||||
|
ok, res = true, &web.GetUserTweetsResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
JsonResp: data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) getUserStarTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
|
||||||
|
stars, totalRows, err := s.Ds.ListUserStarTweets(user, req.Limit, req.Offset)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("getUserStarTweets err[1]: %s", err)
|
||||||
|
return nil, web.ErrGetStarsFailed
|
||||||
|
}
|
||||||
|
var posts []*ms.Post
|
||||||
|
for _, star := range stars {
|
||||||
|
if star.Post != nil {
|
||||||
|
posts = append(posts, star.Post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postsFormated, err := s.Ds.MergePosts(posts)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Ds.MergePosts err: %s", err)
|
||||||
|
return nil, web.ErrGetStarsFailed
|
||||||
|
}
|
||||||
|
userId := int64(-1)
|
||||||
|
if req.User != nil {
|
||||||
|
userId = req.User.ID
|
||||||
|
}
|
||||||
|
if err := s.PrepareTweets(userId, postsFormated); err != nil {
|
||||||
|
logrus.Errorf("getUserStarTweets err[2]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, totalRows)
|
||||||
|
return &web.GetUserTweetsResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
Data: resp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) listUserTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
|
||||||
|
var (
|
||||||
|
tweets []*ms.Post
|
||||||
|
total int64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if req.Style == web.UserPostsStyleComment {
|
||||||
|
tweets, total, err = s.Ds.ListUserCommentTweets(user, req.Limit, req.Offset)
|
||||||
|
} else if req.Style == web.UserPostsStyleMedia {
|
||||||
|
tweets, total, err = s.Ds.ListUserMediaTweets(user, req.Limit, req.Offset)
|
||||||
|
} else {
|
||||||
|
logrus.Errorf("s.listUserTweets unknow style[1]: %s", req.Style)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("s.listUserTweets err[2]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
postsFormated, err := s.Ds.MergePosts(tweets)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("s.listUserTweets err[3]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
userId := int64(-1)
|
||||||
|
if req.User != nil {
|
||||||
|
userId = req.User.ID
|
||||||
|
}
|
||||||
|
if err := s.PrepareTweets(userId, postsFormated); err != nil {
|
||||||
|
logrus.Errorf("s.listUserTweets err[4]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, total)
|
||||||
|
return &web.GetUserTweetsResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
Data: resp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) getUserPostTweets(req *web.GetUserTweetsReq, user *cs.VistUser, isHighlight bool) (*web.GetUserTweetsResp, mir.Error) {
|
||||||
|
style := cs.StyleUserTweetsGuest
|
||||||
|
switch user.RelTyp {
|
||||||
|
case cs.RelationAdmin:
|
||||||
|
style = cs.StyleUserTweetsAdmin
|
||||||
|
case cs.RelationSelf:
|
||||||
|
style = cs.StyleUserTweetsSelf
|
||||||
|
case cs.RelationFriend:
|
||||||
|
style = cs.StyleUserTweetsFriend
|
||||||
|
case cs.RelationFollowing:
|
||||||
|
style = cs.StyleUserTweetsFollowing
|
||||||
|
case cs.RelationGuest:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
posts, total, err := s.Ds.ListUserTweets(user.UserId, style, isHighlight, req.Limit, req.Offset)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("s.GetTweetList error[1]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
postsFormated, xerr := s.Ds.MergePosts(posts)
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("s.GetTweetList error[2]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
userId := int64(-1)
|
||||||
|
if req.User != nil {
|
||||||
|
userId = req.User.ID
|
||||||
|
}
|
||||||
|
if err := s.PrepareTweets(userId, postsFormated); err != nil {
|
||||||
|
logrus.Errorf("s.GetTweetList error[3]: %s", err)
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
resp := joint.PageRespFrom(postsFormated, req.Page, req.PageSize, total)
|
||||||
|
return &web.GetUserTweetsResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
Data: resp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) {
|
||||||
|
he, err := s.Ds.UserProfileByName(req.Username)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("looseSrv.GetUserProfile occurs error[1]: %s", err)
|
||||||
|
return nil, web.ErrNoExistUsername
|
||||||
|
}
|
||||||
|
// 设定自己不是自己的朋友
|
||||||
|
isFriend := !(req.User == nil || req.User.ID == he.ID)
|
||||||
|
if req.User != nil && req.User.ID != he.ID {
|
||||||
|
isFriend = s.Ds.IsFriend(req.User.ID, he.ID)
|
||||||
|
}
|
||||||
|
isFollowing := false
|
||||||
|
if req.User != nil {
|
||||||
|
isFollowing = s.Ds.IsFollow(req.User.ID, he.ID)
|
||||||
|
}
|
||||||
|
follows, followings, err := s.Ds.GetFollowCount(he.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, web.ErrGetPostsFailed
|
||||||
|
}
|
||||||
|
return &web.GetUserProfileResp{
|
||||||
|
ID: he.ID,
|
||||||
|
Nickname: he.Nickname,
|
||||||
|
Username: he.Username,
|
||||||
|
Status: he.Status,
|
||||||
|
Avatar: he.Avatar,
|
||||||
|
IsAdmin: he.IsAdmin,
|
||||||
|
IsFriend: isFriend,
|
||||||
|
IsFollowing: isFollowing,
|
||||||
|
CreatedOn: he.CreatedOn,
|
||||||
|
Follows: follows,
|
||||||
|
Followings: followings,
|
||||||
|
TweetsCount: he.TweetsCount,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
|
||||||
|
var (
|
||||||
|
tags, extralTags cs.TagList
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
num := req.Num
|
||||||
|
switch req.Type {
|
||||||
|
case web.TagTypeHot:
|
||||||
|
tags, err = s.Ds.GetHotTags(req.Uid, num, 0)
|
||||||
|
case web.TagTypeNew:
|
||||||
|
tags, err = s.Ds.GetNewestTags(req.Uid, num, 0)
|
||||||
|
case web.TagTypeFollow:
|
||||||
|
tags, err = s.Ds.GetFollowTags(req.Uid, false, num, 0)
|
||||||
|
case web.TagTypePin:
|
||||||
|
tags, err = s.Ds.GetFollowTags(req.Uid, true, num, 0)
|
||||||
|
case web.TagTypeHotExtral:
|
||||||
|
extralNum := req.ExtralNum
|
||||||
|
if extralNum <= 0 {
|
||||||
|
extralNum = num
|
||||||
|
}
|
||||||
|
tags, err = s.Ds.GetHotTags(req.Uid, num, 0)
|
||||||
|
if err == nil {
|
||||||
|
extralTags, err = s.Ds.GetFollowTags(req.Uid, false, extralNum, 0)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: return good error
|
||||||
|
err = web.ErrGetPostTagsFailed
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, web.ErrGetPostTagsFailed
|
||||||
|
}
|
||||||
|
return &web.TopicListResp{
|
||||||
|
Topics: tags,
|
||||||
|
ExtralTopics: extralTags,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) TweetComments(req *web.TweetCommentsReq) (res *web.TweetCommentsResp, err mir.Error) {
|
||||||
|
// 尝试直接从缓存中获取数据
|
||||||
|
key, ok := "", false
|
||||||
|
if res, key, ok = s.tweetCommentsFromCache(req, req.Limit, req.Offset); ok {
|
||||||
|
logrus.Debugf("looseSrv.TweetComments from cache key:%s", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comments, totalRows, xerr := s.Ds.GetComments(req.TweetId, req.Style.ToInnerValue(), req.Limit, req.Offset)
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("looseSrv.TweetComments occurs error[1]: %s", xerr)
|
||||||
|
return nil, web.ErrGetCommentsFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDs := []int64{}
|
||||||
|
commentIDs := []int64{}
|
||||||
|
for _, comment := range comments {
|
||||||
|
userIDs = append(userIDs, comment.UserID)
|
||||||
|
commentIDs = append(commentIDs, comment.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
users, xerr := s.Ds.GetUsersByIDs(userIDs)
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("looseSrv.TweetComments occurs error[2]: %s", xerr)
|
||||||
|
return nil, web.ErrGetCommentsFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, xerr := s.Ds.GetCommentContentsByIDs(commentIDs)
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("looseSrv.TweetComments occurs error[3]: %s", xerr)
|
||||||
|
return nil, web.ErrGetCommentsFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
replies, xerr := s.Ds.GetCommentRepliesByID(commentIDs)
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("looseSrv.TweetComments occurs error[4]: %s", xerr)
|
||||||
|
return nil, web.ErrGetCommentsFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
var commentThumbs, replyThumbs cs.CommentThumbsMap
|
||||||
|
if req.Uid > 0 {
|
||||||
|
commentThumbs, replyThumbs, xerr = s.Ds.GetCommentThumbsMap(req.Uid, req.TweetId)
|
||||||
|
if xerr != nil {
|
||||||
|
logrus.Errorf("looseSrv.TweetComments occurs error[5]: %s", xerr)
|
||||||
|
return nil, web.ErrGetCommentsFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replyMap := make(map[int64][]*dbr.CommentReplyFormated)
|
||||||
|
if len(replyThumbs) > 0 {
|
||||||
|
for _, reply := range replies {
|
||||||
|
if thumbs, exist := replyThumbs[reply.ID]; exist {
|
||||||
|
reply.IsThumbsUp, reply.IsThumbsDown = thumbs.IsThumbsUp, thumbs.IsThumbsDown
|
||||||
|
}
|
||||||
|
replyMap[reply.CommentID] = append(replyMap[reply.CommentID], reply)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, reply := range replies {
|
||||||
|
replyMap[reply.CommentID] = append(replyMap[reply.CommentID], reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsFormated := []*ms.CommentFormated{}
|
||||||
|
for _, comment := range comments {
|
||||||
|
commentFormated := comment.Format()
|
||||||
|
if thumbs, exist := commentThumbs[comment.ID]; exist {
|
||||||
|
commentFormated.IsThumbsUp, commentFormated.IsThumbsDown = thumbs.IsThumbsUp, thumbs.IsThumbsDown
|
||||||
|
}
|
||||||
|
for _, content := range contents {
|
||||||
|
if content.CommentID == comment.ID {
|
||||||
|
commentFormated.Contents = append(commentFormated.Contents, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if replySlice, exist := replyMap[commentFormated.ID]; exist {
|
||||||
|
commentFormated.Replies = replySlice
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
if user.ID == comment.UserID {
|
||||||
|
commentFormated.User = user.Format()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commentsFormated = append(commentsFormated, commentFormated)
|
||||||
|
}
|
||||||
|
resp := joint.PageRespFrom(commentsFormated, req.Page, req.PageSize, totalRows)
|
||||||
|
// 缓存处理
|
||||||
|
base.OnCacheRespEvent(s.ac, key, resp, s.tweetCommentsExpire)
|
||||||
|
return &web.TweetCommentsResp{
|
||||||
|
CachePageResp: joint.CachePageResp{
|
||||||
|
Data: resp,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *looseSrv) TweetDetail(req *web.TweetDetailReq) (*web.TweetDetailResp, mir.Error) {
|
||||||
|
post, err := s.Ds.GetPostByID(req.TweetId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, web.ErrGetPostFailed
|
||||||
|
}
|
||||||
|
postContents, err := s.Ds.GetPostContentsByIDs([]int64{post.ID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, web.ErrGetPostFailed
|
||||||
|
}
|
||||||
|
users, err := s.Ds.GetUsersByIDs([]int64{post.UserID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, web.ErrGetPostFailed
|
||||||
|
}
|
||||||
|
// 数据整合
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = s.PrepareTweet(req.User, postFormated); err != nil {
|
||||||
|
return nil, web.ErrGetPostFailed
|
||||||
|
}
|
||||||
|
// 检测访问权限
|
||||||
|
// TODO: 提到最前面去检测
|
||||||
|
switch {
|
||||||
|
case req.User != nil && (req.User.ID == postFormated.User.ID || req.User.IsAdmin):
|
||||||
|
// read by self of super admin
|
||||||
|
break
|
||||||
|
case post.Visibility == core.PostVisitPublic:
|
||||||
|
break
|
||||||
|
case post.Visibility == core.PostVisitFriend && postFormated.User.IsFriend:
|
||||||
|
break
|
||||||
|
case post.Visibility == core.PostVisitFollowing && postFormated.User.IsFollowing:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, web.ErrNoPermission
|
||||||
|
}
|
||||||
|
return (*web.TweetDetailResp)(postFormated), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLooseSrv(s *base.DaoServant, ac core.AppCache) api.Loose {
|
||||||
|
cs := conf.CacheSetting
|
||||||
|
return &looseSrv{
|
||||||
|
DaoServant: s,
|
||||||
|
ac: ac,
|
||||||
|
userTweetsExpire: cs.UserTweetsExpire,
|
||||||
|
idxTweetsExpire: cs.IndexTweetsExpire,
|
||||||
|
tweetCommentsExpire: cs.TweetCommentsExpire,
|
||||||
|
prefixUserTweets: conf.PrefixUserTweets,
|
||||||
|
prefixIdxTweetsNewest: conf.PrefixIdxTweetsNewest,
|
||||||
|
prefixIdxTweetsHots: conf.PrefixIdxTweetsHots,
|
||||||
|
prefixIdxTweetsFollowing: conf.PrefixIdxTweetsFollowing,
|
||||||
|
prefixTweetComment: conf.PrefixTweetComment,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
// Copyright 2025 ROC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"regexp"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/afocus/captcha"
|
||||||
|
"github.com/alimy/mir/v4"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
api "github.com/rocboss/paopao-ce/auto/api/v2"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core/ms"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/web"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/servants/base"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/servants/web/assets"
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/app"
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/utils"
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/version"
|
||||||
|
"github.com/rocboss/paopao-ce/pkg/xerror"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ api.Pub = (*pubSrv)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_MaxLoginErrTimes = 10
|
||||||
|
_MaxPhoneCaptcha = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type pubSrv struct {
|
||||||
|
api.UnimplementedPubServant
|
||||||
|
*base.DaoServant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *pubSrv) SendCaptcha(req *web.SendCaptchaReq) mir.Error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 验证图片验证码
|
||||||
|
if captcha, err := s.Redis.GetImgCaptcha(ctx, req.ImgCaptchaID); err != nil || string(captcha) != req.ImgCaptcha {
|
||||||
|
logrus.Debugf("get captcha err:%s expect:%s got:%s", err, captcha, req.ImgCaptcha)
|
||||||
|
return web.ErrErrorCaptchaPassword
|
||||||
|
}
|
||||||
|
s.Redis.DelImgCaptcha(ctx, req.ImgCaptchaID)
|
||||||
|
|
||||||
|
// 今日频次限制
|
||||||
|
if count, _ := s.Redis.GetCountSmsCaptcha(ctx, req.Phone); count >= _MaxPhoneCaptcha {
|
||||||
|
return web.ErrTooManyPhoneCaptchaSend
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Ds.SendPhoneCaptcha(req.Phone); err != nil {
|
||||||
|
return xerror.ServerError
|
||||||
|
}
|
||||||
|
// 写入计数缓存
|
||||||
|
s.Redis.IncrCountSmsCaptcha(ctx, req.Phone)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *pubSrv) GetCaptcha() (*web.GetCaptchaResp, mir.Error) {
|
||||||
|
cap := captcha.New()
|
||||||
|
if err := cap.AddFontFromBytes(assets.ComicBytes); err != nil {
|
||||||
|
logrus.Errorf("cap.AddFontFromBytes err:%s", err)
|
||||||
|
return nil, xerror.ServerError
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err := png.Encode(emptyBuff, img); err != nil {
|
||||||
|
logrus.Errorf("png.Encode err:%s", err)
|
||||||
|
return nil, xerror.ServerError
|
||||||
|
}
|
||||||
|
key := utils.EncodeMD5(uuid.Must(uuid.NewV4()).String())
|
||||||
|
// 五分钟有效期
|
||||||
|
s.Redis.SetImgCaptcha(context.Background(), key, password)
|
||||||
|
return &web.GetCaptchaResp{
|
||||||
|
Id: key,
|
||||||
|
Content: "data:image/png;base64," + base64.StdEncoding.EncodeToString(emptyBuff.Bytes()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *pubSrv) Register(req *web.RegisterReq) (*web.RegisterResp, mir.Error) {
|
||||||
|
if _disallowUserRegister {
|
||||||
|
return nil, web.ErrDisallowUserRegister
|
||||||
|
}
|
||||||
|
// 用户名检查
|
||||||
|
if err := s.validUsername(req.Username); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 密码检查
|
||||||
|
if err := checkPassword(req.Password); err != nil {
|
||||||
|
logrus.Errorf("scheckPassword err: %v", err)
|
||||||
|
return nil, web.ErrUserRegisterFailed
|
||||||
|
}
|
||||||
|
password, salt := encryptPasswordAndSalt(req.Password)
|
||||||
|
user := &ms.User{
|
||||||
|
Nickname: req.Username,
|
||||||
|
Username: req.Username,
|
||||||
|
Password: password,
|
||||||
|
Avatar: getRandomAvatar(),
|
||||||
|
Salt: salt,
|
||||||
|
Status: ms.UserStatusNormal,
|
||||||
|
}
|
||||||
|
user, err := s.Ds.CreateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Ds.CreateUser err: %s", err)
|
||||||
|
return nil, web.ErrUserRegisterFailed
|
||||||
|
}
|
||||||
|
return &web.RegisterResp{
|
||||||
|
UserId: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *pubSrv) Login(req *web.LoginReq) (*web.LoginResp, mir.Error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
user, err := s.Ds.GetUserByUsername(req.Username)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Ds.GetUserByUsername err:%s", err)
|
||||||
|
return nil, xerror.UnauthorizedAuthNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Model != nil && user.ID > 0 {
|
||||||
|
if count, err := s.Redis.GetCountLoginErr(ctx, user.ID); err == nil && count >= _MaxLoginErrTimes {
|
||||||
|
return nil, web.ErrTooManyLoginError
|
||||||
|
}
|
||||||
|
// 对比密码是否正确
|
||||||
|
if validPassword(user.Password, req.Password, user.Salt) {
|
||||||
|
if user.Status == ms.UserStatusClosed {
|
||||||
|
return nil, web.ErrUserHasBeenBanned
|
||||||
|
}
|
||||||
|
// 清空登录计数
|
||||||
|
s.Redis.DelCountLoginErr(ctx, user.ID)
|
||||||
|
} else {
|
||||||
|
// 登录错误计数
|
||||||
|
s.Redis.IncrCountLoginErr(ctx, user.ID)
|
||||||
|
return nil, xerror.UnauthorizedAuthFailed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, xerror.UnauthorizedAuthNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := app.GenerateToken(user)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("app.GenerateToken err: %v", err)
|
||||||
|
return nil, xerror.UnauthorizedTokenGenerate
|
||||||
|
}
|
||||||
|
return &web.LoginResp{
|
||||||
|
Token: token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *pubSrv) Version() (*web.VersionResp, mir.Error) {
|
||||||
|
return &web.VersionResp{
|
||||||
|
BuildInfo: version.ReadBuildInfo(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validUsername 验证用户
|
||||||
|
func (s *pubSrv) validUsername(username string) mir.Error {
|
||||||
|
// 检测用户是否合规
|
||||||
|
if utf8.RuneCountInString(username) < 3 || utf8.RuneCountInString(username) > 12 {
|
||||||
|
return web.ErrUsernameLengthLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(username) {
|
||||||
|
return web.ErrUsernameCharLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重复检查
|
||||||
|
user, _ := s.Ds.GetUserByUsername(username)
|
||||||
|
if user.Model != nil && user.ID > 0 {
|
||||||
|
return web.ErrUsernameHasExisted
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPubSrv(s *base.DaoServant) api.Pub {
|
||||||
|
return &pubSrv{
|
||||||
|
DaoServant: s,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2025 ROC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/alimy/tryst/cfg"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
api "github.com/rocboss/paopao-ce/auto/api/v2"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/core"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/dao"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/dao/cache"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/servants/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_enablePhoneVerify bool
|
||||||
|
_disallowUserRegister bool
|
||||||
|
_ds core.DataService
|
||||||
|
_ac core.AppCache
|
||||||
|
_wc core.WebCache
|
||||||
|
_oss core.ObjectStorageService
|
||||||
|
_onceInitial sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// RouteWeb register web route
|
||||||
|
func RouteWeb(e *gin.Engine) {
|
||||||
|
lazyInitial()
|
||||||
|
ds := base.NewDaoServant()
|
||||||
|
// aways register servants
|
||||||
|
api.RegisterLooseServant(e, newLooseSrv(ds, _ac))
|
||||||
|
api.RegisterPubServant(e, newPubSrv(ds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazyInitial do some package lazy initialize for performance
|
||||||
|
func lazyInitial() {
|
||||||
|
_onceInitial.Do(func() {
|
||||||
|
_enablePhoneVerify = cfg.If("Sms")
|
||||||
|
_disallowUserRegister = cfg.If("Web:DisallowUserRegister")
|
||||||
|
_oss = dao.ObjectStorageService()
|
||||||
|
_ds = dao.DataService()
|
||||||
|
_ac = cache.NewAppCache()
|
||||||
|
_wc = cache.NewWebCache()
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
### Web系列RESTful API
|
### Web系列RESTful API
|
||||||
本目录包含Web系列RESTful API相关定义文件
|
本目录包含Web系列RESTful API相关定义文件
|
||||||
|
|
||||||
* v1 - v1版本API
|
* v2 - v2版本API
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alimy/mir/v4"
|
||||||
|
. "github.com/alimy/mir/v4/engine"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/v2/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Entry[Loose]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loose 宽松授权的服务
|
||||||
|
type Loose struct {
|
||||||
|
Chain `mir:"-"`
|
||||||
|
Group `mir:"v2"`
|
||||||
|
|
||||||
|
// Timeline 获取广场流
|
||||||
|
Timeline func(Get, web.TimelineReq) web.TimelineResp `mir:"posts" binding:"query"`
|
||||||
|
|
||||||
|
// GetUserTweets 获取用户动态列表
|
||||||
|
GetUserTweets func(Get, web.GetUserTweetsReq) web.GetUserTweetsResp `mir:"user/posts" binding:"query"`
|
||||||
|
|
||||||
|
// GetUserProfile 获取用户基本信息
|
||||||
|
GetUserProfile func(Get, web.GetUserProfileReq) web.GetUserProfileResp `mir:"user/profile" binding:"query"`
|
||||||
|
|
||||||
|
// TopicList 获取话题列表
|
||||||
|
TopicList func(Get, web.TopicListReq) web.TopicListResp `mir:"tags" binding:"query"`
|
||||||
|
|
||||||
|
// TweetComments 获取动态评论
|
||||||
|
TweetComments func(Get, web.TweetCommentsReq) web.TweetCommentsResp `mir:"post/comments" binding:"query"`
|
||||||
|
|
||||||
|
// TweetDetail 获取动态详情
|
||||||
|
TweetDetail func(Get, web.TweetDetailReq) web.TweetDetailResp `mir:"post" binding:"query"`
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alimy/mir/v4"
|
||||||
|
. "github.com/alimy/mir/v4/engine"
|
||||||
|
"github.com/rocboss/paopao-ce/internal/model/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Entry[Pub]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pub 不用授权的公开服务
|
||||||
|
type Pub struct {
|
||||||
|
Group `mir:"v2"`
|
||||||
|
|
||||||
|
// Version 获取后台版本信息
|
||||||
|
Version func(Get) web.VersionResp `mir:"/"`
|
||||||
|
|
||||||
|
// Login 用户登录
|
||||||
|
Login func(Post, web.LoginReq) web.LoginResp `mir:"auth/login"`
|
||||||
|
|
||||||
|
// Register 用户注册
|
||||||
|
Register func(Post, web.RegisterReq) web.RegisterResp `mir:"auth/register"`
|
||||||
|
|
||||||
|
// GetCaptcha 获取验证码
|
||||||
|
GetCaptcha func(Get) web.GetCaptchaResp `mir:"captcha"`
|
||||||
|
|
||||||
|
// SendCaptcha 发送验证码
|
||||||
|
SendCaptcha func(Post, web.SendCaptchaReq) `mir:"captcha"`
|
||||||
|
}
|
Loading…
Reference in new issue