add web/v2 api stub logic

x/api-web-v2
alimy 8 months ago
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() {}

@ -44,14 +44,6 @@ const (
AuthenticateServiceLogoutProcedure = "/core.v1.AuthenticateService/logout" AuthenticateServiceLogoutProcedure = "/core.v1.AuthenticateService/logout"
) )
// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
var (
authenticateServiceServiceDescriptor = v1.File_core_v1_auth_proto.Services().ByName("AuthenticateService")
authenticateServicePreLoginMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("preLogin")
authenticateServiceLoginMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("login")
authenticateServiceLogoutMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("logout")
)
// AuthenticateServiceClient is a client for the core.v1.AuthenticateService service. // AuthenticateServiceClient is a client for the core.v1.AuthenticateService service.
type AuthenticateServiceClient interface { type AuthenticateServiceClient interface {
PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
@ -68,23 +60,24 @@ type AuthenticateServiceClient interface {
// http://api.acme.com or https://acme.com/grpc). // http://api.acme.com or https://acme.com/grpc).
func NewAuthenticateServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AuthenticateServiceClient { func NewAuthenticateServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AuthenticateServiceClient {
baseURL = strings.TrimRight(baseURL, "/") baseURL = strings.TrimRight(baseURL, "/")
authenticateServiceMethods := v1.File_core_v1_auth_proto.Services().ByName("AuthenticateService").Methods()
return &authenticateServiceClient{ return &authenticateServiceClient{
preLogin: connect.NewClient[v1.User, v1.ActionReply]( preLogin: connect.NewClient[v1.User, v1.ActionReply](
httpClient, httpClient,
baseURL+AuthenticateServicePreLoginProcedure, baseURL+AuthenticateServicePreLoginProcedure,
connect.WithSchema(authenticateServicePreLoginMethodDescriptor), connect.WithSchema(authenticateServiceMethods.ByName("preLogin")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
login: connect.NewClient[v1.User, v1.LoginReply]( login: connect.NewClient[v1.User, v1.LoginReply](
httpClient, httpClient,
baseURL+AuthenticateServiceLoginProcedure, baseURL+AuthenticateServiceLoginProcedure,
connect.WithSchema(authenticateServiceLoginMethodDescriptor), connect.WithSchema(authenticateServiceMethods.ByName("login")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
logout: connect.NewClient[v1.User, v1.ActionReply]( logout: connect.NewClient[v1.User, v1.ActionReply](
httpClient, httpClient,
baseURL+AuthenticateServiceLogoutProcedure, baseURL+AuthenticateServiceLogoutProcedure,
connect.WithSchema(authenticateServiceLogoutMethodDescriptor), connect.WithSchema(authenticateServiceMethods.ByName("logout")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
} }
@ -125,22 +118,23 @@ type AuthenticateServiceHandler interface {
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression. // and JSON codecs. They also support gzip compression.
func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
authenticateServiceMethods := v1.File_core_v1_auth_proto.Services().ByName("AuthenticateService").Methods()
authenticateServicePreLoginHandler := connect.NewUnaryHandler( authenticateServicePreLoginHandler := connect.NewUnaryHandler(
AuthenticateServicePreLoginProcedure, AuthenticateServicePreLoginProcedure,
svc.PreLogin, svc.PreLogin,
connect.WithSchema(authenticateServicePreLoginMethodDescriptor), connect.WithSchema(authenticateServiceMethods.ByName("preLogin")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
authenticateServiceLoginHandler := connect.NewUnaryHandler( authenticateServiceLoginHandler := connect.NewUnaryHandler(
AuthenticateServiceLoginProcedure, AuthenticateServiceLoginProcedure,
svc.Login, svc.Login,
connect.WithSchema(authenticateServiceLoginMethodDescriptor), connect.WithSchema(authenticateServiceMethods.ByName("login")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
authenticateServiceLogoutHandler := connect.NewUnaryHandler( authenticateServiceLogoutHandler := connect.NewUnaryHandler(
AuthenticateServiceLogoutProcedure, AuthenticateServiceLogoutProcedure,
svc.Logout, svc.Logout,
connect.WithSchema(authenticateServiceLogoutMethodDescriptor), connect.WithSchema(authenticateServiceMethods.ByName("logout")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
return "/core.v1.AuthenticateService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return "/core.v1.AuthenticateService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

@ -37,12 +37,6 @@ const (
GreetServiceGreetProcedure = "/greet.v1.GreetService/Greet" GreetServiceGreetProcedure = "/greet.v1.GreetService/Greet"
) )
// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
var (
greetServiceServiceDescriptor = v1.File_greet_v1_greet_proto.Services().ByName("GreetService")
greetServiceGreetMethodDescriptor = greetServiceServiceDescriptor.Methods().ByName("Greet")
)
// GreetServiceClient is a client for the greet.v1.GreetService service. // GreetServiceClient is a client for the greet.v1.GreetService service.
type GreetServiceClient interface { type GreetServiceClient interface {
Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
@ -57,11 +51,12 @@ type GreetServiceClient interface {
// http://api.acme.com or https://acme.com/grpc). // http://api.acme.com or https://acme.com/grpc).
func NewGreetServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GreetServiceClient { func NewGreetServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GreetServiceClient {
baseURL = strings.TrimRight(baseURL, "/") baseURL = strings.TrimRight(baseURL, "/")
greetServiceMethods := v1.File_greet_v1_greet_proto.Services().ByName("GreetService").Methods()
return &greetServiceClient{ return &greetServiceClient{
greet: connect.NewClient[v1.GreetRequest, v1.GreetResponse]( greet: connect.NewClient[v1.GreetRequest, v1.GreetResponse](
httpClient, httpClient,
baseURL+GreetServiceGreetProcedure, baseURL+GreetServiceGreetProcedure,
connect.WithSchema(greetServiceGreetMethodDescriptor), connect.WithSchema(greetServiceMethods.ByName("Greet")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
} }
@ -88,10 +83,11 @@ type GreetServiceHandler interface {
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression. // and JSON codecs. They also support gzip compression.
func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
greetServiceMethods := v1.File_greet_v1_greet_proto.Services().ByName("GreetService").Methods()
greetServiceGreetHandler := connect.NewUnaryHandler( greetServiceGreetHandler := connect.NewUnaryHandler(
GreetServiceGreetProcedure, GreetServiceGreetProcedure,
svc.Greet, svc.Greet,
connect.WithSchema(greetServiceGreetMethodDescriptor), connect.WithSchema(greetServiceMethods.ByName("Greet")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
return "/greet.v1.GreetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return "/greet.v1.GreetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.36.3
// protoc (unknown) // protoc (unknown)
// source: core/v1/auth.proto // source: core/v1/auth.proto
@ -21,21 +21,18 @@ const (
) )
type User struct { type User struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PhoneNum string `protobuf:"bytes,1,opt,name=phone_num,json=phoneNum,proto3" json:"phone_num,omitempty"` PhoneNum string `protobuf:"bytes,1,opt,name=phone_num,json=phoneNum,proto3" json:"phone_num,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *User) Reset() { func (x *User) Reset() {
*x = User{} *x = User{}
if protoimpl.UnsafeEnabled {
mi := &file_core_v1_auth_proto_msgTypes[0] mi := &file_core_v1_auth_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
}
func (x *User) String() string { func (x *User) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
@ -45,7 +42,7 @@ func (*User) ProtoMessage() {}
func (x *User) ProtoReflect() protoreflect.Message { func (x *User) ProtoReflect() protoreflect.Message {
mi := &file_core_v1_auth_proto_msgTypes[0] mi := &file_core_v1_auth_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@ -68,22 +65,19 @@ func (x *User) GetPhoneNum() string {
} }
type UserVerify struct { type UserVerify struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PhoneNum string `protobuf:"bytes,1,opt,name=phone_num,json=phoneNum,proto3" json:"phone_num,omitempty"` PhoneNum string `protobuf:"bytes,1,opt,name=phone_num,json=phoneNum,proto3" json:"phone_num,omitempty"`
VerificationCode string `protobuf:"bytes,2,opt,name=verification_code,json=verificationCode,proto3" json:"verification_code,omitempty"` VerificationCode string `protobuf:"bytes,2,opt,name=verification_code,json=verificationCode,proto3" json:"verification_code,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *UserVerify) Reset() { func (x *UserVerify) Reset() {
*x = UserVerify{} *x = UserVerify{}
if protoimpl.UnsafeEnabled {
mi := &file_core_v1_auth_proto_msgTypes[1] mi := &file_core_v1_auth_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
}
func (x *UserVerify) String() string { func (x *UserVerify) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
@ -93,7 +87,7 @@ func (*UserVerify) ProtoMessage() {}
func (x *UserVerify) ProtoReflect() protoreflect.Message { func (x *UserVerify) ProtoReflect() protoreflect.Message {
mi := &file_core_v1_auth_proto_msgTypes[1] mi := &file_core_v1_auth_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@ -123,22 +117,19 @@ func (x *UserVerify) GetVerificationCode() string {
} }
type LoginReply struct { type LoginReply struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *LoginReply) Reset() { func (x *LoginReply) Reset() {
*x = LoginReply{} *x = LoginReply{}
if protoimpl.UnsafeEnabled {
mi := &file_core_v1_auth_proto_msgTypes[2] mi := &file_core_v1_auth_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
}
func (x *LoginReply) String() string { func (x *LoginReply) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
@ -148,7 +139,7 @@ func (*LoginReply) ProtoMessage() {}
func (x *LoginReply) ProtoReflect() protoreflect.Message { func (x *LoginReply) ProtoReflect() protoreflect.Message {
mi := &file_core_v1_auth_proto_msgTypes[2] mi := &file_core_v1_auth_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@ -178,21 +169,18 @@ func (x *LoginReply) GetToken() string {
} }
type ActionReply struct { type ActionReply struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ActionReply) Reset() { func (x *ActionReply) Reset() {
*x = ActionReply{} *x = ActionReply{}
if protoimpl.UnsafeEnabled {
mi := &file_core_v1_auth_proto_msgTypes[3] mi := &file_core_v1_auth_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
}
func (x *ActionReply) String() string { func (x *ActionReply) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
@ -202,7 +190,7 @@ func (*ActionReply) ProtoMessage() {}
func (x *ActionReply) ProtoReflect() protoreflect.Message { func (x *ActionReply) ProtoReflect() protoreflect.Message {
mi := &file_core_v1_auth_proto_msgTypes[3] mi := &file_core_v1_auth_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@ -279,7 +267,7 @@ func file_core_v1_auth_proto_rawDescGZIP() []byte {
} }
var file_core_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_core_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_core_v1_auth_proto_goTypes = []interface{}{ var file_core_v1_auth_proto_goTypes = []any{
(*User)(nil), // 0: core.v1.User (*User)(nil), // 0: core.v1.User
(*UserVerify)(nil), // 1: core.v1.UserVerify (*UserVerify)(nil), // 1: core.v1.UserVerify
(*LoginReply)(nil), // 2: core.v1.LoginReply (*LoginReply)(nil), // 2: core.v1.LoginReply
@ -304,56 +292,6 @@ func file_core_v1_auth_proto_init() {
if File_core_v1_auth_proto != nil { if File_core_v1_auth_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled {
file_core_v1_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*User); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_core_v1_auth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserVerify); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_core_v1_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LoginReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_core_v1_auth_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ActionReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.3.0 // - protoc-gen-go-grpc v1.5.1
// - protoc (unknown) // - protoc (unknown)
// source: core/v1/auth.proto // source: core/v1/auth.proto
@ -15,8 +15,8 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const ( const (
AuthenticateService_PreLogin_FullMethodName = "/core.v1.AuthenticateService/preLogin" AuthenticateService_PreLogin_FullMethodName = "/core.v1.AuthenticateService/preLogin"
@ -42,8 +42,9 @@ func NewAuthenticateServiceClient(cc grpc.ClientConnInterface) AuthenticateServi
} }
func (c *authenticateServiceClient) PreLogin(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) { func (c *authenticateServiceClient) PreLogin(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ActionReply) out := new(ActionReply)
err := c.cc.Invoke(ctx, AuthenticateService_PreLogin_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, AuthenticateService_PreLogin_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,8 +52,9 @@ func (c *authenticateServiceClient) PreLogin(ctx context.Context, in *User, opts
} }
func (c *authenticateServiceClient) Login(ctx context.Context, in *User, opts ...grpc.CallOption) (*LoginReply, error) { func (c *authenticateServiceClient) Login(ctx context.Context, in *User, opts ...grpc.CallOption) (*LoginReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LoginReply) out := new(LoginReply)
err := c.cc.Invoke(ctx, AuthenticateService_Login_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, AuthenticateService_Login_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -60,8 +62,9 @@ func (c *authenticateServiceClient) Login(ctx context.Context, in *User, opts ..
} }
func (c *authenticateServiceClient) Logout(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) { func (c *authenticateServiceClient) Logout(ctx context.Context, in *User, opts ...grpc.CallOption) (*ActionReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ActionReply) out := new(ActionReply)
err := c.cc.Invoke(ctx, AuthenticateService_Logout_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, AuthenticateService_Logout_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,7 +73,7 @@ func (c *authenticateServiceClient) Logout(ctx context.Context, in *User, opts .
// AuthenticateServiceServer is the server API for AuthenticateService service. // AuthenticateServiceServer is the server API for AuthenticateService service.
// All implementations must embed UnimplementedAuthenticateServiceServer // All implementations must embed UnimplementedAuthenticateServiceServer
// for forward compatibility // for forward compatibility.
type AuthenticateServiceServer interface { type AuthenticateServiceServer interface {
PreLogin(context.Context, *User) (*ActionReply, error) PreLogin(context.Context, *User) (*ActionReply, error)
Login(context.Context, *User) (*LoginReply, error) Login(context.Context, *User) (*LoginReply, error)
@ -78,9 +81,12 @@ type AuthenticateServiceServer interface {
mustEmbedUnimplementedAuthenticateServiceServer() mustEmbedUnimplementedAuthenticateServiceServer()
} }
// UnimplementedAuthenticateServiceServer must be embedded to have forward compatible implementations. // UnimplementedAuthenticateServiceServer must be embedded to have
type UnimplementedAuthenticateServiceServer struct { // forward compatible implementations.
} //
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedAuthenticateServiceServer struct{}
func (UnimplementedAuthenticateServiceServer) PreLogin(context.Context, *User) (*ActionReply, error) { func (UnimplementedAuthenticateServiceServer) PreLogin(context.Context, *User) (*ActionReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method PreLogin not implemented") return nil, status.Errorf(codes.Unimplemented, "method PreLogin not implemented")
@ -92,6 +98,7 @@ func (UnimplementedAuthenticateServiceServer) Logout(context.Context, *User) (*A
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented")
} }
func (UnimplementedAuthenticateServiceServer) mustEmbedUnimplementedAuthenticateServiceServer() {} func (UnimplementedAuthenticateServiceServer) mustEmbedUnimplementedAuthenticateServiceServer() {}
func (UnimplementedAuthenticateServiceServer) testEmbeddedByValue() {}
// UnsafeAuthenticateServiceServer may be embedded to opt out of forward compatibility for this service. // UnsafeAuthenticateServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AuthenticateServiceServer will // Use of this interface is not recommended, as added methods to AuthenticateServiceServer will
@ -101,6 +108,13 @@ type UnsafeAuthenticateServiceServer interface {
} }
func RegisterAuthenticateServiceServer(s grpc.ServiceRegistrar, srv AuthenticateServiceServer) { func RegisterAuthenticateServiceServer(s grpc.ServiceRegistrar, srv AuthenticateServiceServer) {
// If the following call pancis, it indicates UnimplementedAuthenticateServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&AuthenticateService_ServiceDesc, srv) s.RegisterService(&AuthenticateService_ServiceDesc, srv)
} }

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.36.3
// protoc (unknown) // protoc (unknown)
// source: greet/v1/greet.proto // source: greet/v1/greet.proto
@ -21,21 +21,18 @@ const (
) )
type GreetRequest struct { type GreetRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *GreetRequest) Reset() { func (x *GreetRequest) Reset() {
*x = GreetRequest{} *x = GreetRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_greet_v1_greet_proto_msgTypes[0] mi := &file_greet_v1_greet_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
}
func (x *GreetRequest) String() string { func (x *GreetRequest) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
@ -45,7 +42,7 @@ func (*GreetRequest) ProtoMessage() {}
func (x *GreetRequest) ProtoReflect() protoreflect.Message { func (x *GreetRequest) ProtoReflect() protoreflect.Message {
mi := &file_greet_v1_greet_proto_msgTypes[0] mi := &file_greet_v1_greet_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@ -68,21 +65,18 @@ func (x *GreetRequest) GetName() string {
} }
type GreetResponse struct { type GreetResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"` Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *GreetResponse) Reset() { func (x *GreetResponse) Reset() {
*x = GreetResponse{} *x = GreetResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_greet_v1_greet_proto_msgTypes[1] mi := &file_greet_v1_greet_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
}
func (x *GreetResponse) String() string { func (x *GreetResponse) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
@ -92,7 +86,7 @@ func (*GreetResponse) ProtoMessage() {}
func (x *GreetResponse) ProtoReflect() protoreflect.Message { func (x *GreetResponse) ProtoReflect() protoreflect.Message {
mi := &file_greet_v1_greet_proto_msgTypes[1] mi := &file_greet_v1_greet_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@ -154,7 +148,7 @@ func file_greet_v1_greet_proto_rawDescGZIP() []byte {
} }
var file_greet_v1_greet_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_greet_v1_greet_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_greet_v1_greet_proto_goTypes = []interface{}{ var file_greet_v1_greet_proto_goTypes = []any{
(*GreetRequest)(nil), // 0: greet.v1.GreetRequest (*GreetRequest)(nil), // 0: greet.v1.GreetRequest
(*GreetResponse)(nil), // 1: greet.v1.GreetResponse (*GreetResponse)(nil), // 1: greet.v1.GreetResponse
} }
@ -173,32 +167,6 @@ func file_greet_v1_greet_proto_init() {
if File_greet_v1_greet_proto != nil { if File_greet_v1_greet_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled {
file_greet_v1_greet_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GreetRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_greet_v1_greet_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GreetResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.3.0 // - protoc-gen-go-grpc v1.5.1
// - protoc (unknown) // - protoc (unknown)
// source: greet/v1/greet.proto // source: greet/v1/greet.proto
@ -15,8 +15,8 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const ( const (
GreetService_Greet_FullMethodName = "/greet.v1.GreetService/Greet" GreetService_Greet_FullMethodName = "/greet.v1.GreetService/Greet"
@ -38,8 +38,9 @@ func NewGreetServiceClient(cc grpc.ClientConnInterface) GreetServiceClient {
} }
func (c *greetServiceClient) Greet(ctx context.Context, in *GreetRequest, opts ...grpc.CallOption) (*GreetResponse, error) { func (c *greetServiceClient) Greet(ctx context.Context, in *GreetRequest, opts ...grpc.CallOption) (*GreetResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GreetResponse) out := new(GreetResponse)
err := c.cc.Invoke(ctx, GreetService_Greet_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, GreetService_Greet_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -48,20 +49,24 @@ func (c *greetServiceClient) Greet(ctx context.Context, in *GreetRequest, opts .
// GreetServiceServer is the server API for GreetService service. // GreetServiceServer is the server API for GreetService service.
// All implementations must embed UnimplementedGreetServiceServer // All implementations must embed UnimplementedGreetServiceServer
// for forward compatibility // for forward compatibility.
type GreetServiceServer interface { type GreetServiceServer interface {
Greet(context.Context, *GreetRequest) (*GreetResponse, error) Greet(context.Context, *GreetRequest) (*GreetResponse, error)
mustEmbedUnimplementedGreetServiceServer() mustEmbedUnimplementedGreetServiceServer()
} }
// UnimplementedGreetServiceServer must be embedded to have forward compatible implementations. // UnimplementedGreetServiceServer must be embedded to have
type UnimplementedGreetServiceServer struct { // forward compatible implementations.
} //
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedGreetServiceServer struct{}
func (UnimplementedGreetServiceServer) Greet(context.Context, *GreetRequest) (*GreetResponse, error) { func (UnimplementedGreetServiceServer) Greet(context.Context, *GreetRequest) (*GreetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Greet not implemented") return nil, status.Errorf(codes.Unimplemented, "method Greet not implemented")
} }
func (UnimplementedGreetServiceServer) mustEmbedUnimplementedGreetServiceServer() {} func (UnimplementedGreetServiceServer) mustEmbedUnimplementedGreetServiceServer() {}
func (UnimplementedGreetServiceServer) testEmbeddedByValue() {}
// UnsafeGreetServiceServer may be embedded to opt out of forward compatibility for this service. // UnsafeGreetServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreetServiceServer will // Use of this interface is not recommended, as added methods to GreetServiceServer will
@ -71,6 +76,13 @@ type UnsafeGreetServiceServer interface {
} }
func RegisterGreetServiceServer(s grpc.ServiceRegistrar, srv GreetServiceServer) { func RegisterGreetServiceServer(s grpc.ServiceRegistrar, srv GreetServiceServer) {
// If the following call pancis, it indicates UnimplementedGreetServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&GreetService_ServiceDesc, srv) s.RegisterService(&GreetService_ServiceDesc, srv)
} }

@ -7,7 +7,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/RoaringBitmap/roaring v1.9.4 github.com/RoaringBitmap/roaring v1.9.4
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
github.com/alimy/mir/v4 v4.2.0-alpha.5 github.com/alimy/mir/v4 v4.2.0-alpha.6
github.com/alimy/tryst v0.22.0 github.com/alimy/tryst v0.22.0
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/allegro/bigcache/v3 v3.1.0 github.com/allegro/bigcache/v3 v3.1.0

@ -11,8 +11,8 @@ github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 h1:uFrPOl1VBt/Abfl2z+A/DFc+AwmFLxEHR1+Yq6cXvww= github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 h1:uFrPOl1VBt/Abfl2z+A/DFc+AwmFLxEHR1+Yq6cXvww=
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868/go.mod h1:srphKZ1i+yGXxl/LpBS7ZIECTjCTPzZzAMtJWoG3sLo= github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868/go.mod h1:srphKZ1i+yGXxl/LpBS7ZIECTjCTPzZzAMtJWoG3sLo=
github.com/alimy/mir/v4 v4.2.0-alpha.5 h1:ExSJpbFzKX3Avk1CoTOU3OLyvo4PTB2SnTSQXfeJNIc= github.com/alimy/mir/v4 v4.2.0-alpha.6 h1:ADdpC7zI2Chs4oddvTVu76uif2I9TLbNCpCwS+8MtWk=
github.com/alimy/mir/v4 v4.2.0-alpha.5/go.mod h1:d58dBvw2KImcVbAUANrciEV/of0arMNsI9c/5UNCMMc= github.com/alimy/mir/v4 v4.2.0-alpha.6/go.mod h1:d58dBvw2KImcVbAUANrciEV/of0arMNsI9c/5UNCMMc=
github.com/alimy/tryst v0.22.0 h1:tjZFvHliMDkymEZuuhH/e6Tg41B72LJt8/5TCiJztGw= github.com/alimy/tryst v0.22.0 h1:tjZFvHliMDkymEZuuhH/e6Tg41B72LJt8/5TCiJztGw=
github.com/alimy/tryst v0.22.0/go.mod h1:HPOlTam3dT+of3slvIxpzf1pUQEUAfBJp1zgIuk/uLY= github.com/alimy/tryst v0.22.0/go.mod h1:HPOlTam3dT+of3slvIxpzf1pUQEUAfBJp1zgIuk/uLY=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=

@ -4,6 +4,8 @@
package joint package joint
import "github.com/rocboss/paopao-ce/internal/conf"
type Pager struct { type Pager struct {
Page int `json:"page"` Page int `json:"page"`
PageSize int `json:"page_size"` PageSize int `json:"page_size"`
@ -15,6 +17,11 @@ type PageResp struct {
Pager Pager `json:"pager"` Pager Pager `json:"pager"`
} }
type PageInfo struct {
Limit int `json:"-" form:"-" query:"limit" binding:"-"`
Offset int `json:"-" form:"-" query:"offset" binding:"-"`
}
func PageRespFrom(list any, page int, pageSize int, totalRows int64) *PageResp { func PageRespFrom(list any, page int, pageSize int, totalRows int64) *PageResp {
return &PageResp{ return &PageResp{
List: list, List: list,
@ -25,3 +32,15 @@ func PageRespFrom(list any, page int, pageSize int, totalRows int64) *PageResp {
}, },
} }
} }
func (s *PageInfo) BuildPageInfo(page, size int) {
if page <= 0 {
page = 1
}
if size <= 0 {
s.Limit = conf.AppSetting.DefaultPageSize
} else if size > conf.AppSetting.MaxPageSize {
s.Limit = conf.AppSetting.MaxPageSize
}
s.Offset = (page - 1) * s.Limit
}

@ -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, "功能未实现")
)

@ -55,7 +55,7 @@ type TweetCommentsResp struct {
type TimelineReq struct { type TimelineReq struct {
BaseInfo `form:"-" binding:"-"` BaseInfo `form:"-" binding:"-"`
Query string `form:"query"` Query string `form:"query"`
Visibility []core.PostVisibleT `form:"query"` Visibility []core.PostVisibleT `form:"visibility"`
Type string `form:"type"` Type string `form:"type"`
Style string `form:"style"` Style string `form:"style"`
Page int `form:"-" binding:"-"` Page int `form:"-" binding:"-"`

@ -15,6 +15,7 @@ import (
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
sentrygin "github.com/getsentry/sentry-go/gin" sentrygin "github.com/getsentry/sentry-go/gin"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs" "github.com/rocboss/paopao-ce/internal/core/cs"
@ -31,6 +32,9 @@ import (
type BaseServant struct { type BaseServant struct {
bindAny func(c *gin.Context, obj any) mir.Error bindAny func(c *gin.Context, obj any) mir.Error
bindJson func(c *gin.Context, obj any) mir.Error bindJson func(c *gin.Context, obj any) mir.Error
bindQeury func(c *gin.Context, obj any) mir.Error
bindForm func(c *gin.Context, obj any) mir.Error
bindMultipart func(c *gin.Context, obj any) mir.Error
} }
type DaoServant struct { type DaoServant struct {
@ -58,6 +62,10 @@ type PageInfoSetter interface {
SetPageInfo(page, pageSize int) SetPageInfo(page, pageSize int)
} }
type Ajustment interface {
Ajust()
}
func UserFrom(c *gin.Context) (*ms.User, bool) { func UserFrom(c *gin.Context) (*ms.User, bool) {
if u, exists := c.Get("USER"); exists { if u, exists := c.Get("USER"); exists {
user, ok := u.(*ms.User) user, ok := u.(*ms.User)
@ -82,6 +90,90 @@ func UserNameFrom(c *gin.Context) (string, bool) {
return "", false return "", false
} }
func bindQeury(c *gin.Context, obj any) mir.Error {
var errs xerror.ValidErrors
err := c.BindQuery(obj)
if err != nil {
return mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil
}
func bindMultipart(c *gin.Context, obj any) mir.Error {
var errs xerror.ValidErrors
err := c.MustBindWith(obj, binding.FormMultipart)
if err != nil {
return mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil
}
func bindForm(c *gin.Context, obj any) mir.Error {
var errs xerror.ValidErrors
err := c.MustBindWith(obj, binding.Form)
if err != nil {
return mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil
}
func bindAny(c *gin.Context, obj any) mir.Error { func bindAny(c *gin.Context, obj any) mir.Error {
var errs xerror.ValidErrors var errs xerror.ValidErrors
err := c.ShouldBind(obj) err := c.ShouldBind(obj)
@ -103,6 +195,10 @@ func bindAny(c *gin.Context, obj any) mir.Error {
page, pageSize := app.GetPageInfo(c) page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize) setter.SetPageInfo(page, pageSize)
} }
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil return nil
} }
@ -136,8 +232,122 @@ func bindAnySentry(c *gin.Context, obj any) mir.Error {
page, pageSize := app.GetPageInfo(c) page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize) setter.SetPageInfo(page, pageSize)
} }
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil
}
func bindQuerySentry(c *gin.Context, obj any) mir.Error {
hub := sentrygin.GetHubFromContext(c)
var errs xerror.ValidErrors
err := c.BindQuery(obj)
if err != nil {
xerr := mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
if hub != nil {
hub.CaptureException(errors.Wrap(xerr, "bind object"))
}
return xerr
}
// setup sentry hub if needed
if setter, ok := obj.(SentryHubSetter); ok && hub != nil {
setter.SetSentryHub(hub)
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil return nil
}
func bindFormSentry(c *gin.Context, obj any) mir.Error {
hub := sentrygin.GetHubFromContext(c)
var errs xerror.ValidErrors
err := c.MustBindWith(obj, binding.Form)
if err != nil {
xerr := mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
if hub != nil {
hub.CaptureException(errors.Wrap(xerr, "bind object"))
}
return xerr
}
// setup sentry hub if needed
if setter, ok := obj.(SentryHubSetter); ok && hub != nil {
setter.SetSentryHub(hub)
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil
}
func bindMultipartSentry(c *gin.Context, obj any) mir.Error {
hub := sentrygin.GetHubFromContext(c)
var errs xerror.ValidErrors
err := c.MustBindWith(obj, binding.FormMultipart)
if err != nil {
xerr := mir.NewError(xerror.InvalidParams.StatusCode(), xerror.InvalidParams.WithDetails(errs.Error()))
if hub != nil {
hub.CaptureException(errors.Wrap(xerr, "bind object"))
}
return xerr
}
// setup sentry hub if needed
if setter, ok := obj.(SentryHubSetter); ok && hub != nil {
setter.SetSentryHub(hub)
}
// setup *core.User if needed
if setter, ok := obj.(UserSetter); ok {
user, _ := UserFrom(c)
setter.SetUser(user)
}
// setup UserId if needed
if setter, ok := obj.(UserIdSetter); ok {
uid, _ := UserIdFrom(c)
setter.SetUserId(uid)
}
// setup PageInfo if needed
if setter, ok := obj.(PageInfoSetter); ok {
page, pageSize := app.GetPageInfo(c)
setter.SetPageInfo(page, pageSize)
}
// ajust object fields value
if ajustment, ok := obj.(Ajustment); ok {
ajustment.Ajust()
}
return nil
} }
func RenderAny(c *gin.Context, data any, err mir.Error) { func RenderAny(c *gin.Context, data any, err mir.Error) {
@ -163,6 +373,18 @@ func (s *BaseServant) BindJson(c *gin.Context, obj any) mir.Error {
return s.bindJson(c, obj) return s.bindJson(c, obj)
} }
func (s *BaseServant) BindQuery(c *gin.Context, obj any) mir.Error {
return s.bindQeury(c, obj)
}
func (s *BaseServant) BindForm(c *gin.Context, obj any) mir.Error {
return s.bindForm(c, obj)
}
func (s *BaseServant) BindMultipart(c *gin.Context, obj any) mir.Error {
return s.bindMultipart(c, obj)
}
func (s *BaseServant) Render(c *gin.Context, data any, err mir.Error) { func (s *BaseServant) Render(c *gin.Context, data any, err mir.Error) {
if err == nil { if err == nil {
c.JSON(http.StatusOK, &joint.JsonResp{ c.JSON(http.StatusOK, &joint.JsonResp{
@ -424,6 +646,27 @@ func NewBindAnyFn() func(c *gin.Context, obj any) mir.Error {
return bindAny return bindAny
} }
func NewBindQueryFn() func(c *gin.Context, obj any) mir.Error {
if conf.UseSentryGin() {
return bindQuerySentry
}
return bindQeury
}
func NewBindFormFn() func(c *gin.Context, obj any) mir.Error {
if conf.UseSentryGin() {
return bindFormSentry
}
return bindForm
}
func NewBindMultipart() func(c *gin.Context, obj any) mir.Error {
if conf.UseSentryGin() {
return bindMultipartSentry
}
return bindMultipart
}
func NewBindJsonFn() func(c *gin.Context, obj any) mir.Error { func NewBindJsonFn() func(c *gin.Context, obj any) mir.Error {
if conf.UseSentryGin() { if conf.UseSentryGin() {
return bindAnySentry return bindAnySentry
@ -435,6 +678,9 @@ func NewBaseServant() *BaseServant {
return &BaseServant{ return &BaseServant{
bindAny: NewBindAnyFn(), bindAny: NewBindAnyFn(),
bindJson: NewBindJsonFn(), bindJson: NewBindJsonFn(),
bindQeury: NewBindQueryFn(),
bindForm: NewBindFormFn(),
bindMultipart: NewBindMultipart(),
} }
} }

@ -19,6 +19,7 @@ import (
"github.com/rocboss/paopao-ce/internal/servants/statick" "github.com/rocboss/paopao-ce/internal/servants/statick"
"github.com/rocboss/paopao-ce/internal/servants/triplet" "github.com/rocboss/paopao-ce/internal/servants/triplet"
"github.com/rocboss/paopao-ce/internal/servants/web" "github.com/rocboss/paopao-ce/internal/servants/web"
webv2 "github.com/rocboss/paopao-ce/internal/servants/web/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -31,6 +32,8 @@ func RegisterWebServants(e *gin.Engine) {
localoss.RouteLocalOSS(e) localoss.RouteLocalOSS(e)
}) })
web.RouteWeb(e) web.RouteWeb(e)
// web v2 api
webv2.RouteWeb(e)
} }
// RegisterAdminServants register all the servants to gin.Engine // RegisterAdminServants register all the servants to gin.Engine

@ -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,236 @@
// 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 (
"image"
"math/rand"
"strings"
"time"
"unicode/utf8"
"github.com/alimy/mir/v4"
"github.com/gofrs/uuid/v5"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/ms"
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/pkg/utils"
"github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
)
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))]
}
// checkPassword 密码检查
func checkPassword(password string) mir.Error {
// 检测用户是否合规
if utf8.RuneCountInString(password) < 6 || utf8.RuneCountInString(password) > 16 {
return web.ErrPasswordLengthLimit
}
return nil
}
// ValidPassword 检查密码是否一致
func validPassword(dbPassword, password, salt string) bool {
return strings.Compare(dbPassword, utils.EncodeMD5(utils.EncodeMD5(password)+salt)) == 0
}
// 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
}
// deleteOssObjects 删除推文的媒体内容, 宽松处理错误(就是不处理), 后续完善
func deleteOssObjects(oss core.ObjectStorageService, 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]))
}
}
// persistMediaContents 获取媒体内容并持久化
func persistMediaContents(oss core.ObjectStorageService, contents []*web.PostContentItem) (items []string, err error) {
items = make([]string, 0, len(contents))
for _, item := range contents {
switch item.Type {
case ms.ContentTypeImage,
ms.ContentTypeVideo,
ms.ContentTypeAudio,
ms.ContentTypeAttachment,
ms.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
}
func fileCheck(uploadType string, size int64) mir.Error {
if uploadType != "public/video" &&
uploadType != "public/image" &&
uploadType != "public/avatar" &&
uploadType != "attachment" {
return xerror.InvalidParams
}
if size > 1024*1024*100 {
return web.ErrFileInvalidSize.WithDetails("最大允许100MB")
}
return nil
}
func getFileExt(s string) (string, mir.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",
"application/x-zip",
"application/octet-stream",
"application/x-zip-compressed":
return ".zip", nil
default:
return "", web.ErrFileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型")
}
}
func generatePath(s string) string {
n := len(s)
if n <= 2 {
return s
}
return generatePath(s[:n-2]) + "/" + s[n-2:]
}
func getImageSize(img image.Rectangle) (int, int) {
b := img.Bounds()
width := b.Max.X
height := b.Max.Y
return width, height
}
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
}
// checkPermision 检查是否拥有者或管理员
func checkPermision(user *ms.User, targetUserId int64) mir.Error {
if user == nil || (user.ID != targetUserId && !user.IsAdmin) {
return web.ErrNoPermission
}
return nil
}
// checkPostViewPermission 检查当前用户是否可读指定post
func checkPostViewPermission(user *ms.User, post *ms.Post, ds core.DataService) mir.Error {
if post.Visibility == core.PostVisitPublic {
return nil
}
if user == nil {
return web.ErrNoPermission
}
if user.IsAdmin || user.ID == post.UserID {
return nil
}
if post.Visibility == core.PostVisitPrivate {
return web.ErrNoPermission
}
if post.Visibility == core.PostVisitFriend {
if !ds.IsFriend(post.UserID, user.ID) && !ds.IsFriend(user.ID, post.UserID) {
return web.ErrNoPermission
}
}
// TODO: add following check logic
return nil
}

@ -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()
})
}

@ -19,6 +19,7 @@ import (
_ "github.com/rocboss/paopao-ce/mirc/localoss/v1" _ "github.com/rocboss/paopao-ce/mirc/localoss/v1"
_ "github.com/rocboss/paopao-ce/mirc/space/v1" _ "github.com/rocboss/paopao-ce/mirc/space/v1"
_ "github.com/rocboss/paopao-ce/mirc/web/v1" _ "github.com/rocboss/paopao-ce/mirc/web/v1"
_ "github.com/rocboss/paopao-ce/mirc/web/v2"
) )
//go:generate go run $GOFILE //go:generate go run $GOFILE

@ -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"`
}

@ -60,3 +60,16 @@ func GetPageInfo(c *gin.Context) (page, pageSize int) {
} }
return return
} }
func LimitOffset(page, size int) (limit, offset int) {
if page <= 0 {
page = 1
}
if size <= 0 {
limit = conf.AppSetting.DefaultPageSize
} else if size > conf.AppSetting.MaxPageSize {
limit = conf.AppSetting.MaxPageSize
}
offset = (page - 1) * limit
return
}

Loading…
Cancel
Save