资金账户模块表设计
This commit is contained in:
Binary file not shown.
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// SaveMomentWithImages 新建动态
|
||||
func SaveMomentWithImages(moment *model.Moment, imageURLs []string) error {
|
||||
func SaveMomentWithImages(moment *model.Moment, files []*user.MomentFile) error {
|
||||
db := DB
|
||||
// 使用事务确保数据一致性
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -20,12 +20,13 @@ func SaveMomentWithImages(moment *model.Moment, imageURLs []string) error {
|
||||
}
|
||||
|
||||
// 保存图片(如果有)
|
||||
if len(imageURLs) > 0 {
|
||||
images := make([]model.MomentImage, 0, len(imageURLs))
|
||||
for _, url := range imageURLs {
|
||||
images = append(images, model.MomentImage{
|
||||
if len(files) > 0 {
|
||||
images := make([]model.MomentFile, 0, len(files))
|
||||
for _, url := range files {
|
||||
images = append(images, model.MomentFile{
|
||||
MomentID: moment.ID,
|
||||
ImageURL: url,
|
||||
FileURL: url.FileURL,
|
||||
FileType: url.FileType,
|
||||
})
|
||||
}
|
||||
if err := tx.Create(&images).Error; err != nil {
|
||||
@@ -101,19 +102,21 @@ func convertUserModelToDTO(u model.User) user.UserInfoReq {
|
||||
}
|
||||
|
||||
// GetMomentsImage 获取指定动态的图像列表
|
||||
func GetMomentsImage(ids uint) ([]*user.MomentImage, error) {
|
||||
func GetMomentsImage(ids uint) ([]*user.MomentFile, error) {
|
||||
db := DB
|
||||
var images []model.MomentImage
|
||||
var data []*user.MomentImage
|
||||
err := db.Model(&model.MomentImage{}).Where("moment_id = ?", ids).Find(&images).Error
|
||||
var images []model.MomentFile
|
||||
var data []*user.MomentFile
|
||||
err := db.Model(&model.MomentFile{}).Where("moment_id = ?", ids).Find(&images).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range images {
|
||||
data = append(data, &user.MomentImage{
|
||||
ID: int64(v.ID),
|
||||
MomentID: int64(v.MomentID),
|
||||
ImageURL: v.ImageURL,
|
||||
data = append(data, &user.MomentFile{
|
||||
ID: int64(v.ID),
|
||||
MomentID: int64(v.MomentID),
|
||||
FileURL: v.FileURL,
|
||||
FileType: v.FileType,
|
||||
SortOrder: int8(v.SortOrder),
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
@@ -192,7 +195,7 @@ func GetFriendsMoments(req user.ListMomentsRequest) ([]*user.Moment, int32, erro
|
||||
Status: user.ContentStatus(int64(v.Status)),
|
||||
LikeCount: int32(v.LikeCount),
|
||||
CommentCount: int32(v.CommentCount),
|
||||
Images: images,
|
||||
Files: images,
|
||||
CreatedAt: v.CreatedAt.Format("2006-01-02 15:04"),
|
||||
})
|
||||
}
|
||||
@@ -243,7 +246,7 @@ func GetMomentsAppoint(req user.ListMomentsAppointRequest) ([]*user.Moment, int3
|
||||
Status: user.ContentStatus(int64(v.Status)),
|
||||
LikeCount: int32(v.LikeCount),
|
||||
CommentCount: int32(v.CommentCount),
|
||||
Images: images,
|
||||
Files: images,
|
||||
CreatedAt: v.CreatedAt.Format("2006-01-02 15:04"),
|
||||
})
|
||||
}
|
||||
@@ -275,7 +278,7 @@ func DeleteMoment(req user.DeleteMomentRequest) error {
|
||||
}
|
||||
|
||||
// 2. 删除动态图片
|
||||
if err := tx.Where("moment_id = ?", req.MomentID).Delete(&model.MomentImage{}).Error; err != nil {
|
||||
if err := tx.Where("moment_id = ?", req.MomentID).Delete(&model.MomentFile{}).Error; err != nil {
|
||||
return fmt.Errorf("删除动态图片失败: %w", err)
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,7 @@ func Init() {
|
||||
panic(err)
|
||||
}
|
||||
// 自动迁移
|
||||
err = DB.AutoMigrate(model.User{}, model.Country{}, model.UserRelations{}, model.FriendRelationship{}, model.ChatGroupInfo{}, model.GroupUserRelation{}, model.Moment{}, model.MomentImage{}, model.MomentLike{}, model.MomentComment{})
|
||||
err = DB.AutoMigrate(model.User{}, model.Country{}, model.UserRelations{}, model.FriendRelationship{}, model.ChatGroupInfo{}, model.GroupUserRelation{}, model.Moment{}, model.MomentFile{}, model.MomentLike{}, model.MomentComment{})
|
||||
if err != nil {
|
||||
hlog.Error("AutoMigrate failed: " + err.Error())
|
||||
return
|
||||
|
@@ -43,7 +43,7 @@ func CreateMoment(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// 保存动态到数据库
|
||||
if err = mysql.SaveMomentWithImages(&moment, req.ImageUrls); err != nil {
|
||||
if err = mysql.SaveMomentWithImages(&moment, req.FileUrls); err != nil {
|
||||
c.JSON(consts.StatusInternalServerError, &user.CreateMomentResponse{Code: user.Code_DBErr, Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
123
acquaintances/biz/handler/user_assets/user_assets_service.go
Normal file
123
acquaintances/biz/handler/user_assets/user_assets_service.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Code generated by hertz generator.
|
||||
|
||||
package user_assets
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
user_assets "acquaintances/biz/model/user_assets"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/cloudwego/hertz/pkg/protocol/consts"
|
||||
)
|
||||
|
||||
// GetUserAssets .
|
||||
// @router /v1/user_assets/ [GET]
|
||||
func GetUserAssets(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.GetUserAssetsRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.GetUserAssetsResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// AddDiamond .
|
||||
// @router /v1/user_assets/diamond/ [POST]
|
||||
func AddDiamond(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.AddDiamondRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.AddDiamondResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// AddGoldBean .
|
||||
// @router /v1/user_assets/gold_bean/ [POST]
|
||||
func AddGoldBean(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.AddGoldBeanRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.AddGoldBeanResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// AddGoldLeaf .
|
||||
// @router /v1/user_assets/gold_leaf/ [POST]
|
||||
func AddGoldLeaf(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.AddGoldLeafRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.AddGoldLeafResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// AddBankcard .
|
||||
// @router /v1/user_assets/bankcard/ [POST]
|
||||
func AddBankcard(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.AddBankcardRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.AddBankcardResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// RemoveBankcard .
|
||||
// @router /v1/user_assets/bankcard/ [DELETE]
|
||||
func RemoveBankcard(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.RemoveBankcardRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.RemoveBankcardResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// ListBankcardByUser .
|
||||
// @router /v1/user_assets/bankcard/ [GET]
|
||||
func ListBankcardByUser(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req user_assets.ListBankcardByUserRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(user_assets.ListBankcardByUserResponse)
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
@@ -23,10 +23,12 @@ type Moment struct {
|
||||
}
|
||||
|
||||
// 朋友圈图片表
|
||||
type MomentImage struct {
|
||||
type MomentFile struct {
|
||||
gorm.Model
|
||||
MomentID uint `gorm:"column:moment_id;type:int unsigned;not null;comment:'朋友圈动态ID'"`
|
||||
ImageURL string `gorm:"column:image_url;type:varchar(255);not null;comment:'图片URL'"`
|
||||
MomentID uint `gorm:"column:moment_id;type:int unsigned;not null;comment:'动态ID'"`
|
||||
FileURL string `gorm:"column:file_url;type:varchar(255);not null;comment:'文件URL'"`
|
||||
FileType string `gorm:"column:file_type;type:varchar(15);not null;comment:'文件类型'"`
|
||||
SortOrder int `gorm:"column:sort_order;type:int;default:0;comment:'文件排序'"`
|
||||
}
|
||||
|
||||
// 朋友圈点赞表
|
||||
|
@@ -1825,7 +1825,7 @@ type CreateUserRequest struct {
|
||||
Name string `thrift:"name,1" form:"name" json:"name" vd:"(len($) > 0 && len($) < 100)"`
|
||||
Gender Gender `thrift:"gender,2" form:"gender" json:"gender" vd:"($ == 1||$ == 2)"`
|
||||
Age int64 `thrift:"age,3" form:"age" json:"age" vd:"$>0"`
|
||||
Mobile string `thrift:"mobile,4" form:"mobile" form:"mobile" json:"mobile" vd:"(len($) > 0 && len($) < 12)"`
|
||||
Mobile string `thrift:"mobile,4" form:"mobile" json:"mobile" vd:"(len($) > 0 && len($) < 12)"`
|
||||
Area int64 `thrift:"area,5" form:"area" json:"area" vd:"$>0"`
|
||||
UserPassword string `thrift:"user_password,6" form:"user_password" json:"user_password" vd:"(len($) > 0 && len($) < 15)"`
|
||||
}
|
||||
@@ -13144,7 +13144,7 @@ type Moment struct {
|
||||
// 评论数
|
||||
CommentCount int32 `thrift:"comment_count,9" form:"comment_count" json:"comment_count" query:"comment_count"`
|
||||
// 动态图片列表
|
||||
Images []*MomentImage `thrift:"images,10" form:"images" json:"images" query:"images"`
|
||||
Files []*MomentFile `thrift:"files,10" form:"files" json:"files" query:"files"`
|
||||
// 创建时间
|
||||
CreatedAt string `thrift:"created_at,11" form:"created_at" json:"created_at" query:"created_at"`
|
||||
// 更新时间
|
||||
@@ -13199,8 +13199,8 @@ func (p *Moment) GetCommentCount() (v int32) {
|
||||
return p.CommentCount
|
||||
}
|
||||
|
||||
func (p *Moment) GetImages() (v []*MomentImage) {
|
||||
return p.Images
|
||||
func (p *Moment) GetFiles() (v []*MomentFile) {
|
||||
return p.Files
|
||||
}
|
||||
|
||||
func (p *Moment) GetCreatedAt() (v string) {
|
||||
@@ -13221,7 +13221,7 @@ var fieldIDToName_Moment = map[int16]string{
|
||||
7: "status",
|
||||
8: "like_count",
|
||||
9: "comment_count",
|
||||
10: "images",
|
||||
10: "files",
|
||||
11: "created_at",
|
||||
12: "updated_at",
|
||||
}
|
||||
@@ -13474,8 +13474,8 @@ func (p *Moment) ReadField10(iprot thrift.TProtocol) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_field := make([]*MomentImage, 0, size)
|
||||
values := make([]MomentImage, size)
|
||||
_field := make([]*MomentFile, 0, size)
|
||||
values := make([]MomentFile, size)
|
||||
for i := 0; i < size; i++ {
|
||||
_elem := &values[i]
|
||||
_elem.InitDefault()
|
||||
@@ -13489,7 +13489,7 @@ func (p *Moment) ReadField10(iprot thrift.TProtocol) error {
|
||||
if err := iprot.ReadListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Images = _field
|
||||
p.Files = _field
|
||||
return nil
|
||||
}
|
||||
func (p *Moment) ReadField11(iprot thrift.TProtocol) error {
|
||||
@@ -13732,13 +13732,13 @@ WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 9 end error: ", p), err)
|
||||
}
|
||||
func (p *Moment) writeField10(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("images", thrift.LIST, 10); err != nil {
|
||||
if err = oprot.WriteFieldBegin("files", thrift.LIST, 10); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteListBegin(thrift.STRUCT, len(p.Images)); err != nil {
|
||||
if err := oprot.WriteListBegin(thrift.STRUCT, len(p.Files)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range p.Images {
|
||||
for _, v := range p.Files {
|
||||
if err := v.Write(oprot); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -13796,6 +13796,331 @@ func (p *Moment) String() string {
|
||||
|
||||
}
|
||||
|
||||
// 动态图片结构体
|
||||
type MomentFile struct {
|
||||
// ID
|
||||
ID int64 `thrift:"id,1" form:"id" json:"id" query:"id"`
|
||||
// 所属动态ID
|
||||
MomentID int64 `thrift:"moment_id,2" form:"moment_id" json:"moment_id" query:"moment_id"`
|
||||
// 文件URL
|
||||
FileURL string `thrift:"file_url,3" form:"file_url" json:"file_url" query:"file_url"`
|
||||
// 文件类型
|
||||
FileType string `thrift:"file_type,4" form:"file_type" json:"file_type" query:"file_type"`
|
||||
//文件顺序
|
||||
SortOrder int8 `thrift:"sort_order,5" form:"sort_order" json:"sort_order" query:"sort_order"`
|
||||
}
|
||||
|
||||
func NewMomentFile() *MomentFile {
|
||||
return &MomentFile{}
|
||||
}
|
||||
|
||||
func (p *MomentFile) InitDefault() {
|
||||
}
|
||||
|
||||
func (p *MomentFile) GetID() (v int64) {
|
||||
return p.ID
|
||||
}
|
||||
|
||||
func (p *MomentFile) GetMomentID() (v int64) {
|
||||
return p.MomentID
|
||||
}
|
||||
|
||||
func (p *MomentFile) GetFileURL() (v string) {
|
||||
return p.FileURL
|
||||
}
|
||||
|
||||
func (p *MomentFile) GetFileType() (v string) {
|
||||
return p.FileType
|
||||
}
|
||||
|
||||
func (p *MomentFile) GetSortOrder() (v int8) {
|
||||
return p.SortOrder
|
||||
}
|
||||
|
||||
var fieldIDToName_MomentFile = map[int16]string{
|
||||
1: "id",
|
||||
2: "moment_id",
|
||||
3: "file_url",
|
||||
4: "file_type",
|
||||
5: "sort_order",
|
||||
}
|
||||
|
||||
func (p *MomentFile) Read(iprot thrift.TProtocol) (err error) {
|
||||
var fieldTypeId thrift.TType
|
||||
var fieldId int16
|
||||
|
||||
if _, err = iprot.ReadStructBegin(); err != nil {
|
||||
goto ReadStructBeginError
|
||||
}
|
||||
|
||||
for {
|
||||
_, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
|
||||
if err != nil {
|
||||
goto ReadFieldBeginError
|
||||
}
|
||||
if fieldTypeId == thrift.STOP {
|
||||
break
|
||||
}
|
||||
|
||||
switch fieldId {
|
||||
case 1:
|
||||
if fieldTypeId == thrift.I64 {
|
||||
if err = p.ReadField1(iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else if err = iprot.Skip(fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
case 2:
|
||||
if fieldTypeId == thrift.I64 {
|
||||
if err = p.ReadField2(iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else if err = iprot.Skip(fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
case 3:
|
||||
if fieldTypeId == thrift.STRING {
|
||||
if err = p.ReadField3(iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else if err = iprot.Skip(fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
case 4:
|
||||
if fieldTypeId == thrift.STRING {
|
||||
if err = p.ReadField4(iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else if err = iprot.Skip(fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
case 5:
|
||||
if fieldTypeId == thrift.BYTE {
|
||||
if err = p.ReadField5(iprot); err != nil {
|
||||
goto ReadFieldError
|
||||
}
|
||||
} else if err = iprot.Skip(fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
default:
|
||||
if err = iprot.Skip(fieldTypeId); err != nil {
|
||||
goto SkipFieldError
|
||||
}
|
||||
}
|
||||
if err = iprot.ReadFieldEnd(); err != nil {
|
||||
goto ReadFieldEndError
|
||||
}
|
||||
}
|
||||
if err = iprot.ReadStructEnd(); err != nil {
|
||||
goto ReadStructEndError
|
||||
}
|
||||
|
||||
return nil
|
||||
ReadStructBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
|
||||
ReadFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
|
||||
ReadFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_MomentFile[fieldId]), err)
|
||||
SkipFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
|
||||
|
||||
ReadFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
|
||||
ReadStructEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *MomentFile) ReadField1(iprot thrift.TProtocol) error {
|
||||
|
||||
var _field int64
|
||||
if v, err := iprot.ReadI64(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_field = v
|
||||
}
|
||||
p.ID = _field
|
||||
return nil
|
||||
}
|
||||
func (p *MomentFile) ReadField2(iprot thrift.TProtocol) error {
|
||||
|
||||
var _field int64
|
||||
if v, err := iprot.ReadI64(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_field = v
|
||||
}
|
||||
p.MomentID = _field
|
||||
return nil
|
||||
}
|
||||
func (p *MomentFile) ReadField3(iprot thrift.TProtocol) error {
|
||||
|
||||
var _field string
|
||||
if v, err := iprot.ReadString(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_field = v
|
||||
}
|
||||
p.FileURL = _field
|
||||
return nil
|
||||
}
|
||||
func (p *MomentFile) ReadField4(iprot thrift.TProtocol) error {
|
||||
|
||||
var _field string
|
||||
if v, err := iprot.ReadString(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_field = v
|
||||
}
|
||||
p.FileType = _field
|
||||
return nil
|
||||
}
|
||||
func (p *MomentFile) ReadField5(iprot thrift.TProtocol) error {
|
||||
|
||||
var _field int8
|
||||
if v, err := iprot.ReadByte(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_field = v
|
||||
}
|
||||
p.SortOrder = _field
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MomentFile) Write(oprot thrift.TProtocol) (err error) {
|
||||
var fieldId int16
|
||||
if err = oprot.WriteStructBegin("MomentFile"); err != nil {
|
||||
goto WriteStructBeginError
|
||||
}
|
||||
if p != nil {
|
||||
if err = p.writeField1(oprot); err != nil {
|
||||
fieldId = 1
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField2(oprot); err != nil {
|
||||
fieldId = 2
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField3(oprot); err != nil {
|
||||
fieldId = 3
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField4(oprot); err != nil {
|
||||
fieldId = 4
|
||||
goto WriteFieldError
|
||||
}
|
||||
if err = p.writeField5(oprot); err != nil {
|
||||
fieldId = 5
|
||||
goto WriteFieldError
|
||||
}
|
||||
}
|
||||
if err = oprot.WriteFieldStop(); err != nil {
|
||||
goto WriteFieldStopError
|
||||
}
|
||||
if err = oprot.WriteStructEnd(); err != nil {
|
||||
goto WriteStructEndError
|
||||
}
|
||||
return nil
|
||||
WriteStructBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
|
||||
WriteFieldError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
|
||||
WriteFieldStopError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
|
||||
WriteStructEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *MomentFile) writeField1(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("id", thrift.I64, 1); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteI64(p.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
|
||||
}
|
||||
func (p *MomentFile) writeField2(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("moment_id", thrift.I64, 2); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteI64(p.MomentID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err)
|
||||
}
|
||||
func (p *MomentFile) writeField3(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("file_url", thrift.STRING, 3); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteString(p.FileURL); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err)
|
||||
}
|
||||
func (p *MomentFile) writeField4(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("file_type", thrift.STRING, 4); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteString(p.FileType); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err)
|
||||
}
|
||||
func (p *MomentFile) writeField5(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("sort_order", thrift.BYTE, 5); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteByte(p.SortOrder); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = oprot.WriteFieldEnd(); err != nil {
|
||||
goto WriteFieldEndError
|
||||
}
|
||||
return nil
|
||||
WriteFieldBeginError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 5 begin error: ", p), err)
|
||||
WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err)
|
||||
}
|
||||
|
||||
func (p *MomentFile) String() string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("MomentFile(%+v)", *p)
|
||||
|
||||
}
|
||||
|
||||
// 发布动态请求
|
||||
type CreateMomentRequest struct {
|
||||
// 发布者用户ID
|
||||
@@ -13807,7 +14132,7 @@ type CreateMomentRequest struct {
|
||||
// 发布地点
|
||||
Location string `thrift:"location,4" form:"location" json:"location" query:"location"`
|
||||
// 图片URL列表
|
||||
ImageUrls []string `thrift:"image_urls,5" form:"image_urls" json:"image_urls" query:"image_urls"`
|
||||
FileUrls []*MomentFile `thrift:"file_urls,5" form:"file_urls" json:"file_urls" query:"file_urls"`
|
||||
}
|
||||
|
||||
func NewCreateMomentRequest() *CreateMomentRequest {
|
||||
@@ -13833,8 +14158,8 @@ func (p *CreateMomentRequest) GetLocation() (v string) {
|
||||
return p.Location
|
||||
}
|
||||
|
||||
func (p *CreateMomentRequest) GetImageUrls() (v []string) {
|
||||
return p.ImageUrls
|
||||
func (p *CreateMomentRequest) GetFileUrls() (v []*MomentFile) {
|
||||
return p.FileUrls
|
||||
}
|
||||
|
||||
var fieldIDToName_CreateMomentRequest = map[int16]string{
|
||||
@@ -13842,7 +14167,7 @@ var fieldIDToName_CreateMomentRequest = map[int16]string{
|
||||
2: "content",
|
||||
3: "visibility",
|
||||
4: "location",
|
||||
5: "image_urls",
|
||||
5: "file_urls",
|
||||
}
|
||||
|
||||
func (p *CreateMomentRequest) Read(iprot thrift.TProtocol) (err error) {
|
||||
@@ -13981,14 +14306,14 @@ func (p *CreateMomentRequest) ReadField5(iprot thrift.TProtocol) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_field := make([]string, 0, size)
|
||||
_field := make([]*MomentFile, 0, size)
|
||||
values := make([]MomentFile, size)
|
||||
for i := 0; i < size; i++ {
|
||||
_elem := &values[i]
|
||||
_elem.InitDefault()
|
||||
|
||||
var _elem string
|
||||
if v, err := iprot.ReadString(); err != nil {
|
||||
if err := _elem.Read(iprot); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_elem = v
|
||||
}
|
||||
|
||||
_field = append(_field, _elem)
|
||||
@@ -13996,7 +14321,7 @@ func (p *CreateMomentRequest) ReadField5(iprot thrift.TProtocol) error {
|
||||
if err := iprot.ReadListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.ImageUrls = _field
|
||||
p.FileUrls = _field
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14109,14 +14434,14 @@ WriteFieldEndError:
|
||||
return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err)
|
||||
}
|
||||
func (p *CreateMomentRequest) writeField5(oprot thrift.TProtocol) (err error) {
|
||||
if err = oprot.WriteFieldBegin("image_urls", thrift.LIST, 5); err != nil {
|
||||
if err = oprot.WriteFieldBegin("file_urls", thrift.LIST, 5); err != nil {
|
||||
goto WriteFieldBeginError
|
||||
}
|
||||
if err := oprot.WriteListBegin(thrift.STRING, len(p.ImageUrls)); err != nil {
|
||||
if err := oprot.WriteListBegin(thrift.STRUCT, len(p.FileUrls)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range p.ImageUrls {
|
||||
if err := oprot.WriteString(v); err != nil {
|
||||
for _, v := range p.FileUrls {
|
||||
if err := v.Write(oprot); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
117
acquaintances/biz/model/user_assets.go
Normal file
117
acquaintances/biz/model/user_assets.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"google.golang.org/genproto/googleapis/type/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Asset Type
|
||||
const (
|
||||
diamond = iota
|
||||
goldBean
|
||||
goldLeaf
|
||||
)
|
||||
|
||||
// source Type
|
||||
const (
|
||||
bankcard = iota
|
||||
usdt
|
||||
)
|
||||
|
||||
type UserAssets struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'" json:"user_id"`
|
||||
Status int8 `gorm:"column:status;type:tinyint unsigned;default:0;comment:'资金账户状态;0:正常,1:异常'" json:"status"`
|
||||
Diamond decimal.Decimal `gorm:"-" ;json:"diamond"`
|
||||
GoldBean decimal.Decimal `gorm:"-" ;json:"gold_bean"`
|
||||
GoldLeaf decimal.Decimal `gorm:"-" ;json:"gold_leaf"`
|
||||
}
|
||||
|
||||
func (*UserAssets) TableName() string {
|
||||
return "user_assets"
|
||||
}
|
||||
|
||||
type Diamond struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
|
||||
Diamond decimal.Decimal `gorm:"column:diamond;type:decimal(15,2);not null;default:0.00;comment:'钻石余额'"`
|
||||
}
|
||||
|
||||
func (*Diamond) TableName() string {
|
||||
return "diamond"
|
||||
}
|
||||
|
||||
type GoldBean struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
|
||||
GoldBean decimal.Decimal `gorm:"column:gold_bean;type:decimal(15,2);not null;default:0.00;comment:'金豆余额'"`
|
||||
}
|
||||
|
||||
func (*GoldBean) TableName() string {
|
||||
return "gold_bean"
|
||||
}
|
||||
|
||||
type GoldLeaf struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
|
||||
GoldLeaf decimal.Decimal `gorm:"column:gold_leaf;type:decimal(15,2);not null;default:0.00;comment:'金叶余额'"`
|
||||
}
|
||||
|
||||
func (*GoldLeaf) TableName() string {
|
||||
return "gold_leaf"
|
||||
}
|
||||
|
||||
type Bankcard struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
|
||||
BankCardNumber string `gorm:"column:bankcard_number;type:varchar(19);not null;uniqueIndex:uniq_bank_card_number;comment:'银行卡号码'"`
|
||||
BankName string `gorm:"column:bank_name;type:varchar(100);not null;index:idx_bank_name;comment:'银行名称'"`
|
||||
AccountType string `gorm:"column:account_type;type:varchar(50);not null;default:'basic';comment:'账户类型'"`
|
||||
IsDefault bool `gorm:"column:is_default;type:tinyint(1);not null;default:0;index:idx_is_default;comment:'是否默认银行卡'"`
|
||||
}
|
||||
|
||||
func (*Bankcard) TableName() string {
|
||||
return "bank_card"
|
||||
}
|
||||
|
||||
type AssetHistory struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
|
||||
TargetID string `gorm:"column:target_id;type:bigint;comment:'目标商品id'"`
|
||||
AssetType int8 `gorm:"column:asset_type;type:tinyint;not null;comment:资产类型(0-钻石,1-金叶,2-金豆)"`
|
||||
Amount decimal.Decimal `gorm:"column:amount;type:decimal(15,2);not null;default:0.00;comment:'数量'"`
|
||||
SourceType int8 `gorm:"column:source_type;type:tinyint;not null;comment:来源类型(0-银行卡,1-usdt)"`
|
||||
SourceAccount string `gorm:"column:source_account;type:varchar(50);not null;index:uniq_bank_card_number;comment:'来源账号'"`
|
||||
}
|
||||
|
||||
func (*AssetHistory) TableName() string {
|
||||
return "asset_history"
|
||||
}
|
||||
|
||||
// 物品类型枚举,定义支持的消费物品类型
|
||||
type ItemType uint8
|
||||
|
||||
const (
|
||||
ItemTypeDiamond ItemType = 1 // 钻石
|
||||
ItemTypeGoldBean ItemType = 2 // 金豆
|
||||
ItemTypeGoldLeaf ItemType = 3 // 金叶
|
||||
)
|
||||
|
||||
// ConsumptionRecord 物品消费记录表
|
||||
type ConsumptionRecord struct {
|
||||
gorm.Model
|
||||
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
|
||||
ItemType ItemType `gorm:"column:item_type;type:tinyint unsigned;not null;index:idx_item_type;comment:'物品类型 1:钻石 2:金豆 3:金叶'"`
|
||||
Amount decimal.Decimal `gorm:"column:amount;type:decimal(15,2);not null;comment:'消费数量'"`
|
||||
BeforeAmount decimal.Decimal `gorm:"column:before_amount;type:decimal(15,2);not null;comment:'操作前数量'"`
|
||||
AfterAmount decimal.Decimal `gorm:"column:after_amount;type:decimal(15,2);not null;comment:'操作后数量'"`
|
||||
BusinessType string `gorm:"column:business_type;type:varchar(32);not null;index:idx_business_type;comment:'业务类型,如:buy_vip购买会员、send_gift赠送礼物'"`
|
||||
BusinessID string `gorm:"column:business_id;type:varchar(64);index:idx_business_id;comment:'关联业务ID,如订单ID、礼物ID'"`
|
||||
Remark string `gorm:"column:remark;type:varchar(255);comment:'备注信息'"`
|
||||
Operator string `gorm:"column:operator;type:varchar(32);comment:'操作人,系统操作填system'"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (*ConsumptionRecord) TableName() string {
|
||||
return "consumption_records"
|
||||
}
|
6016
acquaintances/biz/model/user_assets/user_assets.go
Normal file
6016
acquaintances/biz/model/user_assets/user_assets.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,15 @@ package router
|
||||
import (
|
||||
area "acquaintances/biz/router/area"
|
||||
user "acquaintances/biz/router/user"
|
||||
user_assets "acquaintances/biz/router/user_assets"
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
)
|
||||
|
||||
// GeneratedRegister registers routers generated by IDL.
|
||||
func GeneratedRegister(r *server.Hertz) {
|
||||
//INSERT_POINT: DO NOT DELETE THIS LINE!
|
||||
user_assets.Register(r)
|
||||
|
||||
area.Register(r)
|
||||
|
||||
user.Register(r)
|
||||
|
77
acquaintances/biz/router/user_assets/middleware.go
Normal file
77
acquaintances/biz/router/user_assets/middleware.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Code generated by hertz generator.
|
||||
|
||||
package user_assets
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
|
||||
func rootMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _v1Mw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _user_assetsMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _getuserassetsMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _bankcardMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _removebankcardMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _listbankcardbyuserMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _addbankcardMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _diamondMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _adddiamondMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _gold_beanMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _addgoldbeanMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _gold_leafMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _addgoldleafMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
45
acquaintances/biz/router/user_assets/user_assets.go
Normal file
45
acquaintances/biz/router/user_assets/user_assets.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Code generated by hertz generator. DO NOT EDIT.
|
||||
|
||||
package user_assets
|
||||
|
||||
import (
|
||||
user_assets "acquaintances/biz/handler/user_assets"
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
)
|
||||
|
||||
/*
|
||||
This file will register all the routes of the services in the master idl.
|
||||
And it will update automatically when you use the "update" command for the idl.
|
||||
So don't modify the contents of the file, or your code will be deleted when it is updated.
|
||||
*/
|
||||
|
||||
// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
|
||||
func Register(r *server.Hertz) {
|
||||
|
||||
root := r.Group("/", rootMw()...)
|
||||
{
|
||||
_v1 := root.Group("/v1", _v1Mw()...)
|
||||
{
|
||||
_user_assets := _v1.Group("/user_assets", _user_assetsMw()...)
|
||||
_user_assets.GET("/", append(_getuserassetsMw(), user_assets.GetUserAssets)...)
|
||||
{
|
||||
_bankcard := _user_assets.Group("/bankcard", _bankcardMw()...)
|
||||
_bankcard.DELETE("/", append(_removebankcardMw(), user_assets.RemoveBankcard)...)
|
||||
_bankcard.GET("/", append(_listbankcardbyuserMw(), user_assets.ListBankcardByUser)...)
|
||||
_bankcard.POST("/", append(_addbankcardMw(), user_assets.AddBankcard)...)
|
||||
}
|
||||
{
|
||||
_diamond := _user_assets.Group("/diamond", _diamondMw()...)
|
||||
_diamond.POST("/", append(_adddiamondMw(), user_assets.AddDiamond)...)
|
||||
}
|
||||
{
|
||||
_gold_bean := _user_assets.Group("/gold_bean", _gold_beanMw()...)
|
||||
_gold_bean.POST("/", append(_addgoldbeanMw(), user_assets.AddGoldBean)...)
|
||||
}
|
||||
{
|
||||
_gold_leaf := _user_assets.Group("/gold_leaf", _gold_leafMw()...)
|
||||
_gold_leaf.POST("/", append(_addgoldleafMw(), user_assets.AddGoldLeaf)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -147,6 +147,7 @@ struct SmsResponse{
|
||||
struct CheckOtpRequest{
|
||||
1: string phone (api.path="phone",api.body="phone")
|
||||
2: string otp (api.path="otp",api.body="otp")
|
||||
3: string type (api.path="type",api.body="type")
|
||||
}
|
||||
|
||||
struct CheckOtpResponse{
|
||||
@@ -436,18 +437,27 @@ struct Moment {
|
||||
7: ContentStatus status // 状态
|
||||
8: i32 like_count // 点赞数
|
||||
9: i32 comment_count // 评论数
|
||||
10: list<MomentImage> images // 动态图片列表
|
||||
10: list<MomentFile> files // 动态图片列表
|
||||
11: string created_at // 创建时间
|
||||
12: string updated_at // 更新时间
|
||||
}
|
||||
|
||||
// 动态图片结构体
|
||||
struct MomentFile {
|
||||
1: i64 id // ID
|
||||
2: i64 moment_id // 所属动态ID
|
||||
3: string file_url // 文件URL
|
||||
4: string file_type // 文件类型
|
||||
5: i8 sort_order //文件顺序
|
||||
}
|
||||
|
||||
// 发布动态请求
|
||||
struct CreateMomentRequest {
|
||||
1: string user_id // 发布者用户ID
|
||||
2: string content // 动态内容
|
||||
3: MomentVisibility visibility // 可见性,默认公开
|
||||
4: string location // 发布地点
|
||||
5: list<string> image_urls // 图片URL列表
|
||||
5: list<MomentFile> file_urls // 图片URL列表
|
||||
}
|
||||
|
||||
// 发布动态响应
|
||||
|
108
acquaintances/idl/user_assets.thrift
Normal file
108
acquaintances/idl/user_assets.thrift
Normal file
@@ -0,0 +1,108 @@
|
||||
namespace go user_assets
|
||||
|
||||
enum Code {
|
||||
Success = 1
|
||||
ParamInvalid = 2
|
||||
DBErr = 3
|
||||
ServerError = 4
|
||||
}
|
||||
|
||||
struct UserAssets {
|
||||
1: string user_id
|
||||
2: string diamond
|
||||
3: string gold_bean
|
||||
4: string gold_leaf
|
||||
}
|
||||
|
||||
struct GetUserAssetsRequest{
|
||||
1: string user_id
|
||||
}
|
||||
|
||||
struct GetUserAssetsResponse{
|
||||
1: UserAssets user_assets
|
||||
}
|
||||
|
||||
struct AddDiamondRequest {
|
||||
1: string user_id
|
||||
2: string bankcard_number
|
||||
3: string diamond
|
||||
}
|
||||
|
||||
struct AddDiamondResponse {
|
||||
1: Code code
|
||||
2: string msg
|
||||
}
|
||||
|
||||
struct AddGoldBeanRequest {
|
||||
1: string user_id
|
||||
2: string bankcard_number
|
||||
3: string diamond
|
||||
}
|
||||
|
||||
struct AddGoldBeanResponse {
|
||||
1: Code code
|
||||
2: string msg
|
||||
}
|
||||
|
||||
struct AddGoldLeafRequest {
|
||||
1: string user_id
|
||||
2: string bankcard_number
|
||||
3: string diamond
|
||||
}
|
||||
|
||||
struct AddGoldLeafResponse {
|
||||
1: Code code
|
||||
2: string msg
|
||||
}
|
||||
|
||||
struct Bankcard {
|
||||
1: string user_id
|
||||
2: string bankcard_number
|
||||
3: string bank_name
|
||||
4: string account_type
|
||||
5: bool is_default
|
||||
}
|
||||
|
||||
struct AddBankcardRequest {
|
||||
1: string user_id
|
||||
2: string bankcard_number
|
||||
3: string bank_name
|
||||
4: string account_type
|
||||
5: bool is_default
|
||||
}
|
||||
|
||||
struct AddBankcardResponse {
|
||||
1: Code code
|
||||
2: string msg
|
||||
}
|
||||
|
||||
struct RemoveBankcardRequest {
|
||||
1: string user_id
|
||||
2: string bankcard_number
|
||||
}
|
||||
|
||||
struct RemoveBankcardResponse {
|
||||
1: Code code
|
||||
2: string msg
|
||||
}
|
||||
|
||||
struct ListBankcardByUserRequest {
|
||||
1: string user_id
|
||||
}
|
||||
|
||||
struct ListBankcardByUserResponse {
|
||||
1: Code code
|
||||
2: string msg
|
||||
3: list<Bankcard> bankcards
|
||||
4: i32 total
|
||||
}
|
||||
|
||||
service UserAssetsService {
|
||||
GetUserAssetsResponse GetUserAssets(1:GetUserAssetsRequest req)(api.get="/v1/user_assets/") //获取指定用户银行卡列表
|
||||
AddDiamondResponse AddDiamond(1:AddDiamondRequest req)(api.post="/v1/user_assets/diamond/") //充值钻石
|
||||
AddGoldBeanResponse AddGoldBean(1:AddGoldBeanRequest req)(api.post="/v1/user_assets/gold_bean/") //充值金豆
|
||||
AddGoldLeafResponse AddGoldLeaf(1:AddGoldLeafRequest req)(api.post="/v1/user_assets/gold_leaf/") //充值金叶
|
||||
AddBankcardResponse AddBankcard(1:AddBankcardRequest req)(api.post="/v1/user_assets/bankcard/") //添加银行卡
|
||||
RemoveBankcardResponse RemoveBankcard(1:RemoveBankcardRequest req)(api.delete="/v1/user_assets/bankcard/") //删除银行卡
|
||||
ListBankcardByUserResponse ListBankcardByUser(1:ListBankcardByUserRequest req)(api.get="/v1/user_assets/bankcard/") //获取指定用户银行卡列表
|
||||
}
|
@@ -2,6 +2,7 @@ package redis
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/common/hlog"
|
||||
"log"
|
||||
"pushNotificationCenter/protocol"
|
||||
)
|
||||
|
||||
@@ -13,13 +14,48 @@ func PublishToRedis(channel string, message []byte) {
|
||||
}
|
||||
|
||||
// 订阅redis私聊通道消息
|
||||
func SubscribeToUserChannel(sendChan chan []byte, userid string) {
|
||||
//
|
||||
// func SubscribeToUserChannel(sendChan chan []byte, userid string) {
|
||||
// pubsub := rdb.Subscribe(ctx, protocol.TypePrivate+":"+userid)
|
||||
// defer pubsub.Close()
|
||||
//
|
||||
// ch := pubsub.Channel()
|
||||
// for msg := range ch {
|
||||
// sendChan <- []byte(msg.Payload)
|
||||
// }
|
||||
// }
|
||||
func SubscribeToUserChannel(sendChan chan []byte, userid string, done <-chan struct{}) {
|
||||
// 订阅Redis频道
|
||||
pubsub := rdb.Subscribe(ctx, protocol.TypePrivate+":"+userid)
|
||||
defer pubsub.Close()
|
||||
defer pubsub.Close() // 确保函数退出时关闭Redis订阅
|
||||
|
||||
ch := pubsub.Channel()
|
||||
for msg := range ch {
|
||||
sendChan <- []byte(msg.Payload)
|
||||
|
||||
// 使用for循环结合select,同时监听Redis消息和退出信号
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-ch:
|
||||
// 1. 检查Redis订阅通道是否关闭
|
||||
if !ok {
|
||||
hlog.Errorf("用户 %s 的Redis订阅通道已关闭", userid)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 检查sendChan是否可写(避免向关闭的通道发送)
|
||||
select {
|
||||
case sendChan <- []byte(msg.Payload):
|
||||
// 消息发送成功
|
||||
case <-done:
|
||||
// 收到退出信号,优先处理退出
|
||||
log.Printf("用户 %s 的消息发送已终止", userid)
|
||||
return
|
||||
}
|
||||
|
||||
case <-done:
|
||||
// 收到退出信号(如WebSocket连接关闭),立即退出
|
||||
log.Printf("用户 %s 的订阅已取消", userid)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,23 +5,41 @@ import (
|
||||
"github.com/cloudwego/hertz/pkg/common/hlog"
|
||||
"net/http"
|
||||
"pushNotificationCenter/biz/dal/redis"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"pushNotificationCenter/protocol"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// 客户端连接
|
||||
// 定义全局互斥锁(确保已在代码中声明)
|
||||
var clientsMu sync.Mutex
|
||||
|
||||
// Client结构体
|
||||
type Client struct {
|
||||
ID string
|
||||
Conn *websocket.Conn
|
||||
Send chan []byte
|
||||
UserID string // 用户ID
|
||||
ID string
|
||||
Conn *websocket.Conn
|
||||
Send chan []byte
|
||||
UserID string
|
||||
Done chan struct{}
|
||||
isClosed bool // 标记连接是否已关闭
|
||||
mu sync.Mutex // 保护isClosed和lastPingAt的并发读写
|
||||
lastPingAt time.Time // 最后一次收到客户端ping的时间
|
||||
}
|
||||
|
||||
// 客户端管理器
|
||||
var clients = make(map[string]*Client) // 用户ID -> Client
|
||||
// 封装安全关闭done通道的方法
|
||||
func (c *Client) closeDone() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if !c.isClosed {
|
||||
close(c.Done)
|
||||
c.isClosed = true // 标记为已关闭
|
||||
}
|
||||
}
|
||||
|
||||
// clients映射和upgrader保持不变
|
||||
var clients = make(map[string]*Client) // 用户ID -> Client
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // 允许所有跨域请求
|
||||
@@ -29,83 +47,243 @@ var upgrader = websocket.Upgrader{
|
||||
}
|
||||
|
||||
func HandleConnections(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 升级为WebSocket连接
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
hlog.Error(err)
|
||||
return
|
||||
}
|
||||
// 仅在HandleConnections中管理Conn的关闭,避免重复关闭
|
||||
defer ws.Close()
|
||||
|
||||
// 读取用户ID (实际应用中应从认证信息获取)
|
||||
// 读取用户ID
|
||||
userID := r.URL.Query().Get("user_id")
|
||||
if userID == "" {
|
||||
hlog.Info("未提供用户ID")
|
||||
hlog.Info("未提供用户ID,关闭连接")
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新客户端
|
||||
// 创建客户端实例
|
||||
done := make(chan struct{})
|
||||
client := &Client{
|
||||
ID: userID,
|
||||
UserID: userID,
|
||||
Conn: ws,
|
||||
Send: make(chan []byte, 256),
|
||||
Done: done,
|
||||
}
|
||||
|
||||
// 注册客户端
|
||||
// 注册客户端(加锁确保并发安全)
|
||||
clientsMu.Lock()
|
||||
clients[userID] = client
|
||||
clientsMu.Unlock()
|
||||
hlog.Infof("用户 %s 已连接", userID)
|
||||
|
||||
// 订阅redis通道
|
||||
go redis.SubscribeToUserChannel(client.Send, client.UserID)
|
||||
//go redis.SubscribeToUserGroupChannel(client.Send)
|
||||
go redis.SubscribeToSystemChannel(client.Send)
|
||||
// 启动订阅协程(传入done通道)
|
||||
go redis.SubscribeToUserChannel(client.Send, client.UserID, done)
|
||||
|
||||
// 读写协程
|
||||
// 启动写协程
|
||||
go client.writePump()
|
||||
|
||||
// 启动应用层心跳检测协程
|
||||
go client.startAppHeartbeat()
|
||||
// 执行读协程(阻塞,退出时表示连接关闭)
|
||||
client.readPump()
|
||||
|
||||
// 读协程退出后,执行统一清理
|
||||
hlog.Infof("用户 %s 断开连接,开始清理资源", userID)
|
||||
|
||||
// 1. 关闭done通道,通知所有关联协程退出
|
||||
client.closeDone()
|
||||
|
||||
// 2. 从clients中删除(加锁)
|
||||
clientsMu.Lock()
|
||||
delete(clients, userID)
|
||||
clientsMu.Unlock()
|
||||
|
||||
// 3. 关闭Send通道,防止后续发送
|
||||
close(client.Send)
|
||||
}
|
||||
|
||||
// 示例:向所有在线用户广播消息(需加锁)
|
||||
func Broadcast(message []byte) {
|
||||
clientsMu.Lock()
|
||||
defer clientsMu.Unlock() // 确保锁释放
|
||||
|
||||
for _, client := range clients {
|
||||
// 向每个客户端的Send通道发送消息(需检查通道状态)
|
||||
select {
|
||||
case client.Send <- message:
|
||||
default:
|
||||
hlog.Warnf("用户 %s 的消息通道已满,消息丢弃", client.UserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) readPump() {
|
||||
defer func() {
|
||||
// 连接关闭时清理
|
||||
delete(clients, c.UserID)
|
||||
c.Conn.Close()
|
||||
hlog.Infof("用户 %s 已断开", c.UserID)
|
||||
}()
|
||||
// 初始化最后心跳时间为连接建立时间
|
||||
c.mu.Lock()
|
||||
c.lastPingAt = time.Now()
|
||||
c.mu.Unlock()
|
||||
|
||||
for {
|
||||
_, message, err := c.Conn.ReadMessage()
|
||||
mt, message, err := c.Conn.ReadMessage()
|
||||
if err != nil {
|
||||
// 连接错误,退出循环触发清理
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
hlog.Errorf("读取错误: %v", err)
|
||||
hlog.Errorf("用户 %s 读取错误: %v", c.UserID, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 处理应用层心跳(ping请求)
|
||||
if mt == websocket.TextMessage && string(message) == protocol.TypePing {
|
||||
c.mu.Lock()
|
||||
c.lastPingAt = time.Now() // 更新最后心跳时间
|
||||
c.mu.Unlock()
|
||||
|
||||
// 回复pong响应
|
||||
if err := c.Conn.WriteMessage(websocket.TextMessage, []byte(protocol.TypePong)); err != nil {
|
||||
hlog.Errorf("用户 %s 回复pong失败: %v", c.UserID, err)
|
||||
return // 回复失败,断开连接
|
||||
}
|
||||
hlog.Debugf("用户 %s 收到ping,已回复pong", c.UserID)
|
||||
continue // 跳过后续消息处理
|
||||
}
|
||||
|
||||
// 处理非心跳的业务消息
|
||||
handleIncomingMessage(c.UserID, message)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用层心跳检测(检查客户端是否超时未发送ping)
|
||||
func (c *Client) startAppHeartbeat() {
|
||||
// 心跳超时阈值(如30秒未收到ping则断开)
|
||||
const timeout = 30 * time.Second
|
||||
// 检测间隔(每10秒检查一次)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.Done:
|
||||
// 连接已关闭,退出检测
|
||||
return
|
||||
case <-ticker.C:
|
||||
c.mu.Lock()
|
||||
lastPing := c.lastPingAt
|
||||
c.mu.Unlock()
|
||||
|
||||
// 计算距离上次ping的时间
|
||||
elapsed := time.Since(lastPing)
|
||||
if elapsed > timeout {
|
||||
hlog.Warnf("用户 %s 心跳超时(%v未收到ping),断开连接", c.UserID, elapsed)
|
||||
c.closeDone() // 触发连接关闭
|
||||
return
|
||||
}
|
||||
|
||||
hlog.Debugf("用户 %s 心跳正常(最后ping时间: %v)", c.UserID, lastPing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保writePump正确处理done和Send通道关闭的情况(参考之前的改进)
|
||||
func (c *Client) writePump() {
|
||||
defer c.Conn.Close()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
hlog.Errorf("writePump panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.Send:
|
||||
if !ok {
|
||||
// 通道关闭
|
||||
c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
// Send通道关闭,发送关闭消息
|
||||
if c.Conn != nil {
|
||||
_ = c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.Conn == nil {
|
||||
hlog.Errorf("用户 %s 连接已失效,跳过消息发送", c.UserID)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
hlog.Errorf("发送错误: %v", err)
|
||||
hlog.Errorf("用户 %s 发送错误: %v", c.UserID, err)
|
||||
return
|
||||
}
|
||||
case <-c.Done:
|
||||
// 收到退出信号,退出写协程
|
||||
hlog.Infof("用户 %s writePump 退出", c.UserID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func (c *Client) writePump() {
|
||||
// defer c.Conn.Close()
|
||||
//
|
||||
// for {
|
||||
// select {
|
||||
// case message, ok := <-c.Send:
|
||||
// if !ok {
|
||||
// // 通道关闭
|
||||
// c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if err := c.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
// hlog.Errorf("发送错误: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//func (c *Client) writePump() {
|
||||
// // 移除defer c.Conn.Close(),避免重复关闭(由连接管理器统一关闭)
|
||||
// defer func() {
|
||||
// // 捕获panic,避免单个连接错误导致整个服务崩溃
|
||||
// if r := recover(); r != nil {
|
||||
// hlog.Errorf("writePump panic: %v", r)
|
||||
// }
|
||||
// // 关闭Send通道,通知其他协程停止发送消息
|
||||
// close(c.Send)
|
||||
// }()
|
||||
//
|
||||
// for {
|
||||
// select {
|
||||
// case message, ok := <-c.Send:
|
||||
// // 1. 检查Send通道是否关闭
|
||||
// if !ok {
|
||||
// // 通道关闭时,尝试发送CloseMessage(但需先检查连接有效性)
|
||||
// if c.Conn == nil {
|
||||
// hlog.Warnf("连接已关闭,无法发送CloseMessage")
|
||||
// return
|
||||
// }
|
||||
// // 发送CloseMessage时也可能失败,需处理错误
|
||||
// if err := c.Conn.WriteMessage(websocket.CloseMessage, []byte{}); err != nil {
|
||||
// hlog.Errorf("发送CloseMessage失败: %v", err)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 2. 检查连接是否已为nil(避免空指针)
|
||||
// if c.Conn == nil {
|
||||
// hlog.Errorf("连接已失效(nil),跳过消息发送")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 3. 尝试写入消息,处理可能的连接关闭错误
|
||||
// if err := c.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
// hlog.Errorf("发送错误: %v", err)
|
||||
// // 连接已不可用,退出循环
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
func handleIncomingMessage(senderID string, msg []byte) {
|
||||
var message protocol.Message
|
||||
if err := json.Unmarshal(msg, &message); err != nil {
|
||||
|
@@ -9,6 +9,8 @@ const (
|
||||
TypeSys = "sys"
|
||||
TypeAudioCall = "audioCall"
|
||||
TypeVideoCall = "videoCall"
|
||||
TypePing = "ping"
|
||||
TypePong = "pong"
|
||||
)
|
||||
|
||||
// 消息固定值
|
||||
|
Binary file not shown.
BIN
userAuthCenter.zip
Normal file
BIN
userAuthCenter.zip
Normal file
Binary file not shown.
@@ -88,5 +88,5 @@ func LoadRedis(file *ini.File) {
|
||||
RedisHost = file.Section("redis").Key("RedisHost").MustString("127.0.0.1")
|
||||
RedisPort = file.Section("redis").Key("RedisPort").MustString("6379")
|
||||
RedisUser = file.Section("redis").Key("RedisUser").MustString("root")
|
||||
RedisPassWord = file.Section("redis").Key("RedisPassWord").MustString("123456")
|
||||
RedisPassWord = file.Section("redis").Key("RedisPassWord").MustString("cdsrh")
|
||||
}
|
||||
|
BIN
userAuthCenter/userAuthCenter
Normal file
BIN
userAuthCenter/userAuthCenter
Normal file
Binary file not shown.
Reference in New Issue
Block a user