2024-09-02 13:43:03 +00:00
|
|
|
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"
|
2024-09-03 09:11:12 +00:00
|
|
|
"fmt"
|
2024-09-03 09:47:34 +00:00
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
|
|
|
2024-09-03 01:17:50 +00:00
|
|
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
2024-09-02 13:43:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:11:12 +00:00
|
|
|
func (s *SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 01:17:50 +00:00
|
|
|
session, err := s.neo4jClient.Conn()
|
2024-09-02 13:43:03 +00:00
|
|
|
if err != nil {
|
2024-09-03 01:17:50 +00:00
|
|
|
return err
|
2024-09-02 13:43:03 +00:00
|
|
|
}
|
2024-09-03 01:17:50 +00:00
|
|
|
defer session.Close(ctx)
|
2024-09-02 13:43:03 +00:00
|
|
|
|
2024-09-03 01:17:50 +00:00
|
|
|
params := map[string]interface{}{
|
2024-09-02 13:43:03 +00:00
|
|
|
"uid": uid,
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:11:12 +00:00
|
|
|
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
2024-09-03 01:17:50 +00:00
|
|
|
AccessMode: neo4j.AccessModeWrite,
|
|
|
|
}).Run(ctx, "CREATE (n:User {uid: $uid}) RETURN n", params)
|
2024-09-02 13:43:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:11:12 +00:00
|
|
|
// 處理結果
|
|
|
|
if run.Next(ctx) {
|
|
|
|
_ = run.Record().AsMap()
|
|
|
|
}
|
2024-09-02 13:43:03 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
|
|
|
|
func (s *SocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:47:34 +00:00
|
|
|
var uidList []string
|
2024-09-03 09:11:12 +00:00
|
|
|
for run.Next(ctx) {
|
|
|
|
record := run.Record()
|
2024-09-03 09:47:34 +00:00
|
|
|
uid, ok := record.Get("uid")
|
|
|
|
if ok {
|
|
|
|
if uidStr, ok := uid.(string); ok {
|
|
|
|
uidList = append(uidList, uidStr)
|
|
|
|
} else {
|
|
|
|
// TODO 可以印 log
|
|
|
|
continue
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
total, err := s.GetFollowerCount(ctx, req.UID)
|
|
|
|
if err != nil {
|
|
|
|
return repository.FollowResp{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return repository.FollowResp{
|
2024-09-03 09:47:34 +00:00
|
|
|
UIDs: uidList,
|
2024-09-03 09:11:12 +00:00
|
|
|
Total: total,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:47:34 +00:00
|
|
|
var uidList []string
|
2024-09-03 09:11:12 +00:00
|
|
|
for run.Next(ctx) {
|
|
|
|
record := run.Record()
|
2024-09-03 09:47:34 +00:00
|
|
|
uid, ok := record.Get("uid")
|
|
|
|
if ok {
|
|
|
|
if uidStr, ok := uid.(string); ok {
|
|
|
|
uidList = append(uidList, uidStr)
|
|
|
|
} else {
|
|
|
|
// 可以印 log
|
|
|
|
continue
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
total, err := s.GetFolloweeCount(ctx, req.UID)
|
|
|
|
if err != nil {
|
|
|
|
return repository.FollowResp{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return repository.FollowResp{
|
2024-09-03 09:47:34 +00:00
|
|
|
UIDs: uidList,
|
2024-09-03 09:11:12 +00:00
|
|
|
Total: total,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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 {
|
2024-09-03 09:47:34 +00:00
|
|
|
if dc, ok := followerCount.(int64); ok {
|
|
|
|
count = dc
|
|
|
|
} else {
|
|
|
|
logx.Info("followerCount error")
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return count, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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 {
|
2024-09-03 09:47:34 +00:00
|
|
|
if dc, ok := followeeCount.(int64); ok {
|
|
|
|
count = dc
|
|
|
|
} else {
|
|
|
|
logx.Info("followeeCount error")
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return count, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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 {
|
2024-09-03 09:47:34 +00:00
|
|
|
if degreeValue, ok := deg.(int64); ok {
|
|
|
|
degree = degreeValue
|
|
|
|
} else {
|
|
|
|
logx.Info("degree error")
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return degree, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUIDsWithinNDegrees 取得某個節點在 n 度內關係所有 UID
|
|
|
|
func (s *SocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:47:34 +00:00
|
|
|
var uidList []string
|
2024-09-03 09:11:12 +00:00
|
|
|
for run.Next(ctx) {
|
|
|
|
record := run.Record()
|
2024-09-03 09:47:34 +00:00
|
|
|
uid, ok := record.Get("uid")
|
|
|
|
if ok {
|
|
|
|
if uidStr, ok := uid.(string); ok {
|
|
|
|
uidList = append(uidList, uidStr)
|
|
|
|
} else {
|
|
|
|
// 可以印 log
|
|
|
|
continue
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 計算總數
|
|
|
|
totalCount, err := s.getTotalUIDsWithinNDegrees(ctx, uid, degrees)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2024-09-03 09:47:34 +00:00
|
|
|
return uidList, totalCount, nil
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SocialNetworkRepository) getTotalUIDsWithinNDegrees(ctx context.Context, uid string, degrees int64) (int64, error) {
|
2024-09-03 09:47:34 +00:00
|
|
|
//nolint:contextcheck
|
2024-09-03 09:11:12 +00:00
|
|
|
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 {
|
2024-09-03 09:47:34 +00:00
|
|
|
if countV, ok := count.(int64); ok {
|
|
|
|
totalCount = countV
|
|
|
|
} else {
|
|
|
|
logx.Info("totalCount error")
|
|
|
|
}
|
2024-09-03 09:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return totalCount, nil
|
|
|
|
}
|