app-cloudep-tweeting-service/internal/repository/social_network.go

440 lines
9.6 KiB
Go

package repository
import (
"app-cloudep-tweeting-service/internal/config"
"app-cloudep-tweeting-service/internal/domain/repository"
client4J "app-cloudep-tweeting-service/internal/lib/neo4j"
"context"
"fmt"
"github.com/zeromicro/go-zero/core/logx"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
type SocialNetworkParam struct {
Config config.Config
Neo4jClient *client4J.Client
}
type SocialNetworkRepository struct {
cfg config.Config
neo4jClient *client4J.Client
}
func MustSocialNetworkRepository(param SocialNetworkParam) repository.SocialNetworkRepository {
return &SocialNetworkRepository{
cfg: param.Config,
neo4jClient: param.Neo4jClient,
}
}
func (s *SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
//nolint:contextcheck
session, err := s.neo4jClient.Conn()
if err != nil {
return err
}
defer session.Close(ctx)
params := map[string]interface{}{
"uid": uid,
}
run, err := session.NewSession(ctx, neo4j.SessionConfig{
AccessMode: neo4j.AccessModeWrite,
}).Run(ctx, "CREATE (n:User {uid: $uid}) RETURN n", params)
if err != nil {
return err
}
// 處理結果
if run.Next(ctx) {
_ = run.Record().AsMap()
}
return nil
}
func (s *SocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
//nolint:contextcheck
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) {
//nolint:contextcheck
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 uidList []string
for run.Next(ctx) {
record := run.Record()
uid, ok := record.Get("uid")
if ok {
if uidStr, ok := uid.(string); ok {
uidList = append(uidList, uidStr)
} else {
// TODO 可以印 log
continue
}
}
}
total, err := s.GetFollowerCount(ctx, req.UID)
if err != nil {
return repository.FollowResp{}, err
}
return repository.FollowResp{
UIDs: uidList,
Total: total,
}, nil
}
func (s *SocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
//nolint:contextcheck
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 uidList []string
for run.Next(ctx) {
record := run.Record()
uid, ok := record.Get("uid")
if ok {
if uidStr, ok := uid.(string); ok {
uidList = append(uidList, uidStr)
} else {
// 可以印 log
continue
}
}
}
total, err := s.GetFolloweeCount(ctx, req.UID)
if err != nil {
return repository.FollowResp{}, err
}
return repository.FollowResp{
UIDs: uidList,
Total: total,
}, nil
}
func (s *SocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
//nolint:contextcheck
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 {
if dc, ok := followerCount.(int64); ok {
count = dc
} else {
logx.Info("followerCount error")
}
}
}
return count, nil
}
func (s *SocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
//nolint:contextcheck
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 {
if dc, ok := followeeCount.(int64); ok {
count = dc
} else {
logx.Info("followeeCount error")
}
}
}
return count, nil
}
func (s *SocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
//nolint:contextcheck
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) {
//nolint:contextcheck
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 {
if degreeValue, ok := deg.(int64); ok {
degree = degreeValue
} else {
logx.Info("degree error")
}
}
}
return degree, nil
}
// GetUIDsWithinNDegrees 取得某個節點在 n 度內關係所有 UID
func (s *SocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
//nolint:contextcheck
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 uidList []string
for run.Next(ctx) {
record := run.Record()
uid, ok := record.Get("uid")
if ok {
if uidStr, ok := uid.(string); ok {
uidList = append(uidList, uidStr)
} else {
// 可以印 log
continue
}
}
}
// 計算總數
totalCount, err := s.getTotalUIDsWithinNDegrees(ctx, uid, degrees)
if err != nil {
return nil, 0, err
}
return uidList, totalCount, nil
}
func (s *SocialNetworkRepository) getTotalUIDsWithinNDegrees(ctx context.Context, uid string, degrees int64) (int64, error) {
//nolint:contextcheck
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 {
if countV, ok := count.(int64); ok {
totalCount = countV
} else {
logx.Info("totalCount error")
}
}
}
return totalCount, nil
}