Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

44
app/service/main/relation/.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
# Created by .ignore support plugin (hsz.mobi)
### Go template
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# external packages folder
# 特殊处理
# vendor/
relation-service
.idea/
*.iml
dump.rdb
.DS_Store
.vscode/
#cmd

View File

@@ -0,0 +1,25 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/relation/api:all-srcs",
"//app/service/main/relation/cmd:all-srcs",
"//app/service/main/relation/conf:all-srcs",
"//app/service/main/relation/dao:all-srcs",
"//app/service/main/relation/http:all-srcs",
"//app/service/main/relation/model:all-srcs",
"//app/service/main/relation/rpc/client:all-srcs",
"//app/service/main/relation/rpc/server:all-srcs",
"//app/service/main/relation/server/grpc:all-srcs",
"//app/service/main/relation/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,272 @@
### 好友关系
### Version 2.29.0
> 1. 添加grpc
### Version 2.28.0
> 1. 相同关注接口
### Version 2.27.0
> 1. 加关注按appkey限流
### Version 2.26.1
> 1. tag 不存在直接跳过
### Version 2.26.0
> 1. 去除 RemoteIP 方法
> 2. refine test
### Version 2.25.0
> 1. 增加新粉丝通知频率限制
### Version 2.24.4
> 1. block业务迁移依赖member-service
### Version 2.24.3
> 1. 成就奖励获取限制非正式会员
### Version 2.24.2
> 1. 修正取消悄悄关注日志
### Version 2.24.1
> 1. 修正粉丝成就粉丝数
### Version 2.24.0
> 1. 粉丝成就接口
### Version 2.23.9
> 1. 旧Audit接口下线引发的小重构
### Version 2.23.8
> 1. 去除 SQL Prepare
### Version 2.23.7
> 1. 修正 context 使用
### Version 2.23.6
> 1. 去除 xints
### Version 2.23.5
> 1. 去除删除属性的错误
### Version 2.23.4
> 1. 使用 bm Bind
### Version 2.23.3
> 1. 对比缓存不一致删缓存
### Version 2.23.2
> 1. 增加黑名单日志
### Version 2.23.1
> 1. 修复关注日志中 source 不显示的问题
### Version 2.23.0
> 1. 上报关注日志
### Version 2.22.1
> 1. 忽略 redis nil error
### Version 2.22.0
> 1. service 直接用来统计最近粉丝数
### Version 2.21.0
> 1. remove global hot
### Version 2.20.5
> 1. restrict local cache
### Version 2.20.4
> 1. configureable local cache least follower
### Version 2.20.3
> 1. ignore nil stat in local cache
### Version 2.20.2
> 1. local cache stat
### Version 2.20.1
> 1. ignore delete stat cache key not found error
### Version 2.20.0
> 1. move stat cache to memcahched
### Version 2.19.0
> 1. support bm.
### Version 2.18.0
> 1. update infoc sdk
### Version 2.17.5
> 1. 修改 proto package合并 proto 文件
### Version 2.17.4
> 1.add register
### Version 2.17.3
> 1.增加 followers 和 followings 缓存计数
> 2.修正 gitignore
### Version 2.17.2
> 1.去掉account-service的使用
### Version 2.17.1
> 1.关注提示功能
> 2.remove statsd
### Version 2.17.0
> 1.关注推荐功能
### Version 2.16.0
> 1.修复stat 闭包错误
### Version 2.15.1
> 1.修复空缓存无效
### Version 2.15.0
> 1.添加b+特殊关注接口
### Version 2.14.2
> 1.修复stat缓存
### Version 2.14.1
> 1.token interceport
### Version 2.14.0
> 1.打开handshake token验证
### Version 2.13.1
> 1.关注限制修改code未绑定手机且用户未转正时候禁止关注、悄悄关注操作
### Version 2.13.0
> 1.关注限制:未绑定手机且用户未转正时候禁止关注、悄悄关注操作
### Version 2.12.0
> 1.限制mid<=0
### Version 2.11.0
> 1.特殊关注
### Version 2.10.0
> 1.业务场景下关注引导提示
### Version 2.9.1
> 1.分组排序
### Version 2.9.0
> 1.关注分组
### Version 2.8.0
> 1.监控特定用户被关注,返回关注成功
### Version 2.7.3
> 1.空缓存优化
### Version 2.7.2
> 1.停掉写入member log
### Version 2.7.0
> 1.使用protobuf
### Version 2.6.7
> 1.fix 添加悄悄关注 不增加粉丝数
### Version 2.6.6
> 1.接入新trace
### Version 2.6.5
> 1.fix addmlog err report
### Version 2.6.3
> 1.判断midfid > 0
### Version 2.6.0
> 1.迁移到大仓库
### Version 2.5.1
> 1.恢复重复操作错误提示
### Version 2.5.0
> 1.开启gzip和gob
### Version 2.4.0
> 1.更新mc基础包压缩缓存
### Version 2.3.0
> 1.日志上报数据平台infoc方式
### Version 2.2.2
> 1.接入antispam
### Version 2.2.1
> 1.接入rpc平滑发版
### Version 2.2.0
> 1.更新依赖包
> 2.接入普罗米修斯
### Version 2.1.0
> 1.拆分黑名单数量限制
### Version 2.0.15
> 1.memberlog增加src字段
### Version 2.0.14
> 1.写入memberlog表
### Version 2.0.13
> 1.修复uint64转int64溢出
### Version 2.0.12
> 1.加回audit接口
> 1.兼容旧数据允许用户取消拉黑自己
### Version 2.0.11
> 1.删除ctime
### Version 2.0.10
> 1.兼容好友attr
### Version 2.0.9
> 1.改回mtime排序
### Version 2.0.8
> 1.增加ctime
### Version 2.0.7
> 1.修复关注排序
### Version 2.0.6
> 1.黑名单列表增加stat判断
### Version 2.0.5
> 1.修改expire
### Version 2.0.4
> 1.删除黑名单双向关系
### Version 2.0.3
> 1.修改url为internal
### Version 2.0.1
> 1.修复setstat设置stat计数
> 2.添加互相关注关系到粉丝列表
### Version 2.0.1
> 1.暂时注释掉audit以及速率限制接口
### Version 1.0.2
> 1.接入新的配置中心
### Version 1.0.1
> 1.更改/monitor/ping
#### Version 1.0.0
> 1.基础api

View File

@@ -0,0 +1,11 @@
# Owner
linmiao
zhoujiahui
zhaoyixuan
# Author
zhoujiahui
# Reviewer
zhoujiahui
linmiao

View File

@@ -0,0 +1,6 @@
test: test_dao
test_dao:
go test -v ./dao/... -args -conf=`pwd`/cmd/relation-service-example.toml
.PHONY: test

View File

@@ -0,0 +1,15 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- linmiao
- zhaoyixuan
- zhoujiahui
labels:
- main
- service
- service/main/relation
options:
no_parent_owners: true
reviewers:
- linmiao
- zhoujiahui

View File

@@ -0,0 +1,13 @@
#### relation
##### 项目简介
> 1.关系链服务
##### 编译环境
> 请只用golang v1.8.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common
##### 特别说明
> 1.model目录可能会被其他项目引用请谨慎更改并通知各方。

View File

@@ -0,0 +1,64 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "api_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "api_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/main/relation/api",
proto = ":api_proto",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"copy_autogenerated.go",
],
embed = [":api_go_proto"],
importpath = "go-common/app/service/main/relation/api",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/model:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,217 @@
// +bili:type=service
syntax = "proto3";
package relation.service;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "api";
service Relation {
rpc Relation(RelationReq) returns (FollowingReply);
rpc Relations(RelationsReq) returns (FollowingMapReply);
rpc Stat(MidReq) returns (StatReply);
rpc Stats(MidsReq) returns (StatsReply);
rpc Attentions(MidReq) returns (FollowingsReply);
rpc Followings(MidReq) returns (FollowingsReply);
rpc AddFollowing(FollowingReq) returns (EmptyReply);
rpc DelFollowing(FollowingReq) returns (EmptyReply);
rpc Whispers(MidReq) returns (FollowingsReply);
rpc AddWhisper(FollowingReq) returns (EmptyReply);
rpc DelWhisper(FollowingReq) returns (EmptyReply);
rpc Blacks(MidReq) returns (FollowingsReply);
rpc AddBlack(FollowingReq) returns (EmptyReply);
rpc DelBlack(FollowingReq) returns (EmptyReply);
rpc Followers(MidReq) returns (FollowingsReply);
rpc DelFollower(FollowingReq) returns (EmptyReply);
rpc Tag(TagIdReq) returns (TagReply);
rpc Tags(MidReq) returns (TagsCountReply);
rpc UserTag(RelationReq) returns (UserTagReply);
rpc CreateTag(TagReq) returns (CreateTagReply);
rpc UpdateTag(TagUpdateReq) returns (EmptyReply);
rpc DelTag(TagDelReq) returns (EmptyReply);
rpc TagsAddUsers(TagsMoveUsersReq) returns (EmptyReply);
rpc TagsCopyUsers(TagsMoveUsersReq) returns (EmptyReply);
rpc TagsMoveUsers(TagsMoveUsersReq) returns (EmptyReply);
rpc Prompt(PromptReq) returns (PromptReply);
rpc ClosePrompt(PromptReq) returns (EmptyReply);
rpc AddSpecial(FollowingReq) returns (EmptyReply);
rpc DelSpecial(FollowingReq) returns (EmptyReply);
rpc Special(MidReq) returns (SpecialReply);
rpc FollowersUnread(MidReq) returns (FollowersUnreadReply);
rpc FollowersUnreadCount(MidReq) returns (FollowersUnreadCountReply);
rpc AchieveGet(AchieveGetReq) returns (AchieveGetReply);
rpc Achieve(AchieveReq) returns (AchieveReply);
rpc ResetFollowersUnread(MidReq) returns (EmptyReply);
rpc ResetFollowersUnreadCount(MidReq) returns (EmptyReply);
rpc DisableFollowerNotify(MidReq) returns (EmptyReply);
rpc EnableFollowerNotify(MidReq) returns (EmptyReply);
rpc FollowerNotifySetting(MidReq) returns (FollowerNotifySettingReply);
rpc SameFollowings(SameFollowingReq) returns (FollowingsReply);
}
message RelationReq {
int64 mid = 1;
int64 fid = 2;
string real_ip = 3;
}
// +bili:deepcopy-gen=true
// +bili:deepcopy-gen:structs=go-common/app/service/main/relation/model.Following
message FollowingReply {
int64 mid = 1 [ (gogoproto.jsontag) = "mid" ];
uint32 attribute = 2 [ (gogoproto.jsontag) = "attribute" ];
uint32 source = 3 [ (gogoproto.jsontag) = "-" ];
int64 ctime = 4 [
(gogoproto.jsontag) = "-",
(gogoproto.casttype) = "go-common/library/time.Time",
(gogoproto.customname) = "CTime"
];
int64 mtime = 5 [
(gogoproto.jsontag) = "mtime",
(gogoproto.casttype) = "go-common/library/time.Time",
(gogoproto.customname) = "MTime"
];
repeated int64 tag = 6 [ (gogoproto.jsontag) = "tag" ];
int32 special = 7 [ (gogoproto.jsontag) = "special" ];
}
message RelationsReq {
int64 mid = 1;
repeated int64 fid = 2;
string real_ip = 3;
}
message FollowingMapReply { map<int64, FollowingReply> following_map = 1; }
message FollowingsReply { repeated FollowingReply following_list = 1; }
message MidReq {
int64 mid = 1;
string real_ip = 2;
}
// +bili:deepcopy-gen=true
// +bili:deepcopy-gen:structs=go-common/app/service/main/relation/model.Stat
message StatReply {
int64 mid = 1 [ (gogoproto.jsontag) = "mid" ];
int64 following = 2 [ (gogoproto.jsontag) = "following" ];
int64 whisper = 3 [ (gogoproto.jsontag) = "whisper" ];
int64 black = 4 [ (gogoproto.jsontag) = "black" ];
int64 follower = 5 [ (gogoproto.jsontag) = "follower" ];
int64 ctime = 6 [
(gogoproto.jsontag) = "-",
(gogoproto.casttype) = "go-common/library/time.Time",
(gogoproto.customname) = "CTime"
];
int64 mtime = 7 [
(gogoproto.jsontag) = "-",
(gogoproto.casttype) = "go-common/library/time.Time",
(gogoproto.customname) = "MTime"
];
}
message MidsReq {
repeated int64 mids = 1;
string real_ip = 2;
}
message StatsReply { map<int64, StatReply> stat_reply_map = 1; }
message FollowingReq {
int64 mid = 1;
int64 fid = 2;
uint32 source = 3 [ (gogoproto.casttype) = "uint8" ];
string real_ip = 4;
int32 action = 5 [ (gogoproto.casttype) = "int8" ];
map<string, string> infoc = 6;
}
message EmptyReply {}
// ArgTagId tag id
message TagIdReq {
int64 mid = 1;
int64 tag_id = 2;
string real_ip = 3;
}
message TagReply { repeated int64 mids = 1; }
// +bili:deepcopy-gen=true
// +bili:deepcopy-gen:structs=go-common/app/service/main/relation/model.TagCount
message TagCountReply {
int64 tagid = 1 [ (gogoproto.jsontag) = "tagid" ];
string name = 2 [ (gogoproto.jsontag) = "name" ];
int64 count = 3 [ (gogoproto.jsontag) = "count" ];
}
message TagsCountReply { repeated TagCountReply tag_count_list = 1; }
message UserTagReply { map<int64, string> tags = 1; }
message TagReq {
int64 mid = 1;
string tag = 2;
string real_ip = 3;
}
message TagUpdateReq {
int64 mid = 1;
int64 tag_id = 2;
string new = 3;
string real_ip = 4;
}
message TagDelReq {
int64 mid = 1;
int64 tag_id = 2;
string real_ip = 3;
}
message TagsMoveUsersReq {
int64 mid = 1;
int64 before_id = 2;
string after_tag_ids = 3;
string fids = 4;
string real_ip = 5;
}
message PromptReq {
int64 mid = 1;
int64 fid = 2;
int32 btype = 3 [ (gogoproto.casttype) = "int8" ];
}
message CreateTagReply { int64 tag_id = 1; }
message PromptReply { bool success = 1; }
message FollowersUnreadReply { bool has_unread = 1; }
message FollowersUnreadCountReply { int64 unread_count = 1; }
message SpecialReply { repeated int64 mids = 1; }
message AchieveGetReq {
string award = 1;
int64 mid = 2;
}
message AchieveGetReply { string awardToken = 1; }
message AchieveReq { string awardToken = 1; }
message AchieveReply {
string award = 1;
int64 mid = 2;
}
message FollowerNotifySettingReply {
int64 mid = 1;
bool enabled = 2;
}
message SameFollowingReq {
int64 mid = 1;
int64 mid2 = 2;
}

View File

@@ -0,0 +1,22 @@
package api
import (
"context"
"google.golang.org/grpc"
"go-common/library/net/rpc/warden"
)
// AppID AppID
const AppID = "account.service.relation"
// NewClient new member grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (RelationClient, error) {
client := warden.NewClient(cfg, opts...)
conn, err := client.Dial(context.Background(), "discovery://default/"+AppID)
if err != nil {
return nil, err
}
return NewRelationClient(conn), nil
}

View File

@@ -0,0 +1,222 @@
// Code generated by deepcopy-gen. DO NOT EDIT.
package api
import (
model "go-common/app/service/main/relation/model"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FollowingReply) DeepCopyInto(out *FollowingReply) {
*out = *in
if in.Tag != nil {
in, out := &in.Tag, &out.Tag
*out = make([]int64, len(*in))
copy(*out, *in)
}
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FollowingReply.
func (in *FollowingReply) DeepCopy() *FollowingReply {
if in == nil {
return nil
}
out := new(FollowingReply)
in.DeepCopyInto(out)
return out
}
// DeepCopyAsIntoFollowing is an autogenerated deepcopy function, copying the receiver, writing into model.Following.
func (in *FollowingReply) DeepCopyAsIntoFollowing(out *model.Following) {
out.Mid = in.Mid
out.Attribute = in.Attribute
out.Source = in.Source
out.CTime = in.CTime
out.MTime = in.MTime
if in.Tag != nil {
in, out := &in.Tag, &out.Tag
*out = make([]int64, len(*in))
copy(*out, *in)
}
out.Special = in.Special
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
out.XXX_sizecache = in.XXX_sizecache
return
}
// DeepCopyFromFollowing is an autogenerated deepcopy function, copying the receiver, writing into model.Following.
func (out *FollowingReply) DeepCopyFromFollowing(in *model.Following) {
out.Mid = in.Mid
out.Attribute = in.Attribute
out.Source = in.Source
out.CTime = in.CTime
out.MTime = in.MTime
if in.Tag != nil {
in, out := &in.Tag, &out.Tag
*out = make([]int64, len(*in))
copy(*out, *in)
}
out.Special = in.Special
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
out.XXX_sizecache = in.XXX_sizecache
return
}
// DeepCopyAsFollowing is an autogenerated deepcopy function, copying the receiver, creating a new model.Following.
func (in *FollowingReply) DeepCopyAsFollowing() *model.Following {
if in == nil {
return nil
}
out := new(model.Following)
in.DeepCopyAsIntoFollowing(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StatReply) DeepCopyInto(out *StatReply) {
*out = *in
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatReply.
func (in *StatReply) DeepCopy() *StatReply {
if in == nil {
return nil
}
out := new(StatReply)
in.DeepCopyInto(out)
return out
}
// DeepCopyAsIntoStat is an autogenerated deepcopy function, copying the receiver, writing into model.Stat.
func (in *StatReply) DeepCopyAsIntoStat(out *model.Stat) {
out.Mid = in.Mid
out.Following = in.Following
out.Whisper = in.Whisper
out.Black = in.Black
out.Follower = in.Follower
out.CTime = in.CTime
out.MTime = in.MTime
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
out.XXX_sizecache = in.XXX_sizecache
return
}
// DeepCopyFromStat is an autogenerated deepcopy function, copying the receiver, writing into model.Stat.
func (out *StatReply) DeepCopyFromStat(in *model.Stat) {
out.Mid = in.Mid
out.Following = in.Following
out.Whisper = in.Whisper
out.Black = in.Black
out.Follower = in.Follower
out.CTime = in.CTime
out.MTime = in.MTime
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
out.XXX_sizecache = in.XXX_sizecache
return
}
// DeepCopyAsStat is an autogenerated deepcopy function, copying the receiver, creating a new model.Stat.
func (in *StatReply) DeepCopyAsStat() *model.Stat {
if in == nil {
return nil
}
out := new(model.Stat)
in.DeepCopyAsIntoStat(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TagCountReply) DeepCopyInto(out *TagCountReply) {
*out = *in
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TagCountReply.
func (in *TagCountReply) DeepCopy() *TagCountReply {
if in == nil {
return nil
}
out := new(TagCountReply)
in.DeepCopyInto(out)
return out
}
// DeepCopyAsIntoTagCount is an autogenerated deepcopy function, copying the receiver, writing into model.TagCount.
func (in *TagCountReply) DeepCopyAsIntoTagCount(out *model.TagCount) {
out.Tagid = in.Tagid
out.Name = in.Name
out.Count = in.Count
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
out.XXX_sizecache = in.XXX_sizecache
return
}
// DeepCopyFromTagCount is an autogenerated deepcopy function, copying the receiver, writing into model.TagCount.
func (out *TagCountReply) DeepCopyFromTagCount(in *model.TagCount) {
out.Tagid = in.Tagid
out.Name = in.Name
out.Count = in.Count
out.XXX_NoUnkeyedLiteral = in.XXX_NoUnkeyedLiteral
if in.XXX_unrecognized != nil {
in, out := &in.XXX_unrecognized, &out.XXX_unrecognized
*out = make([]byte, len(*in))
copy(*out, *in)
}
out.XXX_sizecache = in.XXX_sizecache
return
}
// DeepCopyAsTagCount is an autogenerated deepcopy function, copying the receiver, creating a new model.TagCount.
func (in *TagCountReply) DeepCopyAsTagCount() *model.TagCount {
if in == nil {
return nil
}
out := new(model.TagCount)
in.DeepCopyAsIntoTagCount(out)
return out
}

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["relation-service-example.toml"],
importpath = "go-common/app/service/main/relation/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/http:go_default_library",
"//app/service/main/relation/rpc/server:go_default_library",
"//app/service/main/relation/server/grpc:go_default_library",
"//app/service/main/relation/service:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus/report:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,58 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"time"
"context"
"go-common/app/service/main/relation/conf"
"go-common/app/service/main/relation/http"
"go-common/app/service/main/relation/rpc/server"
"go-common/app/service/main/relation/server/grpc"
"go-common/app/service/main/relation/service"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/queue/databus/report"
)
func main() {
flag.Parse()
// init conf,log,trace,stat,perf.
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
trace.Init(conf.Conf.Tracer)
defer trace.Close()
// report
report.InitUser(conf.Conf.Report)
// service init WardenServer *warden.ServerConfig
svr := service.New(conf.Conf)
rpcSvr := rpc.New(conf.Conf, svr)
//start grpc
ws := grpc.New(conf.Conf, svr)
http.Init(conf.Conf, svr)
// signal handler
log.Info("relation-service start")
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("relation-service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
ws.Shutdown(context.Background())
rpcSvr.Close()
time.Sleep(time.Second * 2)
log.Info("relation-service exit")
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,163 @@
[log]
stdout = true
[host]
passport = "http://uat-passport.bilibili.co"
[RPCClient2]
[RPCClient2.member]
timeout = "1s"
[bm]
addr = "0.0.0.0:6331"
maxListen = 1000
timeout = "10s"
[mysql]
addr = "172.16.33.205:3306"
dsn = "account:wx2U1MwXRyWEuURw@tcp(172.16.33.205:3306)/relation?timeout=15s&readTimeout=15s&writeTimeout=15s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[memcache]
name = "relation-job"
proto = "tcp"
addr = "172.18.33.61:11212"
idle = 5
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"
[redis]
name = "relation-service"
proto = "tcp"
addr = "172.18.33.60:6969"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "720h"
[rpcServer]
token="1234"
proto = "tcp"
addr = "0.0.0.0:6339"
weight = 10
[verify]
openServiceHost= "http://open.bilibili.co"
[verify.httpClient]
dial = "3000ms"
timeout = "3000ms"
keepAlive = "60s"
key = "53e2fa226f5ad348"
secret = "3cf6bd1b0ff671021da5f424fea4b04a"
[clearPath]
following = "http://api.bilibili.co/x/internal/relation/cache/following/update"
follower = "http://api.bilibili.co/x/internal/relation/cache/follower/del"
stat = "http://api.bilibili.co/x/internal/relation/cache/stat/del"
[apiPath]
followersNotify = "http://message.bilibili.co/api/notify/send.user.notify.do"
[httpClient]
key = "e7482d29be4a95b8"
secret = "9e803791cdef756e75faee68e12b7442"
dial = "100ms"
timeout = "1s"
keepAlive = "60s"
timer = 128
[httpClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[app]
key = "e7482d29be4a95b8"
secret = "9e803791cdef756e75faee68e12b7442"
[databus]
key = "0QBAaL5f8hupBjLQydZt"
secret = "0QBAaL5f8hupBjLQydZu"
group = "Relation-Friends-S"
topic = "Relation-T"
action = "sub"
offset = "old"
buffer = 2048
name = "go-common/app/job/main/relation/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[sms]
phone="15121003430,13601914616"
token="460d52f1-dafe-44a2-911a-9f308968309a"
[relation]
maxFollowingCached = 1000
maxFollowerCached = 1000
maxWhisperCached = 1000
maxFollowingLimit = 1000
mediumFollowingLimit = 5
minFollowingLimit = 3
maxBlackLimit = 200
monitor = false
followersUnread = "168h"
achievekey="VELvdAga7rbzL1O8"
Period="1s"
[statcache]
size=4096
expire="10s"
leastFollower=10000
[infoc]
taskID = "000072"
proto = "tcp"
addr = "172.19.100.20:5401"
chanSize = 10240
[antispam]
on=true
second=2
n=1
hour=12
m=500
[antispam.redis]
name = "relation/relation-service"
proto = "unix"
addr = "/tmp/uat-relation-redis.sock"
idle = 100
active = 100
dialTimeout = "500ms"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
expire = "10m"
[rate]
[rate.apps]
"relationappkey" = {limit = 10000.0, burst = 10000}
[rate.urls]
"/x/internal/relation/following/add" = {limit = 30000.0, burst = 10000}
[wardenServer]
addr = "0.0.0.0:9000"
timeout = "1s"

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/service/main/relation/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/rate:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,173 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/rate"
v "go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
"go-common/library/net/rpc/warden"
)
// Conf global variable.
var (
confPath string
Conf = &Config{}
client *conf.Client
)
// Config struct of conf.
type Config struct {
Host *Host
// bm
BM *bm.ServerConfig
// log
Log *log.Config
// rpc server2
RPCServer *rpc.ServerConfig
// db
Mysql *sql.Config
// mc
Memcache *Memcache
// redis
Redis *Redis
// tracer
Tracer *trace.Config
// realtion
Relation *Relation
// rpc clients
RPCClient2 *RPC
// Infoc
Infoc *infoc.Config
// Antispam
Antispam *antispam.Config
// statCache
StatCache *StatCache
// httpClinet
HTTPClient *bm.ClientConfig
// Report
Report *databus.Config
// Verify
Verify *v.Config
// Rate
AddFollowingRate *rate.Config
// WardenServer
WardenServer *warden.ServerConfig
}
// Host host.
type Host struct {
Passport string
}
// RPC clients config.
type RPC struct {
// member rpc client
Member *rpc.ClientConfig
}
// Redis redis
type Redis struct {
*redis.Config
Expire time.Duration
}
// Memcache memcache
type Memcache struct {
*memcache.Config
Expire time.Duration
FollowerExpire time.Duration
}
// Relation relation related config
type Relation struct {
MaxFollowingCached int
MaxFollowerCached int
MaxWhisperCached int
// max following limit
MaxFollowingLimit int
// max black limit
MaxBlackLimit int
// monitor switch: true, user cannot be following.
Monitor bool
// prompt
Period time.Duration // prompt count flush period
Bcount int64 // business prompt count
Ucount int64 // up prompt count
// followers unread duration
FollowersUnread time.Duration
// achieve key
AchieveKey string
}
// StatCache is
type StatCache struct {
Size int
Expire time.Duration
LeastFollower int
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf.
func Init() (err error) {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,81 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"audit.go",
"dao.go",
"localcache.go",
"memcache.go",
"mysql.go",
"prompt_redis.go",
"recent_redis.go",
"redis.go",
"relation_log.go",
],
importpath = "go-common/app/service/main/relation/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/model/i64b:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/stat/prom:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/bluele/gcache:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"audit_test.go",
"dao_test.go",
"localcache_test.go",
"memcache_test.go",
"mysql_test.go",
"prompt_redis_test.go",
"recent_redis_test.go",
"redis_test.go",
"relation_log_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/model/i64b:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,39 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/app/service/main/relation/model"
"go-common/library/ecode"
"github.com/pkg/errors"
)
// PassportDetail get passport detail from passport service through http
func (d *Dao) PassportDetail(c context.Context, mid int64, ip string) (res *model.PassportDetail, err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
var resp struct {
Code int `json:"code"`
Data *model.PassportDetail `json:"data"`
}
req, err := d.client.NewRequest("GET", d.detailURI, ip, params)
if err != nil {
err = errors.Wrap(err, "dao passport detail")
return
}
req.Header.Set("X-BACKEND-BILI-REAL-IP", ip)
if err = d.client.Do(c, req, &resp); err != nil {
err = errors.Wrap(err, "dao passport detail")
return
}
if resp.Code != 0 {
err = ecode.Int(resp.Code)
err = errors.Wrap(err, "dao passport detail")
return
}
res = resp.Data
return
}

View File

@@ -0,0 +1,23 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPassportDetail(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
ip = ""
)
convey.Convey("PassportDetail", t, func(cv convey.C) {
res, err := d.PassportDetail(c, mid, ip)
cv.Convey("Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,94 @@
package dao
import (
"context"
"time"
"github.com/bluele/gcache"
"go-common/app/service/main/relation/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
)
const (
_passportDetailURL = "/intranet/acc/detail"
)
// Dao struct info of Dao.
type Dao struct {
// mysql
db *sql.DB
// memcache
mc *memcache.Pool
followerExpire int32
mcExpire int32
// redis
redis *redis.Pool
redisExpire int32
// conf
c *conf.Config
// prompt
period int64
bcount int64
ucount int64
// followers unread duration
UnreadDuration int64
// apis
detailURI string
// client
client *bm.Client
// statCache
statStore gcache.Cache
}
// New new a Dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
// conf
c: c,
// db
db: sql.NewMySQL(c.Mysql),
// memcache
mc: memcache.NewPool(c.Memcache.Config),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
followerExpire: int32(time.Duration(c.Memcache.FollowerExpire) / time.Second),
// redis
redis: redis.NewPool(c.Redis.Config),
redisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),
// prompt
period: int64(time.Duration(c.Relation.Period) / time.Second),
bcount: c.Relation.Bcount,
ucount: c.Relation.Ucount,
// followers unread
UnreadDuration: int64(time.Duration(c.Relation.FollowersUnread) / time.Second),
// passport api
detailURI: c.Host.Passport + _passportDetailURL,
client: bm.NewClient(c.HTTPClient),
statStore: gcache.New(c.StatCache.Size).LFU().Build(),
}
return
}
// Ping ping health.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingMC(c); err != nil {
return
}
return d.pingRedis(c)
}
// Close close connections of mc, redis, db.
func (d *Dao) Close() {
if d.mc != nil {
d.mc.Close()
}
if d.redis != nil {
d.redis.Close()
}
if d.db != nil {
d.db.Close()
}
}

View File

@@ -0,0 +1,33 @@
package dao
import (
"flag"
"go-common/app/service/main/relation/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.relation-service")
flag.Set("conf_token", "8hm3I5rWzuhChxrBI6VTqmCs7TpJwFhO")
flag.Set("tree_id", "2139")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,86 @@
package dao
import (
"context"
"time"
"go-common/app/service/main/relation/model"
"go-common/library/log"
"go-common/library/stat/prom"
"github.com/bluele/gcache"
"github.com/pkg/errors"
)
func (d *Dao) loadStat(ctx context.Context, mid int64) (*model.Stat, error) {
stat, err := d.statCache(ctx, mid)
if err != nil {
return nil, err
}
d.storeStat(mid, stat)
return stat, nil
}
func (d *Dao) storeStat(mid int64, stat *model.Stat) {
if stat == nil || stat.Follower < int64(d.c.StatCache.LeastFollower) {
return
}
d.statStore.SetWithExpire(mid, stat, time.Duration(d.c.StatCache.Expire))
}
func (d *Dao) localStat(mid int64) (*model.Stat, error) {
prom.CacheHit.Incr("local_stat_cache")
item, err := d.statStore.Get(mid)
if err != nil {
prom.CacheMiss.Incr("local_stat_cache")
return nil, err
}
stat, ok := item.(*model.Stat)
if !ok {
prom.CacheMiss.Incr("local_stat_cache")
return nil, errors.New("Not a stat")
}
return stat, nil
}
// StatCache get stat cache.
func (d *Dao) StatCache(c context.Context, mid int64) (*model.Stat, error) {
stat, err := d.localStat(mid)
if err != nil {
if err != gcache.KeyNotFoundError {
log.Error("Failed to get stat from local: mid: %d: %+v", mid, err)
}
return d.loadStat(c, mid)
}
return stat, nil
}
// StatsCache get multi stat cache.
func (d *Dao) StatsCache(c context.Context, mids []int64) (map[int64]*model.Stat, []int64, error) {
stats := make(map[int64]*model.Stat, len(mids))
lcMissed := make([]int64, 0, len(mids))
for _, mid := range mids {
stat, err := d.localStat(mid)
if err != nil {
if err != gcache.KeyNotFoundError {
log.Error("Failed to get stat from local: mid: %d: %+v", mid, err)
}
lcMissed = append(lcMissed, mid)
continue
}
stats[stat.Mid] = stat
}
if len(lcMissed) == 0 {
return stats, nil, nil
}
mcStats, mcMissed, err := d.statsCache(c, lcMissed)
if err != nil {
return nil, nil, err
}
for _, stat := range mcStats {
d.storeStat(stat.Mid, stat)
stats[stat.Mid] = stat
}
return stats, mcMissed, nil
}

View File

@@ -0,0 +1,43 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoloadStat(t *testing.T) {
var (
ctx = context.Background()
mid = int64(1)
stat = &model.Stat{
Mid: 1,
Follower: 1,
Following: 1,
Black: 1,
Whisper: 1,
}
)
d.SetStatCache(ctx, mid, stat)
d.storeStat(mid, stat)
convey.Convey("loadStat", t, func(cv convey.C) {
d.storeStat(mid, stat)
cv.Convey("No return values", func(cv convey.C) {
})
s1, err := d.loadStat(ctx, mid)
cv.Convey("loadStat", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s1, convey.ShouldNotBeNil)
})
p1, p2, err := d.StatsCache(ctx, []int64{1})
cv.Convey("StatsCache", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(p2, convey.ShouldNotBeNil)
cv.So(p1, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,405 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/service/main/relation/model"
gmc "go-common/library/cache/memcache"
"go-common/library/log"
)
const (
_prefixFollowing = "pb_a_" // key of following
_prefixFollower = "pb_f_" // key of follower
_prefixTagCount = "rs_tmtc_%d" // key of relation tag by mid & tag's count
_prefixTags = "tags_" // user tag info.
_prefixGlobalHotRec = "gh_rec" // global hot rec
_prefixStat = "c_" // key of stat
_prefixFollowerNotify = "f_notify_"
_emptyExpire = 20 * 24 * 3600
_recExpire = 5 * 24 * 3600
)
func statKey(mid int64) string {
return _prefixStat + strconv.FormatInt(mid, 10)
}
func tagsKey(mid int64) string {
return _prefixTags + strconv.FormatInt(mid, 10)
}
func followingKey(mid int64) string {
return _prefixFollowing + strconv.FormatInt(mid, 10)
}
func followerKey(mid int64) string {
return _prefixFollower + strconv.FormatInt(mid, 10)
}
func tagCountKey(mid int64) string {
return fmt.Sprintf(_prefixTagCount, mid)
}
func globalHotKey() string {
return _prefixGlobalHotRec
}
func followerNotifySetting(mid int64) string {
return _prefixFollowerNotify + strconv.FormatInt(mid, 10)
}
// pingMC ping memcache.
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
if err = conn.Set(&gmc.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcExpire}); err != nil {
log.Error("conn.Store(set, ping, 1) error(%v)", err)
}
conn.Close()
return
}
// SetFollowingCache set following cache.
func (d *Dao) SetFollowingCache(c context.Context, mid int64, followings []*model.Following) (err error) {
return d.setFollowingCache(c, followingKey(mid), followings)
}
// FollowingCache get following cache.
func (d *Dao) FollowingCache(c context.Context, mid int64) (followings []*model.Following, err error) {
return d.followingCache(c, followingKey(mid))
}
// DelFollowingCache delete following cache.
func (d *Dao) DelFollowingCache(c context.Context, mid int64) (err error) {
return d.delFollowingCache(c, followingKey(mid))
}
// SetFollowerCache set follower cache.
func (d *Dao) SetFollowerCache(c context.Context, mid int64, followers []*model.Following) (err error) {
return d.setFollowingCache(c, followerKey(mid), followers)
}
// FollowerCache get follower cache.
func (d *Dao) FollowerCache(c context.Context, mid int64) (followers []*model.Following, err error) {
return d.followingCache(c, followerKey(mid))
}
// DelFollowerCache delete follower cache.
func (d *Dao) DelFollowerCache(c context.Context, mid int64) (err error) {
return d.delFollowingCache(c, followerKey(mid))
}
// setFollowingCache set following cache.
func (d *Dao) setFollowingCache(c context.Context, key string, followings []*model.Following) (err error) {
expire := d.followerExpire
if len(followings) == 0 {
expire = _emptyExpire
}
item := &gmc.Item{Key: key, Object: &model.FollowingList{FollowingList: followings}, Expiration: expire, Flags: gmc.FlagProtobuf}
conn := d.mc.Get(c)
if err = conn.Set(item); err != nil {
log.Error("setFollowingCache err(%v)", err)
}
conn.Close()
return
}
// followingCache get following cache.
func (d *Dao) followingCache(c context.Context, key string) (followings []*model.Following, err error) {
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == gmc.ErrNotFound {
err = nil
return
}
log.Error("d.followingCache err(%v)", err)
return
}
followingList := &model.FollowingList{}
if err = conn.Scan(item, followingList); err != nil {
log.Error("d.followinfCache err(%v)", err)
}
followings = followingList.FollowingList
if followings == nil {
followings = []*model.Following{}
}
return
}
// delFollowingCache delete following cache.
func (d *Dao) delFollowingCache(c context.Context, key string) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(key); err != nil {
if err == gmc.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
conn.Close()
return
}
// TagCountCache tag count cache
func (d *Dao) TagCountCache(c context.Context, mid int64) (tagCount []*model.TagCount, err error) {
conn := d.mc.Get(c)
defer conn.Close()
res, err := conn.Get(tagCountKey(mid))
if err != nil {
if err == gmc.ErrNotFound {
err = nil
return
}
log.Error("mc.Get error(%v)", err)
return
}
tc := &model.TagCountList{}
if err = conn.Scan(res, tc); err != nil {
log.Error("conn.Scan error(%v)", err)
}
tagCount = tc.TagCountList
return
}
// SetTagCountCache set tag count cache
func (d *Dao) SetTagCountCache(c context.Context, mid int64, tagCount []*model.TagCount) (err error) {
item := &gmc.Item{Key: tagCountKey(mid), Object: &model.TagCountList{TagCountList: tagCount}, Expiration: d.mcExpire, Flags: gmc.FlagProtobuf}
conn := d.mc.Get(c)
if err = conn.Set(item); err != nil {
log.Error("setTagMidFidCache(%s) error(%v)", tagCountKey(mid), err)
}
conn.Close()
return
}
// DelTagCountCache del tag count cache
func (d *Dao) DelTagCountCache(c context.Context, mid int64) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(tagCountKey(mid)); err != nil {
if err == gmc.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", tagCountKey(mid), err)
}
}
conn.Close()
return
}
// SetTagsCache set user tags cache.
func (d *Dao) SetTagsCache(c context.Context, mid int64, tags *model.Tags) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
item := &gmc.Item{Key: tagsKey(mid), Object: tags, Expiration: d.mcExpire, Flags: gmc.FlagProtobuf}
if err = conn.Set(item); err != nil {
log.Error("SetTagsCache err %v", err)
}
return
}
// TagsCache get user tags.
func (d *Dao) TagsCache(c context.Context, mid int64) (tags map[int64]*model.Tag, err error) {
conn := d.mc.Get(c)
defer conn.Close()
res, err := conn.Get(tagsKey(mid))
if err != nil {
if err == gmc.ErrNotFound {
err = nil
return
}
return
}
tag := new(model.Tags)
if err = conn.Scan(res, tag); err != nil {
return
}
tags = tag.Tags
return
}
// DelTagsCache del user tags cache.
func (d *Dao) DelTagsCache(c context.Context, mid int64) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(tagsKey(mid)); err != nil {
if err == gmc.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", tagCountKey(mid), err)
}
}
conn.Close()
return
}
// SetGlobalHotRecCache set global hot recommend cache.
func (d *Dao) SetGlobalHotRecCache(c context.Context, fids []int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
item := &gmc.Item{Key: globalHotKey(), Object: &model.GlobalRec{Fids: fids}, Expiration: _recExpire, Flags: gmc.FlagProtobuf}
if err = conn.Set(item); err != nil {
log.Error("SetGlobalHotRecCache err %v", err)
}
return
}
// GlobalHotRecCache get global hot recommend.
func (d *Dao) GlobalHotRecCache(c context.Context) (fs []int64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
res, err := conn.Get(globalHotKey())
if err != nil {
if err == gmc.ErrNotFound {
err = nil
}
return
}
gh := new(model.GlobalRec)
if err = conn.Scan(res, gh); err != nil {
return
}
fs = gh.Fids
return
}
// SetStatCache set stat cache.
func (d *Dao) SetStatCache(c context.Context, mid int64, st *model.Stat) error {
conn := d.mc.Get(c)
defer conn.Close()
item := &gmc.Item{
Key: statKey(mid),
Object: st,
Expiration: d.mcExpire,
Flags: gmc.FlagProtobuf,
}
if err := conn.Set(item); err != nil {
log.Error("Failed to set stat cache: mid: %d stat: %+v: %+v", mid, st, err)
return err
}
return nil
}
// statCache get stat cache.
func (d *Dao) statCache(c context.Context, mid int64) (*model.Stat, error) {
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(statKey(mid))
if err != nil {
if err == gmc.ErrNotFound {
return nil, nil
}
return nil, err
}
stat := &model.Stat{}
if err := conn.Scan(item, stat); err != nil {
log.Error("Failed to get stat cache: mid: %d: %+v", mid, err)
return nil, err
}
return stat, nil
}
// statsCache get multi stat cache.
func (d *Dao) statsCache(c context.Context, mids []int64) (map[int64]*model.Stat, []int64, error) {
conn := d.mc.Get(c)
defer conn.Close()
keys := make([]string, 0, len(mids))
for _, mid := range mids {
keys = append(keys, statKey(mid))
}
items, err := conn.GetMulti(keys)
if err != nil {
log.Error("Failed to get multi stat: keys: %+v: %+v", keys, err)
return nil, nil, err
}
stats := make(map[int64]*model.Stat, len(mids))
for _, item := range items {
stat := &model.Stat{}
if err := conn.Scan(item, stat); err != nil {
log.Error("Failed to scan item: key: %s item: %+v: %+v", item.Key, item, err)
continue
}
stats[stat.Mid] = stat
}
missed := make([]int64, 0, len(mids))
for _, mid := range mids {
if _, ok := stats[mid]; !ok {
missed = append(missed, mid)
}
}
return stats, missed, nil
}
// DelStatCache delete stat cache.
func (d *Dao) DelStatCache(c context.Context, mid int64) error {
conn := d.mc.Get(c)
defer conn.Close()
if err := conn.Delete(statKey(mid)); err != nil {
if err == gmc.ErrNotFound {
return nil
}
log.Error("Failed to delete stat cache: mid: %d: %+v", mid, err)
return err
}
return nil
}
// GetFollowerNotifyCache get data from mc
func (d *Dao) GetFollowerNotifyCache(c context.Context, mid int64) (res *model.FollowerNotifySetting, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := followerNotifySetting(mid)
reply, err := conn.Get(key)
if err != nil {
if err == gmc.ErrNotFound {
err = nil
return
}
log.Errorv(c, log.KV("GetFollowerNotifyCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.FollowerNotifySetting{}
err = conn.Scan(reply, res)
if err != nil {
log.Errorv(c, log.KV("GetFollowerNotifyCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// SetFollowerNotifyCache Set data to mc
func (d *Dao) SetFollowerNotifyCache(c context.Context, mid int64, val *model.FollowerNotifySetting) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := followerNotifySetting(mid)
item := &gmc.Item{
Key: key,
Object: val,
Expiration: 86400,
Flags: gmc.FlagJSON,
}
if err = conn.Set(item); err != nil {
log.Errorv(c, log.KV("SetFollowerNotifyCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelFollowerNotifyCache Del data from mc
func (d *Dao) DelFollowerNotifyCache(c context.Context, mid int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := followerNotifySetting(mid)
if err = conn.Delete(key); err != nil {
if err == gmc.ErrNotFound {
err = nil
return
}
log.Errorv(c, log.KV("DelFollowerNotifyCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}

View File

@@ -0,0 +1,375 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaostatKey(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("statKey", t, func(cv convey.C) {
p1 := statKey(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaotagsKey(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("tagsKey", t, func(cv convey.C) {
p1 := tagsKey(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaofollowingKey(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("followingKey", t, func(cv convey.C) {
p1 := followingKey(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaofollowerKey(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("followerKey", t, func(cv convey.C) {
p1 := followerKey(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaotagCountKey(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("tagCountKey", t, func(cv convey.C) {
p1 := tagCountKey(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoglobalHotKey(t *testing.T) {
convey.Convey("globalHotKey", t, func(cv convey.C) {
p1 := globalHotKey()
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaofollowerNotifySetting(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("followerNotifySetting", t, func(cv convey.C) {
p1 := followerNotifySetting(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaopingMC(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("pingMC", t, func(cv convey.C) {
err := d.pingMC(c)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetFollowingCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
followings = []*model.Following{}
)
convey.Convey("SetFollowingCache", t, func(cv convey.C) {
err := d.SetFollowingCache(c, mid, followings)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoFollowingCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("FollowingCache", t, func(cv convey.C) {
followings, err := d.FollowingCache(c, mid)
cv.Convey("Then err should be nil.followings should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(followings, convey.ShouldNotBeNil)
})
})
}
func TestDaoDelFollowingCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("DelFollowingCache", t, func(cv convey.C) {
err := d.DelFollowingCache(c, mid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetFollowerCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
followers = []*model.Following{}
)
convey.Convey("SetFollowerCache", t, func(cv convey.C) {
err := d.SetFollowerCache(c, mid, followers)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoFollowerCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("FollowerCache", t, func(cv convey.C) {
followers, err := d.FollowerCache(c, mid)
cv.Convey("Then err should be nil.followers should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(followers, convey.ShouldNotBeNil)
})
})
}
func TestDaoDelFollowerCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("DelFollowerCache", t, func(cv convey.C) {
err := d.DelFollowerCache(c, mid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaosetFollowingCache(t *testing.T) {
var (
c = context.Background()
key = followingKey(1)
followings = []*model.Following{}
)
convey.Convey("setFollowingCache", t, func(cv convey.C) {
err := d.setFollowingCache(c, key, followings)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaofollowingCache(t *testing.T) {
var (
c = context.Background()
key = followingKey(1)
)
convey.Convey("followingCache", t, func(cv convey.C) {
followings, err := d.followingCache(c, key)
cv.Convey("Then err should be nil.followings should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(followings, convey.ShouldNotBeNil)
})
})
}
func TestDaodelFollowingCache(t *testing.T) {
var (
c = context.Background()
key = followingKey(1)
)
convey.Convey("delFollowingCache", t, func(cv convey.C) {
err := d.delFollowingCache(c, key)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoTagCountCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
tagCount = []*model.TagCount{
{
Tagid: 1,
Name: "test",
Count: 1,
},
}
)
convey.Convey("TagCountCache", t, func(cv convey.C) {
err := d.SetTagCountCache(c, mid, tagCount)
cv.So(err, convey.ShouldBeNil)
tagCount, err := d.TagCountCache(c, mid)
cv.So(err, convey.ShouldBeNil)
cv.So(tagCount, convey.ShouldNotBeNil)
err = d.DelTagCountCache(c, mid)
cv.So(err, convey.ShouldBeNil)
})
}
func TestDaoTagsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
tags = &model.Tags{
Tags: map[int64]*model.Tag{
1: {
Id: 1,
Name: "1",
Status: 1,
},
},
}
)
convey.Convey("TagsCache", t, func(cv convey.C) {
err := d.SetTagsCache(c, mid, tags)
cv.Convey("SetTagsCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
tags, err := d.TagsCache(c, mid)
cv.Convey("TagsCache; Then err should be nil.tags should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(tags, convey.ShouldNotBeNil)
})
err = d.DelTagsCache(c, mid)
cv.Convey("DelTagsCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetGlobalHotRecCache(t *testing.T) {
var (
c = context.Background()
fids = []int64{1}
)
convey.Convey("SetGlobalHotRecCache", t, func(cv convey.C) {
err := d.SetGlobalHotRecCache(c, fids)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoGlobalHotRecCache(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("GlobalHotRecCache", t, func(cv convey.C) {
fs, err := d.GlobalHotRecCache(c)
cv.Convey("Then err should be nil.fs should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(fs, convey.ShouldNotBeNil)
})
})
}
func TestDaostatCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
st = &model.Stat{
Mid: 1,
Follower: 1,
Following: 1,
Black: 1,
Whisper: 1,
}
)
convey.Convey("statCache", t, func(cv convey.C) {
err := d.SetStatCache(c, mid, st)
cv.Convey("SetStatCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
s1, err := d.statCache(c, mid)
cv.Convey("statCache; Then err should be nil.p1 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s1, convey.ShouldNotBeNil)
})
p1, p2, err := d.statsCache(c, []int64{1})
cv.Convey("statsCache; Then err should be nil.p1,p2 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(p2, convey.ShouldNotBeNil)
cv.So(p1, convey.ShouldNotBeNil)
})
err = d.DelStatCache(c, mid)
cv.Convey("DelStatCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoGetFollowerNotifyCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
val = &model.FollowerNotifySetting{
Mid: 1,
Enabled: true,
}
)
convey.Convey("GetFollowerNotifyCache", t, func(cv convey.C) {
err := d.SetFollowerNotifyCache(c, mid, val)
cv.Convey("SetFollowerNotifyCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
res, err := d.GetFollowerNotifyCache(c, mid)
cv.Convey("GetFollowerNotifyCache; Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
err = d.DelFollowerNotifyCache(c, mid)
cv.Convey("DelFollowerNotifyCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,499 @@
package dao
import (
"context"
xsql "database/sql"
"fmt"
"time"
"go-common/app/service/main/relation/model"
"go-common/app/service/main/relation/model/i64b"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_shard = 500
_statShard = 50
_tagShard = 100
_tagUserShard = 500
_defaultTag = "默认分组"
_specialTag = "特别关注"
// relation
_getFollowingSQL = "SELECT fid,attribute,mtime FROM user_relation_mid_%03d WHERE mid=? AND status=0"
_getFollowingInSQL = "SELECT fid,attribute,mtime FROM user_relation_mid_%03d WHERE mid=? AND status=0 AND fid IN (%s)"
_addFollowingSQL = "INSERT INTO user_relation_mid_%03d (mid,fid,attribute,source,status, ctime,mtime) VALUES (?,?,?,?,0,?,?) ON DUPLICATE KEY UPDATE attribute=attribute|?,source=?,status=0,mtime=?"
_setFollowingSQL = "UPDATE user_relation_mid_%03d SET attribute=?,source=?,status=?,mtime=? WHERE mid=? AND fid=?"
_getFollowersSQL = "SELECT mid,attribute,mtime FROM user_relation_fid_%03d WHERE fid=? AND status=0 AND attribute IN (2,6) ORDER BY mtime DESC LIMIT 1000"
_addFollowersSQL = "INSERT INTO user_relation_fid_%03d (mid,fid,attribute,source,status,ctime,mtime) VALUES (?,?,?,?,0,?,?) ON DUPLICATE KEY UPDATE attribute=attribute|?,source=?,status=0,mtime=?"
_setFollowersSQL = "UPDATE user_relation_fid_%03d SET attribute=?,source=?,status=?,mtime=? WHERE fid=? AND mid=?"
_getRelationSQL = "SELECT attribute FROM user_relation_mid_%03d WHERE mid=? AND fid=? AND status=0 LIMIT 1"
_addStatIgnoreSQL = "INSERT IGNORE INTO user_relation_stat_%02d (mid,following,whisper,black,follower,ctime,mtime) VALUES (?,?,?,?,?,?,?)"
_addStatSQL = "UPDATE user_relation_stat_%02d SET following=following+?,whisper=whisper+?,black=black+?,follower=follower+?,mtime=? WHERE mid=?"
_setStatSQL = "UPDATE user_relation_stat_%02d SET following=?,whisper=?,black=?,follower=?,mtime=? WHERE mid=?"
_getStatSQL = "SELECT mid,following,whisper,black,follower, ctime,mtime from user_relation_stat_%02d where mid=?"
_getTxStatSQL = "SELECT mid,following,whisper,black,follower,ctime,mtime from user_relation_stat_%02d where mid=? FOR UPDATE"
// relation tag table
_getTagsSQL = "SELECT id,name,status,mtime FROM user_relation_tag_%02d WHERE mid=? AND status=0"
_addTagSQL = "INSERT INTO user_relation_tag_%02d (mid,name,status,ctime,mtime) VALUES (?,?,0,?,?)"
_delTagSQL = "DELETE FROM user_relation_tag_%02d WHERE id=? AND mid=?"
_setTagNameSQL = "UPDATE user_relation_tag_%02d SET name=?,mtime=? WHERE id=?"
// relation tag user table
_getTagUserSQL = "SELECT fid,tag FROM user_relation_tag_user_%03d WHERE mid=?"
_getUsersTagSQL = "SELECT fid,tag FROM user_relation_tag_user_%03d WHERE mid=? AND fid IN(%s)"
_getTagsByMidFidSQL = "SELECT fid,tag,mtime FROM user_relation_tag_user_%03d WHERE mid=? and fid=?"
_addTagUserSQL = "INSERT INTO user_relation_tag_user_%03d (mid,fid,tag,ctime,mtime) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE tag=?"
_setTagUserSQL = "UPDATE user_relation_tag_user_%03d SET tag=?,mtime=? WHERE mid=? AND fid=?"
_delTagUserSQL = "DELETE FROM user_relation_tag_user_%03d WHERE mid=? AND fid=?"
// relation monitor
_loadMonitorSQL = "SELECT mid FROM user_relation_monitor"
_addMonitorSQL = "INSERT IGNORE INTO user_relation_monitor (mid,ctime,mtime) VALUES (?,?,?)"
_delMonitorSQL = "DELETE FROM user_relation_monitor WHERE mid = ?"
// relation achieve
_hasReachAchieve = "SELECT count(1) FROM user_addit WHERE mid=? AND achieve_flags>=?"
// follower notify
_getFollowerNotifySettingSQL = "SELECT disable_follower_notify FROM user_addit where mid=?"
_enableFollowerNotifySQL = "INSERT INTO user_addit (mid, disable_follower_notify) VALUES(?, 0) ON DUPLICATE KEY UPDATE disable_follower_notify=0"
_disableFollowerNotifySQL = "INSERT INTO user_addit (mid, disable_follower_notify) VALUES(?, 1) ON DUPLICATE KEY UPDATE disable_follower_notify=1"
)
func hit(id int64) int64 {
return id % _shard
}
func statHit(id int64) int64 {
return id % _statShard
}
func tagHit(id int64) int64 {
return id % _tagShard
}
func tagUserHit(id int64) int64 {
return id % _tagUserShard
}
// BeginTran begin transaction.
func (d *Dao) BeginTran(c context.Context) (*sql.Tx, error) {
return d.db.Begin(c)
}
// Followings get user's following list.
func (d *Dao) Followings(c context.Context, mid int64) (res []*model.Following, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_getFollowingSQL, hit(mid)), mid); err != nil {
log.Error("d.query[%s].hit(%d).mid(%d) error(%v)", _getFollowingSQL, hit(mid), mid, err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.Following)
if err = rows.Scan(&r.Mid, &r.Attribute, &r.MTime); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// FollowingsIn get user's following list by in fids
func (d *Dao) FollowingsIn(c context.Context, mid int64, fids []int64) (res []*model.Following, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_getFollowingInSQL, hit(mid), xstr.JoinInts(fids)), mid); err != nil {
log.Error("d.query[%d].Query(%d) error(%v)", hit(mid), mid, err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.Following)
if err = rows.Scan(&r.Mid, &r.Attribute, &r.MTime); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// TxAddFollowing add following by transaction.
func (d *Dao) TxAddFollowing(c context.Context, tx *sql.Tx, mid, fid int64, mask uint32, source uint8, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_addFollowingSQL, hit(mid)), mid, fid, mask, source, now, now, mask, source, now); err != nil {
log.Error("add following: tx.Exec(%v, %d, %d, %d) error(%v)", mid, fid, mask, source, err)
return
}
return res.RowsAffected()
}
// TxSetFollowing set following by transaction.
func (d *Dao) TxSetFollowing(c context.Context, tx *sql.Tx, mid, fid int64, attribute uint32, source uint8, status int, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_setFollowingSQL, hit(mid)), attribute, source, status, now, mid, fid); err != nil {
log.Error("tx.Exec(%d, %d, %d, %d, %d) error(%v)", mid, fid, attribute, source, status, err)
return
}
return res.RowsAffected()
}
// Followers get user's latest 1000 followers(attribute = AttrFollowing), order by mtime desc.
func (d *Dao) Followers(c context.Context, mid int64) (res []*model.Following, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_getFollowersSQL, hit(mid)), mid); err != nil {
log.Error("d.query(%s).hit(%d).mid(%d) error(%v)", _getFollowersSQL, hit(mid), mid, err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.Following)
if err = rows.Scan(&r.Mid, &r.Attribute, &r.MTime); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// TxAddFollower add follower by transaction.
func (d *Dao) TxAddFollower(c context.Context, tx *sql.Tx, mid, fid int64, mask uint32, source uint8, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_addFollowersSQL, hit(fid)), mid, fid, mask, source, now, now, mask, source, now); err != nil {
log.Error("add follower: tx.Exec(%v, %d, %d, %d), error(%v)", mid, fid, mask, source, err)
return
}
return res.RowsAffected()
}
// TxSetFollower set follower by transaction.
func (d *Dao) TxSetFollower(c context.Context, tx *sql.Tx, mid, fid int64, attribute uint32, source uint8, status int, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_setFollowersSQL, hit(fid)), attribute, source, status, now, fid, mid); err != nil {
log.Error("tx.Exec(%d, %d, %d, %d, %d) error(%v)", mid, fid, attribute, source, status, err)
return
}
return res.RowsAffected()
}
// Stat get stat.
func (d *Dao) Stat(c context.Context, mid int64) (stat *model.Stat, err error) {
var row = d.db.QueryRow(c, fmt.Sprintf(_getStatSQL, statHit(mid)), mid)
stat = new(model.Stat)
if err = row.Scan(&stat.Mid, &stat.Following, &stat.Whisper, &stat.Black, &stat.Follower, &stat.CTime, &stat.MTime); err != nil {
if err == sql.ErrNoRows {
stat = nil
err = nil
} else {
log.Error("row.Scan() error(%v)", err)
}
}
return
}
// TxStat get stat for update by transaction.
func (d *Dao) TxStat(c context.Context, tx *sql.Tx, mid int64) (stat *model.Stat, err error) {
row := tx.QueryRow(fmt.Sprintf(_getTxStatSQL, statHit(mid)), mid)
stat = new(model.Stat)
if err = row.Scan(&stat.Mid, &stat.Following, &stat.Whisper, &stat.Black, &stat.Follower, &stat.CTime, &stat.MTime); err != nil {
if err == sql.ErrNoRows {
stat = nil
err = nil
} else {
log.Error("row.Scan() error(%v)", err)
}
}
return
}
// AddStat try add stat.
func (d *Dao) AddStat(c context.Context, mid int64, stat *model.Stat, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_addStatIgnoreSQL, statHit(mid)), mid, stat.Following, stat.Whisper, stat.Black, stat.Follower, now, now); err != nil {
log.Error("d.db.Exec(%s, %d, %v, %v) error(%v)", _addStatIgnoreSQL, mid, stat, now, err)
return
}
return res.RowsAffected()
}
// TxAddStat add params stat to stat by transaction.
func (d *Dao) TxAddStat(c context.Context, tx *sql.Tx, mid int64, stat *model.Stat, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_addStatSQL, statHit(mid)), stat.Following, stat.Whisper, stat.Black, stat.Follower, now, mid); err != nil {
log.Error("tx.Exec(%s, %d, %v, %v) error(%v)", _addStatSQL, mid, stat, now, err)
return
}
return res.RowsAffected()
}
// TxSetStat set stat to params stat by transaction.
func (d *Dao) TxSetStat(c context.Context, tx *sql.Tx, mid int64, stat *model.Stat, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_setStatSQL, statHit(mid)), stat.Following, stat.Whisper, stat.Black, stat.Follower, now, mid); err != nil {
log.Error("tx.Exec(%s, %d, %v, %v) error(%v)", _setStatSQL, mid, stat, now, err)
return
}
return res.RowsAffected()
}
// Relation get relation between mid and fid.
func (d *Dao) Relation(c context.Context, mid, fid int64) (attr uint32, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_getRelationSQL, hit(mid)), mid, fid)
if err = row.Scan(&attr); err != nil {
if err == sql.ErrNoRows {
attr = model.AttrNoRelation
err = nil
} else {
log.Error("row.Scan() error(%v)", err)
}
}
return
}
// LoadMonitor load all mids into redis set.
func (d *Dao) LoadMonitor(c context.Context) (mids []int64, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _loadMonitorSQL); err != nil {
log.Error("d.Query.Exec(%s) error(%v)", _loadMonitorSQL, err)
return
}
defer rows.Close()
for rows.Next() {
var mid int64
if err = rows.Scan(&mid); err != nil {
log.Error("row.Scan() error(%v)", err)
mids = nil
return
}
mids = append(mids, mid)
}
err = rows.Err()
return
}
// AddMonitor add mid to monitor table
func (d *Dao) AddMonitor(c context.Context, mid int64, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _addMonitorSQL, mid, now, now); err != nil {
log.Error("d.AddMonitor.Exec(%s, %d) error(%v)", _addMonitorSQL, mid, err)
return
}
return res.RowsAffected()
}
// DelMonitor del mid from monitor table
func (d *Dao) DelMonitor(c context.Context, mid int64) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _delMonitorSQL, mid); err != nil {
log.Error("d.DelMonitor.Exec(%s, %d)", _delMonitorSQL, mid)
return
}
return res.RowsAffected()
}
// TxDelTagUser delete tag user record.
func (d *Dao) TxDelTagUser(c context.Context, tx *sql.Tx, mid, fid int64) (affected int64, err error) {
var res xsql.Result
if res, err = tx.Exec(fmt.Sprintf(_delTagUserSQL, tagUserHit(mid)), mid, fid); err != nil {
log.Error("tx.Exec(%d, %d) error(%v)", mid, fid, err)
return
}
return res.RowsAffected()
}
// Tags get tags list.
func (d *Dao) Tags(c context.Context, mid int64) (res map[int64]*model.Tag, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_getTagsSQL, tagHit(mid)), mid); err != nil {
log.Error("d.getTagsStmt[%d].Query(%d) error(%sv)", tagHit(mid), mid, err)
return
}
defer rows.Close()
res = make(map[int64]*model.Tag)
for rows.Next() {
r := new(model.Tag)
if err = rows.Scan(&r.Id, &r.Name, &r.Status, &r.MTime); err != nil {
log.Error("d.getTagsStmt[%d].Query(%d) row.Scan() error(%v)", tagHit(mid), mid, err)
res = nil
return
}
res[r.Id] = r
}
res[0] = &model.Tag{Id: 0, Name: _defaultTag}
res[-10] = &model.Tag{Id: -10, Name: _specialTag}
err = rows.Err()
return
}
// AddTag add tag.
func (d *Dao) AddTag(c context.Context, mid, fid int64, tag string, now time.Time) (lastID int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_addTagSQL, tagHit(mid)), mid, tag, now, now); err != nil {
log.Error("d.db.Exec(%s, %d, %d, %s) error(%v)", _addTagSQL, mid, fid, tag, err)
return
}
return res.LastInsertId()
}
// DelTag del tag.
func (d *Dao) DelTag(c context.Context, mid, id int64) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_delTagSQL, tagHit(mid)), id, mid); err != nil {
log.Error("d.db.Exec(%s, %d, %d) error(%v)", _delTagSQL, mid, id, err)
return
}
return res.RowsAffected()
}
// SetTagName update tag name info.
func (d *Dao) SetTagName(c context.Context, id, mid int64, name string, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_setTagNameSQL, tagHit(mid)), name, now, id); err != nil {
log.Error("d.db.Exec(%s, %s, %d, %d)", _setTagNameSQL, name, mid, id)
return
}
return res.RowsAffected()
}
// TagUserByMidFid get tagIds by mid and fid.
func (d *Dao) TagUserByMidFid(c context.Context, mid, fid int64) (tag *model.TagUser, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_getTagsByMidFidSQL, tagUserHit(mid)), mid, fid)
var tids i64b.Int64Bytes
tag = new(model.TagUser)
if err = row.Scan(&tag.Fid, &tids, &tag.MTime); err != nil {
if err == sql.ErrNoRows {
tag = nil
err = nil
} else {
log.Error("d.getTagStmt[%d].Query(%d) row.Scan() error(%v)", tagUserHit(mid), mid, err)
}
return
}
tag.Tag = tids
return
}
// UsersTags users tag by fids.
func (d *Dao) UsersTags(c context.Context, mid int64, fid []int64) (tags map[int64]*model.TagUser, err error) {
row, err := d.db.Query(c, fmt.Sprintf(_getUsersTagSQL, tagUserHit(mid), xstr.JoinInts(fid)), mid)
if err != nil {
return
}
defer row.Close()
tags = make(map[int64]*model.TagUser)
for row.Next() {
tag := new(model.TagUser)
var tids i64b.Int64Bytes
if err = row.Scan(&tag.Fid, &tids); err != nil {
return
}
tag.Tag = tids
tags[tag.Fid] = tag
}
return
}
// UserTag user tag
func (d *Dao) UserTag(c context.Context, mid int64) (tags map[int64][]int64, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_getTagUserSQL, tagUserHit(mid)), mid); err != nil {
log.Error("d.Query[%d].Query(%d) error(%sv)", tagUserHit(mid), mid, err)
return
}
defer rows.Close()
tags = make(map[int64][]int64)
for rows.Next() {
var (
tids i64b.Int64Bytes
fid int64
)
if err = rows.Scan(&fid, &tids); err != nil {
log.Error("d.Scan[%d].Query(%d) row.Scan() error(%v)", tagUserHit(mid), mid, err)
tags = nil
return
}
tags[fid] = tids
}
err = rows.Err()
return
}
// SetTagUser setTagUser info.
func (d *Dao) SetTagUser(c context.Context, mid, fid int64, tag i64b.Int64Bytes, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_setTagUserSQL, tagUserHit(mid)), tag.Bytes(), now, mid, fid); err != nil {
log.Error("d.db.Exec(%s, %d, %d, %v) error (%v)", _setTagUserSQL, mid, fid, tag.Bytes(), err)
return
}
return res.RowsAffected()
}
// AddTagUser update tag name info.
func (d *Dao) AddTagUser(c context.Context, mid, fid int64, tag []int64, now time.Time) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_addTagUserSQL, tagUserHit(mid)), mid, fid, i64b.Int64Bytes(tag), now, now, i64b.Int64Bytes(tag)); err != nil {
log.Error("d.db.Exec(%s, %d, %d, %v) error (%v)", _addTagUserSQL, mid, fid, tag, err)
return
}
return res.RowsAffected()
}
// HasReachAchieve is
func (d *Dao) HasReachAchieve(c context.Context, mid int64, achieve model.AchieveFlag) bool {
row := d.db.QueryRow(c, _hasReachAchieve, mid, uint64(achieve))
count := 0
if err := row.Scan(&count); err != nil {
if err == xsql.ErrNoRows {
return false
}
log.Warn("Failed to check has reach achieve: mid: %d, achieve: %d, error: %+v", mid, achieve, err)
return false
}
if count > 0 {
return true
}
return false
}
// FollowerNotifySetting get follower-notify setting
// 这里返回用户通知开关的状态(和数据库存储的状态值相反)
func (d *Dao) FollowerNotifySetting(c context.Context, mid int64) (bool, error) {
row := d.db.QueryRow(c, _getFollowerNotifySettingSQL, mid)
var disableFollowerNotify bool
if err := row.Scan(&disableFollowerNotify); err != nil {
if err != sql.ErrNoRows {
log.Error("row.Scan() error(%v)", err)
}
return true, nil
}
if disableFollowerNotify {
return false, nil
}
return true, nil
}
// EnableFollowerNotify enable follower-notify setting
func (d *Dao) EnableFollowerNotify(c context.Context, mid int64) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _enableFollowerNotifySQL, mid); err != nil {
log.Error("enable follower-notify: tx.Exec(%d) error(%v)", mid, err)
return
}
return res.RowsAffected()
}
// DisableFollowerNotify disable follower-notify setting
func (d *Dao) DisableFollowerNotify(c context.Context, mid int64) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _disableFollowerNotifySQL, mid); err != nil {
log.Error("diabel follower-notify: tx.Exec(%d) error(%v)", mid, err)
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,506 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
"go-common/app/service/main/relation/model/i64b"
"math/rand"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func TestDaohit(t *testing.T) {
var (
id = int64(1)
)
convey.Convey("hit", t, func(cv convey.C) {
p1 := hit(id)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaostatHit(t *testing.T) {
var (
id = int64(1)
)
convey.Convey("statHit", t, func(cv convey.C) {
p1 := statHit(id)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaotagHit(t *testing.T) {
var (
id = int64(1)
)
convey.Convey("tagHit", t, func(cv convey.C) {
p1 := tagHit(id)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaotagUserHit(t *testing.T) {
var (
id = int64(1)
)
convey.Convey("tagUserHit", t, func(cv convey.C) {
p1 := tagUserHit(id)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoBeginTran(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("BeginTran", t, func(cv convey.C) {
p1, err := d.BeginTran(c)
cv.Convey("Then err should be nil.p1 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(p1, convey.ShouldNotBeNil)
})
p1.Commit()
})
}
func TestDaoFollowings(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
fid = int64(2)
mask = uint32(2)
source = uint8(1)
now = time.Now()
)
convey.Convey("Followings", t, func(cv convey.C) {
affected, err := d.TxAddFollowing(c, tx, mid, fid, mask, source, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
res, err := d.Followings(c, mid)
cv.Convey("Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoFollowingsIn(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fids = []int64{1, 2}
)
convey.Convey("FollowingsIn", t, func(cv convey.C) {
res, err := d.FollowingsIn(c, mid, fids)
cv.Convey("Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoTxSetFollowing(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
fid = int64(2)
attribute = uint32(2)
source = uint8(1)
status = int(0)
now = time.Now()
)
convey.Convey("TxSetFollowing", t, func(cv convey.C) {
affected, err := d.TxSetFollowing(c, tx, mid, fid, attribute, source, status, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoFollowers(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("Followers", t, func(cv convey.C) {
res, err := d.Followers(c, mid)
cv.Convey("Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoTxAddFollower(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
fid = int64(2)
mask = uint32(2)
source = uint8(1)
now = time.Now()
)
convey.Convey("TxAddFollower", t, func(cv convey.C) {
affected, err := d.TxAddFollower(c, tx, mid, fid, mask, source, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoTxSetFollower(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
fid = int64(2)
attribute = uint32(2)
source = uint8(1)
status = int(0)
now = time.Now()
)
convey.Convey("TxSetFollower", t, func(cv convey.C) {
affected, err := d.TxSetFollower(c, tx, mid, fid, attribute, source, status, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoStat(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("Stat", t, func(cv convey.C) {
stat, err := d.Stat(c, mid)
cv.Convey("Then err should be nil.stat should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(stat, convey.ShouldNotBeNil)
})
})
}
func TestDaoTxStat(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
)
convey.Convey("TxStat", t, func(cv convey.C) {
stat, err := d.TxStat(c, tx, mid)
cv.Convey("Then err should be nil.stat should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(stat, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoAddStat(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
stat = &model.Stat{
Mid: 1,
}
now = time.Now()
)
convey.Convey("AddStat", t, func(cv convey.C) {
affected, err := d.AddStat(c, mid, stat, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoTxAddStat(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
stat = &model.Stat{
Mid: 1,
}
now = time.Now()
)
convey.Convey("TxAddStat", t, func(cv convey.C) {
affected, err := d.TxAddStat(c, tx, mid, stat, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoTxSetStat(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
stat = &model.Stat{
Mid: 1,
}
now = time.Now()
)
convey.Convey("TxSetStat", t, func(cv convey.C) {
affected, err := d.TxSetStat(c, tx, mid, stat, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoRelation(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fid = int64(2)
)
convey.Convey("Relation", t, func(cv convey.C) {
attr, err := d.Relation(c, mid, fid)
cv.Convey("Then err should be nil.attr should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(attr, convey.ShouldNotBeNil)
})
})
}
func TestDaoLoadMonitor(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("LoadMonitor", t, func(cv convey.C) {
affected, err := d.AddMonitor(c, 1, time.Now())
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
mids, err := d.LoadMonitor(c)
cv.Convey("Then err should be nil.mids should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(mids, convey.ShouldNotBeNil)
})
})
}
func TestDaoDelMonitor(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("DelMonitor", t, func(cv convey.C) {
affected, err := d.DelMonitor(c, mid)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoTxDelTagUser(t *testing.T) {
var (
c = context.Background()
tx, _ = d.BeginTran(c)
mid = int64(1)
fid = int64(2)
)
convey.Convey("TxDelTagUser", t, func(cv convey.C) {
affected, err := d.TxDelTagUser(c, tx, mid, fid)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
tx.Commit()
})
}
func TestDaoTags(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("Tags", t, func(cv convey.C) {
res, err := d.Tags(c, mid)
cv.Convey("Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoDelTag(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
id = int64(2)
)
convey.Convey("DelTag", t, func(cv convey.C) {
affected, err := d.DelTag(c, mid, id)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoSetTagName(t *testing.T) {
var (
c = context.Background()
id = int64(1)
mid = int64(2)
name = "test"
now = time.Now()
)
convey.Convey("SetTagName", t, func(cv convey.C) {
affected, err := d.SetTagName(c, id, mid, name, now)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoTagUserByMidFid(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fid = int64(2)
)
convey.Convey("TagUserByMidFid", t, func(cv convey.C) {
lastID, err := d.AddTag(c, mid, fid, "test"+RandStringRunes(5), time.Now())
cv.Convey("AddTag; Then err should be nil.lastID should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(lastID, convey.ShouldNotBeNil)
})
tids := i64b.Int64Bytes([]int64{lastID})
affected, err := d.SetTagUser(c, mid, fid, tids, time.Now())
cv.Convey("SetTagUser; Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
affected2, err := d.AddTagUser(c, mid, fid, tids, time.Now())
cv.Convey("AddTagUser; Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected2, convey.ShouldNotBeNil)
})
tag1, err := d.TagUserByMidFid(c, mid, fid)
cv.Convey("TagUserByMidFid; Then err should be nil.tag1 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(tag1, convey.ShouldNotBeNil)
})
tags2, err := d.UsersTags(c, mid, []int64{fid})
cv.Convey("UsersTags; Then err should be nil.tags2 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(tags2, convey.ShouldNotBeNil)
})
tags3, err := d.UserTag(c, mid)
cv.Convey("UserTag; Then err should be nil.tags3 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(tags3, convey.ShouldNotBeNil)
})
})
}
func TestDaoHasReachAchieve(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
achieve model.AchieveFlag
)
convey.Convey("HasReachAchieve", t, func(cv convey.C) {
p1 := d.HasReachAchieve(c, mid, achieve)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoFollowerNotifySetting(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("FollowerNotifySetting", t, func(cv convey.C) {
p1, err := d.FollowerNotifySetting(c, mid)
cv.Convey("Then err should be nil.p1 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoEnableFollowerNotify(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("EnableFollowerNotify", t, func(cv convey.C) {
affected, err := d.EnableFollowerNotify(c, mid)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoDisableFollowerNotify(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("DisableFollowerNotify", t, func(cv convey.C) {
affected, err := d.DisableFollowerNotify(c, mid)
cv.Convey("Then err should be nil.affected should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(affected, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,78 @@
package dao
import (
"context"
"fmt"
"go-common/library/cache/redis"
)
const (
_cacheShard = 10000
_upPrompt = "rl_up_%d_%d" // key of upper prompt; hashes(fid-count)
_buPrompt = "rl_bu_%d_%d_%d" // key of business type prompt;hashes(mid-count)
)
// key upPrompt : rl_up_mid_ts/period
func (d *Dao) upPrompt(mid, ts int64) string {
return fmt.Sprintf(_upPrompt, mid, ts/d.period)
}
// key _buPrompt : rl_bu_businesstype_mid/10000_ts
func (d *Dao) buPrompt(btype int8, mid, ts int64) string {
return fmt.Sprintf(_buPrompt, btype, mid/_cacheShard, ts/d.period)
}
// IncrPromptCount incr up prompt count and business type prompt count.
func (d *Dao) IncrPromptCount(c context.Context, mid, fid, ts int64, btype int8) (ucount, bcount int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
keyUp := d.upPrompt(mid, ts)
keyBs := d.buPrompt(btype, mid, ts)
conn.Send("HINCRBY", keyUp, fid, 1)
conn.Send("EXPIRE", keyUp, d.period)
conn.Send("HINCRBY", keyBs, mid, 1)
conn.Send("EXPIRE", keyBs, d.period)
err = conn.Flush()
if err != nil {
return
}
ucount, err = redis.Int64(conn.Receive())
if err != nil {
return
}
conn.Receive()
bcount, err = redis.Int64(conn.Receive())
if err != nil {
return
}
conn.Receive()
return
}
// ClosePrompt set prompt count to max config value.
func (d *Dao) ClosePrompt(c context.Context, mid, fid, ts int64, btype int8) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
keyUp := d.upPrompt(mid, ts)
keyBs := d.buPrompt(btype, mid, ts)
conn.Send("HSET", keyUp, fid, d.ucount)
conn.Send("HSET", keyBs, mid, d.bcount)
return conn.Flush()
}
// UpCount get upper prompt count.
func (d *Dao) UpCount(c context.Context, mid, fid, ts int64) (count int64, err error) {
conn := d.redis.Get(c)
count, err = redis.Int64(conn.Do("HGET", d.upPrompt(mid, ts), fid))
conn.Close()
return
}
// BCount get business type prompt count.
func (d *Dao) BCount(c context.Context, mid, ts int64, btype int8) (count int64, err error) {
conn := d.redis.Get(c)
count, err = redis.Int64(conn.Do("HGET", d.buPrompt(btype, mid, ts), mid))
conn.Close()
return
}

View File

@@ -0,0 +1,102 @@
package dao
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoupPrompt(t *testing.T) {
var (
mid = int64(1)
ts = time.Now().Unix()
)
convey.Convey("upPrompt", t, func(cv convey.C) {
p1 := d.upPrompt(mid, ts)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaobuPrompt(t *testing.T) {
var (
btype = int8(1)
mid = int64(1)
ts = time.Now().Unix()
)
convey.Convey("buPrompt", t, func(cv convey.C) {
p1 := d.buPrompt(btype, mid, ts)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoIncrPromptCount(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fid = int64(2)
ts = time.Now().Unix()
btype = int8(1)
)
convey.Convey("IncrPromptCount", t, func(cv convey.C) {
ucount, bcount, err := d.IncrPromptCount(c, mid, fid, ts, btype)
cv.Convey("Then err should be nil.ucount,bcount should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(bcount, convey.ShouldNotBeNil)
cv.So(ucount, convey.ShouldNotBeNil)
})
})
}
func TestDaoClosePrompt(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fid = int64(2)
ts = time.Now().Unix()
btype = int8(1)
)
convey.Convey("ClosePrompt", t, func(cv convey.C) {
err := d.ClosePrompt(c, mid, fid, ts, btype)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoUpCount(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fid = int64(2)
ts = time.Now().Unix()
)
convey.Convey("UpCount", t, func(cv convey.C) {
count, err := d.UpCount(c, mid, fid, ts)
cv.Convey("Then err should be nil.count should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(count, convey.ShouldNotBeNil)
})
})
}
func TestDaoBCount(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
btype = int8(1)
ts = time.Now().Unix()
)
convey.Convey("BCount", t, func(cv convey.C) {
count, err := d.BCount(c, mid, ts, btype)
cv.Convey("Then err should be nil.count should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(count, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,95 @@
package dao
import (
"context"
"time"
"go-common/library/cache/redis"
)
// AddRctFollower is
func (d *Dao) AddRctFollower(c context.Context, mid, fid int64) error {
key := recentFollower(fid)
conn := d.redis.Get(c)
defer conn.Close()
if err := conn.Send("ZADD", key, time.Now().Unix(), mid); err != nil {
return err
}
if err := conn.Send("EXPIRE", key, d.UnreadDuration); err != nil {
return err
}
if err := conn.Flush(); err != nil {
return err
}
return nil
}
// DelRctFollower is
func (d *Dao) DelRctFollower(c context.Context, mid, fid int64) error {
key := recentFollower(fid)
conn := d.redis.Get(c)
defer conn.Close()
_, err := conn.Do("ZREM", key, mid)
return err
}
// RctFollowerCount is
func (d *Dao) RctFollowerCount(ctx context.Context, fid int64) (int64, error) {
key := recentFollower(fid)
conn := d.redis.Get(ctx)
defer conn.Close()
count, err := redis.Int64(conn.Do("ZCARD", key))
if err != nil {
return 0, err
}
return count, nil
}
// EmptyRctFollower is
func (d *Dao) EmptyRctFollower(ctx context.Context, fid int64) error {
key := recentFollower(fid)
conn := d.redis.Get(ctx)
defer conn.Close()
_, err := conn.Do("DEL", key)
return err
}
// RctFollowerNotify is
func (d *Dao) RctFollowerNotify(c context.Context, fid int64) (bool, error) {
key := recentFollowerNotify(fid)
conn := d.redis.Get(c)
defer conn.Close()
flagi, err := redis.Int64(conn.Do("HGET", key, fid))
if err != nil {
if err == redis.ErrNil {
return false, nil
}
return false, err
}
flag := false
if flagi > 0 {
flag = true
}
return flag, err
}
// SetRctFollowerNotify is
func (d *Dao) SetRctFollowerNotify(c context.Context, fid int64, flag bool) error {
key := recentFollowerNotify(fid)
flagi := 0
if flag {
flagi = 1
}
conn := d.redis.Get(c)
defer conn.Close()
if err := conn.Send("HSET", key, fid, flagi); err != nil {
return err
}
if err := conn.Send("EXPIRE", key, d.UnreadDuration); err != nil {
return err
}
if err := conn.Flush(); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,91 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddRctFollower(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
convey.Convey("AddRctFollower", t, func(cv convey.C) {
err := d.AddRctFollower(c, mid, fid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelRctFollower(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
convey.Convey("DelRctFollower", t, func(cv convey.C) {
err := d.DelRctFollower(c, mid, fid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoRctFollowerCount(t *testing.T) {
var (
ctx = context.Background()
fid = int64(0)
)
convey.Convey("RctFollowerCount", t, func(cv convey.C) {
p1, err := d.RctFollowerCount(ctx, fid)
cv.Convey("Then err should be nil.p1 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoEmptyRctFollower(t *testing.T) {
var (
ctx = context.Background()
fid = int64(0)
)
convey.Convey("EmptyRctFollower", t, func(cv convey.C) {
err := d.EmptyRctFollower(ctx, fid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoRctFollowerNotify(t *testing.T) {
var (
c = context.Background()
fid = int64(0)
)
convey.Convey("RctFollowerNotify", t, func(cv convey.C) {
p1, err := d.RctFollowerNotify(c, fid)
cv.Convey("Then err should be nil.p1 should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoSetRctFollowerNotify(t *testing.T) {
var (
c = context.Background()
fid = int64(0)
flag bool
)
convey.Convey("SetRctFollowerNotify", t, func(cv convey.C) {
err := d.SetRctFollowerNotify(c, fid, flag)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,330 @@
package dao
import (
"context"
"fmt"
"strconv"
gtime "time"
"go-common/app/service/main/relation/model"
"go-common/library/cache/redis"
"go-common/library/log"
"go-common/library/time"
)
const (
_prefixFollowings = "at_" // key of public following with tags datas.
_prefixMonitor = "rs_mo_list" // key of monitor
_prefixRecentFollower = "rf_" // recent follower sorted set
_prefixRecentFollowerTime = "rft_" // recent follower sorted set
_prefixDailyNotifyCount = "dnc_%d_%s" // daily new-follower notificaiton count
_notifyCountExpire = 24 * 3600 // notify count scope is daily
)
func followingsKey(mid int64) string {
return _prefixFollowings + strconv.FormatInt(mid, 10)
}
func monitorKey() string {
return _prefixMonitor
}
func recentFollower(mid int64) string {
return _prefixRecentFollower + strconv.FormatInt(mid, 10)
}
func recentFollowerNotify(mid int64) string {
return _prefixRecentFollowerTime + strconv.FormatInt(mid%10000, 10)
}
func dailyNotifyCount(mid int64, date gtime.Time) string {
// _cacheShard 作为sharding
return fmt.Sprintf(_prefixDailyNotifyCount, mid%_cacheShard, date.Format("2006-01-02"))
}
// pingRedis ping redis.
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
if _, err = conn.Do("SET", "PING", "PONG"); err != nil {
log.Error("conn.Do(SET,PING,PONG) error(%v)", err)
}
conn.Close()
return
}
// SetFollowingsCache set followings cache.
func (d *Dao) SetFollowingsCache(c context.Context, mid int64, followings []*model.Following) (err error) {
key := followingsKey(mid)
args := redis.Args{}.Add(key)
expire := d.redisExpire
if len(followings) == 0 {
expire = 7200
}
ef, _ := d.encode(0, 0, nil, 0)
args = args.Add(0, ef)
for i := 0; i < len(followings); i++ {
var ef []byte
if ef, err = d.encode(followings[i].Attribute, followings[i].MTime, followings[i].Tag, followings[i].Special); err != nil {
return
}
args = args.Add(followings[i].Mid, ef)
}
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Send(DEL, %s) error(%v)", key, err)
return
}
if err = conn.Send("HMSET", args...); err != nil {
log.Error("conn.Send(HMSET, %s) error(%v)", key, err)
return
}
if err = conn.Send("EXPIRE", key, expire); err != nil {
log.Error("conn.Send(EXPIRE, %s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 3; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() %d error(%v)", i+1, err)
break
}
}
return
}
// AddFollowingCache add following cache.
func (d *Dao) AddFollowingCache(c context.Context, mid int64, following *model.Following) (err error) {
var (
ok bool
key = followingsKey(mid)
)
conn := d.redis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisExpire)); err != nil {
log.Error("redis.Bool(conn.Do(EXPIRE, %s)) error(%v)", key, err)
} else if ok {
var ef []byte
if ef, err = d.encode(following.Attribute, following.MTime, following.Tag, following.Special); err != nil {
return
}
if _, err = conn.Do("HSET", key, following.Mid, ef); err != nil {
log.Error("conn.Do(HSET, %s, %d) error(%v)", key, following.Mid, err)
}
}
conn.Close()
return
}
// DelFollowing del following cache.
func (d *Dao) DelFollowing(c context.Context, mid int64, following *model.Following) (err error) {
var (
ok bool
key = followingsKey(mid)
)
conn := d.redis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisExpire)); err != nil {
log.Error("redis.Bool(conn.Do(EXPIRE, %s)) error(%v)", key, err)
} else if ok {
if _, err = conn.Do("HDEL", key, following.Mid); err != nil {
log.Error("conn.Do(HDEL, %s, %d) error(%v)", key, following.Mid, err)
}
}
conn.Close()
return
}
// FollowingsCache get followings cache.
func (d *Dao) FollowingsCache(c context.Context, mid int64) (followings []*model.Following, err error) {
key := followingsKey(mid)
conn := d.redis.Get(c)
defer conn.Close()
tmp, err := redis.StringMap(conn.Do("HGETALL", key))
if err != nil {
return
}
if err == nil && len(tmp) > 0 {
for k, v := range tmp {
if mid, err = strconv.ParseInt(k, 10, 64); err != nil {
return
}
if mid <= 0 {
continue
}
vf := &model.FollowingTags{}
if err = d.decode([]byte(v), vf); err != nil {
//todo
return
}
followings = append(followings, &model.Following{
Mid: mid,
Attribute: vf.Attr,
Tag: vf.TagIds,
MTime: vf.Ts,
Special: vf.Special,
})
}
}
return
}
// DelFollowingsCache delete followings cache.
func (d *Dao) DelFollowingsCache(c context.Context, mid int64) (err error) {
key := followingsKey(mid)
conn := d.redis.Get(c)
if _, err = conn.Do("DEL", key); err != nil {
log.Error("conn.Do(DEL, %s) error(%v)", key, err)
}
conn.Close()
return
}
// RelationsCache relations cache.
func (d *Dao) RelationsCache(c context.Context, mid int64, fids []int64) (resMap map[int64]*model.Following, err error) {
var retRedis [][]byte
key := followingsKey(mid)
args := redis.Args{}.Add(key)
for _, fid := range fids {
args = args.Add(fid)
}
args.Add(0)
conn := d.redis.Get(c)
defer conn.Close()
if retRedis, err = redis.ByteSlices(conn.Do("HMGET", args...)); err != nil {
log.Error("redis.Int64s(conn.DO(HMGET, %v)) error(%v)", args, err)
return
}
resMap = make(map[int64]*model.Following)
for index, fid := range fids {
if retRedis[index] == nil {
continue
}
v := &model.FollowingTags{}
if err = d.decode(retRedis[index], v); err != nil {
return
}
resMap[fid] = &model.Following{
Mid: fid,
Attribute: v.Attr,
Tag: v.TagIds,
MTime: v.Ts,
Special: v.Special,
}
}
return
}
// encode
func (d *Dao) encode(attribute uint32, mtime time.Time, tagids []int64, special int32) (res []byte, err error) {
ft := &model.FollowingTags{Attr: attribute, Ts: mtime, TagIds: tagids, Special: special}
return ft.Marshal()
}
// decode
func (d *Dao) decode(src []byte, v *model.FollowingTags) (err error) {
return v.Unmarshal(src)
}
// MonitorCache monitor cache
func (d *Dao) MonitorCache(c context.Context, mid int64) (exist bool, err error) {
key := monitorKey()
conn := d.redis.Get(c)
if exist, err = redis.Bool(conn.Do("SISMEMBER", key, mid)); err != nil {
log.Error("redis.Bool(conn.Do(SISMEMBER, %s, %d)) error(%v)", key, mid, err)
}
conn.Close()
return
}
// SetMonitorCache set monitor cache
func (d *Dao) SetMonitorCache(c context.Context, mid int64) (err error) {
var (
key = monitorKey()
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("SADD", key, mid); err != nil {
log.Error("SADD conn.Do error(%v)", err)
return
}
return
}
// DelMonitorCache del monitor cache
func (d *Dao) DelMonitorCache(c context.Context, mid int64) (err error) {
var (
key = monitorKey()
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = redis.Int64(conn.Do("SREM", key, mid)); err != nil {
log.Error("SREM conn.Do(%s,%d) err(%v)", key, mid, err)
}
return
}
// LoadMonitorCache load monitor cache
func (d *Dao) LoadMonitorCache(c context.Context, mids []int64) (err error) {
var (
key = monitorKey()
conn = d.redis.Get(c)
)
defer conn.Close()
for _, v := range mids {
if err = conn.Send("SADD", key, v); err != nil {
log.Error("SADD conn.Do error(%v)", err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
return
}
// TodayNotifyCountCache get notify count in the current day
func (d *Dao) TodayNotifyCountCache(c context.Context, mid int64) (notifyCount int64, err error) {
var (
key = dailyNotifyCount(mid, gtime.Now())
conn = d.redis.Get(c)
)
defer conn.Close()
if notifyCount, err = redis.Int64(conn.Do("HGET", key, mid)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("HGET conn.Do error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
return
}
// IncrTodayNotifyCount increment the today notify count in the current day
func (d *Dao) IncrTodayNotifyCount(c context.Context, mid int64) (err error) {
var (
key = dailyNotifyCount(mid, gtime.Now())
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("HINCRBY", key, mid, 1); err != nil {
log.Error("HINCRBY conn.Do error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, _notifyCountExpire); err != nil {
log.Error("EXPIRE conn.Do error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
return
}

View File

@@ -0,0 +1,249 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
xtime "go-common/library/time"
"testing"
gtime "time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaofollowingsKey(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("followingsKey", t, func(cv convey.C) {
p1 := followingsKey(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaomonitorKey(t *testing.T) {
convey.Convey("monitorKey", t, func(cv convey.C) {
p1 := monitorKey()
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaorecentFollower(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("recentFollower", t, func(cv convey.C) {
p1 := recentFollower(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaorecentFollowerNotify(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("recentFollowerNotify", t, func(cv convey.C) {
p1 := recentFollowerNotify(mid)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaodailyNotifyCount(t *testing.T) {
var (
mid = int64(1)
date gtime.Time
)
convey.Convey("dailyNotifyCount", t, func(cv convey.C) {
p1 := dailyNotifyCount(mid, date)
cv.Convey("Then p1 should not be nil.", func(cv convey.C) {
cv.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaopingRedis(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("pingRedis", t, func(cv convey.C) {
err := d.pingRedis(c)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoFollowingsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
)
convey.Convey("FollowingsCache", t, func(cv convey.C) {
err := d.SetFollowingsCache(c, mid, []*model.Following{
{Mid: 2},
})
cv.Convey("SetFollowingsCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
err = d.AddFollowingCache(c, mid, &model.Following{Mid: 2})
cv.Convey("AddFollowingCache; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
followings, err := d.FollowingsCache(c, mid)
cv.Convey("FollowingsCache; Then err should be nil.followings should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(followings, convey.ShouldNotBeNil)
})
err = d.DelFollowing(c, mid, &model.Following{Mid: 2})
cv.Convey("DelFollowing; Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelFollowingsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("DelFollowingsCache", t, func(cv convey.C) {
err := d.DelFollowingsCache(c, mid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoRelationsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
fids = []int64{2}
)
convey.Convey("RelationsCache", t, func(cv convey.C) {
resMap, err := d.RelationsCache(c, mid, fids)
cv.Convey("Then err should be nil.resMap should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(resMap, convey.ShouldNotBeNil)
})
})
}
func TestDaoencode(t *testing.T) {
var (
attribute = uint32(0)
mtime = xtime.Time(int64(0))
tagids = []int64{}
special = int32(0)
)
convey.Convey("encode", t, func(cv convey.C) {
res, err := d.encode(attribute, mtime, tagids, special)
cv.Convey("Then err should be nil.res should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaodecode(t *testing.T) {
var (
src = []byte("")
v = &model.FollowingTags{}
)
convey.Convey("decode", t, func(cv convey.C) {
err := d.decode(src, v)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoMonitorCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("MonitorCache", t, func(cv convey.C) {
exist, err := d.MonitorCache(c, mid)
cv.Convey("Then err should be nil.exist should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(exist, convey.ShouldNotBeNil)
})
})
}
func TestDaoSetMonitorCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("SetMonitorCache", t, func(cv convey.C) {
err := d.SetMonitorCache(c, mid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelMonitorCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("DelMonitorCache", t, func(cv convey.C) {
err := d.DelMonitorCache(c, mid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoLoadMonitorCache(t *testing.T) {
var (
c = context.Background()
mids = []int64{}
)
convey.Convey("LoadMonitorCache", t, func(cv convey.C) {
err := d.LoadMonitorCache(c, mids)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoTodayNotifyCountCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("TodayNotifyCountCache", t, func(cv convey.C) {
notifyCount, err := d.TodayNotifyCountCache(c, mid)
cv.Convey("Then err should be nil.notifyCount should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(notifyCount, convey.ShouldNotBeNil)
})
})
}
func TestDaoIncrTodayNotifyCountCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("IncrTodayNotifyCount", t, func(cv convey.C) {
err := d.IncrTodayNotifyCount(c, mid)
cv.Convey("Then err should be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,87 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
"go-common/library/log"
"go-common/library/queue/databus/report"
"go-common/library/time"
)
// consts
const (
RelationLogID = 13
)
// AddFollowingLog is
func (d *Dao) AddFollowingLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_add_following", rl)
d.addLog(ctx, RelationLogID, "log_follower_incr", rl.Reverse())
}
// DelFollowingLog is
func (d *Dao) DelFollowingLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_del_following", rl)
d.addLog(ctx, RelationLogID, "log_follower_decr", rl.Reverse())
}
// DelFollowerLog is
func (d *Dao) DelFollowerLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_del_follower", rl)
d.addLog(ctx, RelationLogID, "log_following_decr", rl.Reverse())
}
// AddWhisperLog is
func (d *Dao) AddWhisperLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_add_whisper", rl)
d.addLog(ctx, RelationLogID, "log_whisper_follower_incr", rl.Reverse())
}
// DelWhisperLog is
func (d *Dao) DelWhisperLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_del_whisper", rl)
d.addLog(ctx, RelationLogID, "log_whisper_follower_decr", rl.Reverse())
}
// AddBlackLog is
func (d *Dao) AddBlackLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_add_black", rl)
d.addLog(ctx, RelationLogID, "log_black_incr", rl.Reverse())
}
// DelBlackLog is
func (d *Dao) DelBlackLog(ctx context.Context, rl *model.RelationLog) {
d.addLog(ctx, RelationLogID, "log_del_black", rl)
d.addLog(ctx, RelationLogID, "log_black_decr", rl.Reverse())
}
func (d *Dao) addLog(ctx context.Context, business int, action string, rl *model.RelationLog) {
t := time.Time(rl.Ts)
content := make(map[string]interface{}, len(rl.Content))
for k, v := range rl.Content {
content[k] = v
}
content["from_attr"] = rl.FromAttr
content["to_attr"] = rl.ToAttr
content["from_rev_attr"] = rl.FromRevAttr
content["to_rev_attr"] = rl.ToRevAttr
content["source"] = rl.Source
ui := &report.UserInfo{
Mid: rl.Mid,
Platform: "",
Build: 0,
Buvid: rl.Buvid,
Business: business,
Type: 0,
Oid: rl.Fid,
Action: action,
Ctime: t.Time(),
IP: rl.Ip,
// extra
Index: []interface{}{int64(rl.Source), 0, "", "", ""},
Content: content,
}
report.User(ui)
log.Info("add log to report: relationlog: %+v userinfo: %+v", rl, ui)
}

View File

@@ -0,0 +1,107 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddFollowingLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("AddFollowingLog", t, func(cv convey.C) {
d.AddFollowingLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoDelFollowingLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("DelFollowingLog", t, func(cv convey.C) {
d.DelFollowingLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoDelFollowerLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("DelFollowerLog", t, func(cv convey.C) {
d.DelFollowerLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoAddWhisperLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("AddWhisperLog", t, func(cv convey.C) {
d.AddWhisperLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoDelWhisperLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("DelWhisperLog", t, func(cv convey.C) {
d.DelWhisperLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoAddBlackLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("AddBlackLog", t, func(cv convey.C) {
d.AddBlackLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoDelBlackLog(t *testing.T) {
var (
ctx = context.Background()
rl = &model.RelationLog{}
)
convey.Convey("DelBlackLog", t, func(cv convey.C) {
d.DelBlackLog(ctx, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}
func TestDaoaddLog(t *testing.T) {
var (
ctx = context.Background()
business = int(0)
action = ""
rl = &model.RelationLog{}
)
convey.Convey("addLog", t, func(cv convey.C) {
d.addLog(ctx, business, action, rl)
cv.Convey("No return values", func(cv convey.C) {
})
})
}

View File

@@ -0,0 +1,52 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"black.go",
"follower.go",
"following.go",
"http.go",
"monitor.go",
"relation.go",
"stat.go",
"tag.go",
"whisper.go",
],
importpath = "go-common/app/service/main/relation/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/rate:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/metadata:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,81 @@
package http
import (
"go-common/library/ecode"
"strconv"
bm "go-common/library/net/http/blademaster"
)
// Blacks get user's black list.
func blacks(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Blacks(c, mid))
}
// addBlack add black.
func addBlack(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 || fid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
c.JSON(nil, relationSvc.AddBlack(c, mid, fid, uint8(src), ric))
}
// delBlack del black.
func delBlack(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
c.JSON(nil, relationSvc.DelBlack(c, mid, fid, uint8(src), ric))
}

View File

@@ -0,0 +1,70 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
// followers get user's follower list.
func followers(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Followers(c, mid))
}
// delFollower del follower.
func delFollower(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 || fid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
// mid移除粉丝fid对fid对mid的关注状态进行更改
c.JSON(nil, relationSvc.DelFollower(c, fid, mid, uint8(src), ric))
}
// delFollowerCache del follower cache.
func delFollowerCache(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.DelFollowerCache(c, mid))
}

View File

@@ -0,0 +1,148 @@
package http
import (
"strconv"
"go-common/app/service/main/relation/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/time"
)
// followings get user's following list.
func followings(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Followings(c, mid))
}
// addFollowing add following.
func addFollowing(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 || fid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
c.JSON(nil, relationSvc.AddFollowing(c, mid, fid, uint8(src), ric))
}
// delFollowing del following.
func delFollowing(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 || fid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
c.JSON(nil, relationSvc.DelFollowing(c, mid, fid, uint8(src), ric))
}
// delFollowingCache del following cache.
func delFollowingCache(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.DelFollowingCache(c, mid))
}
// updateFollowingCache update following cache.
func updateFollowingCache(c *bm.Context) {
var (
err error
mid, fid int64
na uint64
mts int64
following *model.Following
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
naStr = params.Get("attribute")
mtStr = params.Get("mtime")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if na, err = strconv.ParseUint(naStr, 10, 32); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mts, err = strconv.ParseInt(mtStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
following = &model.Following{
Mid: fid,
Attribute: uint32(na),
MTime: time.Time(mts),
}
c.JSON(nil, relationSvc.UpdateFollowingCache(c, mid, following))
}
func sameFollowings(c *bm.Context) {
arg := new(model.ArgSameFollowing)
if err := c.Bind(arg); err != nil {
return
}
c.JSON(relationSvc.SameFollowings(c, arg))
}

View File

@@ -0,0 +1,141 @@
package http
import (
"net/http"
"go-common/app/service/main/relation/conf"
"go-common/app/service/main/relation/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/rate"
v "go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/metadata"
)
var (
anti *antispam.Antispam
relationSvc *service.Service
verify *v.Verify
addFollowingRate *rate.Limiter
)
// Init init http sever instance.
func Init(c *conf.Config, s *service.Service) {
relationSvc = s
verify = v.New(c.Verify)
anti = antispam.New(c.Antispam)
addFollowingRate = rate.New(c.AddFollowingRate)
// init inner router
engine := bm.DefaultServer(c.BM)
setupInnerEngine(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start() error(%v)", err)
panic(err)
}
}
// innerRouter init inner router.
func setupInnerEngine(e *bm.Engine) {
// health check
e.Ping(ping)
e.Register(register)
//new defined api lists
g := e.Group("/x/internal/relation", verify.Verify)
// relation
g.GET("", relation)
g.GET("/relations", relations)
// stat
g.GET("/stat", stat)
g.GET("/stats", stats)
// private api
g.POST("/stat/set", setStat)
g.POST("/tag/cache/del", delTagCache)
g.GET("/tag/special", special)
// following group
following := g.Group("/following")
following.GET("/followings", followings)
following.GET("/same/followings", sameFollowings)
following.POST("/add", addFollowingRate.Handler(), anti.ServeHTTP, addFollowing)
following.POST("/del", anti.ServeHTTP, delFollowing)
// whisper group
whisper := g.Group("/whisper")
whisper.GET("/whispers", whispers)
whisper.POST("/add", anti.ServeHTTP, addWhisper)
whisper.POST("/del", anti.ServeHTTP, delWhisper)
// black group
black := g.Group("/black")
black.GET("/blacks", blacks)
black.POST("/add", anti.ServeHTTP, addBlack)
black.POST("/del", anti.ServeHTTP, delBlack)
// follower group
follower := g.Group("/follower")
follower.GET("/followers", followers)
follower.POST("/del", anti.ServeHTTP, delFollower)
// recommend group
// recommend := g.Group("/recommend")
// recommend.GET("/global/hot", globalHot)
// cache group
cache := g.Group("/cache")
cache.POST("/following/del", delFollowingCache)
cache.POST("/following/update", updateFollowingCache)
cache.POST("/follower/del", delFollowerCache)
cache.POST("/stat/del", delStatCache)
// cache group
admin := g.Group("/admin")
admin.POST("/monitor/add", addMonitor)
admin.POST("/monitor/del", delMonitor)
admin.GET("/monitor/load", loadMonitor)
}
// ping check server ok.
func ping(c *bm.Context) {
if err := relationSvc.Ping(c); err != nil {
log.Error("service ping error(%v)", err)
c.Writer.WriteHeader(http.StatusServiceUnavailable)
}
}
// register check server ok.
func register(c *bm.Context) {
c.JSON(map[string]struct{}{}, nil)
}
func infocArg(c *bm.Context) (arg map[string]string) {
var (
ua string
referer string
sid string
req = c.Request
)
ua = req.Header.Get(service.RelInfocUA)
referer = req.Header.Get(service.RelInfocReferer)
sidCookie, err := req.Cookie(service.RelInfocSid)
if err != nil {
log.Warn("relation infoc get sid failed error(%v)", err)
} else {
sid = sidCookie.Value
}
buvid := req.Header.Get(service.RelInfocHeaderBuvid)
if buvid == "" {
buvidCookie, _ := req.Cookie(service.RelInfocCookieBuvid)
if buvidCookie != nil {
buvid = buvidCookie.Value
}
}
arg = map[string]string{
service.RelInfocIP: metadata.String(c, metadata.RemoteIP),
service.RelInfocReferer: referer,
service.RelInfocSid: sid,
service.RelInfocBuvid: buvid,
service.RelInfocUA: ua,
}
return
}

View File

@@ -0,0 +1,51 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
// addMonitor
func addMonitor(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.AddMonitor(c, mid))
}
// delMonitor
func delMonitor(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.DelMonitor(c, mid))
}
// loadMonitor
func loadMonitor(c *bm.Context) {
c.JSON(nil, relationSvc.LoadMonitor(c))
}

View File

@@ -0,0 +1,54 @@
package http
import (
"strconv"
"strings"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
// relation get relation between mid and fid.
func relation(c *bm.Context) {
var (
err error
mid, fid int64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Relation(c, mid, fid))
}
// relations get relations between mid and fids.
func relations(c *bm.Context) {
var (
err error
mid, fid int64
fids []int64
params = c.Request.Form
midStr = params.Get("mid")
fidsStr = params.Get("fids")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
fidsStrArr := strings.Split(fidsStr, ",")
for _, v := range fidsStrArr {
if fid, err = strconv.ParseInt(v, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
fids = append(fids, fid)
}
c.JSON(relationSvc.Relations(c, mid, fids))
}

View File

@@ -0,0 +1,113 @@
package http
import (
"strconv"
"go-common/app/service/main/relation/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/xstr"
)
// stat get stat.
func stat(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Stat(c, mid))
}
// stat get user's follower list.
func stats(c *bm.Context) {
var (
err error
params = c.Request.Form
midsStr = params.Get("mids")
)
mids, err := xstr.SplitInts(midsStr)
if err != nil || len(mids) > 20 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Stats(c, mids))
}
// setStat set stat.
func setStat(c *bm.Context) {
var (
err error
mid int64
f, w, b, fr int64
st *model.Stat
params = c.Request.Form
midStr = params.Get("mid")
fStr = params.Get("following")
wStr = params.Get("whisper")
bStr = params.Get("black")
frStr = params.Get("follower")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fStr == "" {
f = -1
} else {
if f, err = strconv.ParseInt(fStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if wStr == "" {
w = -1
} else {
if w, err = strconv.ParseInt(wStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if bStr == "" {
b = -1
} else {
if b, err = strconv.ParseInt(bStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if frStr == "" {
fr = -1
} else {
if fr, err = strconv.ParseInt(frStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
st = &model.Stat{Following: f, Whisper: w, Black: b, Follower: fr}
if st.Following == -1 && st.Whisper == -1 && st.Black == -1 && st.Follower == -1 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.SetStat(c, mid, st))
}
// delStatCache del stat cache.
func delStatCache(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.DelStatCache(c, mid))
}

View File

@@ -0,0 +1,36 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func delTagCache(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, relationSvc.DelTagCache(c, mid))
}
func special(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Special(c, mid))
}

View File

@@ -0,0 +1,85 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
// whispers get user's whisper list.
func whispers(c *bm.Context) {
var (
err error
mid int64
params = c.Request.Form
midStr = params.Get("mid")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(relationSvc.Whispers(c, mid))
}
// addWhisper add whisper.
func addWhisper(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 || fid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
c.JSON(nil, relationSvc.AddWhisper(c, mid, fid, uint8(src), ric))
}
// delWhisper del whisper.
func delWhisper(c *bm.Context) {
var (
err error
mid, fid int64
src uint64
params = c.Request.Form
midStr = params.Get("mid")
fidStr = params.Get("fid")
srcStr = params.Get("src")
)
if mid, err = strconv.ParseInt(midStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if mid <= 0 || fid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if src, err = strconv.ParseUint(srcStr, 10, 8); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
ric := infocArg(c)
c.JSON(nil, relationSvc.DelWhisper(c, mid, fid, uint8(src), ric))
}

View File

@@ -0,0 +1,81 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"achieve.go",
"addit.go",
"attr.go",
"audit.go",
"following.go",
"log.go",
"rpc.go",
"stat.go",
],
embed = [":model_go_proto"],
importpath = "go-common/app/service/main/relation/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
],
)
proto_library(
name = "model_proto",
srcs = [
"model.proto",
],
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"@gogo_special_proto//github.com/gogo/protobuf/gogoproto",
],
)
go_proto_library(
name = "model_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogo_proto"],
importpath = "go-common/app/service/main/relation/model",
proto = ":model_proto",
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_gogo_protobuf//sortkeys:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@io_bazel_rules_go//proto/wkt:any_go_proto",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/relation/model/i64b:all-srcs",
"//app/service/main/relation/model/sets:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,12 @@
package model
// Achieve is
type Achieve struct {
Award string `json:"award"`
Mid int64 `json:"mid"`
}
// AchieveGetReply is
type AchieveGetReply struct {
AwardToken string `json:"award_token"`
}

View File

@@ -0,0 +1,12 @@
package model
// AchieveFlag is
type AchieveFlag uint64
// const
var (
EmptyAchieve = AchieveFlag(0)
FollowerAchieve1k = AchieveFlag(1 << 0)
FollowerAchieve5k = AchieveFlag(1 << 1)
FollowerAchieve10k = AchieveFlag(1 << 2)
)

View File

@@ -0,0 +1,51 @@
package model
// attribute bit. priority black > following > whisper > no relation.
const (
AttrNoRelation = uint32(0)
AttrWhisper = uint32(1)
AttrFollowing = uint32(1) << 1
AttrFriend = uint32(1) << 2
AttrBlack = uint32(1) << 7
// 128129,130 变为 0 时候status = 1
StatusOK = 0
StatusDel = 1
)
// relation act type.
const (
ActAddFollowing = int8(1)
ActDelFollowing = int8(2)
ActAddWhisper = int8(3)
ActDelWhisper = int8(4)
ActAddBlack = int8(5)
ActDelBalck = int8(6)
ActDelFollower = int8(7)
)
// Attr get real attribute by the specified priority.
func Attr(attribute uint32) uint32 {
if attribute&AttrBlack > 0 {
return AttrBlack
}
if attribute&AttrFriend > 0 {
return AttrFriend
}
if attribute&AttrFollowing > 0 {
return AttrFollowing
}
if attribute&AttrWhisper > 0 {
return AttrWhisper
}
return AttrNoRelation
}
// SetAttr set attribute.
func SetAttr(attribute uint32, mask uint32) uint32 {
return attribute | mask
}
// UnsetAttr unset attribute.
func UnsetAttr(attribute uint32, mask uint32) uint32 {
return attribute & ^mask // ^ 按位取反
}

View File

@@ -0,0 +1,19 @@
package model
// Audit member audit info
type Audit struct {
Mid int64 `json:"mid"`
BindTel bool `json:"bind_tel"`
BindMail bool `json:"bind_mail"`
Rank int64 `json:"rank"`
Blocked bool `json:"blocked"`
}
// PassportDetail passportDetail
type PassportDetail struct {
Mid int64 `json:"mid"`
Email string `json:"email"`
Phone string `json:"telphone"`
Spacesta int8 `json:"spacesta"`
JoinTime int64 `json:"join_time"`
}

View File

@@ -0,0 +1,68 @@
package model
var (
_emptyFollowings = make([]*Following, 0)
)
// Black get if black.
func (f *Following) Black() bool {
return AttrBlack == Attr(f.Attribute)
}
// Friend get if both way following.
func (f *Following) Friend() bool {
return AttrFriend == Attr(f.Attribute)
}
// Following get if following.
func (f *Following) Following() bool {
return AttrFollowing == Attr(f.Attribute) || Attr(f.Attribute) == AttrFriend
}
// Whisper get if whisper.
func (f *Following) Whisper() bool {
return AttrWhisper == Attr(f.Attribute)
}
// Filter filter followings by the given attribute.
func Filter(fs []*Following, attr uint32) (res []*Following) {
for _, f := range fs {
// NOTE: if current attribute evaluated by Attr() matched, then continue,
// this includes the situation that matches black, friend, whisper, and no-relation directly.
// Now we have following to deal with, since we know that the attribute friend
// can either do not exist or exists with following at the same time,
// to deal with this situation, we need to filter for items which have 1 on the bit that attr stands for,
// and especially, the attribute it self cannot be black because the attribute black has the highest priority,
// when it exists, it shadows other bits, including friend, following, whisper, no-relation,
// there is no need to do further calculate,
// more specifically, black when black included, the value of f.Attribute&attr may greater than 0
// when f.Attribute is 128+2 or 128+1 and the corresponding attr is 2 or 1,
// which is not as we expected.
if f.Attribute == 4 {
f.Attribute = 6
}
if (Attr(f.Attribute) == attr) || (!f.Black() && f.Attribute&attr > 0) {
res = append(res, f)
}
}
if len(res) == 0 {
res = _emptyFollowings
}
return
}
// SortFollowings sort followings by the mtime desc.
type SortFollowings []*Following
func (fs SortFollowings) Len() int {
return len(fs)
}
func (fs SortFollowings) Swap(i, j int) {
fs[i], fs[j] = fs[j], fs[i]
}
func (fs SortFollowings) Less(i, j int) bool {
if fs[i].MTime == fs[j].MTime {
return fs[i].Mid < fs[j].Mid
}
return fs[i].MTime.Time().After(fs[j].MTime.Time())
}

View File

@@ -0,0 +1,35 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["xints.go"],
importpath = "go-common/app/service/main/relation/model/i64b",
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["xints_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)

View File

@@ -0,0 +1,91 @@
package i64b
import (
"database/sql/driver"
"encoding/binary"
)
//Int64Bytes be used to MySql\Protobuf varbinary converting.
type Int64Bytes []int64
// MarshalTo marshal int64 slice to bytes,each int64 will occupy Fixed 8 bytes.
//if the argument data not supplied with the full size,it will return the actual written size
func (is Int64Bytes) MarshalTo(data []byte) (int, error) {
for i, n := range is {
start := i * 8
end := (i + 1) * 8
if len(data) < end {
return start, nil
}
bs := data[start:end]
binary.BigEndian.PutUint64(bs, uint64(n))
}
return 8 * len(is), nil
}
// Size return the total size it will occupy in bytes
func (is Int64Bytes) Size() int {
return len(is) * 8
}
// Unmarshal parse the data into int64 slice
func (is *Int64Bytes) Unmarshal(data []byte) error {
return is.Scan(data)
}
// Scan parse the data into int64 slice
func (is *Int64Bytes) Scan(src interface{}) (err error) {
switch sc := src.(type) {
case []byte:
var res []int64
for i := 0; i < len(sc) && i+8 <= len(sc); i += 8 {
ui := binary.BigEndian.Uint64(sc[i : i+8])
res = append(res, int64(ui))
}
*is = res
}
return
}
// Value marshal int64 slice to driver.Value,each int64 will occupy Fixed 8 bytes
func (is Int64Bytes) Value() (driver.Value, error) {
return is.Bytes(), nil
}
// Bytes marshal int64 slice to bytes,each int64 will occupy Fixed 8 bytes
func (is Int64Bytes) Bytes() []byte {
res := make([]byte, 0, 8*len(is))
for _, i := range is {
bs := make([]byte, 8)
binary.BigEndian.PutUint64(bs, uint64(i))
res = append(res, bs...)
}
return res
}
// Evict get rid of the sepcified num from the slice
func (is *Int64Bytes) Evict(e int64) (ok bool) {
res := make([]int64, len(*is)-1)
for _, v := range *is {
if v != e {
res = append(res, v)
} else {
ok = true
}
}
*is = res
return
}
// Exist judge the sepcified num is in the slice or not
func (is Int64Bytes) Exist(i int64) (e bool) {
for _, v := range is {
if v == i {
e = true
return
}
}
return
}

View File

@@ -0,0 +1,74 @@
package i64b
import (
"testing"
)
func TestMarshalAndUnmarshal(t *testing.T) {
var a = Int64Bytes{1, 2, 3}
data := make([]byte, a.Size())
n, err := a.MarshalTo(data)
if n != 24 {
t.Logf("marshal size must be 24")
t.FailNow()
}
if err != nil {
t.Fatalf("err:%v", err)
}
var b Int64Bytes
err = b.Unmarshal(data)
if err != nil {
t.Fatalf("err:%v", err)
}
if b[0] != 1 || b[1] != 2 || b[2] != 3 {
t.Logf("unmarshal failed!b:%v", b)
t.FailNow()
}
}
func TestUncompleteMarshal(t *testing.T) {
var a = Int64Bytes{1, 2, 3}
data := make([]byte, a.Size()-7)
n, err := a.MarshalTo(data)
if n != 16 {
t.Logf("marshal size must be 16")
t.FailNow()
}
if err != nil {
t.Fatalf("err:%v", err)
}
var b Int64Bytes
err = b.Unmarshal(data)
if err != nil {
t.Fatalf("err:%v", err)
}
if b[0] != 1 || b[1] != 2 {
t.Logf("unmarshal failed!b:%v", b)
t.FailNow()
}
}
func TestNilMarshal(t *testing.T) {
var a = Int64Bytes{1, 2, 3}
var data []byte
n, err := a.MarshalTo(data)
if n != 0 {
t.Logf("marshal size must be 0")
t.FailNow()
}
if err != nil {
t.Fatalf("err:%v", err)
}
var b Int64Bytes
err = b.Unmarshal(data)
if err != nil {
t.Fatalf("err:%v", err)
}
if b != nil {
t.Logf("unmarshal failed!b:%v", b)
t.FailNow()
}
}

View File

@@ -0,0 +1,26 @@
package model
// Reverse is
func (rl *RelationLog) Reverse() *RelationLog {
content := make(map[string]string, len(rl.Content))
for k, v := range rl.Content {
content[k] = v
}
reversed := &RelationLog{
// reverse
Mid: rl.Fid,
Fid: rl.Mid,
Ts: rl.Ts,
Source: rl.Source,
Ip: rl.Ip,
Buvid: rl.Buvid,
// reverse
FromAttr: rl.FromRevAttr,
ToAttr: rl.ToRevAttr,
FromRevAttr: rl.FromAttr,
ToRevAttr: rl.ToAttr,
Content: content,
}
return reversed
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,87 @@
syntax = "proto3";
package account.service.relation;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "model";
option (gogoproto.goproto_enum_prefix_all) = false;
option (gogoproto.goproto_getters_all) = false;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
message Tag {
int64 id = 1 [(gogoproto.jsontag) = "id"];
string name = 2 [(gogoproto.jsontag) = "name"];
int64 status = 3 [(gogoproto.jsontag) = "status"];
int64 ctime = 4 [(gogoproto.jsontag) = "-", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "CTime"];
int64 mtime = 5 [(gogoproto.jsontag) = "mtime", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "MTime"];
}
message Tags {
map<int64,Tag> tags= 1 ;
}
message TagUser {
int64 fid = 1 [(gogoproto.jsontag) = "fid"];
repeated int64 tag = 2 [(gogoproto.jsontag) = "tag"];
int64 ctime = 3 [(gogoproto.jsontag) = "-", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "CTime"];
int64 mtime = 4 [(gogoproto.jsontag) = "mtime", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "MTime"];
}
message TagCount {
int64 tagid = 1 [(gogoproto.jsontag) = "tagid"];
string name = 2 [(gogoproto.jsontag) = "name"];
int64 count = 3 [(gogoproto.jsontag) = "count"];
}
message TagCountList {
repeated TagCount tag_count_list = 1;
}
message Following {
int64 mid = 1 [(gogoproto.jsontag) = "mid"];
uint32 attribute = 2 [(gogoproto.jsontag) = "attribute"];
uint32 source = 3 [(gogoproto.jsontag) = "-"];
int64 ctime = 4 [(gogoproto.jsontag) = "-", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "CTime"];
int64 mtime = 5 [(gogoproto.jsontag) = "mtime", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "MTime"];
repeated int64 tag = 6 [(gogoproto.jsontag) = "tag"];
int32 special = 7 [(gogoproto.jsontag) = "special"];
}
message FollowingList {
repeated Following following_list = 1;
}
message Stat {
int64 mid = 1 [(gogoproto.jsontag) = "mid"];
int64 following = 2 [(gogoproto.jsontag) = "following"];
int64 whisper = 3 [(gogoproto.jsontag) = "whisper"];
int64 black = 4 [(gogoproto.jsontag) = "black"];
int64 follower = 5 [(gogoproto.jsontag) = "follower"];
int64 ctime = 6 [(gogoproto.jsontag) = "-", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "CTime"];
int64 mtime = 7 [(gogoproto.jsontag) = "-", (gogoproto.casttype) = "go-common/library/time.Time", (gogoproto.customname) = "MTime"];
}
message FollowingTags {
uint32 attr = 1 [(gogoproto.jsontag) = "attr"];
int64 ts = 2 [(gogoproto.jsontag) = "ts", (gogoproto.casttype) = "go-common/library/time.Time"];
repeated int64 tag_ids = 3 [(gogoproto.jsontag) = "tag"];
int32 special =4 [(gogoproto.jsontag) = "special"];
}
message GlobalRec {
repeated int64 fids = 1 [(gogoproto.jsontag) = "fids"];
}
message RelationLog {
int64 mid = 1 [(gogoproto.jsontag) = "mid"];
int64 fid = 2 [(gogoproto.jsontag) = "fid"];
int64 ts = 3 [(gogoproto.jsontag) = "ts"];
uint32 source = 4 [(gogoproto.jsontag) = "source"];
string ip = 5 [(gogoproto.jsontag) = "ip"];
string buvid = 6 [(gogoproto.jsontag) = "buvid"];
uint32 from_attr = 7 [(gogoproto.jsontag) = "from_attr"];
uint32 to_attr = 8 [(gogoproto.jsontag) = "to_attr"];
uint32 from_rev_attr = 9 [(gogoproto.jsontag) = "from_rev_attr"];
uint32 to_rev_attr = 10 [(gogoproto.jsontag) = "to_rev_attr"];
map<string, string> content = 11 [(gogoproto.jsontag) = "content"];
}

View File

@@ -0,0 +1,104 @@
package model
// ArgMid mid
type ArgMid struct {
Mid int64 `json:"mid" form:"mid" validate:"required" params:"mid;Required;Min(1)"`
RealIP string
}
// ArgSameFollowing is
type ArgSameFollowing struct {
Mid1 int64 `json:"mid1" form:"mid1" validate:"required" params:"mid1;Required;Min(1)"`
Mid2 int64 `json:"mid2" form:"mid2" validate:"required" params:"mid2;Required;Min(1)"`
}
// ArgMids mids
type ArgMids struct {
Mids []int64
RealIP string
}
// ArgFollowing following
type ArgFollowing struct {
Mid int64
Fid int64 `json:"fid" form:"fid" validate:"required" params:"fid;Required;Min(1)"`
Source uint8
RealIP string
Action int8
Infoc map[string]string
}
// ArgRelation relation
type ArgRelation struct {
Mid, Fid int64
RealIP string
}
// ArgRelations relations
type ArgRelations struct {
Mid int64
Fids []int64
RealIP string
}
// ArgTag tag
type ArgTag struct {
Mid int64
Tag string
RealIP string
}
// ArgTagId tag id
type ArgTagId struct {
Mid int64
TagId int64
RealIP string
}
// ArgTagDel tag del
type ArgTagDel struct {
Mid int64
TagId int64
RealIP string
}
// ArgTagUpdate tag update
type ArgTagUpdate struct {
Mid int64
TagId int64
New string
RealIP string
}
// ArgTagsMoveUsers tags move users
type ArgTagsMoveUsers struct {
Mid int64
BeforeID int64
AfterTagIds string
Fids string
RealIP string
}
// ArgPrompt rpc promt arg.
type ArgPrompt struct {
Mid int64 `form:"mid" params:"mid"`
Fid int64 `form:"fid" validate:"required" params:"fid;Required;Min(1)"`
Btype int8 `form:"btype" validate:"required,min=1" params:"btype;Required;Min(1)"`
}
// ArgAchieveGet is
type ArgAchieveGet struct {
Award string `form:"award" validate:"required"`
Mid int64 `form:"mid" validate:"required"`
}
// ArgAchieve is
type ArgAchieve struct {
AwardToken string `form:"award_token" validate:"required"`
}
// FollowerNotifySetting show the follower-notify setting state
type FollowerNotifySetting struct {
Mid int64 `json:"mid"`
Enabled bool `json:"enabled"`
}

View File

@@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"int64.go",
"sets.go",
],
importpath = "go-common/app/service/main/relation/model/sets",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,185 @@
package sets
import (
"reflect"
"sort"
)
// Int64 is sets.Int64 is a set of int64s, implemented via map[int64]struct{} for minimal memory consumption.
type Int64 map[int64]Empty
// NewInt64 creates a Int64 from a list of values.
func NewInt64(items ...int64) Int64 {
ss := Int64{}
ss.Insert(items...)
return ss
}
// Int64KeySet creates a Int64 from a keys of a map[int64](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func Int64KeySet(theMap interface{}) Int64 {
v := reflect.ValueOf(theMap)
ret := Int64{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(int64))
}
return ret
}
// Insert adds items to the set.
func (s Int64) Insert(items ...int64) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s Int64) Delete(items ...int64) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s Int64) Has(item int64) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s Int64) HasAll(items ...int64) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s Int64) HasAny(items ...int64) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s Int64) Difference(s2 Int64) Int64 {
result := NewInt64()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 Int64) Union(s2 Int64) Int64 {
result := NewInt64()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 Int64) Intersection(s2 Int64) Int64 {
var walk, other Int64
result := NewInt64()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 Int64) IsSuperset(s2 Int64) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 Int64) Equal(s2 Int64) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfInt64 []int64
func (s sortableSliceOfInt64) Len() int { return len(s) }
func (s sortableSliceOfInt64) Less(i, j int) bool { return lessInt64(s[i], s[j]) }
func (s sortableSliceOfInt64) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted int64 slice.
func (s Int64) List() []int64 {
res := make(sortableSliceOfInt64, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []int64(res)
}
// UnsortedList returns the slice with contents in random order.
func (s Int64) UnsortedList() []int64 {
res := make([]int64, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s Int64) PopAny() (int64, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue int64
return zeroValue, false
}
// Len returns the size of the set.
func (s Int64) Len() int {
return len(s)
}
func lessInt64(lhs, rhs int64) bool {
return lhs < rhs
}

View File

@@ -0,0 +1,4 @@
package sets
// Empty is
type Empty struct{}

View File

@@ -0,0 +1,43 @@
package model
// Stat struct of Stat.
// type Stat struct {
// Mid int64 `json:"-"`
// Following int64 `json:"following"`
// Whisper int64 `json:"whisper"`
// Black int64 `json:"black"`
// Follower int64 `json:"follower"`
// CTime time.Time `json:"-"`
// MTime time.Time `json:"-"`
// }
// Count get count of following, including attr following, whisper.
func (st *Stat) Count() int {
return int(st.Following + st.Whisper)
}
// BlackCount get count of black, including attr black.
func (st *Stat) BlackCount() int {
return int(st.Black)
}
// Empty get if the stat is empty.
func (st *Stat) Empty() bool {
return st.Following == 0 && st.Whisper == 0 && st.Black == 0 && st.Follower == 0
}
// Fill fill by the incoming stat with its non-negative fields.
func (st *Stat) Fill(ost *Stat) {
if ost.Following >= 0 {
st.Following = ost.Following
}
if ost.Whisper >= 0 {
st.Whisper = ost.Whisper
}
if ost.Black >= 0 {
st.Black = ost.Black
}
if ost.Follower >= 0 {
st.Follower = ost.Follower
}
}

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["relation.go"],
importpath = "go-common/app/service/main/relation/rpc/client",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/model:go_default_library",
"//library/net/rpc:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,299 @@
package relation
import (
"context"
"go-common/app/service/main/relation/model"
"go-common/library/net/rpc"
)
const (
// relation
_relation = "RPC.Relation"
_relations = "RPC.Relations"
_stat = "RPC.Stat"
_stats = "RPC.Stats"
_followings = "RPC.Followings"
_sameFollowings = "RPC.SameFollowings"
_whispers = "RPC.Whispers"
_blacks = "RPC.Blacks"
_followers = "RPC.Followers"
_addFollowing = "RPC.AddFollowing"
_delFollowing = "RPC.DelFollowing"
_addWhisper = "RPC.AddWhisper"
_delWhisper = "RPC.DelWhisper"
_addBlack = "RPC.AddBlack"
_delBlack = "RPC.DelBlack"
_delFollower = "RPC.DelFollower"
// relation tag
_tag = "RPC.Tag"
_tags = "RPC.Tags"
_userTag = "RPC.UserTag"
_createTag = "RPC.CreateTag"
_updateTag = "RPC.UpdateTag"
_delTag = "RPC.DelTag"
_tagsAddUsers = "RPC.TagsAddUsers"
_tagsCopyUsers = "RPC.TagsCopyUsers"
_tagsMoveUsers = "RPC.TagsMoveUsers"
_AddSpecial = "RPC.AddSpecial"
_DelSpecial = "RPC.DelSpecial"
_Special = "RPC.Special"
// prompt
_prompt = "RPC.Prompt"
_closePrompt = "RPC.ClosePrompt"
// followers incr notify
_FollowersUnread = "RPC.FollowersUnread"
_FollowersUnreadCount = "RPC.FollowersUnreadCount"
_ResetFollowersUnread = "RPC.ResetFollowersUnread"
_ResetFollowersUnreadCount = "RPC.ResetFollowersUnreadCount"
_DisableFollowerNotify = "RPC.DisableFollowerNotify"
_EnableFollowerNotify = "RPC.EnableFollowerNotify"
_FollowerNotifySetting = "RPC.FollowerNotifySetting"
// achieve
_AchieveGet = "RPC.AchieveGet"
_Achieve = "RPC.Achieve"
)
var (
_noRes = &struct{}{}
)
const (
_appid = "account.service.relation"
)
// Service struct info.
type Service struct {
client *rpc.Client2
}
// New new service instance and return.
func New(c *rpc.ClientConfig) (s *Service) {
s = &Service{}
s.client = rpc.NewDiscoveryCli(_appid, c)
return
}
// Relation get relation info.
func (s *Service) Relation(c context.Context, arg *model.ArgRelation) (res *model.Following, err error) {
res = new(model.Following)
err = s.client.Call(c, _relation, arg, res)
return
}
// Relations get relation infos.
func (s *Service) Relations(c context.Context, arg *model.ArgRelations) (res map[int64]*model.Following, err error) {
err = s.client.Call(c, _relations, arg, &res)
return
}
// Followings get followings infos.
func (s *Service) Followings(c context.Context, arg *model.ArgMid) (res []*model.Following, err error) {
err = s.client.Call(c, _followings, arg, &res)
return
}
// Whispers get whispers infos.
func (s *Service) Whispers(c context.Context, arg *model.ArgMid) (res []*model.Following, err error) {
err = s.client.Call(c, _whispers, arg, &res)
return
}
// Blacks get black list.
func (s *Service) Blacks(c context.Context, arg *model.ArgMid) (res []*model.Following, err error) {
err = s.client.Call(c, _blacks, arg, &res)
return
}
// Followers get followers list.
func (s *Service) Followers(c context.Context, arg *model.ArgMid) (res []*model.Following, err error) {
err = s.client.Call(c, _followers, arg, &res)
return
}
// Stat get user relation stat.
func (s *Service) Stat(c context.Context, arg *model.ArgMid) (res *model.Stat, err error) {
res = new(model.Stat)
err = s.client.Call(c, _stat, arg, &res)
return
}
// Stats get users relation stat.
func (s *Service) Stats(c context.Context, arg *model.ArgMids) (res map[int64]*model.Stat, err error) {
err = s.client.Call(c, _stats, arg, &res)
return
}
// ModifyRelation modify user relation.
func (s *Service) ModifyRelation(c context.Context, arg *model.ArgFollowing) (err error) {
switch arg.Action {
case model.ActAddBlack:
err = s.client.Call(c, _addBlack, arg, _noRes)
case model.ActAddFollowing:
err = s.client.Call(c, _addFollowing, arg, _noRes)
case model.ActAddWhisper:
err = s.client.Call(c, _addWhisper, arg, _noRes)
case model.ActDelBalck:
err = s.client.Call(c, _delBlack, arg, _noRes)
case model.ActDelFollower:
err = s.client.Call(c, _delFollower, arg, _noRes)
case model.ActDelFollowing:
err = s.client.Call(c, _delFollowing, arg, _noRes)
case model.ActDelWhisper:
err = s.client.Call(c, _delWhisper, arg, _noRes)
default:
}
return
}
// Tag tag
func (s *Service) Tag(c context.Context, arg *model.ArgTagId) (res []int64, err error) {
err = s.client.Call(c, _tag, arg, &res)
return
}
// Tags tags
func (s *Service) Tags(c context.Context, arg *model.ArgMid) (res []*model.TagCount, err error) {
err = s.client.Call(c, _tags, arg, &res)
return
}
// UserTag user tag
func (s *Service) UserTag(c context.Context, arg *model.ArgRelation) (res map[int64]string, err error) {
err = s.client.Call(c, _userTag, arg, &res)
return
}
// CreateTag create tag
func (s *Service) CreateTag(c context.Context, arg *model.ArgTag) (res int64, err error) {
err = s.client.Call(c, _createTag, arg, &res)
return
}
// UpdateTag update tag
func (s *Service) UpdateTag(c context.Context, arg *model.ArgTagUpdate) (err error) {
err = s.client.Call(c, _updateTag, arg, _noRes)
return
}
// DelTag del tag
func (s *Service) DelTag(c context.Context, arg *model.ArgTagDel) (err error) {
err = s.client.Call(c, _delTag, arg, _noRes)
return
}
// TagsAddUsers tags add users
func (s *Service) TagsAddUsers(c context.Context, arg *model.ArgTagsMoveUsers) (err error) {
err = s.client.Call(c, _tagsAddUsers, arg, _noRes)
return
}
// TagsCopyUsers tags copy users
func (s *Service) TagsCopyUsers(c context.Context, arg *model.ArgTagsMoveUsers) (err error) {
err = s.client.Call(c, _tagsCopyUsers, arg, _noRes)
return
}
// TagsMoveUsers tags move users
func (s *Service) TagsMoveUsers(c context.Context, arg *model.ArgTagsMoveUsers) (err error) {
err = s.client.Call(c, _tagsMoveUsers, arg, _noRes)
return
}
// Prompt rpc rpompt client
func (s *Service) Prompt(c context.Context, arg *model.ArgPrompt) (b bool, err error) {
err = s.client.Call(c, _prompt, arg, &b)
return
}
// ClosePrompt close prompt client.
func (s *Service) ClosePrompt(c context.Context, arg *model.ArgPrompt) (err error) {
err = s.client.Call(c, _closePrompt, arg, _noRes)
return
}
// AddSpecial add specail.
func (s *Service) AddSpecial(c context.Context, arg *model.ArgFollowing) (err error) {
err = s.client.Call(c, _AddSpecial, arg, &_noRes)
return
}
// DelSpecial del special.
func (s *Service) DelSpecial(c context.Context, arg *model.ArgFollowing) (err error) {
err = s.client.Call(c, _DelSpecial, arg, &_noRes)
return
}
// Special get special.
func (s *Service) Special(c context.Context, arg *model.ArgMid) (res []int64, err error) {
err = s.client.Call(c, _Special, arg, &res)
return
}
// FollowersUnread check unread status, for the 'show red point' function.
func (s *Service) FollowersUnread(c context.Context, arg *model.ArgMid) (show bool, err error) {
err = s.client.Call(c, _FollowersUnread, arg, &show)
return
}
// FollowersUnreadCount unread count.
func (s *Service) FollowersUnreadCount(c context.Context, arg *model.ArgMid) (count int64, err error) {
err = s.client.Call(c, _FollowersUnreadCount, arg, &count)
return
}
// AchieveGet is
func (s *Service) AchieveGet(c context.Context, arg *model.ArgAchieveGet) (*model.AchieveGetReply, error) {
reply := &model.AchieveGetReply{}
err := s.client.Call(c, _AchieveGet, arg, &reply)
return reply, err
}
// Achieve is
func (s *Service) Achieve(c context.Context, arg *model.ArgAchieve) (*model.Achieve, error) {
reply := &model.Achieve{}
err := s.client.Call(c, _Achieve, arg, &reply)
return reply, err
}
// ResetFollowersUnread is
func (s *Service) ResetFollowersUnread(c context.Context, arg *model.ArgMid) (err error) {
err = s.client.Call(c, _ResetFollowersUnread, arg, &_noRes)
return
}
// ResetFollowersUnreadCount is
func (s *Service) ResetFollowersUnreadCount(c context.Context, arg *model.ArgMid) (err error) {
err = s.client.Call(c, _ResetFollowersUnreadCount, arg, &_noRes)
return
}
// DisableFollowerNotify set followerNotify as disabled.
func (s *Service) DisableFollowerNotify(c context.Context, arg *model.ArgMid) (err error) {
err = s.client.Call(c, _DisableFollowerNotify, arg, &_noRes)
return
}
// EnableFollowerNotify set followerNotify as disabled.
func (s *Service) EnableFollowerNotify(c context.Context, arg *model.ArgMid) (err error) {
err = s.client.Call(c, _EnableFollowerNotify, arg, &_noRes)
return
}
// FollowerNotifySetting get followerNotify setting.
func (s *Service) FollowerNotifySetting(c context.Context, arg *model.ArgMid) (followerNotify *model.FollowerNotifySetting, err error) {
followerNotify = &model.FollowerNotifySetting{}
err = s.client.Call(c, _FollowerNotifySetting, arg, followerNotify)
return
}
// SameFollowings is
func (s *Service) SameFollowings(c context.Context, arg *model.ArgSameFollowing) (res []*model.Following, err error) {
err = s.client.Call(c, _sameFollowings, arg, &res)
return
}

View File

@@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["rpc.go"],
importpath = "go-common/app/service/main/relation/rpc/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/service:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,288 @@
package rpc
import (
"go-common/app/service/main/relation/conf"
"go-common/app/service/main/relation/model"
"go-common/app/service/main/relation/service"
"go-common/library/net/rpc"
"go-common/library/net/rpc/context"
)
// RPC rpc
type RPC struct {
s *service.Service
}
// New new rpc server.
func New(c *conf.Config, s *service.Service) (svr *rpc.Server) {
r := &RPC{s: s}
svr = rpc.NewServer(c.RPCServer)
if err := svr.Register(r); err != nil {
panic(err)
}
return
}
// Ping check connection success.
func (r *RPC) Ping(c context.Context, arg *struct{}, res *struct{}) (err error) {
return
}
// Relation relation
func (r *RPC) Relation(c context.Context, a *model.ArgRelation, res *model.Following) (err error) {
var f *model.Following
if f, err = r.s.Relation(c, a.Mid, a.Fid); err == nil && f != nil {
*res = *f
}
return
}
// Relations relations
func (r *RPC) Relations(c context.Context, a *model.ArgRelations, res *map[int64]*model.Following) (err error) {
*res, err = r.s.Relations(c, a.Mid, a.Fids)
return
}
// Stat stat
func (r *RPC) Stat(c context.Context, a *model.ArgMid, res *model.Stat) (err error) {
var st *model.Stat
if st, err = r.s.Stat(c, a.Mid); err == nil && st != nil {
*res = *st
}
return
}
// Stats stats
func (r *RPC) Stats(c context.Context, a *model.ArgMids, res *map[int64]*model.Stat) (err error) {
*res, err = r.s.Stats(c, a.Mids)
return
}
// Attentions attentions
func (r *RPC) Attentions(c context.Context, a *model.ArgMid, res *[]*model.Following) (err error) {
*res, err = r.s.Attentions(c, a.Mid)
return
}
// Followings followings
func (r *RPC) Followings(c context.Context, a *model.ArgMid, res *[]*model.Following) (err error) {
*res, err = r.s.Followings(c, a.Mid)
return
}
// AddFollowing add following
func (r *RPC) AddFollowing(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.AddFollowing(c, a.Mid, a.Fid, a.Source, a.Infoc)
return
}
// DelFollowing del following
func (r *RPC) DelFollowing(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.DelFollowing(c, a.Mid, a.Fid, a.Source, a.Infoc)
return
}
// Whispers whispers
func (r *RPC) Whispers(c context.Context, a *model.ArgMid, res *[]*model.Following) (err error) {
*res, err = r.s.Whispers(c, a.Mid)
return
}
// AddWhisper add whisper
func (r *RPC) AddWhisper(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.AddWhisper(c, a.Mid, a.Fid, a.Source, a.Infoc)
return
}
// DelWhisper del whisper
func (r *RPC) DelWhisper(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.DelWhisper(c, a.Mid, a.Fid, a.Source, a.Infoc)
return
}
// Blacks blacks
func (r *RPC) Blacks(c context.Context, a *model.ArgMid, res *[]*model.Following) (err error) {
*res, err = r.s.Blacks(c, a.Mid)
return
}
// AddBlack add black
func (r *RPC) AddBlack(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.AddBlack(c, a.Mid, a.Fid, a.Source, a.Infoc)
return
}
// DelBlack del black
func (r *RPC) DelBlack(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.DelBlack(c, a.Mid, a.Fid, a.Source, a.Infoc)
return
}
// Followers followers
func (r *RPC) Followers(c context.Context, a *model.ArgMid, res *[]*model.Following) (err error) {
*res, err = r.s.Followers(c, a.Mid)
return
}
// DelFollower del Follower
func (r *RPC) DelFollower(c context.Context, a *model.ArgFollowing, res *struct{}) (err error) {
err = r.s.DelFollowing(c, a.Fid, a.Mid, a.Source, a.Infoc)
return
}
// Tag tag
func (r *RPC) Tag(c context.Context, a *model.ArgTagId, res *[]int64) (err error) {
*res, err = r.s.Tag(c, a.Mid, a.TagId, a.RealIP)
return
}
// Tags tags
func (r *RPC) Tags(c context.Context, a *model.ArgMid, res *[]*model.TagCount) (err error) {
*res, err = r.s.Tags(c, a.Mid, a.RealIP)
return
}
// UserTag user tag
func (r *RPC) UserTag(c context.Context, a *model.ArgRelation, res *map[int64]string) (err error) {
*res, err = r.s.UserTag(c, a.Mid, a.Fid, a.RealIP)
return
}
// CreateTag create tag
func (r *RPC) CreateTag(c context.Context, a *model.ArgTag, res *int64) (err error) {
*res, err = r.s.CreateTag(c, a.Mid, a.Tag, a.RealIP)
return
}
// UpdateTag update tag
func (r *RPC) UpdateTag(c context.Context, a *model.ArgTagUpdate, res *struct{}) (err error) {
err = r.s.UpdateTag(c, a.Mid, a.TagId, a.New, a.RealIP)
return
}
// DelTag del tag
func (r *RPC) DelTag(c context.Context, a *model.ArgTagDel, res *struct{}) (err error) {
err = r.s.DelTag(c, a.Mid, a.TagId, a.RealIP)
return
}
// TagsAddUsers tags add users
func (r *RPC) TagsAddUsers(c context.Context, a *model.ArgTagsMoveUsers, res *struct{}) (err error) {
err = r.s.TagsAddUsers(c, a.Mid, a.AfterTagIds, a.Fids, a.RealIP)
return
}
// TagsCopyUsers tags copy users
func (r *RPC) TagsCopyUsers(c context.Context, a *model.ArgTagsMoveUsers, res *struct{}) (err error) {
err = r.s.TagsMoveUsers(c, a.Mid, a.BeforeID, a.AfterTagIds, a.Fids, a.RealIP)
return
}
// TagsMoveUsers tags move users
func (r *RPC) TagsMoveUsers(c context.Context, a *model.ArgTagsMoveUsers, res *struct{}) (err error) {
err = r.s.TagsMoveUsers(c, a.Mid, a.BeforeID, a.AfterTagIds, a.Fids, a.RealIP)
return
}
// Prompt rpc prompt.
func (r *RPC) Prompt(c context.Context, m *model.ArgPrompt, res *bool) (err error) {
*res, err = r.s.Prompt(c, m)
return
}
// ClosePrompt close prompt.
func (r *RPC) ClosePrompt(c context.Context, m *model.ArgPrompt, res *struct{}) (err error) {
return r.s.ClosePrompt(c, m)
}
// AddSpecial add user to special.
func (r *RPC) AddSpecial(c context.Context, m *model.ArgFollowing, res *struct{}) (err error) {
return r.s.AddSpecial(c, m.Mid, m.Fid)
}
// DelSpecial del user from sepcial.
func (r *RPC) DelSpecial(c context.Context, m *model.ArgFollowing, res *struct{}) (err error) {
return r.s.DelSpecial(c, m.Mid, m.Fid)
}
// Special get user specail list.
func (r *RPC) Special(c context.Context, m *model.ArgMid, res *[]int64) (err error) {
*res, err = r.s.Special(c, m.Mid)
return
}
// FollowersUnread is
func (r *RPC) FollowersUnread(c context.Context, arg *model.ArgMid, res *bool) (err error) {
*res, err = r.s.Unread(c, arg.Mid)
return
}
// FollowersUnreadCount is
func (r *RPC) FollowersUnreadCount(c context.Context, arg *model.ArgMid, res *int64) (err error) {
*res, err = r.s.UnreadCount(c, arg.Mid)
return
}
// AchieveGet is
func (r *RPC) AchieveGet(c context.Context, arg *model.ArgAchieveGet, res *model.AchieveGetReply) error {
reply, err := r.s.AchieveGet(c, arg)
if err != nil {
return err
}
*res = *reply
return nil
}
// Achieve is
func (r *RPC) Achieve(c context.Context, arg *model.ArgAchieve, res *model.Achieve) error {
reply, err := r.s.Achieve(c, arg)
if err != nil {
return err
}
*res = *reply
return nil
}
// ResetFollowersUnread is
func (r *RPC) ResetFollowersUnread(c context.Context, arg *model.ArgMid, res *struct{}) (err error) {
err = r.s.ResetUnread(c, arg.Mid)
return
}
// ResetFollowersUnreadCount is
func (r *RPC) ResetFollowersUnreadCount(c context.Context, arg *model.ArgMid, res *struct{}) (err error) {
err = r.s.ResetUnreadCount(c, arg.Mid)
return
}
// DisableFollowerNotify set followerNotify as disabled.
func (r *RPC) DisableFollowerNotify(c context.Context, arg *model.ArgMid, res *struct{}) (err error) {
err = r.s.DisableFollowerNotify(c, arg)
return
}
// EnableFollowerNotify set followerNotify as enabled.
func (r *RPC) EnableFollowerNotify(c context.Context, arg *model.ArgMid, res *struct{}) (err error) {
err = r.s.EnableFollowerNotify(c, arg)
return
}
// FollowerNotifySetting get member follower notify setting
func (r *RPC) FollowerNotifySetting(c context.Context, arg *model.ArgMid, res *model.FollowerNotifySetting) (err error) {
rely, err := r.s.FollowerNotifySetting(c, arg)
if err != nil {
return
}
*res = *rely
return
}
// SameFollowings is
func (r *RPC) SameFollowings(c context.Context, arg *model.ArgSameFollowing, res *[]*model.Following) error {
reply, err := r.s.SameFollowings(c, arg)
if err != nil {
return err
}
*res = reply
return nil
}

View File

@@ -0,0 +1,50 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "go-common/app/service/main/relation/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/relation/api:go_default_library",
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/service:go_default_library",
"//library/net/rpc/warden:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["server_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/main/relation/api:go_default_library",
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/service:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,392 @@
package grpc
import (
"context"
pb "go-common/app/service/main/relation/api"
"go-common/app/service/main/relation/conf"
"go-common/app/service/main/relation/model"
"go-common/app/service/main/relation/service"
"go-common/library/net/rpc/warden"
)
// New warden rpc server
func New(c *conf.Config, s *service.Service) *warden.Server {
svr := warden.NewServer(c.WardenServer)
pb.RegisterRelationServer(svr.Server(), &server{as: s})
svr, err := svr.Start()
if err != nil {
panic(err)
}
return svr
}
type server struct {
as *service.Service
}
var _ pb.RelationServer = &server{}
func (s *server) Relation(ctx context.Context, req *pb.RelationReq) (*pb.FollowingReply, error) {
following, err := s.as.Relation(ctx, req.Mid, req.Fid)
if err != nil {
return nil, err
}
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(following)
return followingReply, nil
}
func (s *server) Relations(ctx context.Context, req *pb.RelationsReq) (*pb.FollowingMapReply, error) {
followsing, err := s.as.Relations(ctx, req.Mid, req.Fid)
if err != nil {
return nil, err
}
followingMap := map[int64]*pb.FollowingReply{}
for key, value := range followsing {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingMap[key] = followingReply
}
return &pb.FollowingMapReply{FollowingMap: followingMap}, nil
}
func (s *server) Stat(ctx context.Context, req *pb.MidReq) (*pb.StatReply, error) {
stat, err := s.as.Stat(ctx, req.Mid)
if err != nil {
return nil, err
}
statReply := new(pb.StatReply)
statReply.DeepCopyFromStat(stat)
return statReply, nil
}
func (s *server) Stats(ctx context.Context, req *pb.MidsReq) (*pb.StatsReply, error) {
stat, err := s.as.Stats(ctx, req.Mids)
if err != nil {
return nil, err
}
statMap := map[int64]*pb.StatReply{}
for key, value := range stat {
statReply := new(pb.StatReply)
statReply.DeepCopyFromStat(value)
statMap[key] = statReply
}
return &pb.StatsReply{StatReplyMap: statMap}, nil
}
func (s *server) Attentions(ctx context.Context, req *pb.MidReq) (*pb.FollowingsReply, error) {
followings, err := s.as.Attentions(ctx, req.Mid)
if err != nil {
return nil, err
}
followingList := make([]*pb.FollowingReply, len(followings))
for index, value := range followings {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingList[index] = followingReply
}
return &pb.FollowingsReply{FollowingList: followingList}, nil
}
func (s *server) Followings(ctx context.Context, req *pb.MidReq) (*pb.FollowingsReply, error) {
followings, err := s.as.Followings(ctx, req.Mid)
if err != nil {
return nil, err
}
followingList := make([]*pb.FollowingReply, len(followings))
for index, value := range followings {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingList[index] = followingReply
}
return &pb.FollowingsReply{FollowingList: followingList}, nil
}
func (s *server) AddFollowing(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.AddFollowing(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) DelFollowing(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.DelFollowing(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) Whispers(ctx context.Context, req *pb.MidReq) (*pb.FollowingsReply, error) {
followings, err := s.as.Whispers(ctx, req.Mid)
if err != nil {
return nil, err
}
followingList := make([]*pb.FollowingReply, len(followings))
for index, value := range followings {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingList[index] = followingReply
}
return &pb.FollowingsReply{FollowingList: followingList}, nil
}
func (s *server) AddWhisper(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.AddWhisper(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) DelWhisper(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.DelWhisper(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) Blacks(ctx context.Context, req *pb.MidReq) (*pb.FollowingsReply, error) {
followings, err := s.as.Blacks(ctx, req.Mid)
if err != nil {
return nil, err
}
followingList := make([]*pb.FollowingReply, len(followings))
for index, value := range followings {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingList[index] = followingReply
}
return &pb.FollowingsReply{FollowingList: followingList}, nil
}
func (s *server) AddBlack(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.AddBlack(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) DelBlack(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.DelBlack(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) Followers(ctx context.Context, req *pb.MidReq) (*pb.FollowingsReply, error) {
followings, err := s.as.Followers(ctx, req.Mid)
if err != nil {
return nil, err
}
followingList := make([]*pb.FollowingReply, len(followings))
for index, value := range followings {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingList[index] = followingReply
}
return &pb.FollowingsReply{FollowingList: followingList}, nil
}
func (s *server) DelFollower(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.DelFollower(ctx, req.Mid, req.Fid, req.Source, req.Infoc); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) Tag(ctx context.Context, req *pb.TagIdReq) (*pb.TagReply, error) {
mids, err := s.as.Tag(ctx, req.Mid, req.TagId, req.RealIp)
if err != nil {
return nil, err
}
return &pb.TagReply{Mids: mids}, nil
}
func (s *server) Tags(ctx context.Context, req *pb.MidReq) (*pb.TagsCountReply, error) {
tagCount, err := s.as.Tags(ctx, req.Mid, req.RealIp)
if err != nil {
return nil, err
}
tagCountList := make([]*pb.TagCountReply, len(tagCount))
for index, value := range tagCount {
tagCountReply := new(pb.TagCountReply)
tagCountReply.DeepCopyFromTagCount(value)
tagCountList[index] = tagCountReply
}
return &pb.TagsCountReply{TagCountList: tagCountList}, nil
}
func (s *server) UserTag(ctx context.Context, req *pb.RelationReq) (*pb.UserTagReply, error) {
res, err := s.as.UserTag(ctx, req.Mid, req.Fid, req.RealIp)
if err != nil {
return nil, err
}
return &pb.UserTagReply{Tags: res}, nil
}
func (s *server) CreateTag(ctx context.Context, req *pb.TagReq) (*pb.CreateTagReply, error) {
res, err := s.as.CreateTag(ctx, req.Mid, req.Tag, req.RealIp)
if err != nil {
return nil, err
}
return &pb.CreateTagReply{TagId: res}, nil
}
func (s *server) UpdateTag(ctx context.Context, req *pb.TagUpdateReq) (*pb.EmptyReply, error) {
if err := s.as.UpdateTag(ctx, req.Mid, req.TagId, req.New, req.RealIp); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) DelTag(ctx context.Context, req *pb.TagDelReq) (*pb.EmptyReply, error) {
if err := s.as.DelTag(ctx, req.Mid, req.TagId, req.RealIp); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) TagsAddUsers(ctx context.Context, req *pb.TagsMoveUsersReq) (*pb.EmptyReply, error) {
if err := s.as.TagsAddUsers(ctx, req.Mid, req.AfterTagIds, req.Fids, req.RealIp); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) TagsCopyUsers(ctx context.Context, req *pb.TagsMoveUsersReq) (*pb.EmptyReply, error) {
if err := s.as.TagsMoveUsers(ctx, req.Mid, req.BeforeId, req.AfterTagIds, req.Fids, req.RealIp); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) TagsMoveUsers(ctx context.Context, req *pb.TagsMoveUsersReq) (*pb.EmptyReply, error) {
if err := s.as.TagsMoveUsers(ctx, req.Mid, req.BeforeId, req.AfterTagIds, req.Fids, req.RealIp); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) Prompt(ctx context.Context, req *pb.PromptReq) (*pb.PromptReply, error) {
argPrompt := &model.ArgPrompt{Mid: req.Mid, Fid: req.Fid, Btype: req.Btype}
success, err := s.as.Prompt(ctx, argPrompt)
if err != nil {
return nil, err
}
return &pb.PromptReply{Success: success}, nil
}
func (s *server) ClosePrompt(ctx context.Context, req *pb.PromptReq) (*pb.EmptyReply, error) {
argPrompt := &model.ArgPrompt{Mid: req.Mid, Fid: req.Fid, Btype: req.Btype}
if err := s.as.ClosePrompt(ctx, argPrompt); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) AddSpecial(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.AddSpecial(ctx, req.Mid, req.Fid); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) DelSpecial(ctx context.Context, req *pb.FollowingReq) (*pb.EmptyReply, error) {
if err := s.as.DelSpecial(ctx, req.Mid, req.Fid); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) Special(ctx context.Context, req *pb.MidReq) (*pb.SpecialReply, error) {
mids, err := s.as.Special(ctx, req.Mid)
if err != nil {
return nil, err
}
return &pb.SpecialReply{Mids: mids}, nil
}
func (s *server) FollowersUnread(ctx context.Context, req *pb.MidReq) (*pb.FollowersUnreadReply, error) {
hasUnread, err := s.as.Unread(ctx, req.Mid)
if err != nil {
return nil, err
}
return &pb.FollowersUnreadReply{HasUnread: hasUnread}, nil
}
func (s *server) FollowersUnreadCount(ctx context.Context, req *pb.MidReq) (*pb.FollowersUnreadCountReply, error) {
count, err := s.as.UnreadCount(ctx, req.Mid)
if err != nil {
return nil, err
}
return &pb.FollowersUnreadCountReply{UnreadCount: count}, nil
}
func (s *server) AchieveGet(ctx context.Context, req *pb.AchieveGetReq) (*pb.AchieveGetReply, error) {
argAchieveGet := &model.ArgAchieveGet{Mid: req.Mid, Award: req.Award}
achieveGetReply, err := s.as.AchieveGet(ctx, argAchieveGet)
if err != nil {
return nil, err
}
return &pb.AchieveGetReply{AwardToken: achieveGetReply.AwardToken}, nil
}
func (s *server) Achieve(ctx context.Context, req *pb.AchieveReq) (*pb.AchieveReply, error) {
argAchieve := &model.ArgAchieve{AwardToken: req.AwardToken}
achieveReply, err := s.as.Achieve(ctx, argAchieve)
if err != nil {
return nil, err
}
return &pb.AchieveReply{Award: achieveReply.Award, Mid: achieveReply.Mid}, nil
}
func (s *server) ResetFollowersUnread(ctx context.Context, req *pb.MidReq) (*pb.EmptyReply, error) {
if err := s.as.ResetUnread(ctx, req.Mid); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) ResetFollowersUnreadCount(ctx context.Context, req *pb.MidReq) (*pb.EmptyReply, error) {
if err := s.as.ResetUnreadCount(ctx, req.Mid); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) DisableFollowerNotify(ctx context.Context, req *pb.MidReq) (*pb.EmptyReply, error) {
if err := s.as.DisableFollowerNotify(ctx, &model.ArgMid{Mid: req.Mid}); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) EnableFollowerNotify(ctx context.Context, req *pb.MidReq) (*pb.EmptyReply, error) {
if err := s.as.EnableFollowerNotify(ctx, &model.ArgMid{Mid: req.Mid}); err != nil {
return nil, err
}
return &pb.EmptyReply{}, nil
}
func (s *server) FollowerNotifySetting(ctx context.Context, req *pb.MidReq) (*pb.FollowerNotifySettingReply, error) {
followerNotifySetting, err := s.as.FollowerNotifySetting(ctx, &model.ArgMid{Mid: req.Mid})
if err != nil {
return nil, err
}
return &pb.FollowerNotifySettingReply{Mid: followerNotifySetting.Mid, Enabled: followerNotifySetting.Enabled}, nil
}
func (s *server) SameFollowings(ctx context.Context, req *pb.SameFollowingReq) (*pb.FollowingsReply, error) {
followings, err := s.as.SameFollowings(ctx, &model.ArgSameFollowing{Mid1: req.Mid, Mid2: req.Mid2})
if err != nil {
return nil, err
}
followingList := make([]*pb.FollowingReply, len(followings))
for index, value := range followings {
followingReply := new(pb.FollowingReply)
followingReply.DeepCopyFromFollowing(value)
followingList[index] = followingReply
}
return &pb.FollowingsReply{FollowingList: followingList}, nil
}

View File

@@ -0,0 +1,563 @@
package grpc
import (
"context"
"flag"
"fmt"
"github.com/smartystreets/goconvey/convey"
"go-common/app/service/main/relation/model"
"os"
"testing"
"time"
"go-common/app/service/main/relation/api"
pb "go-common/app/service/main/relation/api"
"go-common/app/service/main/relation/conf"
"go-common/app/service/main/relation/service"
"go-common/library/net/rpc/warden"
xtime "go-common/library/time"
)
var (
cli api.RelationClient
svr *service.Service
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.relation-service")
flag.Set("conf_token", "8hm3I5rWzuhChxrBI6VTqmCs7TpJwFhO")
flag.Set("tree_id", "2139")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
svr = service.New(conf.Conf)
cfg := &warden.ClientConfig{
Dial: xtime.Duration(time.Second * 3),
Timeout: xtime.Duration(time.Second * 3),
}
var err error
cli, err = api.NewClient(cfg)
if err != nil {
panic(err)
}
m.Run()
os.Exit(0)
}
func TestServerRelation(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Relation", t, func(cv convey.C) {
rr := pb.RelationReq{Mid: 1, Fid: 2, RealIp: "127.0.0.1"}
fr, err := cli.Relation(c, &rr)
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(fr, convey.ShouldNotBeNil)
})
fmt.Println(fr)
f, err2 := svr.Relation(c, 1, 2)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(f, convey.ShouldNotBeNil)
})
fmt.Println(f)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(f.Mid, convey.ShouldEqual, fr.Mid)
cv.So(f.Attribute, convey.ShouldEqual, fr.Attribute)
cv.So(f.CTime, convey.ShouldEqual, fr.CTime)
})
})
}
func TestServerRelations(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Relations", t, func(cv convey.C) {
rr := pb.RelationsReq{Mid: 1, Fid: []int64{2, 3}, RealIp: "127.0.0.1"}
fr, err := cli.Relations(c, &rr)
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(fr, convey.ShouldNotBeNil)
})
fmt.Println(fr)
f, err2 := svr.Relations(c, 1, []int64{2, 3})
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(f, convey.ShouldNotBeNil)
})
fmt.Println(f)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
f1 := fr.FollowingMap[2]
cv.So(f[2].Attribute, convey.ShouldEqual, f1.Attribute)
cv.So(f[2].CTime, convey.ShouldEqual, f1.CTime)
})
})
}
func TestServerStat(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Stat", t, func(cv convey.C) {
st, err := cli.Stat(c, &pb.MidReq{Mid: 2})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(st, convey.ShouldNotBeNil)
})
fmt.Println(st)
s, err2 := svr.Stat(c, 2)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(st.CTime, convey.ShouldEqual, s.CTime)
cv.So(st.Mid, convey.ShouldEqual, s.Mid)
cv.So(st.Black, convey.ShouldEqual, s.Black)
cv.So(st.Follower, convey.ShouldEqual, s.Follower)
cv.So(st.Following, convey.ShouldEqual, s.Following)
cv.So(st.MTime, convey.ShouldEqual, s.MTime)
cv.So(st.Whisper, convey.ShouldEqual, s.Whisper)
})
})
}
func TestServerStats(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Stats", t, func(cv convey.C) {
st, err := cli.Stats(c, &pb.MidsReq{Mids: []int64{2, 3}, RealIp: "127.0.0.1"})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(st, convey.ShouldNotBeNil)
})
fmt.Println(st)
s, err2 := svr.Stats(c, []int64{2, 3})
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
st2 := st.StatReplyMap[2]
s2 := s[2]
cv.So(s2.CTime, convey.ShouldEqual, st2.CTime)
cv.So(s2.Mid, convey.ShouldEqual, st2.Mid)
cv.So(s2.Black, convey.ShouldEqual, st2.Black)
cv.So(s2.Follower, convey.ShouldEqual, st2.Follower)
cv.So(s2.Following, convey.ShouldEqual, st2.Following)
cv.So(s2.MTime, convey.ShouldEqual, st2.MTime)
cv.So(s2.Whisper, convey.ShouldEqual, st2.Whisper)
})
})
}
func TestServerAttentions(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Attentions", t, func(cv convey.C) {
at, err := cli.Attentions(c, &pb.MidReq{Mid: 2})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(at, convey.ShouldNotBeNil)
})
fmt.Println(at)
a, err2 := svr.Attentions(c, 2)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(a, convey.ShouldNotBeNil)
})
fmt.Println(a)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
f1 := at.FollowingList[0]
f2 := a[0]
cv.So(f1.Mid, convey.ShouldEqual, f2.Mid)
cv.So(f1.Attribute, convey.ShouldEqual, f2.Attribute)
cv.So(f1.CTime, convey.ShouldEqual, f2.CTime)
})
})
}
func TestServerFollowings(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Followings", t, func(cv convey.C) {
at, err := cli.Followings(c, &pb.MidReq{Mid: 2})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(at, convey.ShouldNotBeNil)
})
fmt.Println(at)
a, err2 := svr.Followings(c, 2)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(a, convey.ShouldNotBeNil)
})
fmt.Println(a)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
f1 := at.FollowingList[0]
f2 := a[0]
cv.So(f1.Mid, convey.ShouldEqual, f2.Mid)
cv.So(f1.Attribute, convey.ShouldEqual, f2.Attribute)
cv.So(f1.CTime, convey.ShouldEqual, f2.CTime)
})
})
}
func TestServerWhispers(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Whispers", t, func(cv convey.C) {
at, err := cli.Whispers(c, &pb.MidReq{Mid: 2231365})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(at, convey.ShouldNotBeNil)
})
fmt.Println(at)
a, err2 := svr.Whispers(c, 2231365)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(a, convey.ShouldNotBeNil)
})
fmt.Println(a)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
f1 := at.FollowingList[0]
f2 := a[0]
cv.So(f1.Mid, convey.ShouldEqual, f2.Mid)
cv.So(f1.Attribute, convey.ShouldEqual, f2.Attribute)
cv.So(f1.CTime, convey.ShouldEqual, f2.CTime)
})
})
}
func TestServerFollowers(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Followers", t, func(cv convey.C) {
at, err := cli.Followers(c, &pb.MidReq{Mid: 2})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(at, convey.ShouldNotBeNil)
})
fmt.Println(at)
a, err2 := svr.Followers(c, 2)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(a, convey.ShouldNotBeNil)
})
fmt.Println(a)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
f1 := at.FollowingList[0]
f2 := a[0]
cv.So(f1.Mid, convey.ShouldEqual, f2.Mid)
cv.So(f1.Attribute, convey.ShouldEqual, f2.Attribute)
cv.So(f1.CTime, convey.ShouldEqual, f2.CTime)
})
})
}
func TestServerTag(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Tag", t, func(cv convey.C) {
t, err := cli.Tag(c, &pb.TagIdReq{Mid: 1, TagId: -10, RealIp: "127.0.0.1"})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(t, convey.ShouldNotBeNil)
})
fmt.Println(t)
tg, err2 := svr.Tag(c, 1, -10, "127.0.0.1")
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(tg, convey.ShouldNotBeNil)
})
fmt.Println(tg)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(len(t.Mids), convey.ShouldEqual, len(tg))
cv.So(t.Mids[0], convey.ShouldEqual, tg[0])
})
})
}
func TestServerTags(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Tags", t, func(cv convey.C) {
t, err := cli.Tags(c, &pb.MidReq{Mid: 1})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(t, convey.ShouldNotBeNil)
})
fmt.Println(t)
tg, err2 := svr.Tags(c, 1, "127.0.0.1")
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(tg, convey.ShouldNotBeNil)
})
fmt.Println(tg)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(t.TagCountList[0].Tagid, convey.ShouldEqual, tg[0].Tagid)
cv.So(t.TagCountList[0].Count, convey.ShouldEqual, tg[0].Count)
cv.So(t.TagCountList[0].Name, convey.ShouldEqual, tg[0].Name)
})
})
}
func TestUserTag(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("UserTag", t, func(cv convey.C) {
tt, err := cli.UserTag(c, &pb.RelationReq{Mid: 1, Fid: 2, RealIp: "127.0.0.1"})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(tt, convey.ShouldNotBeNil)
})
fmt.Println(tt)
tg, err2 := svr.UserTag(c, 1, 2, "127.0.0.1")
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(tg, convey.ShouldNotBeNil)
})
fmt.Println(tg)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(len(tt.Tags), convey.ShouldEqual, len(tg))
})
})
}
func TestSpecial(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("Special", t, func(cv convey.C) {
s, err := cli.Special(c, &pb.MidReq{Mid: 1})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
sg, err2 := svr.Special(c, 1)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(sg, convey.ShouldNotBeNil)
})
fmt.Println(sg)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(len(sg), convey.ShouldEqual, len(s.Mids))
})
})
}
func TestFollowersUnread(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("FollowersUnread", t, func(cv convey.C) {
s, err := cli.FollowersUnread(c, &pb.MidReq{Mid: 1})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
h, err2 := svr.Unread(c, 1)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(h, convey.ShouldNotBeNil)
})
fmt.Println(h)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(s.HasUnread, convey.ShouldEqual, h)
})
})
}
func TestFollowersUnreadCount(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("FollowersUnreadCount", t, func(cv convey.C) {
s, err := cli.FollowersUnreadCount(c, &pb.MidReq{Mid: 1})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
h, err2 := svr.UnreadCount(c, 1)
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(h, convey.ShouldNotBeNil)
})
fmt.Println(h)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(s.UnreadCount, convey.ShouldEqual, h)
})
})
}
func TestAchieveGet(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("FollowersUnreadCount", t, func(cv convey.C) {
s, err := cli.AchieveGet(c, &pb.AchieveGetReq{Award: "10k", Mid: 3})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
s2, err := cli.Achieve(c, &pb.AchieveReq{AwardToken: s.AwardToken})
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s2, convey.ShouldNotBeNil)
})
fmt.Println(s2)
h, err2 := svr.AchieveGet(c, &model.ArgAchieveGet{Award: "10k", Mid: 3})
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(h, convey.ShouldNotBeNil)
})
fmt.Println(h)
h2, err2 := svr.Achieve(c, &model.ArgAchieve{AwardToken: h.AwardToken})
cv.Convey("Then err should be nil reslut should not be nil. ", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(h2, convey.ShouldNotBeNil)
})
fmt.Println(h2)
cv.Convey("Then err should be nil reslut should not be nil.", func(cv convey.C) {
cv.So(s2.Mid, convey.ShouldEqual, h2.Mid)
cv.So(s2.Award, convey.ShouldEqual, h2.Award)
})
})
}
func TestFollowerNotifySetting(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("FollowerNotifySetting", t, func(cv convey.C) {
s, err := cli.FollowerNotifySetting(c, &pb.MidReq{Mid: 3})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
h, err2 := svr.FollowerNotifySetting(c, &model.ArgMid{Mid: 3})
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(h, convey.ShouldNotBeNil)
})
fmt.Println(h)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(s.Mid, convey.ShouldEqual, h.Mid)
cv.So(s.Enabled, convey.ShouldEqual, h.Enabled)
})
})
}
func TestSameFollowings(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("SameFollowings", t, func(cv convey.C) {
s, err := cli.SameFollowings(c, &pb.SameFollowingReq{Mid: 3, Mid2: 4})
cv.Convey("Then err should be nil.reslut should not be nil.", func(cv convey.C) {
cv.So(err, convey.ShouldBeNil)
cv.So(s, convey.ShouldNotBeNil)
})
fmt.Println(s)
h, err2 := svr.SameFollowings(c, &model.ArgSameFollowing{Mid1: 3, Mid2: 4})
cv.Convey("Then err should be nil. reslut should not be nil.", func(cv convey.C) {
cv.So(err2, convey.ShouldBeNil)
cv.So(h, convey.ShouldNotBeNil)
})
fmt.Println(h)
cv.Convey("the grpc result should be equal to service result", func(cv convey.C) {
cv.So(len(s.FollowingList), convey.ShouldEqual, len(h))
f1 := s.FollowingList[0]
f2 := h[0]
cv.So(f1.Mid, convey.ShouldEqual, f2.Mid)
cv.So(f1.MTime, convey.ShouldEqual, f2.MTime)
cv.So(f1.CTime, convey.ShouldEqual, f2.CTime)
cv.So(f1.Attribute, convey.ShouldEqual, f2.Attribute)
cv.So(f1.Source, convey.ShouldEqual, f2.Source)
cv.So(f1.Special, convey.ShouldEqual, f2.Special)
})
})
}

View File

@@ -0,0 +1,60 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"achieve.go",
"aes.go",
"audit.go",
"black.go",
"follower.go",
"following.go",
"infoc.go",
"monitor.go",
"prompt.go",
"relation.go",
"service.go",
"stat.go",
"tag.go",
"whisper.go",
],
importpath = "go-common/app/service/main/relation/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/member/api/gorpc:go_default_library",
"//app/service/main/member/model:go_default_library",
"//app/service/main/member/model/block:go_default_library",
"//app/service/main/relation/conf:go_default_library",
"//app/service/main/relation/dao:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/model/sets:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/metadata:go_default_library",
"//library/stat/prom:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,87 @@
package service
import (
"context"
"encoding/json"
"go-common/app/service/main/relation/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"github.com/pkg/errors"
)
func (s *Service) award10kCondition(ctx context.Context, mid int64, achieve model.AchieveFlag) error {
audit, err := s.Audit(ctx, mid, metadata.String(ctx, metadata.RemoteIP))
if err != nil {
return err
}
log.Info("award 10k audit info by mid: %d and audit: %+v", mid, audit)
if audit.Rank < 10000 {
return ecode.RelAwardInsufficientRank
}
if !audit.BindTel {
return ecode.RelAwardPhoneRequired
}
if audit.Blocked {
return ecode.RelAwardIsBlocked
}
if s.dao.HasReachAchieve(ctx, mid, achieve) {
return nil
}
stat, err := s.Stat(ctx, mid)
if err != nil {
return err
}
if stat.Follower < 10000 {
return ecode.RelAwardInsufficientFollower
}
return nil
}
// AchieveGet is
func (s *Service) AchieveGet(ctx context.Context, arg *model.ArgAchieveGet) (*model.AchieveGetReply, error) {
if arg.Award != "10k" {
return nil, ecode.RequestErr
}
if err := s.award10kCondition(ctx, arg.Mid, model.FollowerAchieve10k); err != nil {
return nil, err
}
achieve := &model.Achieve{
Award: arg.Award,
Mid: arg.Mid,
}
js, err := json.Marshal(achieve)
if err != nil {
return nil, errors.Wrapf(err, "achieve: %s", achieve)
}
token, err := encrypt([]byte(s.c.Relation.AchieveKey), string(js))
if err != nil {
log.Error("Failed to encrypt achieve with key: %s, text: %s: %+v", s.c.Relation.AchieveKey, string(js), errors.WithStack(err))
return nil, ecode.RelAwardIsBlocked
}
return &model.AchieveGetReply{AwardToken: token}, nil
}
// Achieve is
func (s *Service) Achieve(ctx context.Context, arg *model.ArgAchieve) (*model.Achieve, error) {
js, err := decrypt([]byte(s.c.Relation.AchieveKey), arg.AwardToken)
if err != nil {
log.Error("Failed to decrypt achieve with key: %s, token: %s: %+v", s.c.Relation.AchieveKey, arg.AwardToken, errors.WithStack(err))
return nil, ecode.RelAwardInfoFailed
}
achieve := new(model.Achieve)
if err := json.Unmarshal([]byte(js), achieve); err != nil {
log.Error("Failed to parse achieve data: %s: %+v", js, err)
return nil, ecode.RelAwardInfoFailed
}
return achieve, nil
}

View File

@@ -0,0 +1,92 @@
package service
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
"strings"
)
func addBase64Padding(value string) string {
m := len(value) % 4
if m != 0 {
value += strings.Repeat("=", 4-m)
}
return value
}
func removeBase64Padding(value string) string {
return strings.Replace(value, "=", "", -1)
}
// Pad is
func Pad(src []byte) []byte {
padding := aes.BlockSize - len(src)%aes.BlockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// Unpad is
func Unpad(src []byte) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1])
if unpadding > length {
return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")
}
return src[:(length - unpadding)], nil
}
func encrypt(key []byte, text string) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
msg := Pad([]byte(text))
ciphertext := make([]byte, aes.BlockSize+len(msg))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
finalMsg := removeBase64Padding(base64.URLEncoding.EncodeToString(ciphertext))
return finalMsg, nil
}
func decrypt(key []byte, text string) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
decodedMsg, err := base64.URLEncoding.DecodeString(addBase64Padding(text))
if err != nil {
return "", err
}
if (len(decodedMsg) % aes.BlockSize) != 0 {
return "", errors.New("blocksize must be multipe of decoded message length")
}
iv := decodedMsg[:aes.BlockSize]
msg := decodedMsg[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(msg, msg)
unpadMsg, err := Unpad(msg)
if err != nil {
return "", err
}
return string(unpadMsg), nil
}

View File

@@ -0,0 +1,62 @@
package service
import (
"context"
mmodel "go-common/app/service/main/member/model"
bmodel "go-common/app/service/main/member/model/block"
"go-common/app/service/main/relation/model"
"go-common/library/log"
)
// Audit get member audit info
func (s *Service) Audit(c context.Context, mid int64, realIP string) (rs *model.Audit, err error) {
rs = &model.Audit{
Mid: mid,
}
var (
detail *model.PassportDetail
blockInfo *bmodel.RPCResInfo
baseInfo *mmodel.BaseInfo
)
// get bindMainStatus and bindTelStatus from passport-service
if detail, err = s.dao.PassportDetail(c, mid, realIP); err != nil {
log.Error("s.accRPC.PassportDetail() error(%v) return(%v)", err, detail)
return
}
rs.BindMail = bindEmailStatus(detail.Email, detail.Spacesta)
rs.BindTel = bindPhoneStatus(detail.Phone)
// get block status from block-service
blockArg := &bmodel.RPCArgInfo{
MID: mid,
}
if blockInfo, err = s.memberRPC.BlockInfo(c, blockArg); err != nil {
log.Error("s.memberRPC.BlockInfo() error(%v) return(%v)", err, blockInfo)
return
}
if blockInfo.BlockStatus != bmodel.BlockStatusFalse {
rs.Blocked = true
}
// get rank from member-service
memberArg := &mmodel.ArgMemberMid{
Mid: mid,
RemoteIP: realIP,
}
if baseInfo, err = s.memberRPC.Base(c, memberArg); err != nil {
log.Error("s.memberRPC.Base() error(%v) return(%v)", err, baseInfo)
return
}
rs.Rank = baseInfo.Rank
return
}
func bindEmailStatus(email string, spacesta int8) bool {
return spacesta > -10 && len(email) > 0
}
func bindPhoneStatus(phone string) bool {
return len(phone) > 0
}

View File

@@ -0,0 +1,307 @@
package service
import (
"context"
"sort"
"time"
"go-common/app/service/main/relation/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
)
// Blacks get black list.
func (s *Service) Blacks(c context.Context, mid int64) (fs []*model.Following, err error) {
if mid <= 0 {
return
}
stat, err := s.Stat(c, mid)
if err != nil || stat.Black == 0 {
return
}
if fs, err = s.followings(c, mid); err != nil {
return
}
fs = model.Filter(fs, model.AttrBlack)
sort.Sort(model.SortFollowings(fs))
return
}
// AddBlack add black.
func (s *Service) AddBlack(c context.Context, mid int64, fid int64, src uint8, ric map[string]string) (err error) {
var (
a, ra uint32
na, nra uint32
at uint32
st *model.Stat
tx *sql.Tx
n = new(model.Stat)
rn = new(model.Stat)
now = time.Now()
realIP = ric[RelInfocIP]
)
if mid <= 0 || fid <= 0 {
return
}
if mid == fid {
err = ecode.RelFollowSelfBanned
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if _, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if ra, err = s.dao.Relation(c, fid, mid); err != nil {
return
}
at = model.Attr(a)
if st, err = s.dao.TxStat(c, tx, mid); err != nil {
return
} else if st != nil && st.BlackCount() >= s.c.Relation.MaxBlackLimit {
err = ecode.RelBlackReachMaxLimit
return
}
switch at {
case model.AttrBlack:
return
case model.AttrNoRelation:
if _, err = s.dao.TxAddFollowing(c, tx, mid, fid, model.AttrBlack, src, now); err != nil {
return
}
if _, err = s.dao.TxAddFollower(c, tx, mid, fid, model.AttrBlack, src, now); err != nil {
return
}
case model.AttrFriend:
nra = model.UnsetAttr(ra, model.AttrFriend)
na = model.AttrBlack
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxDelTagUser(c, tx, mid, fid); err != nil {
return
}
case model.AttrFollowing:
if _, err = s.dao.TxAddFollowing(c, tx, mid, fid, model.AttrBlack, src, now); err != nil {
return
}
if _, err = s.dao.TxAddFollower(c, tx, mid, fid, model.AttrBlack, src, now); err != nil {
return
}
if _, err = s.dao.TxDelTagUser(c, tx, mid, fid); err != nil {
return
}
case model.AttrWhisper:
if _, err = s.dao.TxAddFollowing(c, tx, mid, fid, model.AttrBlack, src, now); err != nil {
return
}
if _, err = s.dao.TxAddFollower(c, tx, mid, fid, model.AttrBlack, src, now); err != nil {
return
}
}
n.Black = 1
switch at {
case model.AttrFriend, model.AttrFollowing:
n.Following = -1
rn.Follower = -1
case model.AttrWhisper:
n.Whisper = -1
rn.Follower = -1
}
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
_, err = s.dao.TxAddStat(c, tx, fid, rn, now)
}
s.RelationInfoc(mid, fid, now.Unix(), ric[RelInfocIP], ric[RelInfocSid], ric[RelInfocBuvid], addBlackURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: na,
FromRevAttr: ra,
ToRevAttr: nra,
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": addBlackURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.AddBlackLog(c, l)
return
}
// DelBlack del black.
func (s *Service) DelBlack(c context.Context, mid, fid int64, src uint8, ric map[string]string) (err error) {
var (
a, ra uint32
na, nra uint32
nat, rat uint32
tx *sql.Tx
friend = false
n = new(model.Stat)
rn = new(model.Stat)
status = model.StatusOK
now = time.Now()
realIP = ric[RelInfocIP]
)
// if mid == fid {
// err = ecode.RelFollowSelfBanned
// return
// }
if mid <= 0 || fid <= 0 {
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if _, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if ra, err = s.dao.Relation(c, fid, mid); err != nil {
return
}
if model.AttrBlack != model.Attr(a) {
s.CompareAndDelCache(c, mid, fid, a)
// err = ecode.RelFollowAttrNotSet
log.Warn("Invalid state between %d and %d with attribute: %d", mid, fid, a)
return
}
n.Black = -1
na = model.AttrNoRelation
nra = ra
// no recover relation
//na = model.UnsetAttr(a, model.AttrBlack)
//nat = model.Attr(na)
// switch nat {
// case model.AttrFollowing:
// n.Following = 1
// rn.Follower = 1
// case model.AttrWhisper:
// n.Whisper = 1
// rn.Follower = 1
// }
rat = model.Attr(ra)
switch rat {
case model.AttrBlack:
na = model.AttrNoRelation
status = model.StatusDel
n.Following = 0
n.Whisper = 0
rn.Follower = 0
case model.AttrFollowing:
if nat == model.AttrFollowing {
na = model.SetAttr(na, model.AttrFriend)
nra = model.SetAttr(nra, model.AttrFriend)
friend = true
}
}
if friend {
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, status, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, status, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, status, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, status, now); err != nil {
return
}
} else {
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, status, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, status, now); err != nil {
return
}
}
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
_, err = s.dao.TxAddStat(c, tx, fid, rn, now)
}
s.RelationInfoc(mid, fid, now.Unix(), ric[RelInfocIP], ric[RelInfocSid], ric[RelInfocBuvid], delBlackURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: na,
FromRevAttr: ra,
ToRevAttr: nra,
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": delBlackURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.DelBlackLog(c, l)
return
}

View File

@@ -0,0 +1,310 @@
package service
import (
"context"
"time"
"go-common/app/service/main/relation/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/stat/prom"
)
const (
_dailyFollowerNotifyLimit = 2
)
// Followers get follower list.
func (s *Service) Followers(c context.Context, mid int64) (fs []*model.Following, err error) {
if mid <= 0 {
return
}
var mc = true
prom.CacheHit.Incr("followers")
if fs, err = s.dao.FollowerCache(c, mid); err != nil {
err = nil
mc = false
} else if fs != nil {
return
}
prom.CacheMiss.Incr("followers")
if fs, err = s.dao.Followers(c, mid); err != nil {
return
} else if len(fs) == 0 {
fs = _emptyFollowings
}
if mc {
s.addCache(func() {
s.dao.SetFollowerCache(context.TODO(), mid, fs)
})
}
return
}
// DelFollower del follower.
func (s *Service) DelFollower(c context.Context, mid, fid int64, src uint8, ric map[string]string) (err error) {
if mid <= 0 || fid <= 0 {
return
}
var (
a uint32
ra, na, nra uint32
tx *sql.Tx
friend = false
n = new(model.Stat)
rn = new(model.Stat)
now = time.Now()
realIP = ric[RelInfocIP]
)
if mid == fid {
err = ecode.RelFollowSelfBanned
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if _, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if ra, err = s.dao.Relation(c, fid, mid); err != nil {
return
}
switch model.Attr(a) {
case model.AttrFriend:
friend = true
case model.AttrFollowing:
default:
// s.CompareAndDelCache(c, mid, fid, a)
// err = ecode.RelFollowAttrNotSet
log.Warn("Invalid state between %d and %d with attribute: %d", mid, fid, a)
return
}
na = model.AttrNoRelation
if friend {
nra = model.AttrFollowing
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
} else {
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
}
n.Following = -1
rn.Follower = -1
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
_, err = s.dao.TxAddStat(c, tx, fid, rn, now)
}
_, err = s.dao.TxDelTagUser(c, tx, mid, fid)
s.RelationInfoc(mid, fid, now.Unix(), ric[RelInfocIP], ric[RelInfocSid], ric[RelInfocBuvid], delFollowerURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: na,
FromRevAttr: ra,
ToRevAttr: nra,
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": delFollowerURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.DelFollowerLog(c, l)
return
}
// DelFollowerCache delete follower cache.
func (s *Service) DelFollowerCache(c context.Context, mid int64) (err error) {
err = s.dao.DelFollowerCache(c, mid)
return
}
// Unread is
// 展示最近有未知晓的新粉丝
func (s *Service) Unread(c context.Context, fid int64) (bool, error) {
if shouldNotified := s.shouldNotified(c, fid); !shouldNotified {
return false, nil
}
flag, err := s.dao.RctFollowerNotify(c, fid)
if err != nil {
return false, err
}
count, err := s.dao.RctFollowerCount(c, fid)
if err != nil {
return false, err
}
notify := false
if flag && count > 0 {
notify = true
}
s.addCache(func() {
s.dao.SetRctFollowerNotify(context.Background(), fid, false)
// 真的看见红点了再加一
if notify {
s.dao.IncrTodayNotifyCount(context.Background(), fid)
}
})
return notify, nil
}
// UnreadCount unread count.
func (s *Service) UnreadCount(c context.Context, fid int64) (int64, error) {
count, err := s.dao.RctFollowerCount(c, fid)
if err != nil {
return 0, err
}
if count < 0 {
count = 0
}
s.addCache(func() {
s.ResetUnreadCount(context.Background(), fid)
})
return count, nil
}
// ResetUnread is
// 重置未知晓的新粉丝
func (s *Service) ResetUnread(ctx context.Context, fid int64) error {
if err := s.dao.SetRctFollowerNotify(ctx, fid, false); err != nil {
log.Error("Failed to reset recent follower notify with fid: %d: %+v", fid, err)
}
if err := s.dao.IncrTodayNotifyCount(ctx, fid); err != nil {
log.Error("Failed to incr today notify count with fid: %d: %+v", fid, err)
}
return nil
}
// ResetUnreadCount is
func (s *Service) ResetUnreadCount(ctx context.Context, fid int64) error {
if err := s.dao.EmptyRctFollower(ctx, fid); err != nil {
log.Error("Failed to empty recent follower with fid: %d: %+v", fid, err)
}
if err := s.dao.SetRctFollowerNotify(ctx, fid, false); err != nil {
log.Error("Failed to reset recent follower with fid: %d: %+v", fid, err)
}
return nil
}
// FollowerNotifySetting get follower-notify setting
func (s *Service) FollowerNotifySetting(c context.Context, arg *model.ArgMid) (followerNotify *model.FollowerNotifySetting, err error) {
var (
enabled bool
addCache = true
)
followerNotify, err = s.dao.GetFollowerNotifyCache(c, arg.Mid)
if err != nil {
addCache = false
err = nil
}
if followerNotify != nil {
prom.CacheHit.Incr("FollowerNotify")
return
}
prom.CacheMiss.Incr("FollowerNotify")
if enabled, err = s.dao.FollowerNotifySetting(c, arg.Mid); err != nil {
return
}
followerNotify = &model.FollowerNotifySetting{
Mid: arg.Mid,
Enabled: enabled,
}
if !addCache {
return
}
// 异步的更新缓存
s.addCache(func() {
s.dao.SetFollowerNotifyCache(context.TODO(), arg.Mid, followerNotify)
})
return
}
// DisableFollowerNotify disable follower-notify setting
func (s *Service) DisableFollowerNotify(c context.Context, arg *model.ArgMid) (err error) {
if _, err = s.dao.DisableFollowerNotify(c, arg.Mid); err != nil {
return
}
s.dao.DelFollowerNotifyCache(context.TODO(), arg.Mid)
return
}
// EnableFollowerNotify enable follower-notify setting
func (s *Service) EnableFollowerNotify(c context.Context, arg *model.ArgMid) (err error) {
if _, err = s.dao.EnableFollowerNotify(c, arg.Mid); err != nil {
return
}
s.dao.DelFollowerNotifyCache(context.TODO(), arg.Mid)
return
}
func (s *Service) shouldNotified(c context.Context, mid int64) (shouldNotified bool) {
var (
notifyCount int64
err error
followerNotify *model.FollowerNotifySetting
)
// 得到用户新粉丝消息提醒设置,如果被禁用,则不再提醒
if followerNotify, err = s.FollowerNotifySetting(c, &model.ArgMid{Mid: mid}); err != nil {
log.Error("Failed to get follower notify setting: fid: %d: %+v", mid, err)
return true
}
if !followerNotify.Enabled {
return false
}
// 得到当日新粉丝提醒数量, 如果大于等于限制值,则不再提醒
if notifyCount, err = s.dao.TodayNotifyCountCache(c, mid); err != nil {
log.Error("Failed to get notify count: fid: %d: %+v", mid, err)
return true
}
if notifyCount >= _dailyFollowerNotifyLimit {
return false
}
return true
}

View File

@@ -0,0 +1,517 @@
package service
import (
"context"
"sort"
"time"
"go-common/app/service/main/relation/model"
"go-common/app/service/main/relation/model/sets"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/stat/prom"
)
// Followings get user's following list.
func (s *Service) Followings(c context.Context, mid int64) (fs []*model.Following, err error) {
if fs, err = s.followings(c, mid); err != nil {
return
}
fs = model.Filter(fs, model.AttrFollowing)
sort.Sort(model.SortFollowings(fs))
return
}
// Attentions return user's following and whisper list.
func (s *Service) Attentions(c context.Context, mid int64) (fs []*model.Following, err error) {
if fs, err = s.followings(c, mid); err != nil {
return
}
fs = model.Filter(fs, model.AttrFollowing|model.AttrWhisper)
sort.Sort(model.SortFollowings(fs))
return
}
func (s *Service) followings(c context.Context, mid int64) (fs []*model.Following, err error) {
if mid <= 0 {
return
}
var mc, redis = true, true
prom.CacheHit.Incr("followings_mc")
if fs, err = s.dao.FollowingCache(c, mid); err != nil {
mc = false
err = nil
} else if fs != nil {
return
}
prom.CacheMiss.Incr("followings_mc")
prom.CacheHit.Incr("followings_redis")
if fs, err = s.dao.FollowingsCache(c, mid); err != nil {
redis = false
err = nil
} else if len(fs) > 0 {
if mc {
s.addCache(func() {
s.dao.SetFollowingCache(context.TODO(), mid, fs)
})
}
return
}
prom.CacheMiss.Incr("followings_redis")
if fs, err = s.dao.Followings(c, mid); err != nil {
return
} else if len(fs) == 0 {
fs = _emptyFollowings
} else {
var (
ts map[int64][]int64
)
if ts, err = s.dao.UserTag(c, mid); err != nil {
return
}
for _, f := range fs {
if tags, ok := ts[f.Mid]; ok {
f.Tag = tags
for _, id := range f.Tag {
if id == -10 {
f.Special = 1
break
}
}
}
}
}
if redis || mc {
s.addCache(func() {
if redis {
s.dao.SetFollowingsCache(context.TODO(), mid, fs)
}
if mc {
s.dao.SetFollowingCache(context.TODO(), mid, fs)
}
})
}
return
}
// AddFollowing add following.
func (s *Service) AddFollowing(c context.Context, mid, fid int64, src uint8, ric map[string]string) (err error) {
var (
a, ra uint32
na, nra uint32
at uint32
st *model.Stat
tx *sql.Tx
friend = false
n = new(model.Stat)
rn = new(model.Stat)
now = time.Now()
audit *model.Audit
monitor bool
)
if mid <= 0 || fid <= 0 {
return
}
if mid == fid {
err = ecode.RelFollowSelfBanned
return
}
if monitor, err = s.Monitor(c, fid); err != nil {
return
} else if monitor {
return
}
realIP := ric[RelInfocIP]
if audit, err = s.Audit(c, mid, realIP); err != nil {
log.Error("s.Audit.mid(%d) error(%v) return(%v)", mid, err, audit)
return
}
if audit.Blocked {
err = ecode.UserDisabled
return
}
if audit.Rank == UserRank && !audit.BindTel {
err = ecode.RelFollowReachTelLimit
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if st, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
} else if st == nil {
log.Error("s.txStat(%d %d) error(%v)", mid, fid, err)
err = ecode.ServerErr
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if ra, err = s.dao.Relation(c, fid, mid); err != nil {
return
}
na = model.AttrFollowing
nra = ra
switch model.Attr(ra) {
case model.AttrFollowing:
friend = true
na = model.SetAttr(na, model.AttrFriend)
nra = model.SetAttr(ra, model.AttrFriend)
}
at = model.Attr(a)
switch at {
case model.AttrBlack:
err = ecode.RelFollowAlreadyBlack
return
case model.AttrFriend, model.AttrFollowing:
return
case model.AttrWhisper:
if friend {
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
} else {
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
}
n.Whisper = -1
n.Following = 1
case model.AttrNoRelation:
if st.Count() >= s.c.Relation.MaxFollowingLimit {
err = ecode.RelFollowReachMaxLimit
return
}
if friend {
if _, err = s.dao.TxAddFollowing(c, tx, mid, fid, na, src, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxAddFollower(c, tx, mid, fid, na, src, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
} else {
if _, err = s.dao.TxAddFollowing(c, tx, mid, fid, na, src, now); err != nil {
return
}
if _, err = s.dao.TxAddFollower(c, tx, mid, fid, na, src, now); err != nil {
return
}
}
n.Following = 1
rn.Follower = 1
}
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
_, err = s.dao.TxAddStat(c, tx, fid, rn, now)
}
s.RelationInfoc(mid, fid, now.Unix(), realIP, ric[RelInfocSid], ric[RelInfocBuvid], addFollowingURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: na,
FromRevAttr: ra,
ToRevAttr: nra,
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": addFollowingURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.AddFollowingLog(c, l)
// 后续逻辑
s.addCache(func() {
s.onAddFollowing(context.Background(), mid, fid)
})
return
}
// DelFollowing del following.
func (s *Service) DelFollowing(c context.Context, mid, fid int64, src uint8, ric map[string]string) (err error) {
var (
a uint32
ra, na, nra uint32
tx *sql.Tx
friend = false
n = new(model.Stat)
rn = new(model.Stat)
now = time.Now()
realIP = ric[RelInfocIP]
)
if mid <= 0 || fid <= 0 {
return
}
if mid == fid {
err = ecode.RelFollowSelfBanned
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if _, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if ra, err = s.dao.Relation(c, fid, mid); err != nil {
return
}
switch model.Attr(a) {
case model.AttrFriend:
friend = true
case model.AttrFollowing:
default:
s.CompareAndDelCache(c, mid, fid, a)
// err = ecode.RelFollowAttrNotSet
log.Warn("Invalid state between %d and %d with attribute: %d", mid, fid, a)
return
}
na = model.AttrNoRelation
if friend {
nra = model.AttrFollowing
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
} else {
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusDel, now); err != nil {
return
}
}
n.Following = -1
rn.Follower = -1
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
if _, err = s.dao.TxAddStat(c, tx, fid, rn, now); err != nil {
return
}
}
_, err = s.dao.TxDelTagUser(c, tx, mid, fid)
s.RelationInfoc(mid, fid, now.Unix(), ric[RelInfocIP], ric[RelInfocSid], ric[RelInfocBuvid], delFollowingURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: na,
FromRevAttr: ra,
ToRevAttr: nra,
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": addFollowingURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.DelFollowingLog(c, l)
// 后续逻辑
s.addCache(func() {
s.onDelFollowing(context.Background(), mid, fid)
})
return
}
// DelFollowingCache delete following cache.
func (s *Service) DelFollowingCache(c context.Context, mid int64) (err error) {
if err = s.dao.DelFollowingsCache(c, mid); err != nil {
return
}
err = s.dao.DelFollowingCache(c, mid)
return
}
// UpdateFollowingCache update following cache.
func (s *Service) UpdateFollowingCache(c context.Context, mid int64, following *model.Following) (err error) {
if following.Attribute == model.AttrNoRelation {
err = s.dao.DelFollowing(c, mid, following)
} else {
err = s.dao.AddFollowingCache(c, mid, following)
}
if err != nil {
return
}
err = s.dao.DelFollowingCache(c, mid)
return
}
func (s *Service) onAddFollowing(ctx context.Context, mid, fid int64) {
// 最近有新粉丝通知逻辑
if err := s.dao.AddRctFollower(ctx, mid, fid); err != nil {
log.Error("Failed to add recent follower: mid: %d fid: %d: %+v", mid, fid, err)
return
}
if err := s.dao.SetRctFollowerNotify(ctx, fid, true); err != nil {
log.Error("Failed to set recent follower notify: fid: %d flag: true: %+v", mid, err)
return
}
}
func (s *Service) onDelFollowing(ctx context.Context, mid, fid int64) {
// 最近有新粉丝通知逻辑
if err := s.dao.DelRctFollower(ctx, mid, fid); err != nil {
log.Error("Failed to del recent follower: mid: %d fid: %d: %+v", mid, fid, err)
return
}
count, err := s.dao.RctFollowerCount(ctx, fid)
if err != nil {
log.Error("Failed to get recent follower count: fid: %d: %+v", fid, err)
return
}
if count > 0 {
return
}
if err := s.dao.SetRctFollowerNotify(ctx, fid, false); err != nil {
log.Error("Failed to set recent follower notify: fid: %d flag: false: %+v", fid, err)
return
}
}
// CompareAndDelCache is
func (s *Service) CompareAndDelCache(ctx context.Context, mid, fid int64, inAttr uint32) {
fr, err := s.Relation(ctx, mid, fid)
if err != nil {
return
}
cacheAttr := model.AttrNoRelation
if fr != nil {
cacheAttr = model.Attr(fr.Attribute)
}
refAttr := model.Attr(inAttr)
if cacheAttr == refAttr {
return
}
log.Warn("The relation attribute is inconsistent: mid: %d, fid: %d, reference: %d, cache: %d", mid, fid, refAttr, cacheAttr)
s.DelFollowingCache(ctx, mid)
s.DelFollowerCache(ctx, mid)
}
// SameFollowings get users' same following list.
func (s *Service) SameFollowings(c context.Context, arg *model.ArgSameFollowing) ([]*model.Following, error) {
flw1, err := s.Followings(c, arg.Mid1)
if err != nil {
return nil, err
}
flw2, err := s.Followings(c, arg.Mid2)
if err != nil {
return nil, err
}
smids1 := sets.NewInt64(asMids(flw1)...)
smids2 := sets.NewInt64(asMids(flw2)...)
sameMids := smids1.Intersection(smids2)
// zhuangsusu: 以第一个用户的关注时间的倒序来展示给第二个用户看
flw1Map := asFollowingMap(flw1)
flw2Map := asFollowingMap(flw2)
sorted := make([]*model.Following, 0, sameMids.Len())
for mid := range sameMids {
sorted = append(sorted, flw1Map[mid])
}
sort.Sort(model.SortFollowings(sorted))
result := make([]*model.Following, 0, sameMids.Len())
for _, f := range sorted {
result = append(result, flw2Map[f.Mid])
}
return result, nil
}
func asMids(fs []*model.Following) []int64 {
mids := make([]int64, 0, len(fs))
for _, f := range fs {
mids = append(mids, f.Mid)
}
return mids
}
func asFollowingMap(fs []*model.Following) map[int64]*model.Following {
flwMap := make(map[int64]*model.Following, len(fs))
for _, f := range fs {
flwMap[f.Mid] = f
}
return flwMap
}

View File

@@ -0,0 +1,84 @@
package service
import (
"go-common/library/log"
"go-common/library/log/infoc"
"strconv"
)
const (
_prefix = "/x/internal/relation"
addFollowingURL = _prefix + "/following/add"
delFollowingURL = _prefix + "/following/del"
addWhisperURL = _prefix + "/whisper/add"
delWhisperURL = _prefix + "/whisper/del"
addBlackURL = _prefix + "/black/add"
delBlackURL = _prefix + "/black/del"
delFollowerURL = _prefix + "/follower/del"
// RelInfocIP map key string
RelInfocIP = "ip"
// RelInfocSid RelInfocSid
RelInfocSid = "sid"
// RelInfocBuvid RelInfocBuvid
RelInfocBuvid = "buvid"
// RelInfocHeaderBuvid RelInfocHeaderBuvid
RelInfocHeaderBuvid = "Buvid"
// RelInfocCookieBuvid RelInfocCookieBuvid
RelInfocCookieBuvid = "buvid3"
// RelInfocReferer RelInfocReferer
RelInfocReferer = "Referer"
// RelInfocUA RelInfocUA
RelInfocUA = "User-Agent"
)
type relationInfoc struct {
ip string //账号进行关注的ip
mid string //进行关注行为的账号
fid string //被关注的账号id
ts string //关注行为发生时的服务器时间
sid string //cookie里记录的sid标识一次登录访问
buvid string //移动端上报再请求header里有标识设备
url string //请求的关注接口
refer string //浏览器上报的请求上级
ua string //访问的浏览器或客户端版本
src string //业务页面来源编号
}
// RelationInfoc relation related info to BigData for anti-spam
func (s *Service) RelationInfoc(mid, fid, ts int64, ip, sid, buvid, url, refer, ua string, src uint8) {
s.infoc(relationInfoc{
ip,
strconv.FormatInt(mid, 10),
strconv.FormatInt(fid, 10),
strconv.FormatInt(ts, 10),
sid,
buvid,
url,
refer,
ua,
strconv.FormatUint(uint64(src), 10),
})
}
//infoc
func (s *Service) infoc(i interface{}) {
select {
case s.inCh <- i:
default:
log.Warn("infocproc chan full")
}
}
// infocproc
func (s *Service) infocproc() {
var infoc2 = infoc.New(s.c.Infoc)
for {
i := <-s.inCh
switch v := i.(type) {
case relationInfoc:
infoc2.Info(v.ip, v.mid, v.fid, v.ts, v.sid, v.buvid, v.url, v.refer, v.ua, v.src)
default:
log.Warn("infocproc can't process the type")
}
}
}

View File

@@ -0,0 +1,41 @@
package service
import (
"context"
"time"
)
// Monitor if mid is monitored
func (s *Service) Monitor(c context.Context, mid int64) (monitor bool, err error) {
if !s.c.Relation.Monitor {
return
}
return s.dao.MonitorCache(c, mid)
}
// AddMonitor add mid to monitor table.
func (s *Service) AddMonitor(c context.Context, mid int64) (err error) {
if _, err = s.dao.AddMonitor(c, mid, time.Now()); err != nil {
return
}
return s.dao.SetMonitorCache(c, mid)
}
// DelMonitor del mid from monitor table
func (s *Service) DelMonitor(c context.Context, mid int64) (err error) {
if _, err = s.dao.DelMonitor(c, mid); err != nil {
return
}
return s.dao.DelMonitorCache(c, mid)
}
// LoadMonitor load monitor
func (s *Service) LoadMonitor(c context.Context) (err error) {
var (
mids []int64
)
if mids, _ = s.dao.LoadMonitor(c); mids != nil {
return s.dao.LoadMonitorCache(c, mids)
}
return
}

View File

@@ -0,0 +1,32 @@
package service
import (
"context"
"time"
"go-common/app/service/main/relation/model"
)
// Prompt incr user prompt count and return if prompt.
func (s *Service) Prompt(c context.Context, m *model.ArgPrompt) (b bool, err error) {
r, err := s.Relation(c, m.Mid, m.Fid)
if err != nil {
return
}
if r != nil && r.Following() {
return
}
ucount, bcount, err := s.dao.IncrPromptCount(c, m.Mid, m.Fid, time.Now().Unix(), m.Btype)
if err != nil {
return
}
if s.c.Relation.Bcount > bcount && s.c.Relation.Ucount > ucount {
b = true
}
return
}
// ClosePrompt close prompt.
func (s *Service) ClosePrompt(c context.Context, m *model.ArgPrompt) (err error) {
return s.dao.ClosePrompt(c, m.Mid, m.Fid, time.Now().Unix(), m.Btype)
}

View File

@@ -0,0 +1,57 @@
package service
import (
"context"
"go-common/app/service/main/relation/model"
)
// Relation get relation of mid -> fid.
// black(128) friend(4) following(2) whisper(1), null for no relation.
func (s *Service) Relation(c context.Context, mid int64, fid int64) (f *model.Following, err error) {
res, err := s.Relations(c, mid, []int64{fid})
return res[fid], err
}
// Relations get relations of mid -> fids.
// black(128) friend(4) following(2) whisper(1), key absent for no relation.
func (s *Service) Relations(c context.Context, mid int64, fids []int64) (rm map[int64]*model.Following, err error) {
var (
ok bool
fid int64
f *model.Following
fs []*model.Following
arm map[int64]*model.Following
)
if mid <= 0 {
return
}
for _, v := range fids {
if v <= 0 {
return
}
}
if rm, err = s.dao.RelationsCache(c, mid, fids); err != nil {
err = nil
} else if len(rm) > 0 {
delete(rm, 0)
return
}
if fs, err = s.followings(c, mid); err != nil {
return
} else if len(fs) == 0 {
rm = _emptyFollowingMap
return
}
rm = make(map[int64]*model.Following, len(fids))
arm = make(map[int64]*model.Following, len(fs))
for _, f = range fs {
arm[f.Mid] = f
}
for _, fid = range fids {
if f, ok = arm[fid]; ok {
rm[f.Mid] = f
}
}
return
}

View File

@@ -0,0 +1,75 @@
package service
import (
"context"
memrpc "go-common/app/service/main/member/api/gorpc"
"go-common/app/service/main/relation/conf"
"go-common/app/service/main/relation/dao"
"go-common/app/service/main/relation/model"
"go-common/library/log"
)
var (
_emptyFollowings = make([]*model.Following, 0)
_emptyFollowingMap = make(map[int64]*model.Following)
)
const (
// UserBlockedStatus -2 is blocked.
UserBlockedStatus = -2
// UserRank value
UserRank = 5000
)
// Service struct of service.
type Service struct {
dao *dao.Dao
// conf
c *conf.Config
// cache
missch chan func()
inCh chan interface{}
memberRPC *memrpc.Service
}
// New create service instance and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
missch: make(chan func(), 10240),
inCh: make(chan interface{}, 10240),
memberRPC: memrpc.New(c.RPCClient2.Member),
}
go s.cacheproc()
go s.infocproc()
return
}
// Ping check server ok
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close dao
func (s *Service) Close() {
s.dao.Close()
}
// addCache
func (s *Service) addCache(f func()) {
select {
case s.missch <- f:
default:
log.Warn("cacheproc chan full")
}
}
// cacheproc is a routine for executing closure.
func (s *Service) cacheproc() {
for {
f := <-s.missch
f()
}
}

View File

@@ -0,0 +1,160 @@
package service
import (
"context"
"time"
"go-common/app/service/main/relation/model"
"go-common/library/database/sql"
"go-common/library/log"
)
func (s *Service) initStat(c context.Context, mid int64, fid int64) (err error) {
var (
s1, s2 *model.Stat
es = &model.Stat{}
now = time.Now()
)
if s1, err = s.Stat(c, mid); err != nil {
return
} else if s1.Empty() {
if _, err = s.dao.AddStat(c, mid, es, now); err != nil {
return
}
}
if s2, err = s.Stat(c, fid); err != nil {
return
} else if s2.Empty() {
if _, err = s.dao.AddStat(c, fid, es, now); err != nil {
return
}
}
return
}
func (s *Service) txStat(c context.Context, tx *sql.Tx, mid, fid int64) (mst *model.Stat, sst *model.Stat, err error) {
// NOTE avoid db deadlock
if mid < fid {
if mst, err = s.dao.TxStat(c, tx, mid); err != nil {
return
}
if sst, err = s.dao.TxStat(c, tx, fid); err != nil {
return
}
} else {
if sst, err = s.dao.TxStat(c, tx, fid); err != nil {
return
}
if mst, err = s.dao.TxStat(c, tx, mid); err != nil {
return
}
}
return
}
// Stat get stat.
func (s *Service) Stat(c context.Context, mid int64) (stat *model.Stat, err error) {
if mid <= 0 {
return
}
var mc = true
if stat, err = s.dao.StatCache(c, mid); err != nil {
err = nil // ignore error
mc = false
} else if stat != nil {
return
}
if stat, err = s.dao.Stat(c, mid); err != nil {
return
}
if stat == nil {
stat = &model.Stat{
Mid: mid,
}
}
if mc {
s.addCache(func() {
s.dao.SetStatCache(context.TODO(), mid, stat)
})
}
return
}
// Stats get stats.
func (s *Service) Stats(c context.Context, mids []int64) (sts map[int64]*model.Stat, err error) {
for _, v := range mids {
if v <= 0 {
return
}
}
var (
cache = true
miss []int64
)
if sts, miss, err = s.dao.StatsCache(c, mids); err != nil {
err = nil // ignore error
cache = false
} else if len(miss) == 0 {
return
}
for _, i := range miss {
mid := i
var stat *model.Stat
if stat, err = s.dao.Stat(c, mid); err != nil {
return
}
if stat == nil {
stat = &model.Stat{
Mid: mid,
}
}
sts[mid] = stat
if cache {
s.addCache(func() {
s.dao.SetStatCache(context.TODO(), mid, stat)
})
}
}
return
}
// SetStat set stat.
func (s *Service) SetStat(c context.Context, mid int64, st *model.Stat) (err error) {
if mid <= 0 {
return
}
var (
tx *sql.Tx
nst *model.Stat
now = time.Now()
)
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if nst, err = s.dao.TxStat(c, tx, mid); err != nil {
return
}
if nst == nil {
nst = new(model.Stat)
}
nst.Fill(st)
_, err = s.dao.TxSetStat(c, tx, mid, nst, now)
return
}
// DelStatCache delete stat cache.
func (s *Service) DelStatCache(c context.Context, mid int64) (err error) {
err = s.dao.DelStatCache(c, mid)
return
}

View File

@@ -0,0 +1,497 @@
package service
import (
"context"
"regexp"
"sort"
"time"
"go-common/app/service/main/relation/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
var (
_tagRegexp = regexp.MustCompile(`^[A-Z0-9a-z\x{4e00}-\x{9fa5}]+$`) // only letter digital and chinese legal.
_keepedTagName = []string{"默认分组", "特别关注", "悄悄关注", "互相关注", "被任命为骑士团"}
_tagNumLimit = 15 + 2
_tagLenLimit = 16
)
// Tag get user list by tagid.
func (s *Service) Tag(c context.Context, mid int64, tagid int64, ip string) (tagInfo []int64, err error) {
if mid <= 0 {
return
}
var (
fs []*model.Following
)
tagInfo = make([]int64, 0)
alltags, err := s.tags(c, mid)
if err != nil {
return
}
if _, ok := alltags[tagid]; !ok && tagid != -10 {
err = ecode.RelTagNotExist
return
}
if fs, err = s.followings(c, mid); err != nil || fs == nil {
return
}
var list []*model.Following
for _, f := range fs {
if !f.Following() {
continue
}
if tagid == 0 {
if len(f.Tag) == 0 {
list = append(list, f)
continue
}
var exist = false
for _, t := range f.Tag {
if _, ok := alltags[t]; ok {
exist = true
break
}
}
if !exist {
list = append(list, f)
}
continue
}
for _, ft := range f.Tag {
if tagid == ft {
list = append(list, f)
}
}
}
sort.Slice(list, func(i, j int) bool { return list[i].MTime > list[j].MTime })
for _, f := range list {
tagInfo = append(tagInfo, f.Mid)
}
return
}
// Tags get tag list.
func (s *Service) Tags(c context.Context, mid int64, ip string) (tags []*model.TagCount, err error) {
if mid <= 0 {
return
}
if tags, err = s.dao.TagCountCache(c, mid); err != nil {
return
} else if tags != nil {
return
}
var (
fs []*model.Following
)
if fs, err = s.followings(c, mid); err != nil || fs == nil {
return
}
alltags, err := s.tags(c, mid)
if err != nil {
return
}
tc := make(map[int64]int64, len(alltags))
// init tag count
for tid := range alltags {
tc[tid] = 0
}
for _, f := range fs {
var deleted = true
if !f.Following() {
continue
}
if len(f.Tag) == 0 {
tc[0]++
} else {
for _, v := range f.Tag {
if _, ok := tc[v]; ok {
tc[v]++
deleted = false
}
}
if deleted {
tc[0]++
}
}
}
for k, v := range tc {
tmp := &model.TagCount{Tagid: k, Name: alltags[k].Name, Count: v}
tags = append(tags, tmp)
}
sort.Slice(tags, func(i, j int) bool { return tags[i].Tagid < tags[j].Tagid })
s.addCache(func() {
s.dao.SetTagCountCache(context.Background(), mid, tags)
})
return
}
// UserTag get user tags.
func (s *Service) UserTag(c context.Context, mid int64, fid int64, ip string) (tags map[int64]string, err error) {
if mid <= 0 || fid <= 0 {
return
}
if mid == fid {
return
}
var (
mpf map[int64]*model.Following
tag *model.TagUser
)
tags = make(map[int64]string)
if mpf, err = s.dao.RelationsCache(c, mid, []int64{fid}); err != nil {
return
} else if mpf != nil {
if tag, ok := mpf[fid]; ok {
return s.tagidToName(c, mid, tag.Tag)
}
}
if tag, err = s.dao.TagUserByMidFid(c, mid, fid); err != nil {
return
} else if tag == nil || len(tag.Tag) == 0 {
return
}
return s.tagidToName(c, mid, tag.Tag)
}
func (s *Service) tagidToName(c context.Context, mid int64, tagids []int64) (ttn map[int64]string, err error) {
if len(tagids) == 0 {
return
}
alltags, err := s.tags(c, mid)
if err != nil {
return
}
ttn = make(map[int64]string)
for _, id := range tagids {
if tag, ok := alltags[id]; ok {
ttn[tag.Id] = tag.Name
}
}
return
}
// CreateTag add tag.
func (s *Service) CreateTag(c context.Context, mid int64, tagStr string, ip string) (res int64, err error) {
if mid <= 0 {
return
}
for _, v := range _keepedTagName {
if tagStr == v {
err = ecode.RelTagExisted
return
}
}
if len([]rune(tagStr)) > _tagLenLimit {
err = ecode.RelTagLenLimit
return
}
if !s.tagCheck(tagStr) {
err = ecode.RelTagExistNotAllowedWords
return
}
tags, err := s.dao.Tags(c, mid)
if err != nil {
return
}
if len(tags) >= _tagNumLimit {
err = ecode.RelTagNumLimit
return
}
for _, tag := range tags {
if tag.Name == tagStr {
err = ecode.RelTagExisted
return
}
}
res, err = s.dao.AddTag(c, mid, mid, tagStr, time.Now())
s.addCache(func() {
s.dao.DelTagCountCache(context.Background(), mid)
s.dao.DelTagsCache(context.Background(), mid)
})
return
}
// UpdateTag update tag name.
func (s *Service) UpdateTag(c context.Context, mid int64, tagID int64, newTag string, ip string) (err error) {
if mid <= 0 {
return
}
for _, v := range _keepedTagName {
if newTag == v {
err = ecode.RelTagExisted
return
}
}
if len([]rune(newTag)) > _tagLenLimit {
err = ecode.RelTagLenLimit
return
}
if !s.tagCheck(newTag) {
err = ecode.RelTagExistNotAllowedWords
return
}
alltags, err := s.tags(c, mid)
if err != nil {
return
}
if _, ok := alltags[tagID]; !ok {
err = ecode.RelTagNotExist
return
}
for _, tag := range alltags {
if tag.Name == newTag {
err = ecode.RelTagExisted
return
}
}
if _, err = s.dao.SetTagName(c, tagID, mid, newTag, time.Now()); err != nil {
return
}
s.addCache(func() {
s.dao.DelTagCountCache(context.Background(), mid)
s.dao.DelTagsCache(context.Background(), mid)
})
return
}
// DelTag del user tg by tagid.
func (s *Service) DelTag(c context.Context, mid int64, tagID int64, ip string) (err error) {
if mid <= 0 {
return
}
if _, err = s.dao.DelTag(c, mid, tagID); err != nil {
return
}
s.addCache(func() {
s.dao.DelTagCountCache(context.Background(), mid)
s.dao.DelTagsCache(context.Background(), mid)
})
return
}
// TagsMoveUsers move user to new tags from beforeid.
// if beforeid equal zero,just copy
func (s *Service) TagsMoveUsers(c context.Context, mid, beforeid int64, afterIdsStr, fidStr string, ip string) (err error) {
if mid <= 0 {
return
}
var (
fids []int64
tids []int64
rms []*model.Following
mtags map[int64]*model.Tag
)
if tids, err = xstr.SplitInts(afterIdsStr); err != nil || len(tids) > _tagNumLimit {
err = ecode.RequestErr
return
}
if fids, err = xstr.SplitInts(fidStr); err != nil {
err = ecode.RequestErr
return
}
// 判断是否已经关注
if rms, err = s.dao.FollowingsIn(c, mid, fids); err != nil {
return
}
if len(rms) != len(fids) {
err = ecode.RelTagAddFollowingFirst
return
}
for _, v := range rms {
if !v.Following() {
err = ecode.RelTagAddFollowingFirst
return
}
}
if mtags, err = s.dao.Tags(c, mid); err != nil {
return
}
tmpTids := make([]int64, 0)
for _, tid := range tids {
if tid == 0 {
continue
}
if tag, ok := mtags[tid]; !ok || tag.Status != 0 {
err = ecode.RelTagNotExist
return
}
tmpTids = append(tmpTids, tid)
}
utags, err := s.dao.UsersTags(c, mid, fids)
if err != nil {
return
}
for _, fid := range fids {
var atags []int64
if tag, ok := utags[fid]; ok {
btags := make(map[int64]struct{})
for _, tid := range tag.Tag {
if mtag, ok := mtags[tid]; ok && mtag.Status == 0 && tid != beforeid {
btags[mtag.Id] = struct{}{}
}
}
for _, t := range tmpTids {
btags[t] = struct{}{}
}
for tid := range btags {
atags = append(atags, tid)
}
} else {
atags = tmpTids
}
// TODO:add all user in once.
_, err = s.dao.AddTagUser(c, mid, fid, atags, time.Now())
}
return
}
// TagsAddUsers add user to tidStr.
func (s *Service) TagsAddUsers(c context.Context, mid int64, tidStr, fidStr string, ip string) (err error) {
if mid <= 0 {
return
}
var (
fids []int64
tids []int64
rms []*model.Following
mtags map[int64]*model.Tag
)
if tids, err = xstr.SplitInts(tidStr); err != nil || len(tids) > _tagNumLimit {
err = ecode.RequestErr
return
}
if fids, err = xstr.SplitInts(fidStr); err != nil {
err = ecode.RequestErr
return
}
// 判断是否已经关注
if rms, err = s.dao.FollowingsIn(c, mid, fids); err != nil {
return
}
if len(rms) != len(fids) {
err = ecode.RelTagAddFollowingFirst
return
}
for _, v := range rms {
if !v.Following() {
err = ecode.RelTagAddFollowingFirst
return
}
}
if mtags, err = s.dao.Tags(c, mid); err != nil {
return
}
tmpTids := make([]int64, 0)
for _, tid := range tids {
if tid == 0 {
continue
}
if tag, ok := mtags[tid]; !ok || tag.Status != 0 {
log.Warn("Invalid tag id: %d: tag not exist", tid)
continue
// err = ecode.RelTagNotExist
// return
}
tmpTids = append(tmpTids, tid)
}
for _, fid := range fids {
_, err = s.dao.AddTagUser(c, mid, fid, tmpTids, time.Now())
}
return
}
func (s *Service) tagCheck(tag string) bool {
return _tagRegexp.MatchString(tag)
}
func (s *Service) tags(c context.Context, mid int64) (alltags map[int64]*model.Tag, err error) {
alltags, err = s.dao.TagsCache(c, mid)
if err != nil {
return
}
// cache miss.
if len(alltags) == 0 {
alltags, err = s.dao.Tags(c, mid)
if err != nil {
return
}
s.addCache(func() {
s.dao.SetTagsCache(context.Background(), mid, &model.Tags{Tags: alltags})
})
}
return
}
// DelTagCache all tag related cache.
func (s *Service) DelTagCache(c context.Context, mid int64) (err error) {
if err = s.DelFollowingCache(c, mid); err != nil {
return
}
if err = s.dao.DelTagCountCache(c, mid); err != nil {
return
}
if err = s.dao.DelTagsCache(c, mid); err != nil {
return
}
return s.dao.DelStatCache(c, mid)
}
// AddSpecial add fid to special list.
func (s *Service) AddSpecial(c context.Context, mid, fid int64) (err error) {
rl, err := s.Relation(c, mid, fid)
if err != nil {
return
}
if rl == nil || !rl.Following() {
err = ecode.RelTagAddFollowingFirst
return
}
var sp bool
for _, id := range rl.Tag {
if id == -10 {
sp = true
}
}
ids := rl.Tag
if !sp {
ids = append(ids, -10)
s.dao.AddTagUser(c, mid, fid, ids, time.Now())
} else {
err = ecode.RelFollowAttrAlreadySet
}
return
}
// DelSpecial del fid from special list.
func (s *Service) DelSpecial(c context.Context, mid, fid int64) (err error) {
rl, err := s.Relation(c, mid, fid)
if err != nil {
return
}
if rl == nil || !rl.Following() {
err = ecode.RelTagAddFollowingFirst
return
}
var ids []int64
for _, id := range rl.Tag {
if id != -10 {
ids = append(ids, id)
}
}
// no special before.
if len(ids) == len(rl.Tag) {
err = ecode.RelFollowAttrNotSet
return
}
s.dao.AddTagUser(c, mid, fid, ids, time.Now())
return
}
// Special get special list.
func (s *Service) Special(c context.Context, mid int64) (list []int64, err error) {
return s.Tag(c, mid, -10, "")
}

View File

@@ -0,0 +1,265 @@
package service
import (
"context"
"sort"
"time"
"go-common/app/service/main/relation/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
)
// Whispers get user's whisper list.
func (s *Service) Whispers(c context.Context, mid int64) (fs []*model.Following, err error) {
if fs, err = s.followings(c, mid); err != nil {
return
}
fs = model.Filter(fs, model.AttrWhisper)
sort.Sort(model.SortFollowings(fs))
return
}
// AddWhisper add whisper.
func (s *Service) AddWhisper(c context.Context, mid, fid int64, src uint8, ric map[string]string) (err error) {
var (
a, ra uint32
na, nra uint32
at uint32
st *model.Stat
tx *sql.Tx
n = new(model.Stat)
rn = new(model.Stat)
now = time.Now()
audit *model.Audit
monitor bool
)
if mid <= 0 || fid <= 0 {
return
}
if mid == fid {
err = ecode.RelFollowSelfBanned
return
}
if monitor, err = s.Monitor(c, fid); err != nil {
return
} else if monitor {
return
}
realIP := ric[RelInfocIP]
if audit, err = s.Audit(c, mid, realIP); err != nil {
log.Error("s.Audit.mid(%d) error(%v) return(%v)", mid, err, audit)
return
}
if audit.Blocked {
err = ecode.UserDisabled
return
}
if audit.Rank == UserRank && !audit.BindTel {
err = ecode.RelFollowReachTelLimit
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if _, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if ra, err = s.dao.Relation(c, fid, mid); err != nil {
return
}
at = model.Attr(a)
switch at {
case model.AttrBlack:
err = ecode.RelFollowAlreadyBlack
return
case model.AttrWhisper:
return
}
na = model.AttrWhisper
switch at {
case model.AttrFriend:
nra = model.UnsetAttr(ra, model.AttrFriend)
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollowing(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, fid, mid, nra, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxDelTagUser(c, tx, mid, fid); err != nil {
return
}
n.Following = -1
n.Whisper = 1
case model.AttrFollowing:
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, na, src, model.StatusOK, now); err != nil {
return
}
if _, err = s.dao.TxDelTagUser(c, tx, mid, fid); err != nil {
return
}
n.Following = -1
n.Whisper = 1
case model.AttrNoRelation:
if st, err = s.dao.TxStat(c, tx, mid); err != nil {
return
} else if st != nil && st.Count() >= s.c.Relation.MaxFollowingLimit {
err = ecode.RelFollowReachMaxLimit
return
}
if _, err = s.dao.TxAddFollowing(c, tx, mid, fid, na, src, now); err != nil {
return
}
if _, err = s.dao.TxAddFollower(c, tx, mid, fid, na, src, now); err != nil {
return
}
n.Whisper = 1
rn.Follower = 1
}
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
_, err = s.dao.TxAddStat(c, tx, fid, rn, now)
}
s.RelationInfoc(mid, fid, now.Unix(), realIP, ric[RelInfocSid], ric[RelInfocBuvid], addWhisperURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: na,
FromRevAttr: ra,
ToRevAttr: nra,
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": addWhisperURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.AddWhisperLog(c, l)
return
}
// DelWhisper del whisper.
func (s *Service) DelWhisper(c context.Context, mid, fid int64, src uint8, ric map[string]string) (err error) {
var (
a uint32
tx *sql.Tx
n = new(model.Stat)
rn = new(model.Stat)
now = time.Now()
realIP = ric[RelInfocIP]
)
if mid <= 0 || fid <= 0 {
return
}
if mid == fid {
err = ecode.RelFollowSelfBanned
return
}
if err = s.initStat(c, mid, fid); err != nil {
return
}
if tx, err = s.dao.BeginTran(c); err != nil {
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if _, _, err = s.txStat(c, tx, mid, fid); err != nil {
return
}
if a, err = s.dao.Relation(c, mid, fid); err != nil {
return
}
if model.AttrWhisper != model.Attr(a) {
s.CompareAndDelCache(c, mid, fid, a)
// err = ecode.RelFollowAttrNotSet
log.Warn("Invalid state between %d and %d with attribute: %d", mid, fid, a)
return
}
if _, err = s.dao.TxSetFollowing(c, tx, mid, fid, model.AttrNoRelation, src, model.StatusDel, now); err != nil {
return
}
if _, err = s.dao.TxSetFollower(c, tx, mid, fid, model.AttrNoRelation, src, model.StatusDel, now); err != nil {
return
}
n.Whisper = -1
rn.Follower = -1
if !n.Empty() {
if _, err = s.dao.TxAddStat(c, tx, mid, n, now); err != nil {
return
}
}
if !rn.Empty() {
_, err = s.dao.TxAddStat(c, tx, fid, rn, now)
}
s.RelationInfoc(mid, fid, now.Unix(), ric[RelInfocIP], ric[RelInfocSid], ric[RelInfocBuvid], delWhisperURL, ric[RelInfocReferer], ric[RelInfocUA], src)
// log to report
l := &model.RelationLog{
Mid: mid,
Fid: fid,
Ts: now.Unix(),
Ip: realIP,
Source: uint32(src),
FromAttr: a,
ToAttr: model.AttrNoRelation,
FromRevAttr: 0, // no means on whisper
ToRevAttr: 0, // no means on whisper
Content: map[string]string{
"sid": ric[RelInfocSid],
"buvid": ric[RelInfocBuvid],
"url": delWhisperURL,
"referer": ric[RelInfocReferer],
"user-agent": ric[RelInfocUA],
},
}
s.dao.DelWhisperLog(c, l)
return
}