feature/post #1

Open
daniel.w wants to merge 7 commits from feature/post into main
29 changed files with 1110 additions and 86 deletions

View File

@ -4,3 +4,25 @@ Etcd:
Hosts: Hosts:
- 127.0.0.1:2379 - 127.0.0.1:2379
Key: tweeting.rpc Key: tweeting.rpc
Cache:
- Host: 127.0.0.1:7001
type: cluster
- Host: 127.0.0.1:7002
type: cluster
- Host: 127.0.0.1:7003
type: cluster
- Host: 127.0.0.1:7004
type: cluster
- Host: 127.0.0.1:7005
type: cluster
- Host: 127.0.0.1:7006
type: cluster
Mongo:
Schema: mongodb
Host: 127.0.0.1
User: ""
Password: ""
Port: "27017"
Database: digimon_tweeting

View File

@ -0,0 +1,5 @@
use digimon_tweeting;
db.post.createIndex({ "uid": 1, "create_time": 1 });
db.post.createIndex({ "is_ad": 1, "create_time": 1 });
db.post.createIndex({ "create_time": 1 });

View File

@ -0,0 +1,5 @@
db.post_likes.createIndex(
{ "target_id": 1, "uid": 1, "type": 1 },
{ unique: true }
);
db.post_likes.createIndex({ "create_time": 1 });

View File

@ -15,12 +15,17 @@ message Pager {
int64 index=3; // int64 index=3; //
} }
message Media{
string type =1;
string url =2;
}
// //
message NewPostReq { message NewPostReq {
int64 user_id = 1; // ID string uid = 1; // ID
string content = 2; // string content = 2; //
repeated string tags = 3; // repeated string tags = 3; //
repeated string media_url = 4; // Media URL repeated Media media = 4; // Media URL
bool is_ad = 5; // bool is_ad = 5; //
} }
@ -38,7 +43,7 @@ message DeletePostsReq {
message UpdatePostReq { message UpdatePostReq {
string post_id = 1; // ID string post_id = 1; // ID
repeated string tags = 2; // repeated string tags = 2; //
repeated string media_url = 3; // Media URL repeated Media media = 3; // Media URL
optional string content = 4; // optional string content = 4; //
optional int64 like_count = 5; // optional int64 like_count = 5; //
optional int64 dislike_count = 6; // optional int64 dislike_count = 6; //
@ -46,10 +51,9 @@ message UpdatePostReq {
// //
message QueryPostsReq { message QueryPostsReq {
repeated int64 user_id = 1; // ID篩選貼文 repeated string uid = 1; // ID篩選貼文
repeated int64 id = 2; // ID篩選貼文 repeated string id = 2; // ID篩選貼文
repeated string tags = 3; // optional int32 only_ads = 4; // 0 1 2
optional bool only_ads = 4; //
int32 page_index = 5; // int32 page_index = 5; //
int32 page_size = 6; // int32 page_size = 6; //
} }
@ -57,10 +61,10 @@ message QueryPostsReq {
// //
message PostDetailItem { message PostDetailItem {
string post_id = 1; // ID string post_id = 1; // ID
int64 user_id = 2; // ID string uid = 2; // ID
string content = 3; // string content = 3; //
repeated string tags = 4; // repeated string tags = 4; //
repeated string media_url = 5; // URL repeated Media media = 5; // URL
bool is_ad = 6; // bool is_ad = 6; //
int64 created_at = 7; // int64 created_at = 7; //
int64 update_at = 8; // int64 update_at = 8; //
@ -77,14 +81,30 @@ message ListPostsResp {
// / // /
message LikeReq { message LikeReq {
string target_id = 1; // IDID或評論ID string target_id = 1; // IDID或評論ID
int64 user_id = 2; // ID string uid = 2; // ID
int64 like_type = 3; // int64 like_type = 3; //
} }
message GetLikeStatusReq{
string uid = 1; // ID
repeated string target_id = 2; // IDID或評論ID
int64 like_type = 3; //
}
message GetLikeStatusItem{
string target_id = 1; // IDID或評論ID
bool status = 2; //
}
message GetLikeStatusResp{
repeated GetLikeStatusItem data = 1; // IDID或評論ID
}
// / // /
message LikeItem { message LikeItem {
string target_id = 1; // IDID或評論ID string target_id = 1; // IDID或評論ID
int64 user_id = 2; // ID string uid = 2; // ID
int64 like_type = 3; // int64 like_type = 3; //
} }
@ -92,8 +112,8 @@ message LikeItem {
message LikeListReq { message LikeListReq {
string target_id = 1; // IDID或評論ID string target_id = 1; // IDID或評論ID
int64 like_type = 2; // int64 like_type = 2; //
int32 page_index = 3; // int64 page_index = 3; //
int32 page_size = 4; // int64 page_size = 4; //
} }
// / // /
@ -110,7 +130,7 @@ message LikeCountReq {
// / // /
message LikeCountResp { message LikeCountResp {
string count = 1; // int64 count = 1; //
} }
// //
@ -154,6 +174,18 @@ message UpdateCommentReq {
string content = 2; // string content = 2; //
} }
message PostReactionActionResp {
string PostID =1; // ID
int64 reaction_type = 2; //
bool is_increment = 3; // true false
}
message IncDecLikeDislikeCountReq {
string PostID =1; // ID
int64 reaction_type = 2; //
bool is_increment = 3; // true false
}
// //
service PostService { service PostService {
// NewPost // NewPost
@ -164,11 +196,13 @@ service PostService {
rpc UpdatePost(UpdatePostReq) returns (OKResp); rpc UpdatePost(UpdatePostReq) returns (OKResp);
// ListPosts // ListPosts
rpc ListPosts(QueryPostsReq) returns (ListPostsResp); rpc ListPosts(QueryPostsReq) returns (ListPostsResp);
// IncDecLikeDislikeCount
rpc IncDecLikeDislikeCount(IncDecLikeDislikeCountReq) returns (OKResp);
// Like / // Like /
rpc Like(LikeReq) returns (OKResp); rpc Like(LikeReq) returns (PostReactionActionResp);
// GetLikeStatus / // GetLikeStatus /
rpc GetLikeStatus(LikeReq) returns (OKResp); rpc GetLikeStatus(GetLikeStatusReq) returns (GetLikeStatusResp);
// LikeList / // LikeList /
rpc LikeList(LikeListReq) returns (LikeListResp); rpc LikeList(LikeListReq) returns (LikeListResp);
// CountLike / // CountLike /

17
go.mod
View File

@ -3,7 +3,10 @@ module app-cloudep-tweeting-service
go 1.22.3 go 1.22.3
require ( require (
code.30cm.net/digimon/library-go/errs v1.2.4
code.30cm.net/digimon/library-go/validator v1.0.0
github.com/zeromicro/go-zero v1.7.0 github.com/zeromicro/go-zero v1.7.0
go.mongodb.org/mongo-driver v1.16.1
google.golang.org/grpc v1.66.0 google.golang.org/grpc v1.66.0
google.golang.org/protobuf v1.34.2 google.golang.org/protobuf v1.34.2
) )
@ -18,14 +21,19 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fatih/color v1.17.0 // indirect github.com/fatih/color v1.17.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
@ -33,11 +41,14 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@ -47,6 +58,10 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/redis/go-redis/v9 v9.6.1 // indirect github.com/redis/go-redis/v9 v9.6.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect go.etcd.io/etcd/client/v3 v3.5.15 // indirect
@ -65,8 +80,10 @@ require (
go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect

View File

@ -1,7 +1,21 @@
package config package config
import "github.com/zeromicro/go-zero/zrpc" import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct { type Config struct {
zrpc.RpcServerConf zrpc.RpcServerConf
Mongo struct {
Schema string
User string
Password string
Host string
Port string
Database string
}
// 快取
Cache cache.CacheConf
} }

24
internal/domain/const.go Normal file
View File

@ -0,0 +1,24 @@
package domain
type AdType int32
func (a AdType) ToInt32() int32 {
return int32(a)
}
const (
AdTypeAll AdType = iota
AdTypeOnlyAd
AdTypeOnlyNotAd
)
type LikeType int8
func (l LikeType) ToInt8() int8 {
return int8(l)
}
const (
LikeTypeLike LikeType = iota + 1 // 按揍
LikeTypeDisLike
)

39
internal/domain/errors.go Normal file
View File

@ -0,0 +1,39 @@
package domain
import (
"fmt"
"strings"
"code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
"github.com/zeromicro/go-zero/core/logx"
)
type ErrorCode uint32
func (e ErrorCode) ToUint32() uint32 {
return uint32(e)
}
const (
_ = iota
PostMongoErrorCode ErrorCode = iota
)
// PostMongoError ...
func PostMongoError(s ...string) *errs.LibError {
return errs.NewError(code.CloudEPTweeting, code.DBError,
PostMongoErrorCode.ToUint32(),
fmt.Sprintf("%s", strings.Join(s, " ")))
}
// PostMongoErrorL logs error message and returns Err
func PostMongoErrorL(l logx.Logger, filed []logx.LogField, s ...string) *errs.LibError {
e := PostMongoError(s...)
if filed != nil || len(filed) >= 0 {
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
}
l.WithCallerSkip(1).Error(e.Error())
return e
}

12
internal/domain/status.go Normal file
View File

@ -0,0 +1,12 @@
package domain
type TweetingStatus int8
func (t TweetingStatus) ToInt8() int8 {
return int8(t)
}
const (
TweetingStatusNotReviewedYet TweetingStatus = iota + 1
TweetingStatusPass
)

View File

@ -1,10 +1,12 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/domain"
"app-cloudep-tweeting-service/internal/svc"
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -23,9 +25,35 @@ func NewCountLikeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CountLi
} }
} }
type countLikeReq struct {
PostID string `json:"post_id" validate:"required"` // 貼文的 ID
ReactionType domain.LikeType `json:"reaction_type" validate:"required,oneof=1 2"` // 用戶的反應類型,可能是讚或不讚
}
// CountLike 取得讚/不讚數量 // CountLike 取得讚/不讚數量
func (l *CountLikeLogic) CountLike(in *tweeting.LikeCountReq) (*tweeting.LikeCountResp, error) { func (l *CountLikeLogic) CountLike(in *tweeting.LikeCountReq) (*tweeting.LikeCountResp, error) {
// todo: add your logic here and delete this line // 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&countLikeReq{
PostID: in.GetTargetId(),
ReactionType: domain.LikeType(in.GetLikeType()),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
return &tweeting.LikeCountResp{}, nil count, err := l.svcCtx.PostLikeModel.Count(l.ctx, in.GetTargetId(), domain.LikeType(in.GetLikeType()))
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostLikeModel.Count"},
{Key: "err", Value: err},
},
"failed to count like or dislike").Wrap(err)
return nil, e
}
return &tweeting.LikeCountResp{
Count: count,
}, nil
} }

View File

@ -1,6 +1,7 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/internal/domain"
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
@ -25,7 +26,18 @@ func NewDeletePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete
// DeletePost 刪除貼文 // DeletePost 刪除貼文
func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKResp, error) { func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line _, err := l.svcCtx.PostModel.DeleteMany(l.ctx, in.GetPostId()...)
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.DeletePost"},
{Key: "err", Value: err},
},
"failed to add del post").Wrap(err)
return nil, e
}
return &tweeting.OKResp{}, nil return &tweeting.OKResp{}, nil
} }

View File

@ -1,8 +1,12 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"context" "context"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-tweeting-service/internal/svc"
@ -23,9 +27,50 @@ func NewGetLikeStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get
} }
} }
// GetLikeStatus 取得讚/不讚狀態 // 這個人按讚的文章列表輸入UID 以及文章id返回這個人有沒有對這些文章按讚
func (l *GetLikeStatusLogic) GetLikeStatus(in *tweeting.LikeReq) (*tweeting.OKResp, error) { type getLikeStatusReq struct {
// todo: add your logic here and delete this line Targets []string `json:"targets" validate:"required"`
LikeType domain.LikeType `json:"like_type" validate:"required,oneof=1 2"`
return &tweeting.OKResp{}, nil UID string `json:"uid" validate:"required"`
}
// GetLikeStatus 取得讚/不讚狀態
func (l *GetLikeStatusLogic) GetLikeStatus(in *tweeting.GetLikeStatusReq) (*tweeting.GetLikeStatusResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&getLikeStatusReq{
Targets: in.GetTargetId(),
LikeType: domain.LikeType(in.GetLikeType()),
UID: in.GetUid(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
list, err := l.svcCtx.PostLikeModel.FindUIDPostLikeStatus(l.ctx, &model.QueryUIDPostLikeStatusReq{
Targets: in.GetTargetId(),
LikeType: domain.LikeType(in.GetLikeType()),
UID: in.GetUid(),
})
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.FindUIDPostLikeStatus"},
{Key: "err", Value: err},
},
"failed to find uid post like status list").Wrap(err)
return nil, e
}
var result = make([]*tweeting.GetLikeStatusItem, 0, len(list))
for _, item := range list {
result = append(result, &tweeting.GetLikeStatusItem{
TargetId: item.TargetID,
Status: item.LikeStatus,
})
}
return &tweeting.GetLikeStatusResp{
Data: result,
}, nil
} }

View File

@ -0,0 +1,65 @@
package postservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"context"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type IncDecLikeDislikeCountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewIncDecLikeDislikeCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IncDecLikeDislikeCountLogic {
return &IncDecLikeDislikeCountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type postReactionAction struct {
PostID string `json:"post_id" validate:"required"` // 貼文的 ID
ReactionType domain.LikeType `json:"reaction_type" validate:"required,oneof=1 2"` // 用戶的反應類型,可能是讚或不讚
IsIncrement bool `json:"is_increment" validate:"required"` // 表示是否增加true 表示增加false 表示減少)
}
// IncDecLikeDislikeCount 增減數量
func (l *IncDecLikeDislikeCountLogic) IncDecLikeDislikeCount(in *tweeting.IncDecLikeDislikeCountReq) (*tweeting.OKResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&postReactionAction{
PostID: in.GetPostID(),
ReactionType: domain.LikeType(in.GetReactionType()),
IsIncrement: in.GetIsIncrement(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
err := l.svcCtx.PostModel.IncDecLikeDislikeCountLogic(l.ctx, &model.PostReactionAction{
PostID: in.GetPostID(),
ReactionType: domain.LikeType(in.GetReactionType()),
IsIncrement: in.GetIsIncrement(),
})
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.IncDecLikeDislikeCountLogic"},
{Key: "err", Value: err},
},
"failed to inc like or dislike").Wrap(err)
return nil, e
}
return &tweeting.OKResp{}, nil
}

View File

@ -1,8 +1,12 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"context" "context"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-tweeting-service/internal/svc"
@ -23,9 +27,60 @@ func NewLikeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LikeList
} }
} }
// 換句話說就是對這個文章按讚的人的列表
type likeListReq struct {
Target string `json:"target" validate:"required"`
LikeType domain.LikeType `json:"like_type" validate:"required,oneof=1 2"`
PageSize int64 `json:"page_size" validate:"required"`
PageIndex int64 `json:"page_index" validate:"required"`
}
// LikeList 取得讚/不讚列表 // LikeList 取得讚/不讚列表
func (l *LikeListLogic) LikeList(in *tweeting.LikeListReq) (*tweeting.LikeListResp, error) { func (l *LikeListLogic) LikeList(in *tweeting.LikeListReq) (*tweeting.LikeListResp, error) {
// todo: add your logic here and delete this line // 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&likeListReq{
Target: in.TargetId,
LikeType: domain.LikeType(in.GetLikeType()),
PageSize: in.GetPageSize(),
PageIndex: in.GetPageIndex(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
return &tweeting.LikeListResp{}, nil result, total, err := l.svcCtx.PostLikeModel.FindLikeUsers(l.ctx, &model.QueryPostLikeReq{
Target: in.GetTargetId(),
LikeType: domain.LikeType(in.GetLikeType()),
PageSize: in.GetPageSize(),
PageIndex: in.GetPageIndex(),
})
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.LikeDislike"},
{Key: "err", Value: err},
},
"failed to like or dislike").Wrap(err)
return nil, e
}
var list = make([]*tweeting.LikeItem, 0, len(result))
for _, item := range result {
list = append(list, &tweeting.LikeItem{
LikeType: int64(item.Type),
TargetId: item.TargetID,
Uid: item.UID,
})
}
return &tweeting.LikeListResp{
List: list,
Page: &tweeting.Pager{
Size: in.GetPageSize(),
Index: in.GetPageIndex(),
Total: total,
},
}, nil
} }

View File

@ -1,8 +1,12 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"context" "context"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-tweeting-service/internal/svc"
@ -23,9 +27,43 @@ func NewLikeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LikeLogic {
} }
} }
// Like 點讚/取消讚 貼文 type likeReq struct {
func (l *LikeLogic) Like(in *tweeting.LikeReq) (*tweeting.OKResp, error) { Target string `json:"target" validate:"required"`
// todo: add your logic here and delete this line UID string `json:"uid" validate:"required"`
}
return &tweeting.OKResp{}, nil
// Like 點讚/取消讚 貼文
func (l *LikeLogic) Like(in *tweeting.LikeReq) (*tweeting.PostReactionActionResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&likeReq{
Target: in.TargetId,
UID: in.GetUid(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
likeResp, err := l.svcCtx.PostLikeModel.LikeDislike(l.ctx, &model.PostLikes{
TargetID: in.GetTargetId(),
UID: in.GetUid(),
Type: int8(in.GetLikeType()),
})
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.LikeDislike"},
{Key: "err", Value: err},
},
"failed to like or dislike").Wrap(err)
return nil, e
}
// 將文章的數量增加或減少的功能是業務邏輯從外面再決定要不要丟MQ 後算,或是怎麼算
return &tweeting.PostReactionActionResp{
PostID: likeResp.PostID,
ReactionType: int64(likeResp.ReactionType),
IsIncrement: likeResp.IsIncrement,
}, nil
} }

View File

@ -1,10 +1,14 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"app-cloudep-tweeting-service/internal/svc"
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/internal/svc" "google.golang.org/protobuf/proto"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -23,9 +27,84 @@ func NewListPostsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPos
} }
} }
type listReq struct {
OnlyAdds int32 `json:"only_adds" validate:"oneof=0 1 2 3"`
PageSize int64 `json:"page_size" validate:"required"`
PageIndex int64 `json:"page_index" validate:"required"`
}
// ListPosts 查詢貼文 // ListPosts 查詢貼文
func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) { func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) {
// todo: add your logic here and delete this line // 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&listReq{
PageSize: int64(in.GetPageSize()),
PageIndex: int64(in.GetPageIndex()),
OnlyAdds: in.GetOnlyAds(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
return &tweeting.ListPostsResp{}, nil query := &model.QueryPostModelReq{
UID: in.GetUid(),
Id: in.GetId(),
PageSize: int64(in.GetPageSize()),
PageIndex: int64(in.GetPageIndex()),
}
if in.OnlyAds != nil {
switch in.GetOnlyAds() {
case domain.AdTypeOnlyAd.ToInt32():
query.OnlyAds = proto.Bool(true)
case domain.AdTypeOnlyNotAd.ToInt32():
query.OnlyAds = proto.Bool(false)
default:
}
}
find, count, err := l.svcCtx.PostModel.Find(l.ctx, query)
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "query", Value: query},
{Key: "func", Value: "PostModel.Find"},
{Key: "err", Value: err},
},
"failed to add new post").Wrap(err)
return nil, e
}
result := make([]*tweeting.PostDetailItem, 0, count)
for _, item := range find {
media := make([]*tweeting.Media, 0, len(item.Media))
for _, subItem := range item.Media {
media = append(media, &tweeting.Media{
Type: subItem.Type,
Url: subItem.Links,
})
}
result = append(result, &tweeting.PostDetailItem{
PostId: item.ID.Hex(),
Uid: item.UID,
Content: item.Content,
Tags: item.Tags,
Media: media,
IsAd: item.IsAd,
CreatedAt: item.CreateAt,
UpdateAt: item.UpdateAt,
LikeCount: int64(item.Like),
DislikeCount: int64(item.DisLike),
})
}
return &tweeting.ListPostsResp{
Posts: result,
Page: &tweeting.Pager{
Total: count,
Index: int64(in.GetPageIndex()),
Size: int64(in.GetPageSize()),
},
}, nil
} }

View File

@ -1,10 +1,13 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"app-cloudep-tweeting-service/internal/svc"
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -23,9 +26,64 @@ func NewNewPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewPostLo
} }
} }
type newTweetingReq struct {
UID string `json:"uid" validate:"required"`
Content string `json:"content" validate:"required,lte=500"` // 貼文限制 500 字內
Tags []string `json:"tags"`
MediaUrl []string `json:"media_url"`
IsAd bool `json:"is_ad"` // default false
}
// NewPost 新增貼文 // NewPost 新增貼文
func (l *NewPostLogic) NewPost(in *tweeting.NewPostReq) (*tweeting.PostResp, error) { func (l *NewPostLogic) NewPost(in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
// todo: add your logic here and delete this line // 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&newTweetingReq{
UID: in.GetUid(),
Content: in.GetContent(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// ============ prepare ============
// 新增資料
tweet := &model.Post{
UID: in.GetUid(),
Content: in.GetContent(),
Status: domain.TweetingStatusNotReviewedYet.ToInt8(),
IsAd: in.IsAd,
}
if len(in.GetTags()) > 0 {
// 存在貼文內的不提供搜尋純顯示用只不過在原始的tag 發生變動的時候,並不會一起改變
// 搜尋會貼文與Tag 的表會再另外一邊做關聯
// 暫時業務邏輯上tag 只提供新增,不提供修改以及刪除,故目前版本可行
tweet.Tags = in.GetTags()
}
return &tweeting.PostResp{}, nil if len(in.Media) > 0 {
// 將 Media 存入
var media []model.Media
for _, item := range in.GetMedia() {
media = append(media, model.Media{
Links: item.Url,
Type: item.Type,
})
}
tweet.Media = media
}
// ============ insert ============
err := l.svcCtx.PostModel.Insert(l.ctx, tweet)
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.Insert"},
{Key: "err", Value: err},
},
"failed to add new post").Wrap(err)
return nil, e
}
return &tweeting.PostResp{
PostId: tweet.ID.Hex(),
}, nil
} }

View File

@ -1,8 +1,13 @@
package postservicelogic package postservicelogic
import ( import (
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"context" "context"
ers "code.30cm.net/digimon/library-go/errs"
"go.mongodb.org/mongo-driver/bson/primitive"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-tweeting-service/internal/svc"
@ -23,9 +28,53 @@ func NewUpdatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Update
} }
} }
type checkPostId struct {
PostID string `validate:"required"`
Content string `json:"content,omitempty" validate:"lte=500"`
}
// UpdatePost 更新貼文 // UpdatePost 更新貼文
func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) { func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line // 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&checkPostId{
PostID: in.GetPostId(),
Content: in.GetContent(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// 沒有就沒有,有就走全覆蓋
update := model.Post{}
oid, err := primitive.ObjectIDFromHex(in.GetPostId())
if err != nil {
return nil, ers.InvalidFormat("failed to get correct post id")
}
update.ID = oid
update.Tags = in.GetTags()
// 將 Media 存入
var media []model.Media
for _, item := range in.GetMedia() {
media = append(media, model.Media{
Links: item.Url,
Type: item.Type,
})
}
update.Media = media
update.Content = in.GetContent()
update.Like = uint64(in.GetLikeCount())
update.DisLike = uint64(in.GetDislikeCount())
_, err = l.svcCtx.PostModel.UpdateOptional(l.ctx, &update)
if err != nil {
e := domain.PostMongoErrorL(
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "PostModel.UpdateOptional"},
{Key: "err", Value: err},
},
"failed to add new post").Wrap(err)
return nil, e
}
return &tweeting.OKResp{}, nil return &tweeting.OKResp{}, nil
} }

View File

@ -12,9 +12,9 @@ import (
) )
type comment_likesModel interface { type comment_likesModel interface {
Insert(ctx context.Context, data *Comment_likes) error Insert(ctx context.Context, data *CommentLikes) error
FindOne(ctx context.Context, id string) (*Comment_likes, error) FindOne(ctx context.Context, id string) (*CommentLikes, error)
Update(ctx context.Context, data *Comment_likes) (*mongo.UpdateResult, error) Update(ctx context.Context, data *CommentLikes) (*mongo.UpdateResult, error)
Delete(ctx context.Context, id string) (int64, error) Delete(ctx context.Context, id string) (int64, error)
} }
@ -26,7 +26,7 @@ func newDefaultComment_likesModel(conn *mon.Model) *defaultComment_likesModel {
return &defaultComment_likesModel{conn: conn} return &defaultComment_likesModel{conn: conn}
} }
func (m *defaultComment_likesModel) Insert(ctx context.Context, data *Comment_likes) error { func (m *defaultComment_likesModel) Insert(ctx context.Context, data *CommentLikes) error {
if data.ID.IsZero() { if data.ID.IsZero() {
data.ID = primitive.NewObjectID() data.ID = primitive.NewObjectID()
data.CreateAt = time.Now() data.CreateAt = time.Now()
@ -37,13 +37,13 @@ func (m *defaultComment_likesModel) Insert(ctx context.Context, data *Comment_li
return err return err
} }
func (m *defaultComment_likesModel) FindOne(ctx context.Context, id string) (*Comment_likes, error) { func (m *defaultComment_likesModel) FindOne(ctx context.Context, id string) (*CommentLikes, error) {
oid, err := primitive.ObjectIDFromHex(id) oid, err := primitive.ObjectIDFromHex(id)
if err != nil { if err != nil {
return nil, ErrInvalidObjectId return nil, ErrInvalidObjectId
} }
var data Comment_likes var data CommentLikes
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid}) err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
switch err { switch err {
@ -56,7 +56,7 @@ func (m *defaultComment_likesModel) FindOne(ctx context.Context, id string) (*Co
} }
} }
func (m *defaultComment_likesModel) Update(ctx context.Context, data *Comment_likes) (*mongo.UpdateResult, error) { func (m *defaultComment_likesModel) Update(ctx context.Context, data *CommentLikes) (*mongo.UpdateResult, error) {
data.UpdateAt = time.Now() data.UpdateAt = time.Now()
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data}) res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})

View File

@ -6,7 +6,7 @@ import (
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
) )
type Comment_likes struct { type CommentLikes struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
// TODO: Fill your own fields // TODO: Fill your own fields
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`

View File

@ -1,6 +1,17 @@
package model package model
import "github.com/zeromicro/go-zero/core/stores/mon" import (
"app-cloudep-tweeting-service/internal/domain"
"context"
"errors"
"time"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var _ Post_likesModel = (*customPost_likesModel)(nil) var _ Post_likesModel = (*customPost_likesModel)(nil)
@ -9,6 +20,34 @@ type (
// and implement the added methods in customPost_likesModel. // and implement the added methods in customPost_likesModel.
Post_likesModel interface { Post_likesModel interface {
post_likesModel post_likesModel
LikeDislike(ctx context.Context, postLike *PostLikes) (*PostReactionAction, error)
FindLikeUsers(ctx context.Context, param *QueryPostLikeReq) ([]*PostLikes, int64, error)
FindUIDPostLikeStatus(ctx context.Context, param *QueryUIDPostLikeStatusReq) ([]UIDPostLikeStatusResp, error)
Count(ctx context.Context, target string, likeType domain.LikeType) (int64, error)
}
PostReactionAction struct {
PostID string // 貼文的 ID
ReactionType domain.LikeType // 用戶的反應類型,可能是讚或不讚
IsIncrement bool // 表示是否增加true 表示增加false 表示減少)
}
QueryPostLikeReq struct {
Target string `bson:"target"`
LikeType domain.LikeType `bson:"like_type"`
PageIndex int64 `bson:"page_index"`
PageSize int64 `bson:"page_size"`
}
QueryUIDPostLikeStatusReq struct {
Targets []string `bson:"target"`
LikeType domain.LikeType `bson:"like_type"`
UID string
}
UIDPostLikeStatusResp struct {
TargetID string `json:"target_id"`
LikeStatus bool `json:"like_status"`
} }
customPost_likesModel struct { customPost_likesModel struct {
@ -23,3 +62,134 @@ func NewPost_likesModel(url, db, collection string) Post_likesModel {
defaultPost_likesModel: newDefaultPost_likesModel(conn), defaultPost_likesModel: newDefaultPost_likesModel(conn),
} }
} }
func (m *defaultPost_likesModel) LikeDislike(ctx context.Context, postLike *PostLikes) (*PostReactionAction, error) {
result := &PostReactionAction{
PostID: postLike.TargetID,
ReactionType: domain.LikeType(postLike.Type),
}
// 使用 target_id、uid、type 來查詢資料是否存在
filter := bson.M{
"target_id": postLike.TargetID,
"uid": postLike.UID,
"type": postLike.Type,
}
// 查詢資料是否存在
var existingPostLike PostLikes
err := m.conn.FindOne(ctx, &existingPostLike, filter)
if err == nil {
// 資料存在,進行刪除操作
result.IsIncrement = false
_, err = m.conn.DeleteOne(ctx, filter)
if err != nil {
return nil, err // 刪除失敗
}
return result, nil // 刪除成功
} else if errors.Is(mongo.ErrNoDocuments, err) {
// 資料不存在,進行插入操作
result.IsIncrement = true
postLike.ID = primitive.NewObjectID() // 設置新的 ObjectID
postLike.CreateAt = time.Now().UTC().UnixNano()
_, err = m.conn.InsertOne(ctx, postLike)
if err != nil {
return nil, err // 插入失敗
}
return result, nil // 插入成功
} else {
// 其他錯誤
return nil, err
}
}
func (m *defaultPost_likesModel) FindLikeUsers(ctx context.Context, param *QueryPostLikeReq) ([]*PostLikes, int64, error) {
// 建立篩選條件
filter := bson.M{}
// 如果指定了 Target 條件,將其添加到篩選條件中
if param.Target != "" {
filter["target_id"] = param.Target
}
// 如果指定了 LikeType 條件,將其添加到篩選條件中
if param.LikeType != 0 {
filter["type"] = param.LikeType
}
// 計算符合條件的文檔總數
totalCount, err := m.conn.CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 設置分頁和排序選項
opts := options.Find()
opts.SetSort(bson.D{{"createAt", -1}}) // 按照創建時間倒序排序
if param.PageSize > 0 {
opts.SetLimit(param.PageSize)
}
if param.PageIndex > 0 && param.PageSize > 0 {
opts.SetSkip((param.PageIndex - 1) * param.PageSize)
}
// 查詢符合條件的文檔
var results []*PostLikes
err = m.conn.Find(ctx, &results, filter, opts)
if err != nil {
return nil, 0, err
}
// 返回結果集、總數和錯誤信息
return results, totalCount, nil
}
func (m *defaultPost_likesModel) FindUIDPostLikeStatus(ctx context.Context, param *QueryUIDPostLikeStatusReq) ([]UIDPostLikeStatusResp, error) {
// 初始化返回結果的切片
var results []UIDPostLikeStatusResp
// 建立篩選條件
filter := bson.M{
"uid": param.UID, // 篩選指定的 UID
"target_id": bson.M{"$in": param.Targets}, // 篩選多個目標 ID
"type": param.LikeType, // 篩選指定的 LikeType
}
// 查詢符合條件的點讚記錄
var postLikes []PostLikes
err := m.conn.Find(ctx, &postLikes, filter)
if err != nil {
return nil, err
}
// 構建一個 map 來保存查詢到的點讚記錄
targetLikeMap := make(map[string]bool)
for _, like := range postLikes {
targetLikeMap[like.TargetID] = true
}
// 構建每個目標的點讚狀態
for _, targetID := range param.Targets {
likeStatus := UIDPostLikeStatusResp{
TargetID: targetID,
LikeStatus: targetLikeMap[targetID], // 如果 map 中有該 targetID返回 true否則返回 false
}
// 如果該 targetID 沒有點讚記錄,默認 LikeStatus 為 false
if _, found := targetLikeMap[targetID]; !found {
likeStatus.LikeStatus = false
}
results = append(results, likeStatus)
}
return results, nil
}
func (c customPost_likesModel) Count(ctx context.Context, target string, likeType domain.LikeType) (int64, error) {
// TODO implement me
panic("implement me")
}

View File

@ -12,9 +12,9 @@ import (
) )
type post_likesModel interface { type post_likesModel interface {
Insert(ctx context.Context, data *Post_likes) error Insert(ctx context.Context, data *PostLikes) error
FindOne(ctx context.Context, id string) (*Post_likes, error) FindOne(ctx context.Context, id string) (*PostLikes, error)
Update(ctx context.Context, data *Post_likes) (*mongo.UpdateResult, error) Update(ctx context.Context, data *PostLikes) (*mongo.UpdateResult, error)
Delete(ctx context.Context, id string) (int64, error) Delete(ctx context.Context, id string) (int64, error)
} }
@ -26,24 +26,23 @@ func newDefaultPost_likesModel(conn *mon.Model) *defaultPost_likesModel {
return &defaultPost_likesModel{conn: conn} return &defaultPost_likesModel{conn: conn}
} }
func (m *defaultPost_likesModel) Insert(ctx context.Context, data *Post_likes) error { func (m *defaultPost_likesModel) Insert(ctx context.Context, data *PostLikes) error {
if data.ID.IsZero() { if data.ID.IsZero() {
data.ID = primitive.NewObjectID() data.ID = primitive.NewObjectID()
data.CreateAt = time.Now() data.CreateAt = time.Now().UTC().UnixNano()
data.UpdateAt = time.Now()
} }
_, err := m.conn.InsertOne(ctx, data) _, err := m.conn.InsertOne(ctx, data)
return err return err
} }
func (m *defaultPost_likesModel) FindOne(ctx context.Context, id string) (*Post_likes, error) { func (m *defaultPost_likesModel) FindOne(ctx context.Context, id string) (*PostLikes, error) {
oid, err := primitive.ObjectIDFromHex(id) oid, err := primitive.ObjectIDFromHex(id)
if err != nil { if err != nil {
return nil, ErrInvalidObjectId return nil, ErrInvalidObjectId
} }
var data Post_likes var data PostLikes
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid}) err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
switch err { switch err {
@ -56,9 +55,7 @@ func (m *defaultPost_likesModel) FindOne(ctx context.Context, id string) (*Post_
} }
} }
func (m *defaultPost_likesModel) Update(ctx context.Context, data *Post_likes) (*mongo.UpdateResult, error) { func (m *defaultPost_likesModel) Update(ctx context.Context, data *PostLikes) (*mongo.UpdateResult, error) {
data.UpdateAt = time.Now()
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data}) res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
return res, err return res, err
} }

View File

@ -1,14 +1,17 @@
package model package model
import ( import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
) )
type Post_likes struct { type PostLikes struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
// TODO: Fill your own fields TargetID string `bson:"target_id" json:"target_id"`
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` UID string `bson:"uid" json:"uid"`
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` Type int8 `bson:"type" json:"type"`
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"`
}
func (p *PostLikes) CollectionName() string {
return "post_like"
} }

View File

@ -1,8 +1,19 @@
package model package model
import ( import (
"app-cloudep-tweeting-service/internal/domain"
"context"
"errors"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/monc" "github.com/zeromicro/go-zero/core/stores/monc"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
) )
var _ PostModel = (*customPostModel)(nil) var _ PostModel = (*customPostModel)(nil)
@ -12,11 +23,23 @@ type (
// and implement the added methods in customPostModel. // and implement the added methods in customPostModel.
PostModel interface { PostModel interface {
postModel postModel
DeleteMany(ctx context.Context, id ...string) (int64, error)
UpdateOptional(ctx context.Context, data *Post) (*mongo.UpdateResult, error)
Find(ctx context.Context, param *QueryPostModelReq) ([]*Post, int64, error)
IncDecLikeDislikeCountLogic(ctx context.Context, param *PostReactionAction) error
} }
customPostModel struct { customPostModel struct {
*defaultPostModel *defaultPostModel
} }
QueryPostModelReq struct {
UID []string
Id []string
OnlyAds *bool
PageSize int64
PageIndex int64
}
) )
// NewPostModel returns a model for the mongo. // NewPostModel returns a model for the mongo.
@ -26,3 +49,185 @@ func NewPostModel(url, db, collection string, c cache.CacheConf) PostModel {
defaultPostModel: newDefaultPostModel(conn), defaultPostModel: newDefaultPostModel(conn),
} }
} }
func (m *defaultPostModel) DeleteMany(ctx context.Context, id ...string) (int64, error) {
objectIDs := make([]primitive.ObjectID, 0, len(id))
key := make([]string, 0, len(id))
// prepare
for _, item := range id {
oid, err := primitive.ObjectIDFromHex(item)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "defaultPostModel.DeleteMany"),
logx.Field("id", item),
).Error(err.Error())
continue
}
objectIDs = append(objectIDs, oid)
key = append(key, prefixPostCacheKey+item)
}
// 檢查是否有有效的 ObjectIDs
if len(objectIDs) == 0 {
return 0, ErrNotFound
}
// 刪除文檔
res, err := m.conn.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": objectIDs}})
if err != nil {
return 0, err
}
err = m.conn.DelCache(ctx, key...)
if err != nil {
return 0, err
}
return res, err
}
func (m *defaultPostModel) UpdateOptional(ctx context.Context, data *Post) (*mongo.UpdateResult, error) {
update := bson.M{"$set": bson.M{}}
if data.UID != "" {
update["$set"].(bson.M)["uid"] = data.UID
}
if data.Content != "" {
update["$set"].(bson.M)["content"] = data.Content
}
if data.Status != 0 {
update["$set"].(bson.M)["status"] = data.Status
}
if data.IsAd {
update["$set"].(bson.M)["is_ad"] = data.IsAd
}
if len(data.Tags) > 0 {
update["$set"].(bson.M)["tags"] = data.Tags
}
if len(data.Media) > 0 {
update["$set"].(bson.M)["media_url"] = data.Media
}
if data.Like != 0 {
update["$set"].(bson.M)["like"] = data.Like
}
if data.DisLike != 0 {
update["$set"].(bson.M)["dislike"] = data.DisLike
}
// UpdateAt 是每次都需要更新的,不用檢查
update["$set"].(bson.M)["updateAt"] = time.Now().UTC().UnixNano()
fmt.Println("update map", update)
key := prefixPostCacheKey + data.ID.Hex()
res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, update)
return res, err
}
// Find 貼文列表
func (m *defaultPostModel) Find(ctx context.Context, param *QueryPostModelReq) ([]*Post, int64, error) {
filter := bson.M{}
// 添加 UID 過濾條件
if len(param.UID) > 0 {
filter["uid"] = bson.M{"$in": param.UID}
}
// 添加 ID 過濾條件
if len(param.Id) > 0 {
var ids []primitive.ObjectID
for _, item := range param.Id {
oid, err := primitive.ObjectIDFromHex(item)
if err != nil {
// log
continue
}
ids = append(ids, oid)
}
filter["_id"] = bson.M{"$in": ids}
}
// 添加 OnlyAds 過濾條件
if param.OnlyAds != nil {
// true 是廣告 false 不是廣告 , 沒寫就是不過率
filter["is_ad"] = *param.OnlyAds
}
// 分頁處理
opts := options.Find()
opts.SetSort(bson.D{{"create_time", -1}})
if param.PageSize > 0 {
opts.SetLimit(param.PageSize)
}
if param.PageIndex > 0 {
opts.SetSkip((param.PageIndex - 1) * param.PageSize)
}
// 計算總數(不考慮分頁)
totalCount, err := m.conn.CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
result := make([]*Post, 0, param.PageSize)
// 執行查詢
err = m.conn.Find(ctx, &result, filter, opts)
if err != nil {
return nil, 0, err
}
switch {
case err == nil:
return result, totalCount, nil
case errors.Is(err, monc.ErrNotFound):
return nil, 0, ErrNotFound
default:
return nil, 0, err
}
}
func (c *customPostModel) IncDecLikeDislikeCountLogic(ctx context.Context, param *PostReactionAction) error {
// 建立篩選條件,找到要更新的貼文
filter := bson.M{"_id": param.PostID}
// 初始化更新操作
update := bson.M{}
// 根據 ReactionType 和 IsIncrement 決定更新邏輯
if param.ReactionType == domain.LikeTypeLike {
if param.IsIncrement {
// 增加 like 計數
update = bson.M{"$inc": bson.M{"like": 1}}
} else {
// 減少 like 計數
update = bson.M{"$inc": bson.M{"like": -1}}
}
} else if param.ReactionType == domain.LikeTypeDisLike {
if param.IsIncrement {
// 增加 dislike 計數
update = bson.M{"$inc": bson.M{"dislike": 1}}
} else {
// 減少 dislike 計數
update = bson.M{"$inc": bson.M{"dislike": -1}}
}
}
// 執行更新操作
key := prefixPostCacheKey + param.PostID
_, err := c.conn.UpdateOne(ctx, key, filter, update)
if err != nil {
return err // 返回錯誤信息
}
return nil // 成功返回 nil
}

View File

@ -31,8 +31,8 @@ func newDefaultPostModel(conn *monc.Model) *defaultPostModel {
func (m *defaultPostModel) Insert(ctx context.Context, data *Post) error { func (m *defaultPostModel) Insert(ctx context.Context, data *Post) error {
if data.ID.IsZero() { if data.ID.IsZero() {
data.ID = primitive.NewObjectID() data.ID = primitive.NewObjectID()
data.CreateAt = time.Now() data.CreateAt = time.Now().UTC().UnixNano()
data.UpdateAt = time.Now() data.UpdateAt = time.Now().UTC().UnixNano()
} }
key := prefixPostCacheKey + data.ID.Hex() key := prefixPostCacheKey + data.ID.Hex()
@ -60,7 +60,7 @@ func (m *defaultPostModel) FindOne(ctx context.Context, id string) (*Post, error
} }
func (m *defaultPostModel) Update(ctx context.Context, data *Post) (*mongo.UpdateResult, error) { func (m *defaultPostModel) Update(ctx context.Context, data *Post) (*mongo.UpdateResult, error) {
data.UpdateAt = time.Now() data.UpdateAt = time.Now().UTC().UnixNano()
key := prefixPostCacheKey + data.ID.Hex() key := prefixPostCacheKey + data.ID.Hex()
res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, bson.M{"$set": data}) res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, bson.M{"$set": data})
return res, err return res, err

View File

@ -1,14 +1,33 @@
package model package model
import ( import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
) )
// TODO Tag 這裡在效能與正確性之間做取捨
// 存在貼文內的不提供搜尋純顯示用只不過在原始的tag 發生變動的時候,並不會一起改變
// 搜尋會貼文與Tag 的表會再另外一邊做關聯
// 暫時業務邏輯上tag 只提供新增,不提供修改以及刪除,故目前版本可行
type Post struct { type Post struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
// TODO: Fill your own fields UID string `bson:"uid" json:"uid"`
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` Content string `bson:"content" json:"content"`
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` Status int8 `bson:"status" json:"status"` // 1. 等待審核中 , 2 審核通過,預設為 1
IsAd bool `bson:"is_ad" json:"is_ad"` // 此則貼文是否為廣告貼文
Tags []string `bson:"tags" json:"tags"` // 本則貼文的標籤,不提供搜尋,僅提供顯示(存名字ID 建立之後就不提供修改與刪除)
Media []Media `bson:"media_url" json:"media_url"` // 網址
Like uint64 `bson:"like" json:"like"` // 讚
DisLike uint64 `bson:"dislike" json:"dislike"` // 不讚
UpdateAt int64 `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"`
}
type Media struct {
Type string // media type jpeg, m3u8 之類的
Links string // 連結的網址
}
func (p *Post) CollectionName() string {
return "post"
} }

View File

@ -7,7 +7,7 @@ import (
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/logic/commentservice" commentservicelogic "app-cloudep-tweeting-service/internal/logic/commentservice"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-tweeting-service/internal/svc"
) )

View File

@ -7,7 +7,7 @@ import (
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/logic/postservice" postservicelogic "app-cloudep-tweeting-service/internal/logic/postservice"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-tweeting-service/internal/svc"
) )
@ -46,14 +46,20 @@ func (s *PostServiceServer) ListPosts(ctx context.Context, in *tweeting.QueryPos
return l.ListPosts(in) return l.ListPosts(in)
} }
// IncDecLikeDislikeCount 增減數量
func (s *PostServiceServer) IncDecLikeDislikeCount(ctx context.Context, in *tweeting.IncDecLikeDislikeCountReq) (*tweeting.OKResp, error) {
l := postservicelogic.NewIncDecLikeDislikeCountLogic(ctx, s.svcCtx)
return l.IncDecLikeDislikeCount(in)
}
// Like 點讚/取消讚 貼文 // Like 點讚/取消讚 貼文
func (s *PostServiceServer) Like(ctx context.Context, in *tweeting.LikeReq) (*tweeting.OKResp, error) { func (s *PostServiceServer) Like(ctx context.Context, in *tweeting.LikeReq) (*tweeting.PostReactionActionResp, error) {
l := postservicelogic.NewLikeLogic(ctx, s.svcCtx) l := postservicelogic.NewLikeLogic(ctx, s.svcCtx)
return l.Like(in) return l.Like(in)
} }
// GetLikeStatus 取得讚/不讚狀態 // GetLikeStatus 取得讚/不讚狀態
func (s *PostServiceServer) GetLikeStatus(ctx context.Context, in *tweeting.LikeReq) (*tweeting.OKResp, error) { func (s *PostServiceServer) GetLikeStatus(ctx context.Context, in *tweeting.GetLikeStatusReq) (*tweeting.GetLikeStatusResp, error) {
l := postservicelogic.NewGetLikeStatusLogic(ctx, s.svcCtx) l := postservicelogic.NewGetLikeStatusLogic(ctx, s.svcCtx)
return l.GetLikeStatus(in) return l.GetLikeStatus(in)
} }

View File

@ -1,13 +1,36 @@
package svc package svc
import "app-cloudep-tweeting-service/internal/config" import (
"app-cloudep-tweeting-service/internal/config"
model "app-cloudep-tweeting-service/internal/model/mongo"
"fmt"
vi "code.30cm.net/digimon/library-go/validator"
)
type ServiceContext struct { type ServiceContext struct {
Config config.Config Config config.Config
Validate vi.Validate
PostModel model.PostModel
PostLikeModel model.Post_likesModel
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
baseMongo := MustMongoConnectUrl(c)
postCollection := model.Post{}
postLikeCollection := model.PostLikes{}
return &ServiceContext{ return &ServiceContext{
Config: c, Config: c,
Validate: vi.MustValidator(),
PostModel: model.NewPostModel(baseMongo, c.Mongo.Database, postCollection.CollectionName(), c.Cache),
PostLikeModel: model.NewPost_likesModel(baseMongo, c.Mongo.Database, postLikeCollection.CollectionName()),
} }
} }
func MustMongoConnectUrl(c config.Config) string {
return fmt.Sprintf(
"%s://%s:%s", c.Mongo.Schema,
c.Mongo.Host, c.Mongo.Port)
}