From 5599f88787b7bbdb86a2919cba87a22420c2ebf4 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Mon, 2 Sep 2024 21:43:03 +0800 Subject: [PATCH] add user to nerwork --- etc/tweeting.yaml | 9 +++ generate/database/neo4j/relaction.cypher | 5 ++ generate/protobuf/tweeting.proto | 12 ++++ go.mod | 1 + internal/config/config.go | 13 ++++ internal/domain/repository/social_network.go | 7 +++ internal/lib/neo4j/config.go | 14 +++++ internal/lib/neo4j/neo4j.go | 54 +++++++++++++++++ internal/lib/neo4j/option.go | 60 +++++++++++++++++++ .../add_user_to_network_logic.go | 36 +++++++++++ internal/repository/social_network.go | 53 ++++++++++++++++ .../repository/timeline_sort_by_timestamp.go | 2 +- .../commentservice/comment_service_server.go | 2 +- .../server/postservice/post_service_server.go | 2 +- .../social_network_service_server.go | 28 +++++++++ .../timeline_service_server.go | 2 +- internal/svc/service_context.go | 29 ++++++--- 17 files changed, 317 insertions(+), 12 deletions(-) create mode 100644 generate/database/neo4j/relaction.cypher create mode 100644 internal/domain/repository/social_network.go create mode 100644 internal/lib/neo4j/config.go create mode 100644 internal/lib/neo4j/neo4j.go create mode 100644 internal/lib/neo4j/option.go create mode 100644 internal/logic/socialnetworkservice/add_user_to_network_logic.go create mode 100644 internal/repository/social_network.go create mode 100644 internal/server/socialnetworkservice/social_network_service_server.go diff --git a/etc/tweeting.yaml b/etc/tweeting.yaml index 90a1858..61ad1c3 100644 --- a/etc/tweeting.yaml +++ b/etc/tweeting.yaml @@ -20,3 +20,12 @@ TimelineSetting: RedisCluster: Host: 127.0.0.1:7001 Type: cluster + +Neo4J: + URI: neo4j://localhost:7687 + Username: neo4j + Password: yyyytttt + MaxConnectionPoolSize: 20 + MaxConnectionLifetime: 200s + ConnectionTimeout : 200s + LogLevel : debug \ No newline at end of file diff --git a/generate/database/neo4j/relaction.cypher b/generate/database/neo4j/relaction.cypher new file mode 100644 index 0000000..c1c34da --- /dev/null +++ b/generate/database/neo4j/relaction.cypher @@ -0,0 +1,5 @@ +// 企業版才能用,社群版只能用預設的 +CREATE DATABASE relation; + +// 創建 User 節點 UID 是唯一鍵 +CREATE CONSTRAINT FOR (u:User) REQUIRE u.UID IS UNIQUE \ No newline at end of file diff --git a/generate/protobuf/tweeting.proto b/generate/protobuf/tweeting.proto index 7fa65b0..89f0c97 100644 --- a/generate/protobuf/tweeting.proto +++ b/generate/protobuf/tweeting.proto @@ -242,4 +242,16 @@ service TimelineService rpc HasNoMoreData(DoNoMoreDataReq) returns (HasNoMoreDataResp); // ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。 rpc ClearNoMoreDataFlag(DoNoMoreDataReq) returns (OKResp); +} + +// ========== Social Network (關係網路) ========== + +message AddUserToNetworkReq +{ + string uid = 1; +} + +service SocialNetworkService +{ + rpc AddUserToNetwork(AddUserToNetworkReq) returns (OKResp); } \ No newline at end of file diff --git a/go.mod b/go.mod index 4fad66b..f1c33fb 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( code.30cm.net/digimon/library-go/errs v1.2.4 code.30cm.net/digimon/library-go/validator v1.0.0 github.com/alicebob/miniredis/v2 v2.33.0 + github.com/neo4j/neo4j-go-driver/v5 v5.24.0 github.com/stretchr/testify v1.9.0 github.com/zeromicro/go-zero v1.7.0 go.mongodb.org/mongo-driver v1.16.0 diff --git a/internal/config/config.go b/internal/config/config.go index 4fd146d..30a88a1 100755 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,8 @@ package config import ( + "time" + "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/zrpc" ) @@ -24,4 +26,15 @@ type Config struct { // Redis Cluster RedisCluster redis.RedisConf + + // 圖形話資料庫 + Neo4J struct { + URI string + Username string + Password string + MaxConnectionPoolSize int + MaxConnectionLifetime time.Duration + ConnectionTimeout time.Duration + LogLevel string + } } diff --git a/internal/domain/repository/social_network.go b/internal/domain/repository/social_network.go new file mode 100644 index 0000000..d1c07a4 --- /dev/null +++ b/internal/domain/repository/social_network.go @@ -0,0 +1,7 @@ +package repository + +import "context" + +type SocialNetworkRepository interface { + CreateUserNode(ctx context.Context, uid string) error +} diff --git a/internal/lib/neo4j/config.go b/internal/lib/neo4j/config.go new file mode 100644 index 0000000..a90758b --- /dev/null +++ b/internal/lib/neo4j/config.go @@ -0,0 +1,14 @@ +package neo4j + +import "time" + +// Config holds the configuration for Neo4j connection. +type Config struct { + URI string + Username string + Password string + MaxConnectionPoolSize int + MaxConnectionLifetime time.Duration + ConnectionTimeout time.Duration + LogLevel string +} diff --git a/internal/lib/neo4j/neo4j.go b/internal/lib/neo4j/neo4j.go new file mode 100644 index 0000000..736099d --- /dev/null +++ b/internal/lib/neo4j/neo4j.go @@ -0,0 +1,54 @@ +package neo4j + +import ( + "context" + "fmt" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + n4Cfg "github.com/neo4j/neo4j-go-driver/v5/neo4j/config" +) + +// NewNeo4J initializes a Neo4jInit using the provided Config and options. +// If opts is not provided, it will initialize Neo4jInit with default configuration. +func NewNeo4J(conf *Config, opts ...Option) *Client { + driverConfig := &n4Cfg.Config{ + MaxConnectionLifetime: conf.MaxConnectionLifetime, + MaxConnectionPoolSize: conf.MaxConnectionPoolSize, + ConnectionAcquisitionTimeout: conf.ConnectionTimeout, + } + + neo4ji := &Client{ + neo4jConf: driverConfig, + } + + for _, opt := range opts { + opt(neo4ji) + } + + return neo4ji +} + +// Conn initiates connection to the database and returns a Neo4j driver instance. +func (c *Client) Conn() (neo4j.DriverWithContext, error) { + auth := neo4j.BasicAuth(c.serviceConf.Username, c.serviceConf.Password, "") + driver, err := neo4j.NewDriverWithContext(c.serviceConf.URI, auth, func(c *n4Cfg.Config) {}) + if err != nil { + return nil, fmt.Errorf("neo4j driver initialization error: %w", err) + } + defer func(driver neo4j.DriverWithContext, ctx context.Context) { + err := driver.Close(ctx) + if err != nil { + // fmt + + } + }(driver, context.Background()) + + ctx := context.Background() + // Verify the connection to Neo4j. + err = driver.VerifyConnectivity(ctx) + if err != nil { + return nil, fmt.Errorf("neo4j connectivity verification error: %w", err) + } + + return driver, nil +} diff --git a/internal/lib/neo4j/option.go b/internal/lib/neo4j/option.go new file mode 100644 index 0000000..a126618 --- /dev/null +++ b/internal/lib/neo4j/option.go @@ -0,0 +1,60 @@ +package neo4j + +import ( + "strings" + "time" + + n4Cfg "github.com/neo4j/neo4j-go-driver/v5/neo4j/config" + "github.com/neo4j/neo4j-go-driver/v5/neo4j/log" +) + +const ( + defaultMaxConnectionLifetime = 5 * time.Minute + defaultMaxConnectionPoolSize = 25 + defaultConnectionTimeout = 5 * time.Second +) + +// Option configures Neo4jInit behaviour. +type Option func(*Client) + +type Client struct { + neo4jConf *n4Cfg.Config + serviceConf Config +} + +// WithLogLevel sets the log level for the Neo4j driver. +func WithLogLevel(level string) Option { + return func(neo4ji *Client) { + var logger log.Logger + + switch strings.ToLower(level) { + case "panic", "fatal", "error": + logger = log.ToConsole(log.ERROR) + case "warn", "warning": + logger = log.ToConsole(log.WARNING) + case "info", "debug", "trace": + logger = log.ToConsole(log.INFO) + default: + logger = log.ToConsole(log.ERROR) + } + + neo4ji.neo4jConf.Log = logger + } +} + +// WithPerformance configures the Neo4j driver for performance by setting connection pool size and lifetime. +func WithPerformance() Option { + return func(neo4ji *Client) { + if neo4ji.serviceConf.MaxConnectionPoolSize > 0 { + neo4ji.neo4jConf.MaxConnectionPoolSize = neo4ji.serviceConf.MaxConnectionPoolSize + } else { + neo4ji.neo4jConf.MaxConnectionPoolSize = defaultMaxConnectionPoolSize + } + + if neo4ji.serviceConf.MaxConnectionLifetime > 0 { + neo4ji.neo4jConf.MaxConnectionLifetime = neo4ji.serviceConf.MaxConnectionLifetime + } else { + neo4ji.neo4jConf.MaxConnectionLifetime = defaultMaxConnectionLifetime + } + } +} diff --git a/internal/logic/socialnetworkservice/add_user_to_network_logic.go b/internal/logic/socialnetworkservice/add_user_to_network_logic.go new file mode 100644 index 0000000..69f51cb --- /dev/null +++ b/internal/logic/socialnetworkservice/add_user_to_network_logic.go @@ -0,0 +1,36 @@ +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 { + return nil, err + } + fmt.Println(err) + + return &tweeting.OKResp{}, nil +} diff --git a/internal/repository/social_network.go b/internal/repository/social_network.go new file mode 100644 index 0000000..5adb29e --- /dev/null +++ b/internal/repository/social_network.go @@ -0,0 +1,53 @@ +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" + + ers "code.30cm.net/digimon/library-go/errs" + n4 "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 { + // 執行 Cypher + conn, err := s.neo4jClient.Conn() + if err != nil { + return ers.DBError("failed to connect to node4j", err.Error()) + } + + insert := map[string]any{ + "uid": uid, + } + + result, err := conn.NewSession(ctx, n4.SessionConfig{ + AccessMode: n4.AccessModeWrite, + }).Run(ctx, "CREATE (n:Person {name: $name}) RETURN n", insert) + if err != nil { + return err + } + + fmt.Println(result.Keys()) + + return nil + +} diff --git a/internal/repository/timeline_sort_by_timestamp.go b/internal/repository/timeline_sort_by_timestamp.go index fdef560..9599c71 100644 --- a/internal/repository/timeline_sort_by_timestamp.go +++ b/internal/repository/timeline_sort_by_timestamp.go @@ -24,7 +24,7 @@ type TimelineRepository struct { redis redis.Redis } -func MustGenerateUseCase(param TimelineRepositoryParam) repository.TimelineRepository { +func MustGenerateRepository(param TimelineRepositoryParam) repository.TimelineRepository { return &TimelineRepository{ cfg: param.Config, redis: param.Redis, diff --git a/internal/server/commentservice/comment_service_server.go b/internal/server/commentservice/comment_service_server.go index 9fed63f..f434bad 100644 --- a/internal/server/commentservice/comment_service_server.go +++ b/internal/server/commentservice/comment_service_server.go @@ -7,7 +7,7 @@ import ( "context" "app-cloudep-tweeting-service/gen_result/pb/tweeting" - commentservicelogic "app-cloudep-tweeting-service/internal/logic/commentservice" + "app-cloudep-tweeting-service/internal/logic/commentservice" "app-cloudep-tweeting-service/internal/svc" ) diff --git a/internal/server/postservice/post_service_server.go b/internal/server/postservice/post_service_server.go index 639c1f4..07d52d3 100644 --- a/internal/server/postservice/post_service_server.go +++ b/internal/server/postservice/post_service_server.go @@ -7,7 +7,7 @@ import ( "context" "app-cloudep-tweeting-service/gen_result/pb/tweeting" - postservicelogic "app-cloudep-tweeting-service/internal/logic/postservice" + "app-cloudep-tweeting-service/internal/logic/postservice" "app-cloudep-tweeting-service/internal/svc" ) diff --git a/internal/server/socialnetworkservice/social_network_service_server.go b/internal/server/socialnetworkservice/social_network_service_server.go new file mode 100644 index 0000000..3131851 --- /dev/null +++ b/internal/server/socialnetworkservice/social_network_service_server.go @@ -0,0 +1,28 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: tweeting.proto + +package server + +import ( + "context" + + "app-cloudep-tweeting-service/gen_result/pb/tweeting" + "app-cloudep-tweeting-service/internal/logic/socialnetworkservice" + "app-cloudep-tweeting-service/internal/svc" +) + +type SocialNetworkServiceServer struct { + svcCtx *svc.ServiceContext + tweeting.UnimplementedSocialNetworkServiceServer +} + +func NewSocialNetworkServiceServer(svcCtx *svc.ServiceContext) *SocialNetworkServiceServer { + return &SocialNetworkServiceServer{ + svcCtx: svcCtx, + } +} + +func (s *SocialNetworkServiceServer) AddUserToNetwork(ctx context.Context, in *tweeting.AddUserToNetworkReq) (*tweeting.OKResp, error) { + l := socialnetworkservicelogic.NewAddUserToNetworkLogic(ctx, s.svcCtx) + return l.AddUserToNetwork(in) +} diff --git a/internal/server/timelineservice/timeline_service_server.go b/internal/server/timelineservice/timeline_service_server.go index 2d2dc03..771d6c7 100644 --- a/internal/server/timelineservice/timeline_service_server.go +++ b/internal/server/timelineservice/timeline_service_server.go @@ -7,7 +7,7 @@ import ( "context" "app-cloudep-tweeting-service/gen_result/pb/tweeting" - timelineservicelogic "app-cloudep-tweeting-service/internal/logic/timelineservice" + "app-cloudep-tweeting-service/internal/logic/timelineservice" "app-cloudep-tweeting-service/internal/svc" ) diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index 7af4b3f..0c63516 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -3,6 +3,7 @@ package svc import ( "app-cloudep-tweeting-service/internal/config" domainRepo "app-cloudep-tweeting-service/internal/domain/repository" + "app-cloudep-tweeting-service/internal/lib/neo4j" model "app-cloudep-tweeting-service/internal/model/mongo" "app-cloudep-tweeting-service/internal/repository" @@ -12,13 +13,12 @@ import ( ) type ServiceContext struct { - Config config.Config - Validate vi.Validate - - PostModel model.PostModel - CommentModel model.CommentModel - - TimelineRepo domainRepo.TimelineRepository + Config config.Config + Validate vi.Validate + PostModel model.PostModel + CommentModel model.CommentModel + TimelineRepo domainRepo.TimelineRepository + SocialNetworkRepository domainRepo.SocialNetworkRepository } func NewServiceContext(c config.Config) *ServiceContext { @@ -27,14 +27,27 @@ func NewServiceContext(c config.Config) *ServiceContext { panic(err) } + neoClient := neo4j.NewNeo4J(&neo4j.Config{ + URI: c.Neo4J.URI, + Username: c.Neo4J.Username, + Password: c.Neo4J.Password, + MaxConnectionPoolSize: c.Neo4J.MaxConnectionPoolSize, + MaxConnectionLifetime: c.Neo4J.MaxConnectionLifetime, + ConnectionTimeout: c.Neo4J.ConnectionTimeout, + }, neo4j.WithPerformance(), neo4j.WithLogLevel(c.Neo4J.LogLevel)) + return &ServiceContext{ Config: c, Validate: vi.MustValidator(), PostModel: MustPostModel(c), CommentModel: MustCommentModel(c), - TimelineRepo: repository.MustGenerateUseCase(repository.TimelineRepositoryParam{ + TimelineRepo: repository.MustGenerateRepository(repository.TimelineRepositoryParam{ Config: c, Redis: *newRedis, }), + SocialNetworkRepository: repository.MustSocialNetworkRepository(repository.SocialNetworkParam{ + Config: c, + Neo4jClient: neoClient, + }), } }