fix social network server

This commit is contained in:
daniel.w 2024-09-03 17:11:12 +08:00
parent 350bbf086c
commit e352f006b0
13 changed files with 834 additions and 49 deletions

View File

@ -2,4 +2,4 @@
CREATE DATABASE relation; CREATE DATABASE relation;
// 創建 User 節點 UID 是唯一鍵 // 創建 User 節點 UID 是唯一鍵
CREATE CONSTRAINT FOR (u:User) REQUIRE u.UID IS UNIQUE CREATE CONSTRAINT FOR (u:User) REQUIRE u.uid IS UNIQUE

View File

@ -251,7 +251,48 @@ message AddUserToNetworkReq
string uid = 1; string uid = 1;
} }
message DoFollowerRelationReq
{
string follower_uid = 1;
string followee_uid = 2;
}
message FollowReq
{
string uid = 1;
int64 page_size = 2;
int64 page_index = 3;
}
message FollowResp
{
repeated string uid = 1;
Pager page = 2;
}
message FollowCountReq
{
string uid = 1;
}
message FollowCountResp
{
string uid = 1;
int64 total = 2;
}
service SocialNetworkService service SocialNetworkService
{ {
rpc AddUserToNetwork(AddUserToNetworkReq) returns (OKResp); // MarkFollowRelation
rpc MarkFollowRelation(DoFollowerRelationReq) returns (OKResp);
// RemoveFollowRelation
rpc RemoveFollowRelation(DoFollowerRelationReq) returns (OKResp);
// GetFollower
rpc GetFollower(FollowReq) returns (FollowResp);
// GetFollowee
rpc GetFollowee(FollowReq) returns (FollowResp);
// GetFollowerCount
rpc GetFollowerCount(FollowCountReq) returns (FollowCountResp);
// GetFolloweeCount
rpc GetFolloweeCount(FollowCountReq) returns (FollowCountResp);
} }

View File

@ -41,6 +41,15 @@ const (
SetNoMoreDataErrorCode SetNoMoreDataErrorCode
) )
const (
MarkRelationErrorCode ErrorCode = iota + 30
GetFollowerErrorCode
GetFollowerCountErrorCode
GetFolloweeErrorCode
GetFolloweeCountErrorCode
RemoveRelationErrorCode
)
func CommentError(ec ErrorCode, s ...string) *ers.LibError { func CommentError(ec ErrorCode, s ...string) *ers.LibError {
return ers.NewError(code.CloudEPTweeting, code.DBError, return ers.NewError(code.CloudEPTweeting, code.DBError,
ec.ToUint32(), ec.ToUint32(),

View File

@ -4,4 +4,23 @@ import "context"
type SocialNetworkRepository interface { type SocialNetworkRepository interface {
CreateUserNode(ctx context.Context, uid string) error CreateUserNode(ctx context.Context, uid string) error
MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error
RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error
GetFollower(ctx context.Context, req FollowReq) (FollowResp, error)
GetFollowee(ctx context.Context, req FollowReq) (FollowResp, error)
GetFollowerCount(ctx context.Context, uid string) (int64, error)
GetFolloweeCount(ctx context.Context, uid string) (int64, error)
GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error)
GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error)
}
type FollowReq struct {
UID string
PageSize int64
PageIndex int64
}
type FollowResp struct {
UIDs []string
Total int64
} }

View File

@ -1,37 +0,0 @@
package socialnetworkservicelogic
import (
"context"
"fmt"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type AddUserToNetworkLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewAddUserToNetworkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddUserToNetworkLogic {
return &AddUserToNetworkLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *AddUserToNetworkLogic) AddUserToNetwork(in *tweeting.AddUserToNetworkReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
err := l.svcCtx.SocialNetworkRepository.CreateUserNode(l.ctx, in.GetUid())
if err != nil {
fmt.Println("gg88g88g8", err)
return nil, err
}
fmt.Println(err)
return &tweeting.OKResp{}, nil
}

View File

@ -0,0 +1,58 @@
package socialnetworkservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
ers "code.30cm.net/digimon/library-go/errs"
"context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetFolloweeCountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetFolloweeCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFolloweeCountLogic {
return &GetFolloweeCountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// GetFolloweeCount 取得我跟隨的數量
func (l *GetFolloweeCountLogic) GetFolloweeCount(in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&getFollowCountReq{
UID: in.Uid,
}); err != nil {
// 錯誤代碼 05-011-00
return nil, ers.InvalidFormat(err.Error())
}
followeeCount, err := l.svcCtx.SocialNetworkRepository.GetFolloweeCount(l.ctx, in.GetUid())
if err != nil {
// 錯誤代碼 05-021-34
e := domain.CommentErrorL(
domain.GetFolloweeCountErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "SocialNetworkRepository.GetFolloweeCount"},
{Key: "err", Value: err},
},
"failed to count follower").Wrap(err)
return nil, e
}
return &tweeting.FollowCountResp{
Uid: in.GetUid(),
Total: followeeCount,
}, nil
}

View File

@ -0,0 +1,69 @@
package socialnetworkservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
"app-cloudep-tweeting-service/internal/domain/repository"
ers "code.30cm.net/digimon/library-go/errs"
"context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetFolloweeLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetFolloweeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFolloweeLogic {
return &GetFolloweeLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// GetFollowee 取得我跟隨的名單
func (l *GetFolloweeLogic) GetFollowee(in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&getFollowReq{
UID: in.Uid,
PageSize: in.PageSize,
PageIndex: in.PageIndex,
}); err != nil {
// 錯誤代碼 05-011-00
return nil, ers.InvalidFormat(err.Error())
}
followee, err := l.svcCtx.SocialNetworkRepository.GetFollowee(l.ctx, repository.FollowReq{
UID: in.GetUid(),
PageIndex: in.GetPageIndex(),
PageSize: in.GetPageSize(),
})
if err != nil {
// 錯誤代碼 05-021-33
e := domain.CommentErrorL(
domain.GetFolloweeErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "SocialNetworkRepository.GetFollowee"},
{Key: "err", Value: err},
},
"failed to get relation: ", in.GetUid()).Wrap(err)
return nil, e
}
return &tweeting.FollowResp{
Uid: followee.UIDs,
Page: &tweeting.Pager{
Total: followee.Total,
Index: in.GetPageIndex(),
Size: in.GetPageSize(),
},
}, nil
}

View File

@ -0,0 +1,62 @@
package socialnetworkservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
ers "code.30cm.net/digimon/library-go/errs"
"context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetFollowerCountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetFollowerCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowerCountLogic {
return &GetFollowerCountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type getFollowCountReq struct {
UID string `validate:"required"`
}
// GetFollowerCount 取得跟隨者數量
func (l *GetFollowerCountLogic) GetFollowerCount(in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&getFollowCountReq{
UID: in.Uid,
}); err != nil {
// 錯誤代碼 05-011-00
return nil, ers.InvalidFormat(err.Error())
}
followerCount, err := l.svcCtx.SocialNetworkRepository.GetFollowerCount(l.ctx, in.GetUid())
if err != nil {
// 錯誤代碼 05-021-32
e := domain.CommentErrorL(
domain.GetFollowerCountErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "SocialNetworkRepository.GetFollowerCount"},
{Key: "err", Value: err},
},
"failed to count follower").Wrap(err)
return nil, e
}
return &tweeting.FollowCountResp{
Uid: in.GetUid(),
Total: followerCount,
}, nil
}

View File

@ -0,0 +1,75 @@
package socialnetworkservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
"app-cloudep-tweeting-service/internal/domain/repository"
ers "code.30cm.net/digimon/library-go/errs"
"context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetFollowerLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetFollowerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowerLogic {
return &GetFollowerLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type getFollowReq struct {
UID string `validate:"required"`
PageSize int64 `validate:"required"`
PageIndex int64 `validate:"required"`
}
// GetFollower 取得跟隨者名單
func (l *GetFollowerLogic) GetFollower(in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&getFollowReq{
UID: in.Uid,
PageSize: in.PageSize,
PageIndex: in.PageIndex,
}); err != nil {
// 錯誤代碼 05-011-00
return nil, ers.InvalidFormat(err.Error())
}
follower, err := l.svcCtx.SocialNetworkRepository.GetFollower(l.ctx, repository.FollowReq{
UID: in.GetUid(),
PageIndex: in.GetPageIndex(),
PageSize: in.GetPageSize(),
})
if err != nil {
// 錯誤代碼 05-021-31
e := domain.CommentErrorL(
domain.GetFollowerErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "SocialNetworkRepository.GetFollower"},
{Key: "err", Value: err},
},
"failed to get relation: ", in.GetUid()).Wrap(err)
return nil, e
}
return &tweeting.FollowResp{
Uid: follower.UIDs,
Page: &tweeting.Pager{
Total: follower.Total,
Index: in.GetPageIndex(),
Size: in.GetPageSize(),
},
}, nil
}

View File

@ -0,0 +1,62 @@
package socialnetworkservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
ers "code.30cm.net/digimon/library-go/errs"
"context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type MarkFollowRelationLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewMarkFollowRelationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MarkFollowRelationLogic {
return &MarkFollowRelationLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
type doFollowReq struct {
FollowerUID string `json:"follower_uid" validate:"required"` // 追隨者,跟隨你的人(別人關注你)
FolloweeUID string `json:"followee_uid" validate:"required"` // 追蹤者,你跟隨的人(你關注別)
}
// MarkFollowRelation 關注
func (l *MarkFollowRelationLogic) MarkFollowRelation(in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&doFollowReq{
FollowerUID: in.GetFollowerUid(),
FolloweeUID: in.GetFolloweeUid(),
}); err != nil {
// 錯誤代碼 05-011-00
return nil, ers.InvalidFormat(err.Error())
}
// 這裡要幫建立關係, follower 追蹤 -> followee
err := l.svcCtx.SocialNetworkRepository.MarkFollowerRelation(l.ctx, in.GetFollowerUid(), in.GetFolloweeUid())
if err != nil {
// 錯誤代碼 05-021-30
e := domain.CommentErrorL(
domain.MarkRelationErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "SocialNetworkRepository.MarkFollowerRelationBetweenUsers"},
{Key: "err", Value: err},
},
"failed to mark relation form -> to", in.GetFollowerUid(), in.GetFolloweeUid()).Wrap(err)
return nil, e
}
return &tweeting.OKResp{}, nil
}

View File

@ -0,0 +1,57 @@
package socialnetworkservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
ers "code.30cm.net/digimon/library-go/errs"
"context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type RemoveFollowRelationLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewRemoveFollowRelationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveFollowRelationLogic {
return &RemoveFollowRelationLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// RemoveFollowRelation 取消關注
func (l *RemoveFollowRelationLogic) RemoveFollowRelation(in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&doFollowReq{
FollowerUID: in.GetFollowerUid(),
FolloweeUID: in.GetFolloweeUid(),
}); err != nil {
// 錯誤代碼 05-011-00
return nil, ers.InvalidFormat(err.Error())
}
// 這裡要幫刪除關係, follower 追蹤 -> followee
err := l.svcCtx.SocialNetworkRepository.RemoveFollowerRelation(l.ctx, in.GetFollowerUid(), in.GetFolloweeUid())
if err != nil {
// 錯誤代碼 05-021-35
e := domain.CommentErrorL(
domain.RemoveRelationErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "SocialNetworkRepository.RemoveFollowerRelation"},
{Key: "err", Value: err},
},
"failed to remove relation form -> to", in.GetFollowerUid(), in.GetFolloweeUid()).Wrap(err)
return nil, e
}
return &tweeting.OKResp{}, nil
}

View File

@ -5,6 +5,7 @@ import (
"app-cloudep-tweeting-service/internal/domain/repository" "app-cloudep-tweeting-service/internal/domain/repository"
client4J "app-cloudep-tweeting-service/internal/lib/neo4j" client4J "app-cloudep-tweeting-service/internal/lib/neo4j"
"context" "context"
"fmt"
"github.com/neo4j/neo4j-go-driver/v5/neo4j" "github.com/neo4j/neo4j-go-driver/v5/neo4j"
) )
@ -25,7 +26,7 @@ func MustSocialNetworkRepository(param SocialNetworkParam) repository.SocialNetw
} }
} }
func (s SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error { func (s *SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
session, err := s.neo4jClient.Conn() session, err := s.neo4jClient.Conn()
if err != nil { if err != nil {
return err return err
@ -36,18 +37,356 @@ func (s SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string)
"uid": uid, "uid": uid,
} }
_, err = session.NewSession(ctx, neo4j.SessionConfig{ run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite, AccessMode: neo4j.AccessModeWrite,
}).Run(ctx, "CREATE (n:User {uid: $uid}) RETURN n", params) }).Run(ctx, "CREATE (n:User {uid: $uid}) RETURN n", params)
if err != nil { if err != nil {
return err return err
} }
// // 處理結果 // 處理結果
// if run.Next(ctx) { if run.Next(ctx) {
// node := run.Record().AsMap() _ = run.Record().AsMap()
// fmt.Printf("Created Node: %v\n", node) }
// }
return nil return nil
} }
func (s *SocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
session, err := s.neo4jClient.Conn()
if err != nil {
return err
}
defer session.Close(ctx)
params := map[string]interface{}{
"fromUID": fromUID,
"toUID": toUID,
}
// 這是有向的關係 form -> to
query := `
MERGE (from:User {uid: $fromUID})
MERGE (to:User {uid: $toUID})
MERGE (from)-[:FRIENDS_WITH]->(to)
RETURN from, to
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite,
}).Run(ctx, query, params)
if err != nil {
return err
}
// 處理結果
if run.Next(ctx) {
_ = run.Record().AsMap()
}
return nil
}
func (s *SocialNetworkRepository) GetFollower(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return repository.FollowResp{}, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": req.UID,
"skip": (req.PageIndex - 1) * req.PageSize,
"limit": req.PageSize,
}
query := `
MATCH (follower:User)-[:FRIENDS_WITH]->(user:User {uid: $uid})
RETURN follower.uid AS uid
SKIP $skip LIMIT $limit
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return repository.FollowResp{}, err
}
var uids []string
for run.Next(ctx) {
record := run.Record()
if uid, ok := record.Get("uid"); ok {
uids = append(uids, uid.(string))
}
}
total, err := s.GetFollowerCount(ctx, req.UID)
if err != nil {
return repository.FollowResp{}, err
}
return repository.FollowResp{
UIDs: uids,
Total: total,
}, nil
}
func (s *SocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return repository.FollowResp{}, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": req.UID,
"skip": (req.PageIndex - 1) * req.PageSize,
"limit": req.PageSize,
}
query := `
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH]->(followee:User)
RETURN followee.uid AS uid
SKIP $skip LIMIT $limit
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return repository.FollowResp{}, err
}
var uids []string
for run.Next(ctx) {
record := run.Record()
if uid, ok := record.Get("uid"); ok {
uids = append(uids, uid.(string))
}
}
total, err := s.GetFolloweeCount(ctx, req.UID)
if err != nil {
return repository.FollowResp{}, err
}
return repository.FollowResp{
UIDs: uids,
Total: total,
}, nil
}
func (s *SocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return 0, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": uid,
}
query := `
MATCH (:User)-[:FRIENDS_WITH]->(user:User {uid: $uid})
RETURN count(*) AS followerCount
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return 0, err
}
var count int64
if run.Next(ctx) {
record := run.Record()
if followerCount, ok := record.Get("followerCount"); ok {
count = followerCount.(int64)
}
}
return count, nil
}
func (s *SocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return 0, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": uid,
}
query := `
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH]->(:User)
RETURN count(*) AS followeeCount
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return 0, err
}
var count int64
if run.Next(ctx) {
record := run.Record()
if followeeCount, ok := record.Get("followeeCount"); ok {
count = followeeCount.(int64)
}
}
return count, nil
}
func (s *SocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
session, err := s.neo4jClient.Conn()
if err != nil {
return err
}
defer session.Close(ctx)
params := map[string]interface{}{
"fromUID": fromUID,
"toUID": toUID,
}
query := `
MATCH (from:User {uid: $fromUID})-[r:FRIENDS_WITH]->(to:User {uid: $toUID})
DELETE r
`
_, err = session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite,
}).Run(ctx, query, params)
if err != nil {
return fmt.Errorf("failed to remove follower relation: %w", err)
}
return nil
}
// GetDegreeBetweenUsers 取得這兩個點之間的度數 (最短路徑長度)
func (s *SocialNetworkRepository) GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return 0, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid1": uid1,
"uid2": uid2,
}
query := `
MATCH (user1:User {uid: $uid1}), (user2:User {uid: $uid2})
MATCH p = shortestPath((user1)-[*]-(user2))
RETURN length(p) AS degree
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return 0, fmt.Errorf("failed to get degree between users: %w", err)
}
var degree int64
if run.Next(ctx) {
record := run.Record()
if deg, ok := record.Get("degree"); ok {
degree = deg.(int64)
}
}
return degree, nil
}
// GetUIDsWithinNDegrees 取得某個節點在 n 度內關係所有 UID
func (s *SocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return nil, 0, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": uid,
"degrees": degrees,
"skip": (pageIndex - 1) * pageSize,
"limit": pageSize,
}
// 查詢結果帶分頁
query := `
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH*1..$degrees]-(related:User)
WITH DISTINCT related.uid AS uid
SKIP $skip LIMIT $limit
RETURN uid
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return nil, 0, fmt.Errorf("failed to get uids within %d degrees of user: %w", degrees, err)
}
var uids []string
for run.Next(ctx) {
record := run.Record()
if uid, ok := record.Get("uid"); ok {
uids = append(uids, uid.(string))
}
}
// 計算總數
totalCount, err := s.getTotalUIDsWithinNDegrees(ctx, uid, degrees)
if err != nil {
return nil, 0, err
}
return uids, totalCount, nil
}
func (s *SocialNetworkRepository) getTotalUIDsWithinNDegrees(ctx context.Context, uid string, degrees int64) (int64, error) {
session, err := s.neo4jClient.Conn()
if err != nil {
return 0, err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": uid,
"degrees": degrees,
}
query := `
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH*1..$degrees]-(related:User)
RETURN count(DISTINCT related.uid) AS totalCount
`
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeRead,
}).Run(ctx, query, params)
if err != nil {
return 0, fmt.Errorf("failed to get total uids within %d degrees of user: %w", degrees, err)
}
var totalCount int64
if run.Next(ctx) {
record := run.Record()
if count, ok := record.Get("totalCount"); ok {
totalCount = count.(int64)
}
}
return totalCount, nil
}

View File

@ -22,7 +22,38 @@ func NewSocialNetworkServiceServer(svcCtx *svc.ServiceContext) *SocialNetworkSer
} }
} }
func (s *SocialNetworkServiceServer) AddUserToNetwork(ctx context.Context, in *tweeting.AddUserToNetworkReq) (*tweeting.OKResp, error) { // MarkFollowRelation 關注
l := socialnetworkservicelogic.NewAddUserToNetworkLogic(ctx, s.svcCtx) func (s *SocialNetworkServiceServer) MarkFollowRelation(ctx context.Context, in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
return l.AddUserToNetwork(in) l := socialnetworkservicelogic.NewMarkFollowRelationLogic(ctx, s.svcCtx)
return l.MarkFollowRelation(in)
}
// RemoveFollowRelation 取消關注
func (s *SocialNetworkServiceServer) RemoveFollowRelation(ctx context.Context, in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
l := socialnetworkservicelogic.NewRemoveFollowRelationLogic(ctx, s.svcCtx)
return l.RemoveFollowRelation(in)
}
// GetFollower 取得跟隨者名單
func (s *SocialNetworkServiceServer) GetFollower(ctx context.Context, in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
l := socialnetworkservicelogic.NewGetFollowerLogic(ctx, s.svcCtx)
return l.GetFollower(in)
}
// GetFollowee 取得我跟隨的名單
func (s *SocialNetworkServiceServer) GetFollowee(ctx context.Context, in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
l := socialnetworkservicelogic.NewGetFolloweeLogic(ctx, s.svcCtx)
return l.GetFollowee(in)
}
// GetFollowerCount 取得跟隨者數量
func (s *SocialNetworkServiceServer) GetFollowerCount(ctx context.Context, in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
l := socialnetworkservicelogic.NewGetFollowerCountLogic(ctx, s.svcCtx)
return l.GetFollowerCount(in)
}
// GetFolloweeCount 取得我跟隨的數量
func (s *SocialNetworkServiceServer) GetFolloweeCount(ctx context.Context, in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
l := socialnetworkservicelogic.NewGetFolloweeCountLogic(ctx, s.svcCtx)
return l.GetFolloweeCount(in)
} }