Merge pull request 'feature/fanout' (#3) from feature/fanout into main
Reviewed-on: #3
This commit is contained in:
commit
7cbee88aec
|
@ -117,6 +117,14 @@ issues:
|
|||
- gocognit
|
||||
- contextcheck
|
||||
|
||||
exclude-dirs:
|
||||
- internal/model
|
||||
|
||||
exclude-files:
|
||||
- .*_test.go
|
||||
|
||||
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
|
|
9
Makefile
9
Makefile
|
@ -18,6 +18,7 @@ test: # 進行測試
|
|||
fmt: # 格式優化
|
||||
$(GOFMT) -w $(GOFILES)
|
||||
goimports -w ./
|
||||
golangci-lint run
|
||||
|
||||
.PHONY: gen-rpc
|
||||
gen-rpc: # 建立 rpc code
|
||||
|
@ -51,9 +52,9 @@ gen-mongo-model: # 建立 rpc 資料庫
|
|||
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
||||
goctl model mongo -t post --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
goctl model mongo -t comment --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
# goctl model mongo -t tags --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
# goctl model mongo -t post_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
# goctl model mongo -t comment_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
goctl model mongo -t tags --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
goctl model mongo -t post_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
goctl model mongo -t comment_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||
@echo "Generate mongo model files successfully"
|
||||
|
||||
.PHONY: mock-gen
|
||||
|
@ -62,6 +63,8 @@ mock-gen: # 建立 mock 資料
|
|||
mockgen -source=./internal/model/mongo/post_model.go -destination=./internal/mock/model/post_model.go -package=mock
|
||||
mockgen -source=./internal/model/mongo/comment_model_gen.go -destination=./internal/mock/model/comment_model_gen.go -package=mock
|
||||
mockgen -source=./internal/model/mongo/comment_model.go -destination=./internal/mock/model/comment_model.go -package=mock
|
||||
mockgen -source=./internal/domain/repository/social_network.go -destination=./internal/mock/repository/social_network.go -package=mock
|
||||
mockgen -source=./internal/domain/repository/timeline.go -destination=./internal/mock/repository/timeline.go -package=mock
|
||||
@echo "Generate mock files successfully"
|
||||
|
||||
.PHONY: migrate-database
|
||||
|
|
|
@ -12,3 +12,20 @@ Mongo:
|
|||
Password: ""
|
||||
Port: "27017"
|
||||
Database: digimon_tweeting
|
||||
|
||||
TimelineSetting:
|
||||
Expire: 86400
|
||||
MaxLength: 1000
|
||||
|
||||
RedisCluster:
|
||||
Host: 127.0.0.1:7001
|
||||
Type: cluster
|
||||
|
||||
Neo4J:
|
||||
URI: bolt://localhost:7687
|
||||
Username: neo4j
|
||||
Password: yyyytttt
|
||||
MaxConnectionPoolSize: 20
|
||||
MaxConnectionLifetime: 200s
|
||||
ConnectionTimeout : 200s
|
||||
LogLevel : debug
|
|
@ -1,5 +1,2 @@
|
|||
use digimon_tweeting;
|
||||
db.comment.createIndex({ "post_id": 1,"createAt":1});
|
||||
|
||||
|
||||
// TODO 看是否有要刪除過多的索引,要在測試一下
|
|
@ -0,0 +1,5 @@
|
|||
// 企業版才能用,社群版只能用預設的
|
||||
CREATE DATABASE relation;
|
||||
|
||||
// 創建 User 節點 UID 是唯一鍵
|
||||
CREATE CONSTRAINT FOR (u:User) REQUIRE u.uid IS UNIQUE
|
|
@ -182,3 +182,117 @@ service CommentService
|
|||
// UpdateComment 更新評論
|
||||
rpc UpdateComment(UpdateCommentReq) returns (OKResp);
|
||||
}
|
||||
|
||||
// ========== TimeLineService (個人動態時報) ==========
|
||||
|
||||
message GetTimelineReq
|
||||
{
|
||||
string uid = 1; // 用户ID
|
||||
int64 pageIndex = 2; // 頁碼
|
||||
int64 pageSize = 3; // 每一頁大小
|
||||
}
|
||||
|
||||
message FetchTimelineResponse
|
||||
{
|
||||
repeated FetchTimelineItem posts = 1; // 貼文列表
|
||||
Pager page = 2; // 分頁訊息
|
||||
}
|
||||
|
||||
message FetchTimelineItem
|
||||
{
|
||||
string post_id = 1;
|
||||
int64 score = 2;
|
||||
}
|
||||
|
||||
message AddPostToTimelineReq
|
||||
{
|
||||
string uid = 1; // key
|
||||
repeated PostTimelineItem posts = 3;
|
||||
}
|
||||
|
||||
// 貼文更新
|
||||
message PostTimelineItem
|
||||
{
|
||||
string post_id = 1; // 貼文ID
|
||||
int64 created_at = 7; // 發佈時間 -> 排序使用
|
||||
}
|
||||
|
||||
message DoNoMoreDataReq
|
||||
{
|
||||
string uid = 1;
|
||||
}
|
||||
|
||||
message HasNoMoreDataResp
|
||||
{
|
||||
bool status = 1;
|
||||
}
|
||||
|
||||
// TimelineService 業務邏輯在外面組合
|
||||
service TimelineService
|
||||
{
|
||||
// AddPost 加入貼文,只管一股腦全塞,這裡會自動判斷
|
||||
// 誰要砍誰不砍,處理排序,上限是 1000 筆(超過1000 請他回資料庫拿)
|
||||
// 只存活躍用戶的,不活躍不浪費快取的空間
|
||||
rpc AddPost(AddPostToTimelineReq) returns (OKResp);
|
||||
// FetchTimeline 取得這個人的動態時報
|
||||
rpc FetchTimeline(GetTimelineReq) returns (FetchTimelineResponse);
|
||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
||||
rpc SetNoMoreDataFlag(DoNoMoreDataReq) returns (OKResp);
|
||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
||||
rpc HasNoMoreData(DoNoMoreDataReq) returns (HasNoMoreDataResp);
|
||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
||||
rpc ClearNoMoreDataFlag(DoNoMoreDataReq) returns (OKResp);
|
||||
}
|
||||
|
||||
// ========== Social Network (關係網路) ==========
|
||||
|
||||
message AddUserToNetworkReq
|
||||
{
|
||||
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
|
||||
{
|
||||
// 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);
|
||||
}
|
37
go.mod
37
go.mod
|
@ -5,7 +5,10 @@ go 1.22.3
|
|||
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/testcontainers/testcontainers-go v0.33.0
|
||||
github.com/zeromicro/go-zero v1.7.0
|
||||
go.mongodb.org/mongo-driver v1.16.0
|
||||
go.uber.org/mock v0.4.0
|
||||
|
@ -14,18 +17,32 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/containerd v1.7.18 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
|
@ -45,29 +62,49 @@ require (
|
|||
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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // 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
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // 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/v3 v3.5.15 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
package config
|
||||
|
||||
import "github.com/zeromicro/go-zero/zrpc"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
zrpc.RpcServerConf
|
||||
|
||||
Mongo struct {
|
||||
Schema string
|
||||
User string
|
||||
|
@ -12,4 +18,23 @@ type Config struct {
|
|||
Port string
|
||||
Database string
|
||||
}
|
||||
|
||||
TimelineSetting struct {
|
||||
Expire int64 // Second
|
||||
MaxLength int64 // 暫存筆數
|
||||
}
|
||||
|
||||
// Redis Cluster
|
||||
RedisCluster redis.RedisConf
|
||||
|
||||
// 圖形資料庫
|
||||
Neo4J struct {
|
||||
URI string
|
||||
Username string
|
||||
Password string
|
||||
MaxConnectionPoolSize int
|
||||
MaxConnectionLifetime time.Duration
|
||||
ConnectionTimeout time.Duration
|
||||
LogLevel string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,3 +11,7 @@ const (
|
|||
AdTypeOnlyAd
|
||||
AdTypeOnlyNotAd
|
||||
)
|
||||
|
||||
const (
|
||||
LastOfTimelineFlag = "NoMoreData"
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ers "code.30cm.net/digimon/library-go/errs"
|
||||
|
@ -33,10 +32,25 @@ const (
|
|||
CommentListErrorCode
|
||||
)
|
||||
|
||||
const (
|
||||
AddTimeLineErrorCode ErrorCode = iota + 20
|
||||
FetchTimeLineErrorCode
|
||||
ClearNoMoreDataErrorCode
|
||||
HasNoMoreDataErrorCode
|
||||
SetNoMoreDataErrorCode
|
||||
)
|
||||
|
||||
const (
|
||||
MarkRelationErrorCode ErrorCode = iota + 30
|
||||
GetFollowerErrorCode
|
||||
GetFollowerCountErrorCode
|
||||
GetFolloweeErrorCode
|
||||
GetFolloweeCountErrorCode
|
||||
RemoveRelationErrorCode
|
||||
)
|
||||
|
||||
func CommentError(ec ErrorCode, s ...string) *ers.LibError {
|
||||
return ers.NewError(code.CloudEPTweeting, code.DBError,
|
||||
ec.ToUint32(),
|
||||
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||
return ers.NewError(code.CloudEPTweeting, code.DBError, ec.ToUint32(), strings.Join(s, " "))
|
||||
}
|
||||
|
||||
func CommentErrorL(ec ErrorCode,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package domain
|
||||
|
||||
import "strings"
|
||||
|
||||
type RedisKey string
|
||||
|
||||
func (key RedisKey) ToString() string {
|
||||
return string(key)
|
||||
}
|
||||
|
||||
func (key RedisKey) With(s ...string) RedisKey {
|
||||
parts := append([]string{string(key)}, s...)
|
||||
|
||||
return RedisKey(strings.Join(parts, ":"))
|
||||
}
|
||||
|
||||
const (
|
||||
TimelineRedisKey RedisKey = "timeline"
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestRedisKeyToString 測試 ToString 方法
|
||||
func TestRedisKeyToString(t *testing.T) {
|
||||
key := RedisKey("user:timeline")
|
||||
expected := "user:timeline"
|
||||
result := key.ToString()
|
||||
|
||||
assert.Equal(t, expected, result, "ToString should return the correct string representation of RedisKey")
|
||||
}
|
||||
|
||||
// TestRedisKeyWith 測試 With 方法
|
||||
func TestRedisKeyWith(t *testing.T) {
|
||||
key := RedisKey("user:timeline")
|
||||
subKey := "12345"
|
||||
expected := "user:timeline:12345"
|
||||
result := key.With(subKey)
|
||||
|
||||
assert.Equal(t, RedisKey(expected), result, "With should correctly concatenate the RedisKey with the provided subKey")
|
||||
}
|
||||
|
||||
// TestRedisKeyWithMultiple 測試 With 方法與多個參數
|
||||
func TestRedisKeyWithMultiple(t *testing.T) {
|
||||
key := RedisKey("user:timeline")
|
||||
subKeys := []string{"12345", "posts"}
|
||||
expected := "user:timeline:12345:posts"
|
||||
result := key.With(subKeys...)
|
||||
|
||||
assert.Equal(t, RedisKey(expected), result, "With should correctly concatenate the RedisKey with multiple provided subKeys")
|
||||
}
|
||||
|
||||
// TestRedisKeyWithEmpty 測試 With 方法與空參數
|
||||
func TestRedisKeyWithEmpty(t *testing.T) {
|
||||
key := RedisKey("user:timeline")
|
||||
expected := "user:timeline"
|
||||
result := key.With()
|
||||
|
||||
assert.Equal(t, RedisKey(expected), result, "With should return the original key when no subKeys are provided")
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package repository
|
||||
|
||||
import "context"
|
||||
|
||||
type SocialNetworkRepository interface {
|
||||
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
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"context"
|
||||
)
|
||||
|
||||
/*
|
||||
----------------------------------------
|
||||
| | | | |
|
||||
| data A| data B |NO Data | .... |
|
||||
| | | | |
|
||||
----------------------------------------
|
||||
|
||||
動態時報在發現這個 Queue 有 No Data 的 Flag 時
|
||||
就不再去 Query 資料庫,防止被狂刷。
|
||||
只是要注意在業務上何時要 加入/刪除 這個 Flag
|
||||
*/
|
||||
|
||||
// TimelineRepository 定義時間線的存儲接口,可以根據不同的排序策略實現。
|
||||
type TimelineRepository interface {
|
||||
// AddPost 將貼文添加到動態時報,並根據排序策略進行排序。
|
||||
AddPost(ctx context.Context, req AddPostRequest) error
|
||||
// FetchTimeline 獲取指定用戶的動態時報。
|
||||
FetchTimeline(ctx context.Context, req FetchTimelineRequest) (FetchTimelineResponse, error)
|
||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
||||
SetNoMoreDataFlag(ctx context.Context, uid string) error
|
||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
||||
HasNoMoreData(ctx context.Context, uid string) (bool, error)
|
||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
||||
ClearNoMoreDataFlag(ctx context.Context, uid string) error
|
||||
}
|
||||
|
||||
// AddPostRequest 用於將貼文添加到時間線的請求結構體。
|
||||
type AddPostRequest struct {
|
||||
UID string
|
||||
PostItems []TimelineItem
|
||||
}
|
||||
|
||||
// TimelineItem 表示時間線中的一個元素,排序依據取決於 Score。
|
||||
type TimelineItem struct {
|
||||
PostID string // 貼文ID
|
||||
Score int64 // 排序使用的分數,根據具體實現可能代表時間、優先級等
|
||||
}
|
||||
|
||||
// FetchTimelineRequest 用於獲取時間線的請求結構體。
|
||||
type FetchTimelineRequest struct {
|
||||
UID string
|
||||
PageSize int64
|
||||
PageIndex int64
|
||||
}
|
||||
|
||||
// FetchTimelineResponse 表示獲取時間線的回應結構體。
|
||||
type FetchTimelineResponse struct {
|
||||
Items []TimelineItem
|
||||
Page tweeting.Pager
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
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,
|
||||
serviceConf: Config{
|
||||
URI: conf.URI,
|
||||
Username: conf.Username,
|
||||
Password: conf.Password,
|
||||
LogLevel: conf.LogLevel,
|
||||
},
|
||||
}
|
||||
|
||||
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(_ *n4Cfg.Config) {})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("neo4j driver initialization error: %w", err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package neo4j
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
func TestNewNeo4J(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conf *Config
|
||||
expected *Config
|
||||
}{
|
||||
{
|
||||
name: "valid configuration",
|
||||
conf: &Config{
|
||||
URI: "neo4j://localhost:7687",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
expected: &Config{
|
||||
URI: "neo4j://localhost:7687",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "info",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty URI",
|
||||
conf: &Config{
|
||||
URI: "",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
expected: &Config{
|
||||
URI: "",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "info",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty username and password",
|
||||
conf: &Config{
|
||||
URI: "neo4j://localhost:7687",
|
||||
Username: "",
|
||||
Password: "",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
expected: &Config{
|
||||
URI: "neo4j://localhost:7687",
|
||||
Username: "",
|
||||
Password: "",
|
||||
LogLevel: "info",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom log level",
|
||||
conf: &Config{
|
||||
URI: "neo4j://localhost:7687",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "debug",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
expected: &Config{
|
||||
URI: "neo4j://localhost:7687",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "debug",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := NewNeo4J(tt.conf)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, tt.expected.URI, client.serviceConf.URI)
|
||||
assert.Equal(t, tt.expected.Username, client.serviceConf.Username)
|
||||
assert.Equal(t, tt.expected.Password, client.serviceConf.Password)
|
||||
assert.Equal(t, tt.expected.LogLevel, client.serviceConf.LogLevel)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
neo4jContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: testcontainers.ContainerRequest{
|
||||
Image: "neo4j:latest",
|
||||
ExposedPorts: []string{"7687/tcp"},
|
||||
Env: map[string]string{
|
||||
"NEO4J_AUTH": "neo4j/yyyytttt",
|
||||
},
|
||||
WaitingFor: wait.ForLog("Started"),
|
||||
},
|
||||
Started: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer neo4jContainer.Terminate(ctx)
|
||||
|
||||
host, _ := neo4jContainer.Host(ctx)
|
||||
port, _ := neo4jContainer.MappedPort(ctx, "7687")
|
||||
|
||||
uri := fmt.Sprintf("bolt://%s:%s", host, port.Port())
|
||||
t.Log("Neo4j running at:", uri)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
conf *Config
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
name: "successful connection",
|
||||
conf: &Config{
|
||||
URI: uri,
|
||||
Username: "neo4j",
|
||||
Password: "yyyytttt",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "failed connection due to invalid URI",
|
||||
conf: &Config{
|
||||
URI: uri,
|
||||
Username: "neo4j",
|
||||
Password: "wrongpassword",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "failed connection due to missing URI",
|
||||
conf: &Config{
|
||||
URI: "",
|
||||
Username: "neo4j",
|
||||
Password: "password",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "failed connection due to missing username and password",
|
||||
conf: &Config{
|
||||
URI: uri,
|
||||
Username: "",
|
||||
Password: "",
|
||||
LogLevel: "info",
|
||||
MaxConnectionLifetime: time.Minute * 5,
|
||||
MaxConnectionPoolSize: 10,
|
||||
ConnectionTimeout: time.Second * 5,
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := NewNeo4J(tt.conf)
|
||||
driver, err := client.Conn()
|
||||
if tt.shouldFail {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, driver)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, driver)
|
||||
|
||||
// Close the driver after test
|
||||
defer func() {
|
||||
err := driver.Close(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ func (l *DeleteCommentLogic) DeleteComment(in *tweeting.DeleteCommentReq) (*twee
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to del comment").Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ func convertToCommentDetailItem(item *model.Comment) *tweeting.CommentDetail {
|
|||
Uid: item.UID,
|
||||
Content: item.Content,
|
||||
CreatedAt: item.CreateAt,
|
||||
LikeCount: int64(item.LikeCount),
|
||||
DislikeCount: int64(item.DisLikeCount),
|
||||
LikeCount: item.LikeCount,
|
||||
DislikeCount: item.DisLikeCount,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,7 @@ func (l *GetCommentsLogic) GetComments(in *tweeting.GetCommentsReq) (*tweeting.G
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to find comment").Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -27,16 +27,16 @@ func NewUpdateCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Upd
|
|||
}
|
||||
}
|
||||
|
||||
type checkCommentId struct {
|
||||
CommentId string `validate:"required"`
|
||||
type checkCommentID struct {
|
||||
CommentID string `validate:"required"`
|
||||
Content string `json:"content,omitempty" validate:"lte=500"`
|
||||
}
|
||||
|
||||
// UpdateComment 更新評論
|
||||
func (l *UpdateCommentLogic) UpdateComment(in *tweeting.UpdateCommentReq) (*tweeting.OKResp, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&checkCommentId{
|
||||
CommentId: in.GetCommentId(),
|
||||
if err := l.svcCtx.Validate.ValidateAll(&checkCommentID{
|
||||
CommentID: in.GetCommentId(),
|
||||
Content: in.GetContent(),
|
||||
}); err != nil {
|
||||
// 錯誤代碼 05-011-00
|
||||
|
@ -75,6 +75,7 @@ func (l *UpdateCommentLogic) UpdateComment(in *tweeting.UpdateCommentReq) (*twee
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to update comment:", in.CommentId).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ 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"`
|
||||
MediaURL []string `json:"media_url"`
|
||||
IsAd bool `json:"is_ad"` // default false
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,7 @@ func (l *CreatePostLogic) CreatePost(in *tweeting.NewPostReq) (*tweeting.PostRes
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to add new post").Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKR
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to del post").Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -54,8 +54,8 @@ func convertToPostDetailItem(item *model.Post) *tweeting.PostDetailItem {
|
|||
IsAd: item.IsAd,
|
||||
CreatedAt: item.CreateAt,
|
||||
UpdateAt: item.UpdateAt,
|
||||
LikeCount: int64(item.Like),
|
||||
DislikeCount: int64(item.DisLike),
|
||||
LikeCount: item.Like,
|
||||
DislikeCount: item.DisLike,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,7 @@ func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPo
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to find posts").Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ func NewUpdatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Update
|
|||
}
|
||||
}
|
||||
|
||||
type checkPostId struct {
|
||||
type checkPostID struct {
|
||||
PostID string `validate:"required"`
|
||||
Content string `json:"content,omitempty" validate:"lte=500"`
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ type checkPostId struct {
|
|||
// UpdatePost 更新貼文
|
||||
func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&checkPostId{
|
||||
if err := l.svcCtx.Validate.ValidateAll(&checkPostID{
|
||||
PostID: in.GetPostId(),
|
||||
Content: in.GetContent(),
|
||||
}); err != nil {
|
||||
|
@ -53,7 +53,7 @@ func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKRe
|
|||
update.ID = oid
|
||||
update.Tags = in.GetTags()
|
||||
// 將 Media 存入
|
||||
var media []model.Media
|
||||
media := make([]model.Media, 0, len(in.GetMedia()))
|
||||
for _, item := range in.GetMedia() {
|
||||
media = append(media, model.Media{
|
||||
Links: item.Url,
|
||||
|
@ -88,6 +88,7 @@ func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKRe
|
|||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to update post", in.PostId).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 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
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetFolloweeCountLogic_GetFolloweeCount(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 测试数据集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.FollowCountReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
input: &tweeting.FollowCountReq{
|
||||
Uid: "12345",
|
||||
},
|
||||
prepare: func() {
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
mockSocialNetworkRepository.EXPECT().GetFolloweeCount(gomock.Any(), "12345").Return(int64(10), nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.FollowCountReq{
|
||||
Uid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "取得跟隨數量失敗",
|
||||
input: &tweeting.FollowCountReq{
|
||||
Uid: "12345",
|
||||
},
|
||||
prepare: func() {
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
mockSocialNetworkRepository.EXPECT().GetFolloweeCount(gomock.Any(), "12345").Return(int64(0), errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.prepare()
|
||||
|
||||
logic := GetFolloweeCountLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
got, err := logic.GetFolloweeCount(tt.input)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.input.Uid, got.Uid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
"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 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
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetFolloweeLogic_GetFollowee(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.FollowReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.FollowResp
|
||||
}{
|
||||
{
|
||||
name: "成功獲取我跟隨的名單",
|
||||
input: &tweeting.FollowReq{
|
||||
Uid: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬 GetFollowee 返回正確的結果
|
||||
mockSocialNetworkRepository.EXPECT().GetFollowee(gomock.Any(), repository.FollowReq{
|
||||
UID: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
}).Return(repository.FollowResp{
|
||||
UIDs: []string{"user1", "user2"},
|
||||
Total: 2,
|
||||
}, nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.FollowResp{
|
||||
Uid: []string{"user1", "user2"},
|
||||
Page: &tweeting.Pager{
|
||||
Total: 2,
|
||||
Index: 1,
|
||||
Size: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.FollowReq{
|
||||
Uid: "",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "獲取我跟隨的名單失敗",
|
||||
input: &tweeting.FollowReq{
|
||||
Uid: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬 GetFollowee 返回錯誤
|
||||
mockSocialNetworkRepository.EXPECT().GetFollowee(gomock.Any(), repository.FollowReq{
|
||||
UID: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
}).Return(repository.FollowResp{}, errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 GetFolloweeLogic
|
||||
logic := GetFolloweeLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 GetFollowee
|
||||
got, err := logic.GetFollowee(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 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
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetFollowerCountLogic_GetFollowerCount(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.FollowCountReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.FollowCountResp
|
||||
}{
|
||||
{
|
||||
name: "成功獲取跟隨者數量",
|
||||
input: &tweeting.FollowCountReq{
|
||||
Uid: "12345",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬 GetFollowerCount 返回正確的結果
|
||||
mockSocialNetworkRepository.EXPECT().GetFollowerCount(gomock.Any(), "12345").Return(int64(10), nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.FollowCountResp{
|
||||
Uid: "12345",
|
||||
Total: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.FollowCountReq{
|
||||
Uid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "獲取跟隨者數量失敗",
|
||||
input: &tweeting.FollowCountReq{
|
||||
Uid: "12345",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬 GetFollowerCount 返回錯誤
|
||||
mockSocialNetworkRepository.EXPECT().GetFollowerCount(gomock.Any(), "12345").Return(int64(0), errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 GetFollowerCountLogic
|
||||
logic := GetFollowerCountLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 GetFollowerCount
|
||||
got, err := logic.GetFollowerCount(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
"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 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
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetFollowerLogic_GetFollower(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.FollowReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.FollowResp
|
||||
}{
|
||||
{
|
||||
name: "成功獲取跟隨者名單",
|
||||
input: &tweeting.FollowReq{
|
||||
Uid: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬 GetFollower 返回正確的結果
|
||||
mockSocialNetworkRepository.EXPECT().GetFollower(gomock.Any(), repository.FollowReq{
|
||||
UID: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
}).Return(repository.FollowResp{
|
||||
UIDs: []string{"user1", "user2"},
|
||||
Total: 2,
|
||||
}, nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.FollowResp{
|
||||
Uid: []string{"user1", "user2"},
|
||||
Page: &tweeting.Pager{
|
||||
Total: 2,
|
||||
Index: 1,
|
||||
Size: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.FollowReq{
|
||||
Uid: "",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "獲取跟隨者名單失敗",
|
||||
input: &tweeting.FollowReq{
|
||||
Uid: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬 GetFollower 返回錯誤
|
||||
mockSocialNetworkRepository.EXPECT().GetFollower(gomock.Any(), repository.FollowReq{
|
||||
UID: "12345",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
}).Return(repository.FollowResp{}, errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 GetFollowerLogic
|
||||
logic := GetFollowerLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 GetFollower
|
||||
got, err := logic.GetFollower(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 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
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestMarkFollowRelationLogic_MarkFollowRelation(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.DoFollowerRelationReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.OKResp
|
||||
}{
|
||||
{
|
||||
name: "成功建立關注關係",
|
||||
input: &tweeting.DoFollowerRelationReq{
|
||||
FollowerUid: "follower123",
|
||||
FolloweeUid: "followee456",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功建立關注關係
|
||||
mockSocialNetworkRepository.EXPECT().MarkFollowerRelation(gomock.Any(), "follower123", "followee456").Return(nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.OKResp{},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.DoFollowerRelationReq{
|
||||
FollowerUid: "",
|
||||
FolloweeUid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "建立關注關係失敗",
|
||||
input: &tweeting.DoFollowerRelationReq{
|
||||
FollowerUid: "follower123",
|
||||
FolloweeUid: "followee456",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬建立關注關係失敗
|
||||
mockSocialNetworkRepository.EXPECT().MarkFollowerRelation(gomock.Any(), "follower123", "followee456").Return(errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 MarkFollowRelationLogic
|
||||
logic := MarkFollowRelationLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 MarkFollowRelation
|
||||
got, err := logic.MarkFollowRelation(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 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
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package socialnetworkservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestRemoveFollowRelationLogic_RemoveFollowRelation(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.DoFollowerRelationReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.OKResp
|
||||
}{
|
||||
{
|
||||
name: "成功取消關注",
|
||||
input: &tweeting.DoFollowerRelationReq{
|
||||
FollowerUid: "follower123",
|
||||
FolloweeUid: "followee456",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功刪除關注關係
|
||||
mockSocialNetworkRepository.EXPECT().RemoveFollowerRelation(gomock.Any(), "follower123", "followee456").Return(nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.OKResp{},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.DoFollowerRelationReq{
|
||||
FollowerUid: "",
|
||||
FolloweeUid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "取消關注失敗",
|
||||
input: &tweeting.DoFollowerRelationReq{
|
||||
FollowerUid: "follower123",
|
||||
FolloweeUid: "followee456",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬刪除關注關係失敗
|
||||
mockSocialNetworkRepository.EXPECT().RemoveFollowerRelation(gomock.Any(), "follower123", "followee456").Return(errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 RemoveFollowRelationLogic
|
||||
logic := RemoveFollowRelationLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 RemoveFollowRelation
|
||||
got, err := logic.RemoveFollowRelation(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
"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 AddPostLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewAddPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddPostLogic {
|
||||
return &AddPostLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
type addPostReq struct {
|
||||
UID string `json:"uid" validate:"required"`
|
||||
}
|
||||
|
||||
// AddPost 加入貼文,只管一股腦全塞,這裡會自動判斷
|
||||
func (l *AddPostLogic) AddPost(in *tweeting.AddPostToTimelineReq) (*tweeting.OKResp, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&addPostReq{
|
||||
UID: in.GetUid(),
|
||||
}); err != nil {
|
||||
// 錯誤代碼 05-011-00
|
||||
return nil, ers.InvalidFormat(err.Error())
|
||||
}
|
||||
|
||||
if len(in.GetPosts()) == 0 {
|
||||
// 沒資料,直接 OK
|
||||
return &tweeting.OKResp{}, nil
|
||||
}
|
||||
|
||||
post := make([]repository.TimelineItem, 0, len(in.GetPosts()))
|
||||
for _, item := range in.GetPosts() {
|
||||
post = append(post, repository.TimelineItem{
|
||||
PostID: item.PostId,
|
||||
Score: item.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
err := l.svcCtx.TimelineRepo.AddPost(l.ctx, repository.AddPostRequest{
|
||||
UID: in.GetUid(),
|
||||
PostItems: post,
|
||||
})
|
||||
if err != nil {
|
||||
// 錯誤代碼 05-021-20
|
||||
e := domain.CommentErrorL(
|
||||
domain.AddTimeLineErrorCode,
|
||||
logx.WithContext(l.ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: in},
|
||||
{Key: "func", Value: "TimelineRepo.AddPost"},
|
||||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to insert timeline repo :", in.GetUid()).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &tweeting.OKResp{}, nil
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestAddPostLogic_AddPost(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
TimelineRepo: mockTimelineRepo,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.AddPostToTimelineReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.OKResp
|
||||
}{
|
||||
{
|
||||
name: "成功加入貼文",
|
||||
input: &tweeting.AddPostToTimelineReq{
|
||||
Uid: "user123",
|
||||
Posts: []*tweeting.PostTimelineItem{
|
||||
{PostId: "post1", CreatedAt: 1627890123},
|
||||
{PostId: "post2", CreatedAt: 1627890124},
|
||||
},
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功加入貼文
|
||||
mockTimelineRepo.EXPECT().AddPost(gomock.Any(), repository.AddPostRequest{
|
||||
UID: "user123",
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 1627890123},
|
||||
{PostID: "post2", Score: 1627890124},
|
||||
},
|
||||
}).Return(nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.OKResp{},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.AddPostToTimelineReq{
|
||||
Uid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "沒有貼文資料",
|
||||
input: &tweeting.AddPostToTimelineReq{
|
||||
Uid: "user123",
|
||||
Posts: []*tweeting.PostTimelineItem{},
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.OKResp{},
|
||||
},
|
||||
{
|
||||
name: "加入貼文失敗",
|
||||
input: &tweeting.AddPostToTimelineReq{
|
||||
Uid: "user123",
|
||||
Posts: []*tweeting.PostTimelineItem{
|
||||
{PostId: "post1", CreatedAt: 1627890123},
|
||||
},
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬加入貼文失敗
|
||||
mockTimelineRepo.EXPECT().AddPost(gomock.Any(), repository.AddPostRequest{
|
||||
UID: "user123",
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 1627890123},
|
||||
},
|
||||
}).Return(errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 AddPostLogic
|
||||
logic := AddPostLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 AddPost
|
||||
got, err := logic.AddPost(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 ClearNoMoreDataFlagLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewClearNoMoreDataFlagLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ClearNoMoreDataFlagLogic {
|
||||
return &ClearNoMoreDataFlagLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
type clearNoMoreDataFlagReq struct {
|
||||
UID string `json:"uid" validate:"required"`
|
||||
}
|
||||
|
||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
||||
func (l *ClearNoMoreDataFlagLogic) ClearNoMoreDataFlag(in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&clearNoMoreDataFlagReq{
|
||||
UID: in.GetUid(),
|
||||
}); err != nil {
|
||||
// 錯誤代碼 05-011-00
|
||||
return nil, ers.InvalidFormat(err.Error())
|
||||
}
|
||||
|
||||
err := l.svcCtx.TimelineRepo.ClearNoMoreDataFlag(l.ctx, in.GetUid())
|
||||
if err != nil {
|
||||
// 錯誤代碼 05-021-22
|
||||
e := domain.CommentErrorL(
|
||||
domain.ClearNoMoreDataErrorCode,
|
||||
logx.WithContext(l.ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: in},
|
||||
{Key: "func", Value: "TimelineRepo.ClearNoMoreDataFlag"},
|
||||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to clear no more data flag timeline repo :", in.GetUid()).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &tweeting.OKResp{}, nil
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestClearNoMoreDataFlagLogic_ClearNoMoreDataFlag(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
TimelineRepo: mockTimelineRepo,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.DoNoMoreDataReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.OKResp
|
||||
}{
|
||||
{
|
||||
name: "成功清除 NoMoreData 標誌",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "user123",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功清除 NoMoreData 標誌
|
||||
mockTimelineRepo.EXPECT().ClearNoMoreDataFlag(gomock.Any(), "user123").Return(nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.OKResp{},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "清除 NoMoreData 標誌失敗",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "user123",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬清除 NoMoreData 標誌失敗
|
||||
mockTimelineRepo.EXPECT().ClearNoMoreDataFlag(gomock.Any(), "user123").Return(errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 ClearNoMoreDataFlagLogic
|
||||
logic := ClearNoMoreDataFlagLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 ClearNoMoreDataFlag
|
||||
got, err := logic.ClearNoMoreDataFlag(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
"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 FetchTimelineLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewFetchTimelineLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FetchTimelineLogic {
|
||||
return &FetchTimelineLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
type fetchTimelineReq struct {
|
||||
UID string `json:"uid" validate:"required"`
|
||||
PageSize int64 `json:"page_size" validate:"required"`
|
||||
PageIndex int64 `json:"page_index" validate:"required"`
|
||||
}
|
||||
|
||||
// FetchTimeline 取得這個人的動態時報
|
||||
func (l *FetchTimelineLogic) FetchTimeline(in *tweeting.GetTimelineReq) (*tweeting.FetchTimelineResponse, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&fetchTimelineReq{
|
||||
UID: in.GetUid(),
|
||||
PageSize: in.PageSize,
|
||||
PageIndex: in.PageIndex,
|
||||
}); err != nil {
|
||||
// 錯誤代碼 05-011-00
|
||||
return nil, ers.InvalidFormat(err.Error())
|
||||
}
|
||||
|
||||
resp, err := l.svcCtx.TimelineRepo.FetchTimeline(l.ctx, repository.FetchTimelineRequest{
|
||||
UID: in.GetUid(),
|
||||
PageIndex: in.GetPageIndex(),
|
||||
PageSize: in.GetPageSize(),
|
||||
})
|
||||
if err != nil {
|
||||
// 錯誤代碼 05-021-21
|
||||
e := domain.CommentErrorL(
|
||||
domain.FetchTimeLineErrorCode,
|
||||
logx.WithContext(l.ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: in},
|
||||
{Key: "func", Value: "TimelineRepo.FetchTimeline"},
|
||||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to fetch timeline repo :", in.GetUid()).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
result := make([]*tweeting.FetchTimelineItem, 0, resp.Page.Size)
|
||||
for _, item := range resp.Items {
|
||||
result = append(result, &tweeting.FetchTimelineItem{
|
||||
PostId: item.PostID,
|
||||
Score: item.Score,
|
||||
})
|
||||
}
|
||||
|
||||
return &tweeting.FetchTimelineResponse{
|
||||
Posts: result,
|
||||
Page: &tweeting.Pager{
|
||||
Total: resp.Page.Total,
|
||||
Index: resp.Page.Index,
|
||||
Size: resp.Page.Size,
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestFetchTimelineLogic_FetchTimeline(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
TimelineRepo: mockTimelineRepo,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.GetTimelineReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.FetchTimelineResponse
|
||||
}{
|
||||
{
|
||||
name: "成功獲取動態時報",
|
||||
input: &tweeting.GetTimelineReq{
|
||||
Uid: "user123",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功獲取動態時報
|
||||
mockTimelineRepo.EXPECT().FetchTimeline(gomock.Any(), repository.FetchTimelineRequest{
|
||||
UID: "user123",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
}).Return(repository.FetchTimelineResponse{
|
||||
Items: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 100},
|
||||
{PostID: "post2", Score: 200},
|
||||
},
|
||||
Page: tweeting.Pager{
|
||||
Total: 2,
|
||||
Size: 10,
|
||||
Index: 1,
|
||||
},
|
||||
}, nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.FetchTimelineResponse{
|
||||
Posts: []*tweeting.FetchTimelineItem{
|
||||
{PostId: "post1", Score: 100},
|
||||
{PostId: "post2", Score: 200},
|
||||
},
|
||||
Page: &tweeting.Pager{
|
||||
Total: 2,
|
||||
Index: 1,
|
||||
Size: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.GetTimelineReq{
|
||||
Uid: "",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "獲取動態時報失敗",
|
||||
input: &tweeting.GetTimelineReq{
|
||||
Uid: "user123",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬獲取動態時報失敗
|
||||
mockTimelineRepo.EXPECT().FetchTimeline(gomock.Any(), repository.FetchTimelineRequest{
|
||||
UID: "user123",
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
}).Return(repository.FetchTimelineResponse{}, errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 FetchTimelineLogic
|
||||
logic := FetchTimelineLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 FetchTimeline
|
||||
got, err := logic.FetchTimeline(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 HasNoMoreDataLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewHasNoMoreDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HasNoMoreDataLogic {
|
||||
return &HasNoMoreDataLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
type hasNoMoreDataReq struct {
|
||||
UID string `json:"uid" validate:"required"`
|
||||
}
|
||||
|
||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
||||
func (l *HasNoMoreDataLogic) HasNoMoreData(in *tweeting.DoNoMoreDataReq) (*tweeting.HasNoMoreDataResp, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&hasNoMoreDataReq{
|
||||
UID: in.GetUid(),
|
||||
}); err != nil {
|
||||
// 錯誤代碼 05-011-00
|
||||
return nil, ers.InvalidFormat(err.Error())
|
||||
}
|
||||
|
||||
res, err := l.svcCtx.TimelineRepo.HasNoMoreData(l.ctx, in.GetUid())
|
||||
if err != nil {
|
||||
// 錯誤代碼 05-021-23
|
||||
e := domain.CommentErrorL(
|
||||
domain.HasNoMoreDataErrorCode,
|
||||
logx.WithContext(l.ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: in},
|
||||
{Key: "func", Value: "TimelineRepo.HasNoMoreData"},
|
||||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to get no more data flag:", in.GetUid()).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &tweeting.HasNoMoreDataResp{
|
||||
Status: res,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestHasNoMoreDataLogic_HasNoMoreData(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
TimelineRepo: mockTimelineRepo,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.DoNoMoreDataReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.HasNoMoreDataResp
|
||||
}{
|
||||
{
|
||||
name: "成功檢查時間線是否完整",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "user123",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功檢查時間線狀態
|
||||
mockTimelineRepo.EXPECT().HasNoMoreData(gomock.Any(), "user123").Return(true, nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.HasNoMoreDataResp{
|
||||
Status: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "檢查時間線狀態失敗",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "user123",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬檢查時間線狀態失敗
|
||||
mockTimelineRepo.EXPECT().HasNoMoreData(gomock.Any(), "user123").Return(false, errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 HasNoMoreDataLogic
|
||||
logic := HasNoMoreDataLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 HasNoMoreData
|
||||
got, err := logic.HasNoMoreData(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"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 SetNoMoreDataFlagLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewSetNoMoreDataFlagLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SetNoMoreDataFlagLogic {
|
||||
return &SetNoMoreDataFlagLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
type sasNoMoreDataReq struct {
|
||||
UID string `json:"uid" validate:"required"`
|
||||
}
|
||||
|
||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
||||
func (l *SetNoMoreDataFlagLogic) SetNoMoreDataFlag(in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
||||
// 驗證資料
|
||||
if err := l.svcCtx.Validate.ValidateAll(&sasNoMoreDataReq{
|
||||
UID: in.GetUid(),
|
||||
}); err != nil {
|
||||
// 錯誤代碼 05-011-00
|
||||
return nil, ers.InvalidFormat(err.Error())
|
||||
}
|
||||
|
||||
err := l.svcCtx.TimelineRepo.SetNoMoreDataFlag(l.ctx, in.GetUid())
|
||||
if err != nil {
|
||||
// 錯誤代碼 05-021-24
|
||||
e := domain.CommentErrorL(
|
||||
domain.SetNoMoreDataErrorCode,
|
||||
logx.WithContext(l.ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: in},
|
||||
{Key: "func", Value: "TimelineRepo.SetNoMoreDataErrorCode"},
|
||||
{Key: "err", Value: err},
|
||||
},
|
||||
"failed to set no more data flag:", in.GetUid()).Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &tweeting.OKResp{}, nil
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package timelineservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestSetNoMoreDataFlagLogic_SetNoMoreDataFlag(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// 初始化 mock 依賴
|
||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
||||
|
||||
// 初始化服務上下文
|
||||
svcCtx := &svc.ServiceContext{
|
||||
TimelineRepo: mockTimelineRepo,
|
||||
Validate: mockValidate,
|
||||
}
|
||||
|
||||
// 測試數據集
|
||||
tests := []struct {
|
||||
name string
|
||||
input *tweeting.DoNoMoreDataReq
|
||||
prepare func()
|
||||
expectErr bool
|
||||
wantResp *tweeting.OKResp
|
||||
}{
|
||||
{
|
||||
name: "成功標記時間線已完整",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "user123",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬成功標記時間線狀態
|
||||
mockTimelineRepo.EXPECT().SetNoMoreDataFlag(gomock.Any(), "user123").Return(nil).Times(1)
|
||||
},
|
||||
expectErr: false,
|
||||
wantResp: &tweeting.OKResp{},
|
||||
},
|
||||
{
|
||||
name: "驗證失敗",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證失敗
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
{
|
||||
name: "標記時間線狀態失敗",
|
||||
input: &tweeting.DoNoMoreDataReq{
|
||||
Uid: "user123",
|
||||
},
|
||||
prepare: func() {
|
||||
// 模擬驗證通過
|
||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
||||
// 模擬標記時間線狀態失敗
|
||||
mockTimelineRepo.EXPECT().SetNoMoreDataFlag(gomock.Any(), "user123").Return(errors.New("repository error")).Times(1)
|
||||
},
|
||||
expectErr: true,
|
||||
wantResp: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 執行測試
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 設置測試環境
|
||||
tt.prepare()
|
||||
|
||||
// 初始化 SetNoMoreDataFlagLogic
|
||||
logic := SetNoMoreDataFlagLogic{
|
||||
svcCtx: svcCtx,
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
// 執行 SetNoMoreDataFlag
|
||||
got, err := logic.SetNoMoreDataFlag(tt.input)
|
||||
|
||||
// 驗證結果
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantResp, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./internal/domain/repository/social_network.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -source=./internal/domain/repository/social_network.go -destination=./internal/mock/repository/social_network.go -package=mock
|
||||
//
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
||||
import (
|
||||
repository "app-cloudep-tweeting-service/internal/domain/repository"
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSocialNetworkRepository is a mock of SocialNetworkRepository interface.
|
||||
type MockSocialNetworkRepository struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSocialNetworkRepositoryMockRecorder
|
||||
}
|
||||
|
||||
// MockSocialNetworkRepositoryMockRecorder is the mock recorder for MockSocialNetworkRepository.
|
||||
type MockSocialNetworkRepositoryMockRecorder struct {
|
||||
mock *MockSocialNetworkRepository
|
||||
}
|
||||
|
||||
// NewMockSocialNetworkRepository creates a new mock instance.
|
||||
func NewMockSocialNetworkRepository(ctrl *gomock.Controller) *MockSocialNetworkRepository {
|
||||
mock := &MockSocialNetworkRepository{ctrl: ctrl}
|
||||
mock.recorder = &MockSocialNetworkRepositoryMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSocialNetworkRepository) EXPECT() *MockSocialNetworkRepositoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateUserNode mocks base method.
|
||||
func (m *MockSocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateUserNode", ctx, uid)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateUserNode indicates an expected call of CreateUserNode.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) CreateUserNode(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUserNode", reflect.TypeOf((*MockSocialNetworkRepository)(nil).CreateUserNode), ctx, uid)
|
||||
}
|
||||
|
||||
// GetDegreeBetweenUsers mocks base method.
|
||||
func (m *MockSocialNetworkRepository) GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDegreeBetweenUsers", ctx, uid1, uid2)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetDegreeBetweenUsers indicates an expected call of GetDegreeBetweenUsers.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetDegreeBetweenUsers(ctx, uid1, uid2 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDegreeBetweenUsers", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetDegreeBetweenUsers), ctx, uid1, uid2)
|
||||
}
|
||||
|
||||
// GetFollowee mocks base method.
|
||||
func (m *MockSocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFollowee", ctx, req)
|
||||
ret0, _ := ret[0].(repository.FollowResp)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFollowee indicates an expected call of GetFollowee.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollowee(ctx, req any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollowee", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollowee), ctx, req)
|
||||
}
|
||||
|
||||
// GetFolloweeCount mocks base method.
|
||||
func (m *MockSocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFolloweeCount", ctx, uid)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFolloweeCount indicates an expected call of GetFolloweeCount.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFolloweeCount(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFolloweeCount", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFolloweeCount), ctx, uid)
|
||||
}
|
||||
|
||||
// GetFollower mocks base method.
|
||||
func (m *MockSocialNetworkRepository) GetFollower(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFollower", ctx, req)
|
||||
ret0, _ := ret[0].(repository.FollowResp)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFollower indicates an expected call of GetFollower.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollower(ctx, req any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollower", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollower), ctx, req)
|
||||
}
|
||||
|
||||
// GetFollowerCount mocks base method.
|
||||
func (m *MockSocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFollowerCount", ctx, uid)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFollowerCount indicates an expected call of GetFollowerCount.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollowerCount(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollowerCount", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollowerCount), ctx, uid)
|
||||
}
|
||||
|
||||
// GetUIDsWithinNDegrees mocks base method.
|
||||
func (m *MockSocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUIDsWithinNDegrees", ctx, uid, degrees, pageSize, pageIndex)
|
||||
ret0, _ := ret[0].([]string)
|
||||
ret1, _ := ret[1].(int64)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// GetUIDsWithinNDegrees indicates an expected call of GetUIDsWithinNDegrees.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetUIDsWithinNDegrees(ctx, uid, degrees, pageSize, pageIndex any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUIDsWithinNDegrees", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetUIDsWithinNDegrees), ctx, uid, degrees, pageSize, pageIndex)
|
||||
}
|
||||
|
||||
// MarkFollowerRelation mocks base method.
|
||||
func (m *MockSocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "MarkFollowerRelation", ctx, fromUID, toUID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// MarkFollowerRelation indicates an expected call of MarkFollowerRelation.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) MarkFollowerRelation(ctx, fromUID, toUID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkFollowerRelation", reflect.TypeOf((*MockSocialNetworkRepository)(nil).MarkFollowerRelation), ctx, fromUID, toUID)
|
||||
}
|
||||
|
||||
// RemoveFollowerRelation mocks base method.
|
||||
func (m *MockSocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveFollowerRelation", ctx, fromUID, toUID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveFollowerRelation indicates an expected call of RemoveFollowerRelation.
|
||||
func (mr *MockSocialNetworkRepositoryMockRecorder) RemoveFollowerRelation(ctx, fromUID, toUID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFollowerRelation", reflect.TypeOf((*MockSocialNetworkRepository)(nil).RemoveFollowerRelation), ctx, fromUID, toUID)
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./internal/domain/repository/timeline.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -source=./internal/domain/repository/timeline.go -destination=./internal/mock/repository/timeline.go -package=mock
|
||||
//
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
||||
import (
|
||||
repository "app-cloudep-tweeting-service/internal/domain/repository"
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTimelineRepository is a mock of TimelineRepository interface.
|
||||
type MockTimelineRepository struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTimelineRepositoryMockRecorder
|
||||
}
|
||||
|
||||
// MockTimelineRepositoryMockRecorder is the mock recorder for MockTimelineRepository.
|
||||
type MockTimelineRepositoryMockRecorder struct {
|
||||
mock *MockTimelineRepository
|
||||
}
|
||||
|
||||
// NewMockTimelineRepository creates a new mock instance.
|
||||
func NewMockTimelineRepository(ctrl *gomock.Controller) *MockTimelineRepository {
|
||||
mock := &MockTimelineRepository{ctrl: ctrl}
|
||||
mock.recorder = &MockTimelineRepositoryMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTimelineRepository) EXPECT() *MockTimelineRepositoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddPost mocks base method.
|
||||
func (m *MockTimelineRepository) AddPost(ctx context.Context, req repository.AddPostRequest) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddPost", ctx, req)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddPost indicates an expected call of AddPost.
|
||||
func (mr *MockTimelineRepositoryMockRecorder) AddPost(ctx, req any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPost", reflect.TypeOf((*MockTimelineRepository)(nil).AddPost), ctx, req)
|
||||
}
|
||||
|
||||
// ClearNoMoreDataFlag mocks base method.
|
||||
func (m *MockTimelineRepository) ClearNoMoreDataFlag(ctx context.Context, uid string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ClearNoMoreDataFlag", ctx, uid)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClearNoMoreDataFlag indicates an expected call of ClearNoMoreDataFlag.
|
||||
func (mr *MockTimelineRepositoryMockRecorder) ClearNoMoreDataFlag(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearNoMoreDataFlag", reflect.TypeOf((*MockTimelineRepository)(nil).ClearNoMoreDataFlag), ctx, uid)
|
||||
}
|
||||
|
||||
// FetchTimeline mocks base method.
|
||||
func (m *MockTimelineRepository) FetchTimeline(ctx context.Context, req repository.FetchTimelineRequest) (repository.FetchTimelineResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchTimeline", ctx, req)
|
||||
ret0, _ := ret[0].(repository.FetchTimelineResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchTimeline indicates an expected call of FetchTimeline.
|
||||
func (mr *MockTimelineRepositoryMockRecorder) FetchTimeline(ctx, req any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTimeline", reflect.TypeOf((*MockTimelineRepository)(nil).FetchTimeline), ctx, req)
|
||||
}
|
||||
|
||||
// HasNoMoreData mocks base method.
|
||||
func (m *MockTimelineRepository) HasNoMoreData(ctx context.Context, uid string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasNoMoreData", ctx, uid)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HasNoMoreData indicates an expected call of HasNoMoreData.
|
||||
func (mr *MockTimelineRepositoryMockRecorder) HasNoMoreData(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasNoMoreData", reflect.TypeOf((*MockTimelineRepository)(nil).HasNoMoreData), ctx, uid)
|
||||
}
|
||||
|
||||
// SetNoMoreDataFlag mocks base method.
|
||||
func (m *MockTimelineRepository) SetNoMoreDataFlag(ctx context.Context, uid string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetNoMoreDataFlag", ctx, uid)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetNoMoreDataFlag indicates an expected call of SetNoMoreDataFlag.
|
||||
func (mr *MockTimelineRepositoryMockRecorder) SetNoMoreDataFlag(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNoMoreDataFlag", reflect.TypeOf((*MockTimelineRepository)(nil).SetNoMoreDataFlag), ctx, uid)
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package repository
|
|
@ -0,0 +1,144 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"app-cloudep-tweeting-service/internal/config"
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
// TODO 第一版本先使用 Redis 來做,後續如果有效能考量,在考慮使用其他方案
|
||||
|
||||
type TimelineRepositoryParam struct {
|
||||
Config config.Config
|
||||
Redis redis.Redis
|
||||
}
|
||||
|
||||
type TimelineRepository struct {
|
||||
cfg config.Config
|
||||
redis redis.Redis
|
||||
}
|
||||
|
||||
func MustGenerateRepository(param TimelineRepositoryParam) repository.TimelineRepository {
|
||||
return &TimelineRepository{
|
||||
cfg: param.Config,
|
||||
redis: param.Redis,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPost 將貼文添加到時間線,並根據 Score 排序
|
||||
func (t *TimelineRepository) AddPost(ctx context.Context, req repository.AddPostRequest) error {
|
||||
key := domain.TimelineRedisKey.With(req.UID).ToString()
|
||||
|
||||
// 準備要插入的元素
|
||||
zItems := make([]redis.Pair, len(req.PostItems))
|
||||
for i, item := range req.PostItems {
|
||||
zItems[i] = redis.Pair{
|
||||
Score: item.Score,
|
||||
Key: item.PostID,
|
||||
}
|
||||
}
|
||||
|
||||
// 將 ZSet 元素添加到 Redis
|
||||
_, err := t.redis.ZaddsCtx(ctx, key, zItems...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 檢查 ZSet 長度,並在超過 maxLength 時刪除多餘的元素
|
||||
if t.cfg.TimelineSetting.MaxLength > 0 {
|
||||
// 這裡從 0 到 - (maxLength+1) 代表超過限制的元素範圍
|
||||
_, err := t.redis.ZremrangebyrankCtx(ctx, key, 0, -(t.cfg.TimelineSetting.MaxLength + 1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 設置過期時間
|
||||
return t.redis.ExpireCtx(ctx, key, int(t.cfg.TimelineSetting.Expire))
|
||||
}
|
||||
|
||||
// FetchTimeline 獲取指定用戶的動態時報
|
||||
func (t *TimelineRepository) FetchTimeline(ctx context.Context, req repository.FetchTimelineRequest) (repository.FetchTimelineResponse, error) {
|
||||
key := domain.TimelineRedisKey.With(req.UID).ToString()
|
||||
|
||||
start := (req.PageIndex - 1) * req.PageSize
|
||||
end := start + req.PageSize - 1
|
||||
|
||||
// 從 Redis 中按分數由高到低獲取時間線元素
|
||||
pair, err := t.redis.ZrevrangeWithScoresCtx(ctx, key, start, end)
|
||||
if err != nil {
|
||||
return repository.FetchTimelineResponse{}, err
|
||||
}
|
||||
|
||||
// 構建返回結果
|
||||
items := make([]repository.TimelineItem, len(pair))
|
||||
for i, z := range pair {
|
||||
items[i] = repository.TimelineItem{
|
||||
PostID: z.Key,
|
||||
Score: z.Score,
|
||||
}
|
||||
}
|
||||
|
||||
// 計算總數量
|
||||
total, err := t.redis.ZcardCtx(ctx, key)
|
||||
if err != nil {
|
||||
return repository.FetchTimelineResponse{}, err
|
||||
}
|
||||
|
||||
return repository.FetchTimelineResponse{
|
||||
Items: items,
|
||||
Page: tweeting.Pager{
|
||||
Total: int64(total),
|
||||
Index: req.PageIndex,
|
||||
Size: req.PageSize,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫
|
||||
func (t *TimelineRepository) SetNoMoreDataFlag(ctx context.Context, uid string) error {
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
|
||||
// 添加一個標誌到時間線的 ZSet
|
||||
_, err := t.redis.ZaddsCtx(ctx, key, redis.Pair{
|
||||
Score: time.Now().UTC().Unix(),
|
||||
Key: domain.LastOfTimelineFlag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 設置過期時間
|
||||
return t.redis.ExpireCtx(ctx, key, int(t.cfg.TimelineSetting.Expire))
|
||||
}
|
||||
|
||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫
|
||||
func (t *TimelineRepository) HasNoMoreData(ctx context.Context, uid string) (bool, error) {
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
|
||||
// 檢查 "NoMoreData" 標誌是否存在
|
||||
score, err := t.redis.ZscoreCtx(ctx, key, domain.LastOfTimelineFlag)
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return false, nil // 標誌不存在
|
||||
}
|
||||
if err != nil {
|
||||
return false, err // 其他錯誤
|
||||
}
|
||||
|
||||
return score != 0, nil
|
||||
}
|
||||
|
||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌
|
||||
func (t *TimelineRepository) ClearNoMoreDataFlag(ctx context.Context, uid string) error {
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
// 移除 "NoMoreData" 標誌
|
||||
_, err := t.redis.ZremCtx(ctx, key, domain.LastOfTimelineFlag)
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"app-cloudep-tweeting-service/internal/config"
|
||||
"app-cloudep-tweeting-service/internal/domain"
|
||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
func NewRepo() (*miniredis.Miniredis, repository.TimelineRepository, error) {
|
||||
r1, err := miniredis.Run()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
newRedis, err := redis.NewRedis(redis.RedisConf{
|
||||
Host: r1.Addr(),
|
||||
Type: redis.ClusterType,
|
||||
Pass: "",
|
||||
})
|
||||
if err != nil {
|
||||
r1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c := config.Config{
|
||||
TimelineSetting: struct {
|
||||
Expire int64
|
||||
MaxLength int64
|
||||
}{Expire: 86400, MaxLength: 1000},
|
||||
}
|
||||
|
||||
timelineRepo := MustGenerateRepository(TimelineRepositoryParam{
|
||||
Config: c,
|
||||
Redis: *newRedis,
|
||||
})
|
||||
|
||||
return r1, timelineRepo, nil
|
||||
}
|
||||
|
||||
func TestAddPost(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
action func(t *testing.T, repo repository.TimelineRepository) error
|
||||
expectErr bool
|
||||
validate func(t *testing.T, r1 *miniredis.Miniredis)
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
ctx := context.Background()
|
||||
uid := "OOOOOOKJ"
|
||||
|
||||
return repo.AddPost(ctx, repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 100},
|
||||
},
|
||||
})
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
||||
uid := "OOOOOOKJ"
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
score, err := r1.ZScore(key, "post1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, float64(100), score)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timeout",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
ctx := context.Background()
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Millisecond)
|
||||
defer cancel()
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
uid := "OOOOOLK"
|
||||
return repo.AddPost(timeoutCtx, repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post2", Score: 200},
|
||||
},
|
||||
})
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Redis error on Zadd",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
r1.Close() // 模拟 Redis 错误
|
||||
|
||||
ctx := context.Background()
|
||||
uid := "OOOOOWE"
|
||||
return repo.AddPost(ctx, repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post3", Score: 300},
|
||||
},
|
||||
})
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "duplicate Key",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
ctx := context.Background()
|
||||
uid := "OOOOODUP"
|
||||
|
||||
// 第一次插入
|
||||
err := repo.AddPost(ctx, repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 100},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 第二次插入,使用相同的 PostID 但不同的 Score
|
||||
return repo.AddPost(ctx, repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 200},
|
||||
},
|
||||
})
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
||||
uid := "OOOOODUP"
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
score, err := r1.ZScore(key, "post1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, float64(200), score) // 應該是第二次插入的分數
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
err = tt.action(t, repo)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tt.validate != nil {
|
||||
tt.validate(t, r1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchTimeline(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
action func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error)
|
||||
expectErr bool
|
||||
validate func(t *testing.T, r1 *miniredis.Miniredis, resp *repository.FetchTimelineResponse)
|
||||
}{
|
||||
{
|
||||
name: "FetchTimeline - success",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
|
||||
_ = repo.AddPost(ctx, repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post1", Score: 200},
|
||||
{PostID: "post2", Score: 100},
|
||||
},
|
||||
})
|
||||
|
||||
return repo.FetchTimeline(ctx, repository.FetchTimelineRequest{
|
||||
UID: uid,
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
})
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(t *testing.T, r1 *miniredis.Miniredis, resp *repository.FetchTimelineResponse) {
|
||||
assert.Equal(t, 2, len(resp.Items))
|
||||
assert.Equal(t, "post1", resp.Items[0].PostID)
|
||||
assert.Equal(t, "post2", resp.Items[1].PostID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FetchTimeline - timeout",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
||||
ctx := context.Background()
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Millisecond)
|
||||
defer cancel()
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
uid := "user123"
|
||||
return repo.FetchTimeline(timeoutCtx, repository.FetchTimelineRequest{
|
||||
UID: uid,
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
})
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "FetchTimeline - Redis error on ZrangebyscoreWithScoresCtx",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
uid := "user123"
|
||||
|
||||
_ = repo.AddPost(context.Background(), repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post3", Score: 300},
|
||||
},
|
||||
})
|
||||
|
||||
r1.Close()
|
||||
|
||||
return repo.FetchTimeline(context.Background(), repository.FetchTimelineRequest{
|
||||
UID: uid,
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
})
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "FetchTimeline - Redis error on ZcardCtx",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
uid := "user123"
|
||||
|
||||
_ = repo.AddPost(context.Background(), repository.AddPostRequest{
|
||||
UID: uid,
|
||||
PostItems: []repository.TimelineItem{
|
||||
{PostID: "post4", Score: 400},
|
||||
},
|
||||
})
|
||||
|
||||
r1.Close()
|
||||
|
||||
return repo.FetchTimeline(context.Background(), repository.FetchTimelineRequest{
|
||||
UID: uid,
|
||||
PageSize: 10,
|
||||
PageIndex: 1,
|
||||
})
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
resp, err := tt.action(t, repo)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tt.validate != nil {
|
||||
tt.validate(t, r1, &resp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNoMoreDataFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
action func(t *testing.T, repo repository.TimelineRepository) error
|
||||
expectErr bool
|
||||
validate func(t *testing.T, r1 *miniredis.Miniredis)
|
||||
}{
|
||||
{
|
||||
name: "SetNoMoreDataFlag - success",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
return repo.SetNoMoreDataFlag(ctx, uid)
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
||||
uid := "user123"
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
score, _ := r1.ZScore(key, domain.LastOfTimelineFlag)
|
||||
assert.NotZero(t, score)
|
||||
|
||||
// 驗證是否設定過期時間
|
||||
ttl := r1.TTL(key)
|
||||
assert.Equal(t, time.Duration(86400)*time.Second, ttl)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetNoMoreDataFlag - Redis error on ZaddsCtx",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
r1.Close() // 手動關閉,復現錯誤
|
||||
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
return repo.SetNoMoreDataFlag(ctx, uid)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "SetNoMoreDataFlag - Redis error on ExpireCtx",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
|
||||
r1.Close()
|
||||
|
||||
return repo.SetNoMoreDataFlag(ctx, uid)
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
err = tt.action(t, repo)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tt.validate != nil {
|
||||
tt.validate(t, r1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNoMoreData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
action func(t *testing.T, repo repository.TimelineRepository) (bool, error)
|
||||
expectErr bool
|
||||
expected bool
|
||||
setup func(r1 *miniredis.Miniredis)
|
||||
}{
|
||||
{
|
||||
name: "HasNoMoreData - 標誌存在",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) (bool, error) {
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
return repo.HasNoMoreData(ctx, uid)
|
||||
},
|
||||
expectErr: false,
|
||||
expected: true,
|
||||
setup: func(r1 *miniredis.Miniredis) {
|
||||
uid := "user123"
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
_, _ = r1.ZAdd(key, float64(time.Now().UTC().Unix()), domain.LastOfTimelineFlag)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "HasNoMoreData - 標誌不存在",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) (bool, error) {
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
return repo.HasNoMoreData(ctx, uid)
|
||||
},
|
||||
expectErr: false,
|
||||
expected: false,
|
||||
setup: func(r1 *miniredis.Miniredis) {}, // 不設置標誌
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
tt.setup(r1)
|
||||
|
||||
result, err := tt.action(t, repo)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearNoMoreDataFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
action func(t *testing.T, repo repository.TimelineRepository) error
|
||||
expectErr bool
|
||||
setup func(r1 *miniredis.Miniredis)
|
||||
validate func(t *testing.T, r1 *miniredis.Miniredis)
|
||||
}{
|
||||
{
|
||||
name: "ClearNoMoreDataFlag - 成功清除標誌",
|
||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
||||
ctx := context.Background()
|
||||
uid := "user123"
|
||||
return repo.ClearNoMoreDataFlag(ctx, uid)
|
||||
},
|
||||
expectErr: false,
|
||||
setup: func(r1 *miniredis.Miniredis) {
|
||||
uid := "user123"
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
_, err := r1.ZAdd(key, 100, domain.LastOfTimelineFlag) // 設置標誌
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
||||
uid := "user123"
|
||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
||||
_, err := r1.ZScore(key, domain.LastOfTimelineFlag)
|
||||
assert.Error(t, err) // 標誌應該已被移除
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r1, repo, err := NewRepo()
|
||||
assert.NoError(t, err)
|
||||
defer r1.Close()
|
||||
|
||||
tt.setup(r1)
|
||||
|
||||
err = tt.action(t, repo)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
tt.validate(t, r1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Code generated by goctl. DO NOT EDIT.
|
||||
// Source: tweeting.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
socialnetworkservicelogic "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,
|
||||
}
|
||||
}
|
||||
|
||||
// MarkFollowRelation 關注
|
||||
func (s *SocialNetworkServiceServer) MarkFollowRelation(ctx context.Context, in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Code generated by goctl. DO NOT EDIT.
|
||||
// Source: tweeting.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
timelineservicelogic "app-cloudep-tweeting-service/internal/logic/timelineservice"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
)
|
||||
|
||||
type TimelineServiceServer struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
tweeting.UnimplementedTimelineServiceServer
|
||||
}
|
||||
|
||||
func NewTimelineServiceServer(svcCtx *svc.ServiceContext) *TimelineServiceServer {
|
||||
return &TimelineServiceServer{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPost 加入貼文,只管一股腦全塞,這裡會自動判斷
|
||||
func (s *TimelineServiceServer) AddPost(ctx context.Context, in *tweeting.AddPostToTimelineReq) (*tweeting.OKResp, error) {
|
||||
l := timelineservicelogic.NewAddPostLogic(ctx, s.svcCtx)
|
||||
return l.AddPost(in)
|
||||
}
|
||||
|
||||
// FetchTimeline 取得這個人的動態時報
|
||||
func (s *TimelineServiceServer) FetchTimeline(ctx context.Context, in *tweeting.GetTimelineReq) (*tweeting.FetchTimelineResponse, error) {
|
||||
l := timelineservicelogic.NewFetchTimelineLogic(ctx, s.svcCtx)
|
||||
return l.FetchTimeline(in)
|
||||
}
|
||||
|
||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
||||
func (s *TimelineServiceServer) SetNoMoreDataFlag(ctx context.Context, in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
||||
l := timelineservicelogic.NewSetNoMoreDataFlagLogic(ctx, s.svcCtx)
|
||||
return l.SetNoMoreDataFlag(in)
|
||||
}
|
||||
|
||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
||||
func (s *TimelineServiceServer) HasNoMoreData(ctx context.Context, in *tweeting.DoNoMoreDataReq) (*tweeting.HasNoMoreDataResp, error) {
|
||||
l := timelineservicelogic.NewHasNoMoreDataLogic(ctx, s.svcCtx)
|
||||
return l.HasNoMoreData(in)
|
||||
}
|
||||
|
||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
||||
func (s *TimelineServiceServer) ClearNoMoreDataFlag(ctx context.Context, in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
||||
l := timelineservicelogic.NewClearNoMoreDataFlagLogic(ctx, s.svcCtx)
|
||||
return l.ClearNoMoreDataFlag(in)
|
||||
}
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
func mustMongoConnectUrl(c config.Config) string {
|
||||
func mustMongoConnectURL(c config.Config) string {
|
||||
return fmt.Sprintf("%s://%s:%s",
|
||||
c.Mongo.Schema,
|
||||
c.Mongo.Host,
|
||||
|
@ -18,10 +18,12 @@ func mustMongoConnectUrl(c config.Config) string {
|
|||
|
||||
func MustPostModel(c config.Config) model.PostModel {
|
||||
postCollection := model.Post{}
|
||||
return model.NewPostModel(mustMongoConnectUrl(c), c.Mongo.Database, postCollection.CollectionName())
|
||||
|
||||
return model.NewPostModel(mustMongoConnectURL(c), c.Mongo.Database, postCollection.CollectionName())
|
||||
}
|
||||
|
||||
func MustCommentModel(c config.Config) model.CommentModel {
|
||||
m := model.Comment{}
|
||||
return model.NewCommentModel(mustMongoConnectUrl(c), c.Mongo.Database, m.CollectionName())
|
||||
|
||||
return model.NewCommentModel(mustMongoConnectURL(c), c.Mongo.Database, m.CollectionName())
|
||||
}
|
||||
|
|
|
@ -2,7 +2,12 @@ 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"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
|
||||
vi "code.30cm.net/digimon/library-go/validator"
|
||||
)
|
||||
|
@ -10,16 +15,39 @@ import (
|
|||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Validate vi.Validate
|
||||
|
||||
PostModel model.PostModel
|
||||
CommentModel model.CommentModel
|
||||
TimelineRepo domainRepo.TimelineRepository
|
||||
SocialNetworkRepository domainRepo.SocialNetworkRepository
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
newRedis, err := redis.NewRedis(c.RedisCluster, redis.Cluster())
|
||||
if err != nil {
|
||||
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.MustGenerateRepository(repository.TimelineRepositoryParam{
|
||||
Config: c,
|
||||
Redis: *newRedis,
|
||||
}),
|
||||
SocialNetworkRepository: repository.MustSocialNetworkRepository(repository.SocialNetworkParam{
|
||||
Config: c,
|
||||
Neo4jClient: neoClient,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
13
tweeting.go
13
tweeting.go
|
@ -1,13 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||
"app-cloudep-tweeting-service/internal/config"
|
||||
commentserviceServer "app-cloudep-tweeting-service/internal/server/commentservice"
|
||||
postserviceServer "app-cloudep-tweeting-service/internal/server/postservice"
|
||||
socialnetworkserviceServer "app-cloudep-tweeting-service/internal/server/socialnetworkservice"
|
||||
timelineserviceServer "app-cloudep-tweeting-service/internal/server/timelineservice"
|
||||
"app-cloudep-tweeting-service/internal/svc"
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/conf"
|
||||
"github.com/zeromicro/go-zero/core/service"
|
||||
|
@ -27,6 +29,9 @@ func main() {
|
|||
|
||||
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||
tweeting.RegisterPostServiceServer(grpcServer, postserviceServer.NewPostServiceServer(ctx))
|
||||
tweeting.RegisterCommentServiceServer(grpcServer, commentserviceServer.NewCommentServiceServer(ctx))
|
||||
tweeting.RegisterTimelineServiceServer(grpcServer, timelineserviceServer.NewTimelineServiceServer(ctx))
|
||||
tweeting.RegisterSocialNetworkServiceServer(grpcServer, socialnetworkserviceServer.NewSocialNetworkServiceServer(ctx))
|
||||
|
||||
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||
reflection.Register(grpcServer)
|
||||
|
@ -34,6 +39,6 @@ func main() {
|
|||
})
|
||||
defer s.Stop()
|
||||
|
||||
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||
log.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||
s.Start()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue