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

View File

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

View File

@@ -0,0 +1,66 @@
# v1.0.19
修复user_base edit的时候重置了user_type的问题
重新生成pb
修复login的panic
# v1.0.18
修复新用户login时候返回Qing_xxxx给用户
# v1.0.17
添加cms_tag的修改接口
# v1.0.16
对于user_statistics计数统计由于user不存在时候认为成功
# v1.0.15
对于新登录用户插入mysql中为Qing_{{mid}}但是返回给客户端展示为B站昵称
# v1.0.14
未配置databus则不进行消费避免预发布消费线上数据
# v1.0.13
修复user_list可能出现的丢失bug
# v1.0.12
禁止Qing_的前缀的用户名
# v1.0.11
添加运营注册名保护
# v1.0.10
添加用户是否存在的校验
# v1.0.9
监听BFS databus
点赞取消赞增加回包结构为了告知上游是否成功修改db了
# v1.0.8
增加UP作品播放量更新接口
# v1.0.7
修改location因表结构变更导致的bug
去除降级去B站获取用户数据
添加uname修改的屏蔽词校验
# v1.0.6
用户签名数据暂时隐藏
# v1.0.5
用户作品数fix
# v1.0.4
增加是否绑定手机号grpc接口
# v1.0.3
增加IsLike接口
# v1.0.2
90|女 -》 [90, 女]
### v1.0.1
完善user服务
添加ut
给审核提供详情查询接口&用户修改接口
### v1.0.0
1. 上线功能xxx

View File

@@ -0,0 +1,8 @@
# Owner
luxiaowei
jiangdongqi
daiwei
# Author
# Reviewer

View File

@@ -0,0 +1,12 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- daiwei
- jiangdongqi
- luxiaowei
labels:
- bbq
- service
- service/bbq/user
options:
no_parent_owners: true

View File

@@ -0,0 +1,12 @@
# user-service
## 项目简介
1.
## 编译环境
## 依赖包
## 编译执行

View File

@@ -0,0 +1,65 @@
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 = [
"@com_google_protobuf//:empty_proto",
"@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/bbq/user/api",
proto = ":api_proto",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
],
)
go_library(
name = "go_default_library",
srcs = ["user.go"],
embed = [":api_go_proto"],
importpath = "go-common/app/service/bbq/user/api",
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",
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//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"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,188 @@
syntax = "proto3";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "google/protobuf/empty.proto";
package bbq.service.user.v1;
option go_package = "api";
option (gogoproto.goproto_getters_all) = false;
////////////////////////////////Relation Service/////////////////////////////////
message ListRelationUserInfoReq {
int64 mid = 1 [(gogoproto.moretags)='form:"mid"'];
int64 up_mid = 2 [(gogoproto.moretags)='form:"up_mid" validate:"required"'];
string cursor_prev = 3 [(gogoproto.moretags)='form:"cursor_prev"'];
string cursor_next = 4 [(gogoproto.moretags)='form:"cursor_next"'];
}
message ListUserInfoReply {
bool has_more = 1 [(gogoproto.jsontag) = "has_more"];
repeated UserInfo list = 2 [(gogoproto.jsontag) = "list,omitempty"];
}
message ListRelationReq {
int64 mid = 1[(gogoproto.moretags)='form:"mid" validate:"required"'];
}
message ListRelationReply {
repeated int64 list = 1 [(gogoproto.jsontag) = "list,omitempty"];
}
message ModifyRelationReq {
int64 mid = 1 [(gogoproto.moretags)='form:"mid" validate:"required"'];
int64 up_mid = 2 [(gogoproto.moretags)='form:"up_mid" validate:"required"'];
int32 action = 3 [(gogoproto.moretags)='form:"action" validate:"required"'];
}
message ModifyRelationReply {
int32 follow_state = 1 [(gogoproto.jsontag) = "follow_state", (gogoproto.casttype) = "int8"];
}
////////////////////////////////User Service/////////////////////////////////
message ListUserInfoReq {
int64 mid = 1 [(gogoproto.moretags)='form:"mid"'];
repeated int64 up_mid = 2 [(gogoproto.moretags)='form:"up_mid,omitempty" validate:"required"'];
// 是否需要用户信息数组,如区域、性别的字符串化
bool need_desc = 3 [(gogoproto.moretags)='form:"need_desc"'];
// 用户统计信息UserStat
bool need_stat = 4 [(gogoproto.moretags)='form:"need_stat"'];
// 用户关注信息是否需要
bool need_follow_state = 5 [(gogoproto.moretags)='form:"need_follow_state"'];
}
// 获取UserInfo的配置默认是只有UserBase的
message ListUserInfoConf {
// 是否需要用户信息数组,如区域、性别的字符串化
bool need_desc = 1 [(gogoproto.moretags)='form:"need_desc"'];
// 用户统计信息UserStat
bool need_stat = 2 [(gogoproto.moretags)='form:"need_stat"'];
// 用户关注信息是否需要
bool need_follow_state = 3 [(gogoproto.moretags)='form:"need_follow_state"'];
}
message PhoneCheckReq {
int64 mid = 1 [(gogoproto.moretags)='form:"mid"'];
}
message PhoneCheckReply {
int32 tel_status = 2;// 0未绑定1已绑定有效手机号
}
////////////////////////////////Like Service/////////////////////////////////
message LikeReq {
int64 mid = 1 [(gogoproto.moretags)='form:"mid" validate:"required"'];
int64 up_mid = 2 [(gogoproto.moretags)='form:"up_mid" validate:"required"'];
int64 opid = 3 [(gogoproto.moretags)='form:"opid" validate:"required"'];
}
message LikeReply {
int64 affected_num = 1; // 返回是否操作了数据库
}
message ListUserLikeReq {
// int64 mid = 1 [(gogoproto.moretags)='form:"mid"'];
int64 up_mid = 2 [(gogoproto.moretags)='form:"up_mid" validate:"required"'];
string cursor_prev = 3 [(gogoproto.moretags)='form:"cursor_prev"'];
string cursor_next = 4 [(gogoproto.moretags)='form:"cursor_next"'];
}
message LikeSv {
int64 svid = 1 [(gogoproto.jsontag) = "svid"];
string cursor_value = 2 [(gogoproto.jsontag) = "cursor_value"];
}
message ListUserLikeReply {
bool has_more = 1 [(gogoproto.jsontag) = "has_more"];
repeated LikeSv list = 2 [(gogoproto.jsontag) = "list,omitempty"];
}
message IsLikeReq {
int64 mid = 1[(gogoproto.moretags)='form:"mid"'];
repeated int64 svids = 2[(gogoproto.moretags)='form:"svids"'];
}
message IsLikeReply {
repeated int64 list = 1[(gogoproto.jsontag) = "has_more,omitempty"];
}
////////////////////////////////User Service/////////////////////////////////
service User {
rpc Login (UserBase) returns (UserBase);
rpc PhoneCheck (PhoneCheckReq) returns (PhoneCheckReply);
rpc UserEdit (UserBase) returns (.google.protobuf.Empty);
rpc ListUserInfo (ListUserInfoReq) returns (ListUserInfoReply);
/////////////like/////////////
rpc AddLike (LikeReq) returns (LikeReply);
rpc CancelLike (LikeReq) returns (LikeReply);
rpc ListUserLike(ListUserLikeReq) returns (ListUserLikeReply);
rpc IsLike(IsLikeReq) returns (IsLikeReply);
////////////Relation///////////
rpc ModifyRelation (ModifyRelationReq) returns (ModifyRelationReply);
// 返回UserInfo的列表分页
rpc ListFollowUserInfo (ListRelationUserInfoReq) returns (ListUserInfoReply);
rpc ListFanUserInfo (ListRelationUserInfoReq) returns (ListUserInfoReply);
rpc ListBlackUserInfo (ListRelationUserInfoReq) returns (ListUserInfoReply);
// 仅仅返回全部mid列表不包含UserInfo
rpc ListFollow (ListRelationReq) returns (ListRelationReply);
rpc ListBlack (ListRelationReq) returns (ListRelationReply);
}
////////////////////////////////UserInfo/////////////////////////////////
message UserBase {
int64 mid = 1 [(gogoproto.jsontag) = "mid", (gogoproto.moretags)='form:"mid"'];
string uname = 2 [(gogoproto.jsontag) = "uname", (gogoproto.moretags)='form:"uname"'];
string face = 3 [(gogoproto.jsontag) = "face", (gogoproto.moretags)='form:"face"'];
string birthday = 4 [(gogoproto.jsontag) = "birthday", (gogoproto.moretags)='form:"birthday"'];
int64 exp = 5 [(gogoproto.moretags)='form:"exp"'];
int64 level = 6 [(gogoproto.moretags)='form:"level"'];
int64 ctime = 7 [(gogoproto.casttype) = "go-common/library/time.Time"];
int64 mtime = 8 [(gogoproto.casttype) = "go-common/library/time.Time"];
int32 sex = 9 [(gogoproto.jsontag) = "sex", (gogoproto.moretags)='form:"sex"', (gogoproto.casttype) = "int8"];
int64 region = 10[(gogoproto.jsontag) = "region", (gogoproto.moretags)='form:"region"'];
string signature = 11[(gogoproto.jsontag) = "signature", (gogoproto.moretags)='form:"signature"'];
int32 user_type = 12[(gogoproto.jsontag) = "user_type", (gogoproto.casttype) = "int8"];
int32 complete_degree = 13[(gogoproto.jsontag) = "complete_degree", (gogoproto.casttype) = "int8"];
int32 new_tag = 14[(gogoproto.jsontag) = "new_tag", (gogoproto.moretags)='form:"new_tag"', (gogoproto.casttype) = "int8"]; // 是否是新注册用户
string region_name = 21 [(gogoproto.jsontag) = "region_name"];
repeated string user_desc = 22 [(gogoproto.jsontag) = "user_desc,omitempty"];
string face_uptime = 23 [(gogoproto.jsontag) = "face_uptime", (gogoproto.moretags)='form:"uptime"'];
}
message UserStat {
int64 sv = 1 [(gogoproto.jsontag) = "sv"];
int64 like = 2 [(gogoproto.jsontag) = "like"];
int64 liked = 3 [(gogoproto.jsontag) = "liked"];
int64 follow = 4 [(gogoproto.jsontag) = "follow"];
int64 fan = 5 [(gogoproto.jsontag) = "fan"];
int64 black = 6 [(gogoproto.jsontag) = "black"];
int64 view = 7 [(gogoproto.jsontag) = "view"];
}
message UserInfo {
UserBase user_base = 1 [(gogoproto.jsontag) = "user_base,omitempty"];
UserStat user_stat = 2 [(gogoproto.jsontag) = "user_stat,omitempty"];
int32 follow_state = 3 [(gogoproto.jsontag) = "follow_state", (gogoproto.casttype) = "int8"];
string cursor_value = 4 [(gogoproto.jsontag) = "cursor_value"];
}
message LocationItem {
int32 id = 1[(gogoproto.jsontag) = "id"];
int32 pid = 2[(gogoproto.jsontag) = "pid"];
string name = 3[(gogoproto.jsontag) = "name"];
repeated LocationItem child = 4[(gogoproto.jsontag) = "child,omitempty"];
}
message UserVideoView {
int64 mid = 1 [(gogoproto.moretags)='form:"mid"'];
int64 views = 2 [(gogoproto.moretags)='form:"views"'];
}
//message LocationAllReq {
// int64 version = 1[(gogoproto.moretags)='form:"version"'];
//}
//
//message LocationAllReply {
// bool need_update = 1[(gogoproto.jsontag) = "need_update"];
// repeated LocationItem list = 2 [(gogoproto.jsontag) = "list,omitempty"];
//}
//
//service Location {
// rpc LocationAll(LocationAllReq) returns (LocationAllReply);
//}

View File

@@ -0,0 +1,33 @@
package api
// int的action
const (
FollowAdd int32 = 1
FollowCancel int32 = 2
BlackAdd int32 = 3
BlackCancel int32 = 4
)
// relation list type
const (
Follow int32 = 1
Fan int32 = 2
Black int32 = 4
)
//ForbidRequest ..
type ForbidRequest struct {
MID uint64 `json:"mid" form:"mid" validate:"required,gt=0"`
ExpireTime uint64 `json:"expire_time" form:"expire_time" validate:"required,gt=0"`
}
//ReleaseRequest ..
type ReleaseRequest struct {
MID uint64 `json:"mid" form:"mid" validate:"required,gt=0"`
}
// CmsTagRequest 修改cms_tag的请求
type CmsTagRequest struct {
Mid int64 `json:"mid" form:"mid" validate:"required"`
CmsTag int64 `json:"cms_tag" form:"cms_tag"`
}

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 = ["test.toml"],
importpath = "go-common/app/service/bbq/user/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/bbq/user/internal/conf:go_default_library",
"//app/service/bbq/user/internal/server/grpc:go_default_library",
"//app/service/bbq/user/internal/server/http:go_default_library",
"//app/service/bbq/user/internal/service:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/trace: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,69 @@
package main
import (
"context"
"flag"
"go-common/library/conf/paladin"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/service/bbq/user/internal/conf"
"go-common/app/service/bbq/user/internal/server/grpc"
"go-common/app/service/bbq/user/internal/server/http"
"go-common/app/service/bbq/user/internal/service"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/net/trace"
)
var (
_confName string
_unameConfName string
)
func init() {
//线下使用
flag.StringVar(&_confName, "conf_name", "user.toml", "default config filename")
flag.StringVar(&_unameConfName, "uname_conf_name", "uname.json", "default config filename")
}
func main() {
flag.Parse()
if err := paladin.Init(); err != nil {
panic(err)
}
if err := paladin.Watch(_confName, conf.Conf); err != nil {
panic(err)
}
if err := paladin.Watch(_unameConfName, conf.UnameConf); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("user-service start")
trace.Init(conf.Conf.Tracer)
defer trace.Close()
ecode.Init(conf.Conf.Ecode)
svc := service.New(conf.Conf)
grpcServ := grpc.New(conf.Conf.GRPC, svc)
http.Init(conf.Conf, svc)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
svc.Close()
grpcServ.Shutdown(context.Background())
log.Info("user-service exit")
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,77 @@
[log]
stdout = true
# dir = "./log/bbq"
v = 15
[grpc]
timeout = "500ms"
addr = "0.0.0.0:9004"
[bm]
addr = "0.0.0.0:8802"
timeout = "2s"
[mysql]
addr = "172.16.38.91:3306"
dsn = "root:123456@tcp(172.16.38.91:3306)/bbq?allowNativePasswords=true&timeout=800ms&readTimeout=1200ms&writeTimeout=800ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"
readDSN = ["root:123456@tcp(172.16.38.91:3306)/bbq?allowNativePasswords=true&timeout=800ms&readTimeout=1200ms&writeTimeout=800ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"]
active = 20
idle = 10
idleTimeout ="4h"
queryTimeout = "800ms"
execTimeout = "800ms"
tranTimeout = "1000ms"
[redis]
name = "bbq-web"
proto = "tcp"
addr = "172.16.38.91:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1m"
[grpcClient]
[grpcClient.account]
addr = "discovery://default/account.service"
[grpcClient.account.wardenconf]
dial = "100ms"
timeout = "500ms"
[grpcClient.notice]
addr = "discovery://default/bbq.service.notice"
[grpcClient.notice.wardenconf]
dial = "3000ms"
timeout = "5000ms"
[grpcClient.filter]
addr = "discovery://default/filter.service"
[grpcClient.filter.wardenconf]
dial = "100ms"
timeout = "500ms"
[databus]
[databus.bfs]
key = "36ff3e402f7c310a"
secret = "dbd11b140486dc0bc263cf7ec540186c"
group = "Bfs-Yellow-Res-BbqBbq-S"
topic = "Bfs-Yellow-Res-T"
action = "sub"
buffer = 1024
name = "history"
proto = "tcp"
addr = "172.22.33.174:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[databus.bfs.discovery]
domain = "api.bilibili.co"
key = "c8c48e784e05acfb"
secret = "aa63ee0a10afa358d02a07e7abcec546"
region = "sh"
zone = "sh001"
env = "uat"

View File

@@ -0,0 +1,3 @@
{
"forbidden_uname": ["叨叨的小号", "bptest0277"]
}

View File

@@ -0,0 +1,40 @@
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/bbq/user/internal/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus: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,79 @@
package conf
import (
"context"
"encoding/json"
"go-common/library/cache/redis"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"github.com/BurntSushi/toml"
)
var (
// Conf config
Conf = &Config{}
// UnameConf .
UnameConf = &UnameConfig{}
)
// UnameConfig .
type UnameConfig struct {
ForbiddenUname []string `json:"forbidden_uname"`
unameSet map[string]bool
}
// UnameForbidden 判断uname是否被禁用
func (c *UnameConfig) UnameForbidden(uname string) bool {
_, exists := c.unameSet[uname]
return exists
}
// Set .
func (c *UnameConfig) Set(text string) error {
log.Infow(context.Background(), "log", "reload uname config")
if err := json.Unmarshal([]byte(text), &UnameConf); err != nil {
panic(err)
}
c.unameSet = make(map[string]bool)
for _, uname := range c.ForbiddenUname {
c.unameSet[uname] = true
}
log.Infow(context.Background(), "log", "reload uname config succ", "uname_size", len(c.unameSet))
return nil
}
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
Verify *verify.Config
Tracer *trace.Config
Redis *redis.Config
MySQL *sql.Config
Ecode *ecode.Config
GRPC *warden.ServerConfig
GRPCClient map[string]*GRPCConf
Databus map[string]*databus.Config
}
//GRPCConf .
type GRPCConf struct {
WardenConf *warden.ClientConfig
Addr string
}
// Set .
func (c *Config) Set(text string) error {
log.Infow(context.Background(), "log", "reload config")
if _, err := toml.Decode(text, c); err != nil {
panic(err)
}
return nil
}

View File

@@ -0,0 +1,89 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dao.cache.go",
"dao.go",
"forbid_user.go",
"location.go",
"monkey.go",
"user.go",
"user_base.go",
"user_black.go",
"user_card.go",
"user_fan.go",
"user_follow.go",
"user_like.go",
"user_stat.go",
],
importpath = "go-common/app/service/bbq/user/internal/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/bbq/notice-service/api/v1:go_default_library",
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/user/internal/conf:go_default_library",
"//app/service/bbq/user/internal/model:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/filter/api/grpc/v1: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/metadata:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/bouk/monkey: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 = [
"dao.cache_test.go",
"dao_test.go",
"user_base_test.go",
"user_black_test.go",
"user_card_test.go",
"user_fan_test.go",
"user_follow_test.go",
"user_like_test.go",
"user_stat_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/user/internal/conf:go_default_library",
"//app/service/bbq/user/internal/model:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/log:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,102 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
/*
Package dao is a generated cache proxy package.
It is generated from:
type _cache interface {
// cache: -batch=10 -max_group=10 -batch_err=break -nullcache=&api.UserBase{Mid:-1} -check_null_code=$==nil||$.Mid==-1
UserBase(c context.Context, mid []int64) (map[int64]*api.UserBase, error)
}
*/
package dao
import (
"context"
"sync"
"go-common/app/service/bbq/user/api"
"go-common/library/stat/prom"
"go-common/library/sync/errgroup"
)
var _ _cache
// UserBase get data from cache if miss will call source method, then add to cache.
func (d *Dao) UserBase(c context.Context, keys []int64) (res map[int64]*api.UserBase, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheUserBase(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("UserBase", int64(len(keys)-len(miss)))
for k, v := range res {
if v == nil || v.Mid == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
missData := make(map[int64]*api.UserBase, missLen)
prom.CacheMiss.Add("UserBase", int64(missLen))
var mutex sync.Mutex
group, ctx := errgroup.WithContext(c)
if missLen > 10 {
group.GOMAXPROCS(10)
}
var run = func(ms []int64) {
group.Go(func() (err error) {
data, err := d.RawUserBase(ctx, ms)
mutex.Lock()
for k, v := range data {
missData[k] = v
}
mutex.Unlock()
return
})
}
var (
i int
n = missLen / 10
)
for i = 0; i < n; i++ {
run(miss[i*n : (i+1)*n])
}
if len(miss[i*n:]) > 0 {
run(miss[i*n:])
}
err = group.Wait()
if res == nil {
res = make(map[int64]*api.UserBase, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if res[key] == nil {
missData[key] = &api.UserBase{Mid: -1}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheUserBase(c, missData)
})
return
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUserBase(t *testing.T) {
convey.Convey("UserBase", t, func(ctx convey.C) {
var (
c = context.Background()
keys = []int64{88895104, 88895105}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.UserBase(c, keys)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,118 @@
package dao
import (
"context"
"go-common/app/service/bbq/user/api"
"go-common/library/log"
"go-common/library/sync/pipeline/fanout"
notice "go-common/app/service/bbq/notice-service/api/v1"
"go-common/app/service/bbq/user/internal/conf"
acc "go-common/app/service/main/account/api"
filter "go-common/app/service/main/filter/api/grpc/v1"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
"go-common/library/net/rpc/warden"
)
// Dao dao
type Dao struct {
c *conf.Config
cache *fanout.Fanout
redis *redis.Pool
db *xsql.DB
accountClient acc.AccountClient
noticeClient notice.NoticeClient
filterClient filter.FilterClient
}
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// cache: -batch=10 -max_group=10 -batch_err=break -nullcache=&api.UserBase{Mid:-1} -check_null_code=$==nil||$.Mid==-1
UserBase(c context.Context, mid []int64) (map[int64]*api.UserBase, error)
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
redis: redis.NewPool(c.Redis),
db: xsql.NewMySQL(c.MySQL),
accountClient: newAccountClient(c.GRPCClient["account"]),
noticeClient: newNoticeClient(c.GRPCClient["notice"]),
filterClient: newFilterClient(c.GRPCClient["filter"]),
}
return
}
// newNoticeClient .
func newFilterClient(cfg *conf.GRPCConf) filter.FilterClient {
cc, err := warden.NewClient(cfg.WardenConf).Dial(context.Background(), cfg.Addr)
if err != nil {
panic(err)
}
return filter.NewFilterClient(cc)
}
// newNoticeClient .
func newNoticeClient(cfg *conf.GRPCConf) notice.NoticeClient {
cc, err := warden.NewClient(cfg.WardenConf).Dial(context.Background(), cfg.Addr)
if err != nil {
panic(err)
}
return notice.NewNoticeClient(cc)
}
//newAccountClient .
func newAccountClient(cfg *conf.GRPCConf) acc.AccountClient {
cc, err := warden.NewClient(cfg.WardenConf).Dial(context.Background(), cfg.Addr)
if err != nil {
panic(err)
}
return acc.NewAccountClient(cc)
}
// Close close the resource.
func (d *Dao) Close() {
d.redis.Close()
d.db.Close()
}
// Ping dao ping
func (d *Dao) Ping(ctx context.Context) error {
// TODO: add mc,redis... if you use
return d.db.Ping(ctx)
}
// BeginTran begin mysql transaction
func (d *Dao) BeginTran(c context.Context) (*xsql.Tx, error) {
return d.db.Begin(c)
}
// CreateNotice 创建通知
func (d *Dao) CreateNotice(ctx context.Context, notice *notice.NoticeBase) (err error) {
_, err = d.noticeClient.CreateNotice(ctx, notice)
if err != nil {
log.Errorv(ctx, log.KV("log", "create notice fail: notice="+notice.String()))
return
}
log.V(1).Infov(ctx, log.KV("log", "create notice: notice="+notice.String()))
return
}
// Filter .
func (d *Dao) Filter(ctx context.Context, content string, area string) (level int32, err error) {
req := new(filter.FilterReq)
req.Message = content
req.Area = area
reply, err := d.filterClient.Filter(ctx, req)
if err != nil {
log.Errorv(ctx, log.KV("log", "filter fail : req="+req.String()))
return
}
level = reply.Level
log.V(1).Infov(ctx, log.KV("log", "get filter reply="+reply.String()))
return
}

View File

@@ -0,0 +1,39 @@
package dao
import (
"flag"
"go-common/app/service/bbq/user/internal/conf"
"go-common/library/conf/paladin"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "bbq.bbq.user")
flag.Set("conf_token", "3523c3d66dbc91081454d8ef7af9a680")
flag.Set("tree_id", "75578")
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")
} else {
flag.Set("conf", "../../cmd/test.toml")
flag.Set("deploy.env", "uat")
}
flag.Parse()
if err := paladin.Init(); err != nil {
panic(err)
}
if err := paladin.Watch("test.toml", conf.Conf); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,29 @@
package dao
import (
"context"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/log"
)
const (
_insertforbidUser = "insert into forbid_user (`mid`, `expire_time`, `forbid_status`) values (?, ?, ?) on duplicate key update `expire_time` = VALUES(`expire_time`), `forbid_status` = VALUES(`forbid_status`)"
)
//ForbidUser .
func (d *Dao) ForbidUser(c context.Context, mid uint64, exTime uint64) (err error) {
if _, err = d.db.Exec(c, _insertforbidUser, mid, exTime, model.ForbiddenStatus); err != nil {
log.Errorw(c, "event", "ForbidUser", "err", err)
return
}
return
}
//ReleaseUser ..
func (d *Dao) ReleaseUser(c context.Context, mid uint64) (err error) {
if _, err = d.db.Exec(c, _insertforbidUser, mid, 0, model.NormalStatus); err != nil {
log.Errorw(c, "event", "ReleaseUser", "err", err)
return
}
return
}

View File

@@ -0,0 +1,27 @@
package dao
import (
"context"
"go-common/app/service/bbq/user/api"
"go-common/library/database/sql"
"go-common/library/log"
)
var (
locationQuery = "select `loc_id`, `pid`, `name` from `bbq_location` where `loc_id` = ?;"
)
// GetLocation return the location info
func (d *Dao) GetLocation(c context.Context, locId int32) (*api.LocationItem, error) {
row := d.db.QueryRow(c, locationQuery, locId)
var location api.LocationItem
err := row.Scan(&location.Id, &location.Pid, &location.Name)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
log.Errorv(c, log.KV("event", "mysql_select"), log.KV("table", "bbq_location"), log.KV("loc_id", locId))
return nil, err
}
return &location, nil
}

View File

@@ -0,0 +1,337 @@
package dao
import (
"context"
xsql "database/sql"
notice "go-common/app/service/bbq/notice-service/api/v1"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
acc "go-common/app/service/main/account/api"
"go-common/library/database/sql"
"go-common/library/time"
"reflect"
"github.com/bouk/monkey"
)
// MockUserBase .
func (d *Dao) MockUserBase(res map[int64]*api.UserBase, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "UserBase", func(_ *Dao, _ context.Context, _ []int64) (map[int64]*api.UserBase, error) {
return res, err
})
}
// MockPing .
func (d *Dao) MockPing(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "Ping", func(_ *Dao, _ context.Context) error {
return err
})
}
// MockBeginTran .
func (d *Dao) MockBeginTran(p1 *xsql.Tx, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "BeginTran", func(_ *Dao, _ context.Context) (*xsql.Tx, error) {
return p1, err
})
}
// MockCreateNotice .
func (d *Dao) MockCreateNotice(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "CreateNotice", func(_ *Dao, _ context.Context, _ *notice.NoticeBase) error {
return err
})
}
// MockFilter .
func (d *Dao) MockFilter(level int32, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "Filter", func(_ *Dao, _ context.Context, _ string, _ string) (int32, error) {
return level, err
})
}
// MockForbidUser .
func (d *Dao) MockForbidUser(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "ForbidUser", func(_ *Dao, _ context.Context, _ uint64, _ uint64) error {
return err
})
}
// MockReleaseUser .
func (d *Dao) MockReleaseUser(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "ReleaseUser", func(_ *Dao, _ context.Context, _ uint64) error {
return err
})
}
// MockGetLocation .
func (d *Dao) MockGetLocation(p1 *api.LocationItem, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "GetLocation", func(_ *Dao, _ context.Context, _ int32) (*api.LocationItem, error) {
return p1, err
})
}
// MockGetUserBProfile .
func (d *Dao) MockGetUserBProfile(res *acc.ProfileReply, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "GetUserBProfile", func(_ *Dao, _ context.Context, _ *api.PhoneCheckReq) (*acc.ProfileReply, error) {
return res, err
})
}
// MockRawUserBase .
func (d *Dao) MockRawUserBase(res map[int64]*api.UserBase, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "RawUserBase", func(_ *Dao, _ context.Context, _ []int64) (map[int64]*api.UserBase, error) {
return res, err
})
}
// MockCacheUserBase .
func (d *Dao) MockCacheUserBase(res map[int64]*api.UserBase, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "CacheUserBase", func(_ *Dao, _ context.Context, _ []int64) (map[int64]*api.UserBase, error) {
return res, err
})
}
// MockAddCacheUserBase .
func (d *Dao) MockAddCacheUserBase(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "AddCacheUserBase", func(_ *Dao, _ context.Context, _ map[int64]*api.UserBase) error {
return err
})
}
// MockUpdateUserField .
func (d *Dao) MockUpdateUserField(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "UpdateUserField", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ string, _ interface{}) (int64, error) {
return num, err
})
}
// MockAddUserBase .
func (d *Dao) MockAddUserBase(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "AddUserBase", func(_ *Dao, _ context.Context, _ *api.UserBase) (int64, error) {
return num, err
})
}
// MockUpdateUserBaseUname .
func (d *Dao) MockUpdateUserBaseUname(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "UpdateUserBaseUname", func(_ *Dao, _ context.Context, _ int64, _ string) (int64, error) {
return num, err
})
}
// MockUpdateUserBase .
func (d *Dao) MockUpdateUserBase(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "UpdateUserBase", func(_ *Dao, _ context.Context, _ int64, _ *api.UserBase) (int64, error) {
return num, err
})
}
// MockCheckUname .
func (d *Dao) MockCheckUname(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "CheckUname", func(_ *Dao, _ context.Context, _ int64, _ string) error {
return err
})
}
// MockTxAddUserBlack .
func (d *Dao) MockTxAddUserBlack(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxAddUserBlack", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockTxCancelUserBlack .
func (d *Dao) MockTxCancelUserBlack(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxCancelUserBlack", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockFetchBlackList .
func (d *Dao) MockFetchBlackList(upMid []int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "FetchBlackList", func(_ *Dao, _ context.Context, _ int64) ([]int64, error) {
return upMid, err
})
}
// MockFetchPartBlackList .
func (d *Dao) MockFetchPartBlackList(MID2IDMap map[int64]time.Time, blackMIDs []int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "FetchPartBlackList", func(_ *Dao, _ context.Context, _ int64, _ model.CursorValue, _ int) (map[int64]time.Time, []int64, error) {
return MID2IDMap, blackMIDs, err
})
}
// MockIsBlack .
func (d *Dao) MockIsBlack(MIDMap map[int64]bool) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "IsBlack", func(_ *Dao, _ context.Context, _ int64, _ []int64) map[int64]bool {
return MIDMap
})
}
// MockRawUserCard .
func (d *Dao) MockRawUserCard(userCard *model.UserCard, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "RawUserCard", func(_ *Dao, _ context.Context, _ int64) (*model.UserCard, error) {
return userCard, err
})
}
// MockRawUserCards .
func (d *Dao) MockRawUserCards(userCards map[int64]*model.UserCard, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "RawUserCards", func(_ *Dao, _ context.Context, _ []int64) (map[int64]*model.UserCard, error) {
return userCards, err
})
}
// MockRawUserAccCards .
func (d *Dao) MockRawUserAccCards(res *acc.CardsReply, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "RawUserAccCards", func(_ *Dao, _ context.Context, _ []int64) (*acc.CardsReply, error) {
return res, err
})
}
// MockTxAddUserFan .
func (d *Dao) MockTxAddUserFan(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxAddUserFan", func(_ *Dao, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockTxCancelUserFan .
func (d *Dao) MockTxCancelUserFan(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxCancelUserFan", func(_ *Dao, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockIsFan .
func (d *Dao) MockIsFan(MIDMap map[int64]bool) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "IsFan", func(_ *Dao, _ context.Context, _ int64, _ []int64) map[int64]bool {
return MIDMap
})
}
// MockFetchPartFanList .
func (d *Dao) MockFetchPartFanList(MID2IDMap map[int64]time.Time, followedMIDs []int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "FetchPartFanList", func(_ *Dao, _ context.Context, _ int64, _ model.CursorValue, _ int) (map[int64]time.Time, []int64, error) {
return MID2IDMap, followedMIDs, err
})
}
// MockTxAddUserFollow .
func (d *Dao) MockTxAddUserFollow(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxAddUserFollow", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockTxCancelUserFollow .
func (d *Dao) MockTxCancelUserFollow(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxCancelUserFollow", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockFetchFollowList .
func (d *Dao) MockFetchFollowList(upMid []int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "FetchFollowList", func(_ *Dao, _ context.Context, _ int64) ([]int64, error) {
return upMid, err
})
}
// MockFetchPartFollowList .
func (d *Dao) MockFetchPartFollowList(MID2IDMap map[int64]time.Time, followedMIDs []int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "FetchPartFollowList", func(_ *Dao, _ context.Context, _ int64, _ model.CursorValue, _ int) (map[int64]time.Time, []int64, error) {
return MID2IDMap, followedMIDs, err
})
}
// MockIsFollow .
func (d *Dao) MockIsFollow(MIDMap map[int64]bool) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "IsFollow", func(_ *Dao, _ context.Context, _ int64, _ []int64) map[int64]bool {
return MIDMap
})
}
// MockTxAddUserLike .
func (d *Dao) MockTxAddUserLike(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxAddUserLike", func(_ *Dao, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockTxCancelUserLike .
func (d *Dao) MockTxCancelUserLike(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxCancelUserLike", func(_ *Dao, _ *sql.Tx, _ int64, _ int64) (int64, error) {
return num, err
})
}
// MockCheckUserLike .
func (d *Dao) MockCheckUserLike(res []int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "CheckUserLike", func(_ *Dao, _ context.Context, _ int64, _ []int64) ([]int64, error) {
return res, err
})
}
// MockGetUserLikeList .
func (d *Dao) MockGetUserLikeList(likeSvs []*api.LikeSv, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "GetUserLikeList", func(_ *Dao, _ context.Context, _ int64, _ bool, _ model.CursorValue, _ int) ([]*api.LikeSv, error) {
return likeSvs, err
})
}
// MockRawBatchUserStatistics .
func (d *Dao) MockRawBatchUserStatistics(res map[int64]*api.UserStat, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "RawBatchUserStatistics", func(_ *Dao, _ context.Context, _ []int64) (map[int64]*api.UserStat, error) {
return res, err
})
}
// MockTxIncrUserStatisticsFollow .
func (d *Dao) MockTxIncrUserStatisticsFollow(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxIncrUserStatisticsFollow", func(_ *Dao, _ *sql.Tx, _ int64) (int64, error) {
return num, err
})
}
// MockTxIncrUserStatisticsFan .
func (d *Dao) MockTxIncrUserStatisticsFan(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxIncrUserStatisticsFan", func(_ *Dao, _ *sql.Tx, _ int64) (int64, error) {
return num, err
})
}
// MockTxDecrUserStatisticsFollow .
func (d *Dao) MockTxDecrUserStatisticsFollow(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxDecrUserStatisticsFollow", func(_ *Dao, _ *sql.Tx, _ int64) (int64, error) {
return num, err
})
}
// MockTxDecrUserStatisticsFan .
func (d *Dao) MockTxDecrUserStatisticsFan(num int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxDecrUserStatisticsFan", func(_ *Dao, _ *sql.Tx, _ int64) (int64, error) {
return num, err
})
}
// MockTxIncrUserStatisticsField .
func (d *Dao) MockTxIncrUserStatisticsField(rowsAffected int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxIncrUserStatisticsField", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ string) (int64, error) {
return rowsAffected, err
})
}
// MockTxDescUserStatisticsField .
func (d *Dao) MockTxDescUserStatisticsField(rowsAffected int64, err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "TxDescUserStatisticsField", func(_ *Dao, _ context.Context, _ *sql.Tx, _ int64, _ string) (int64, error) {
return rowsAffected, err
})
}
// MockUpdateUserVideoView .
func (d *Dao) MockUpdateUserVideoView(err error) (guard *monkey.PatchGuard) {
return monkey.PatchInstanceMethod(reflect.TypeOf(d), "UpdateUserVideoView", func(_ *Dao, _ context.Context, _ int64, _ int64) error {
return err
})
}

View File

@@ -0,0 +1,123 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
accountv1 "go-common/app/service/main/account/api"
"go-common/library/log"
"go-common/library/time"
"go-common/library/xstr"
)
const (
_userLikeNum = 128
_userFollowNum = 128
_userFanNum = 128
)
//calcTableID .
func (d *Dao) calcTableID(num, mid int64) string {
id := mid % 100
return fmt.Sprintf("%02d", id)
}
func (d *Dao) getTableIndex(mid int64) int64 {
return mid % 100
}
//userLikeSQL .
func (d *Dao) userLikeSQL(mid int64, sql string) string {
tableName := "user_like_" + d.calcTableID(_userLikeNum, mid)
return fmt.Sprintf(sql, tableName)
}
//userFollowSQL .
func (d *Dao) userFollowSQL(mid int64, sql string) string {
tableName := "user_follow_" + d.calcTableID(_userFollowNum, mid)
return fmt.Sprintf(sql, tableName)
}
//userFanSQL .
func (d *Dao) userFanSQL(mid int64, sql string) string {
tableName := "user_fan_" + d.calcTableID(_userFanNum, mid)
return fmt.Sprintf(sql, tableName)
}
// isMidIn 获取mid的关注up主
// 如果key在map中那么value值肯定为1
func (d *Dao) isMidIn(c context.Context, mid int64, candidateMIDs []int64, sql string) (MIDMap map[int64]bool) {
if len(candidateMIDs) == 0 {
return
}
MIDMap = make(map[int64]bool)
tableName := d.getTableIndex(mid)
midstr := xstr.JoinInts(candidateMIDs)
querySQL := fmt.Sprintf(sql, tableName, midstr)
log.V(1).Infov(c, log.KV("event", "fetch_list"), log.KV("sql", querySQL))
rows, err := d.db.Query(c, querySQL, mid)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_select"), log.KV("sql", querySQL))
return
}
defer rows.Close()
for rows.Next() {
var followedMid int64
if err = rows.Scan(&followedMid); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("sql", querySQL))
continue
}
MIDMap[followedMid] = true
}
log.Infov(c, log.KV("event", "mysql_select"), log.KV("sql", querySQL), log.KV("mid", mid),
log.KV("req_size", len(candidateMIDs)), log.KV("rsp_size", len(MIDMap)))
return MIDMap
}
// fetchPartRelationUserList 获取相关的用户列表可以是关注列表也可以是粉丝列表根据sql区别
func (d *Dao) fetchPartRelationUserList(c context.Context, mid int64, cursor model.CursorValue, sql string) (
MID2IDMap map[int64]time.Time, relationMIDs []int64, err error) {
MID2IDMap = make(map[int64]time.Time)
querySQL := fmt.Sprintf(sql, d.getTableIndex(mid), model.UserListLen)
log.V(1).Infov(c, log.KV("event", "fetch_follow_list"), log.KV("sql", querySQL))
rows, err := d.db.Query(c, querySQL, mid, cursor.CursorTime)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_select"), log.KV("sql", querySQL))
return
}
defer rows.Close()
conflict := bool(true)
for rows.Next() {
var relationMID int64
var mtime time.Time
if err = rows.Scan(&relationMID, &mtime); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("sql", querySQL))
return
}
// 为了解决同一个mtime的冲突问题
if mtime == cursor.CursorTime && conflict {
if relationMID == cursor.CursorID {
conflict = false
}
continue
}
relationMIDs = append(relationMIDs, relationMID)
MID2IDMap[relationMID] = mtime
}
log.Infov(c, log.KV("event", "mysql_select"), log.KV("sql", querySQL),
log.KV("mid", mid), log.KV("relation_num", len(relationMIDs)))
return
}
//GetUserBProfile 获取用户全量b站信息
func (d *Dao) GetUserBProfile(c context.Context, in *api.PhoneCheckReq) (res *accountv1.ProfileReply, err error) {
req := &accountv1.MidReq{
Mid: in.Mid,
RealIp: "",
}
res, err = d.accountClient.Profile3(c, req)
return
}

View File

@@ -0,0 +1,281 @@
package dao
import (
"context"
xsql "database/sql"
"encoding/json"
"fmt"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/conf"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/cache/redis"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"strconv"
"strings"
)
const (
_incrUserBase = "insert into user_base (`mid`,`uname`,`face`,`birthday`,`exp`,`level`,`user_type`,`complete_degree`,`sex`) values(?,?,?,?,?,?,?,?,?)"
//_incrUserUname = "insert into user_base (`mid`,`uname`) values(?,?)"
// TODO: 用户签名数据暂时隐藏
// _userBase = "select `mid`,`uname`,`face`,`birthday`,`exp`,`level`,`ctime`,`mtime`,`signature`,`region`,`sex`, `user_type`,`complete_degree`from user_base where `mid` in (%s)"
_userBase = "select `mid`,`uname`,`face`,`birthday`,`exp`,`level`,`ctime`,`mtime`,'',`region`,`sex`, `user_type`,`complete_degree`from user_base where `mid` in (%s)"
_updateUserUname = "update user_base set `uname` = ? where `mid` = ?"
_selectUname = "select `mid` from user_base where `uname` = ? and `mid` != ?"
_selectBZhanUpUname = "select `mid` from user_statistics_hive where `uname` = ? and `mid` != ? and `fan_total` > 10000"
_updateUser = "update user_base set uname=?, face=?, birthday=?, sex=?, region=?, signature=?, complete_degree=? where mid=?"
_updateUserField = "update user_base set `%s` = ? where mid = ?"
)
// keyUserBase 用户基础信息缓存key
func keyUserBase(mid int64) string {
return fmt.Sprintf(model.CacheKeyUserBase, mid)
}
// RawUserBase 从数据库获取用户基础信息
func (d *Dao) RawUserBase(c context.Context, mids []int64) (res map[int64]*api.UserBase, err error) {
if len(mids) == 0 {
return
}
var midStr string
for _, mid := range mids {
if len(midStr) != 0 {
midStr += ","
}
midStr += strconv.FormatInt(mid, 10)
}
querySQL := fmt.Sprintf(_userBase, midStr)
rows, err := d.db.Query(c, querySQL)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_query"), log.KV("error", err), log.KV("sql", querySQL))
return
}
defer rows.Close()
for rows.Next() {
userBase := new(api.UserBase)
if err = rows.Scan(&userBase.Mid,
&userBase.Uname,
&userBase.Face,
&userBase.Birthday,
&userBase.Exp,
&userBase.Level,
&userBase.Ctime,
&userBase.Mtime,
&userBase.Signature,
&userBase.Region,
&userBase.Sex,
&userBase.UserType,
&userBase.CompleteDegree); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("error", err), log.KV("sql", querySQL))
return
}
if res == nil {
res = make(map[int64]*api.UserBase)
}
res[userBase.Mid] = userBase
}
log.Infov(c, log.KV("event", "mysql_query"), log.KV("row_num", len(res)), log.KV("sql", querySQL))
return
}
// CacheUserBase multi get user base from cache.
func (d *Dao) CacheUserBase(c context.Context, mids []int64) (res map[int64]*api.UserBase, err error) {
if res == nil {
res = make(map[int64]*api.UserBase)
}
keys := make([]string, 0, len(mids))
keyMidMap := make(map[int64]bool, len(mids))
for _, mid := range mids {
key := keyUserBase(mid)
if _, exist := keyMidMap[mid]; !exist {
// duplicate mid
keyMidMap[mid] = true
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
for _, key := range keys {
conn.Send("GET", key)
}
conn.Flush()
var data []byte
for i := 0; i < len(keys); i++ {
if data, err = redis.Bytes(conn.Receive()); err != nil {
if err == redis.ErrNil {
err = nil
} else {
log.Errorv(c, log.KV("event", "redis_get"), log.KV("key", keys[i]))
}
continue
}
baseItem := new(api.UserBase)
json.Unmarshal(data, baseItem)
res[baseItem.Mid] = baseItem
}
log.Infov(c, log.KV("event", "redis_get"), log.KV("row_num", len(res)))
return
}
// AddCacheUserBase 添加用户缓存
func (d *Dao) AddCacheUserBase(c context.Context, userBases map[int64]*api.UserBase) (err error) {
keyValueMap := make(map[string][]byte, len(userBases))
for mid, userBase := range userBases {
key := keyUserBase(mid)
if _, exist := keyValueMap[key]; !exist {
data, _ := json.Marshal(userBase)
keyValueMap[key] = data
}
}
conn := d.redis.Get(c)
defer conn.Close()
for key, value := range keyValueMap {
conn.Send("SET", key, value, "EX", model.CacheExpireUserBase)
}
conn.Flush()
for i := 0; i < len(keyValueMap); i++ {
conn.Receive()
}
log.Infov(c, log.KV("event", "redis_set"), log.KV("row_num", len(userBases)))
return
}
//DelCacheUserBase 删除用户缓存
func (d *Dao) DelCacheUserBase(c context.Context, mid int64) {
var key = keyUserBase(mid)
conn := d.redis.Get(c)
defer conn.Close()
conn.Do("DEL", key)
}
////TxAddUserBase .
//func (d *Dao) TxAddUserBase(c context.Context, tx *sql.Tx, userBase *api.UserBase) (num int64, err error) {
// var res xsql.Result
// if res, err = tx.Exec(_incrUserBase, userBase.Mid, userBase.Uname, userBase.Face, userBase.Birthday, userBase.Exp, userBase.Level, userBase.UserType, userBase.CompleteDegree); err != nil {
// log.Error("incr user base err(%v)", err)
// return
// }
// d.DelCacheUserBase(c, userBase.Mid)
// return res.LastInsertId()
//}
//
// UpdateUserField .
func (d *Dao) UpdateUserField(c context.Context, tx *sql.Tx, mid int64, field string, f interface{}) (num int64, err error) {
var res xsql.Result
querySQL := fmt.Sprintf(_updateUserField, field)
if res, err = tx.Exec(querySQL, f, mid); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user mid(%d) field(%s) value(%v) err(%v)", mid, field, f, err)))
return
}
log.V(1).Infow(c, "log", "update user field", "mid", mid, "field", field, "value", f)
d.DelCacheUserBase(c, mid)
return res.RowsAffected()
}
//AddUserBase .
func (d *Dao) AddUserBase(c context.Context, userBase *api.UserBase) (num int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _incrUserBase, userBase.Mid, userBase.Uname, userBase.Face, userBase.Birthday, userBase.Exp, userBase.Level, userBase.UserType, userBase.CompleteDegree, userBase.Sex); err != nil {
log.Error("incr user base err(%v)", err)
return
}
d.DelCacheUserBase(c, userBase.Mid)
return res.LastInsertId()
}
//UpdateUserBaseUname .
func (d *Dao) UpdateUserBaseUname(c context.Context, mid int64, uname string) (num int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _updateUserUname, uname, mid); err != nil {
log.Error("update user base uname err(%v)", err)
return
}
d.DelCacheUserBase(c, mid)
return res.RowsAffected()
}
// UpdateUserBase 更新用户基础信息
func (d *Dao) UpdateUserBase(c context.Context, mid int64, userBase *api.UserBase) (num int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _updateUser, userBase.Uname, userBase.Face, userBase.Birthday, userBase.Sex,
userBase.Region, userBase.Signature, userBase.CompleteDegree, mid); err != nil {
log.Errorv(c, log.KV("event", "mysql_update"), log.KV("mid", mid), log.KV("error", err))
return
}
d.DelCacheUserBase(c, mid)
return res.RowsAffected()
}
// CheckUname 检测昵称
func (d *Dao) CheckUname(c context.Context, mid int64, uname string) (err error) {
// 前缀不能为Qing_
if strings.HasPrefix(uname, "Qing_") {
err = ecode.UserUnamePrefixErr
return
}
//特殊字符
if !model.CheckUnameSpecial(uname) {
err = ecode.UserUnameSpecial
return
}
//字符长度
if !model.CheckUnameLength(uname) {
err = ecode.UserUnameLength
return
}
//bbq是否存在
tmp := int64(0)
row := d.db.QueryRow(c, _selectUname, uname, mid)
if err = row.Scan(&tmp); err != nil && err != sql.ErrNoRows {
err = ecode.EditUserBaseErr
return
}
if tmp != 0 {
err = ecode.UserUnameExisted
log.Infow(c, "log", "uname已存在", "uname", uname, "mid", mid)
return
}
//自己b站的昵称
var userCard *model.UserCard
if userCard, err = d.RawUserCard(c, mid); err != nil {
err = ecode.EditUserBaseErr
return
}
if userCard.Name == uname {
return nil
}
//是否是万粉的昵称
row2 := d.db.QueryRow(c, _selectBZhanUpUname, uname, mid)
if tmpErr := row2.Scan(&tmp); tmpErr != nil && tmpErr != sql.ErrNoRows {
err = ecode.EditUserBaseErr
log.V(1).Infow(c, "log", "获取B站万粉资料为空", "uname", uname)
return
}
if tmp != 0 {
err = ecode.UserUnameExisted
log.Infow(c, "log", "uname命中B站万粉up主", "uname", uname, "mid", mid)
return
}
// 昵称是否包含敏感词
level, filterErr := d.Filter(c, uname, "BBQ_account")
if filterErr != nil {
log.Errorv(c, log.KV("log", "filter fail"))
} else if level >= 20 {
err = ecode.UserUnameFilterErr
log.Warnv(c, log.KV("log", fmt.Sprintf("uname filter fail: uname=%s, level=%d", uname, level)))
return
}
// 运营不允许使用的uname列表中
if conf.UnameConf.UnameForbidden(uname) {
log.Infow(c, "log", "hit fobidden uname", "uname", uname)
err = ecode.UserUnameExisted
return
}
return nil
}

View File

@@ -0,0 +1,276 @@
package dao
import (
"context"
"go-common/app/service/bbq/user/api"
"go-common/library/log"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaokeyUserBase(t *testing.T) {
convey.Convey("keyUserBase", t, func(ctx convey.C) {
var (
mid = int64(88895104)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := keyUserBase(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTmpUserBase(t *testing.T) {
convey.Convey("JustGetUserBase", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{88895104}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.UserBase(c, mids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawUserBase(t *testing.T) {
convey.Convey("RawUserBase", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{88895104}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.RawUserBase(c, mids)
log.Infow(c, "log", "xxxxxxxxxxx", "res", res)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCacheUserBase(t *testing.T) {
convey.Convey("CacheUserBase", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheUserBase(c, mids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddCacheUserBase(t *testing.T) {
convey.Convey("AddCacheUserBase", t, func(ctx convey.C) {
var (
c = context.Background()
userBases map[int64]*api.UserBase
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheUserBase(c, userBases)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelCacheUserBase(t *testing.T) {
convey.Convey("DelCacheUserBase", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
d.DelCacheUserBase(c, mid)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}
//
//func TestDaoTxAddUserBase(t *testing.T) {
// convey.Convey("TxAddUserBase", t, func(ctx convey.C) {
// var (
// c = context.Background()
// tx = &sql.Tx{}
// userBase = &api.UserBase{}
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// num, err := d.TxAddUserBase(c, tx, userBase)
// ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(num, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestDaoAddUserBaseUname(t *testing.T) {
// convey.Convey("AddUserBaseUname", t, func(ctx convey.C) {
// var (
// c = context.Background()
// mid = int64(88895104)
// uname = "dfsfwe"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// num, err := d.AddUserBaseUname(c, mid, uname)
// ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(num, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestDaoAddUserBase(t *testing.T) {
// convey.Convey("AddUserBase", t, func(ctx convey.C) {
// var (
// c = context.Background()
// userBase = &api.UserBase{}
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// num, err := d.AddUserBase(c, userBase)
// ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(num, convey.ShouldNotBeNil)
// })
// })
// })
//}
func TestDaoUpdateUserBaseUname(t *testing.T) {
convey.Convey("UpdateUserBaseUname", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
uname = "sdfewxc"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.UpdateUserBaseUname(c, mid, uname)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUpdateUserBase(t *testing.T) {
convey.Convey("UpdateUserBase", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
userBase = &api.UserBase{Uname: "sdfweo", Sex: 1, Face: "htttttt"}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.UpdateUserBase(c, mid, userBase)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUpdateUserField(t *testing.T) {
convey.Convey("UpdateUserField", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.UpdateUserField(c, tx, mid, "uname", "unnnnnn")
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
convey.Convey("UpdateUserField", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.UpdateUserField(c, tx, mid, "face", "faaaaaa")
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
convey.Convey("UpdateUserField", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.UpdateUserField(c, tx, mid, "region", 11111)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
convey.Convey("update cms_tag", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
//tx.Rollback()
tx.Commit()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.UpdateUserField(c, tx, mid, "cms_tag", 1)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCheckUname(t *testing.T) {
convey.Convey("CheckUname", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
uname = "lkwejroiw"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.CheckUname(c, mid, uname)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,80 @@
package dao
import (
"context"
xsql "database/sql"
"fmt"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/time"
)
const (
_addUserBlack = "insert into user_black_%02d (`mid`,`black_mid`) values (?,?) on duplicate key update state=0"
_cancelUserBlack = "update user_black_%02d set state=1 where mid=? and black_mid=?"
_getUserPartBlacks = "select black_mid, mtime from user_black_%02d where mid=? and state=0 and mtime<=? order by mtime desc, black_mid desc limit %d"
_getUserBlacks = "select black_mid from user_black_%02d where mid=? and state=0"
_isBlack = "select black_mid from user_black_%02d where mid=? and state=0 and black_mid in (%s)"
)
// TxAddUserBlack .
func (d *Dao) TxAddUserBlack(c context.Context, tx *sql.Tx, mid, upMid int64) (num int64, err error) {
var res xsql.Result
// sql中含有ignore该情况为了简化是否已存在的判断
querySQL := fmt.Sprintf(_addUserBlack, d.getTableIndex(mid))
if res, err = tx.Exec(querySQL, mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "add_user_black"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid), log.KV("sql", querySQL))
return
}
return res.RowsAffected()
}
// TxCancelUserBlack .
func (d *Dao) TxCancelUserBlack(c context.Context, tx *sql.Tx, mid, upMid int64) (num int64, err error) {
var res xsql.Result
querySQL := fmt.Sprintf(_cancelUserBlack, d.getTableIndex(mid))
if res, err = tx.Exec(querySQL, mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "cancel_user_black"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid), log.KV("sql", querySQL))
return
}
return res.RowsAffected()
}
// FetchBlackList 获取mid的所有拉黑up主
func (d *Dao) FetchBlackList(c context.Context, mid int64) (upMid []int64, err error) {
querySQL := fmt.Sprintf(_getUserBlacks, d.getTableIndex(mid))
rows, err := d.db.Query(c, querySQL, mid)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_select"), log.KV("table", "user_black"))
return
}
defer rows.Close()
for rows.Next() {
var m int64
if err = rows.Scan(&m); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("table", "user_black"))
return
}
upMid = append(upMid, m)
}
log.Infov(c, log.KV("event", "mysql_select"), log.KV("sql", querySQL),
log.KV("mid", mid), log.KV("black_num", len(upMid)))
return
}
// FetchPartBlackList 获取mid的拉黑up主
func (d *Dao) FetchPartBlackList(c context.Context, mid int64, cursor model.CursorValue, size int) (
MID2IDMap map[int64]time.Time, blackMIDs []int64, err error) {
MID2IDMap, blackMIDs, err = d.fetchPartRelationUserList(c, mid, cursor, _getUserPartBlacks)
return
}
// IsBlack 获取mid的拉黑用户
func (d *Dao) IsBlack(c context.Context, mid int64, candidateMIDs []int64) (MIDMap map[int64]bool) {
if len(candidateMIDs) == 0 {
return
}
MIDMap = d.isMidIn(c, mid, candidateMIDs, _isBlack)
return
}

View File

@@ -0,0 +1,105 @@
package dao
import (
"context"
"github.com/smartystreets/goconvey/convey"
"go-common/app/service/bbq/user/internal/model"
xtime "go-common/library/time"
"testing"
"time"
)
func TestDaoTxAddUserBlack(t *testing.T) {
convey.Convey("TxAddUserBlack", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
upMid = int64(88895105)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxAddUserBlack(c, tx, mid, upMid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxCancelUserBlack(t *testing.T) {
convey.Convey("TxCancelUserBlack", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
upMid = int64(88895105)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxCancelUserBlack(c, tx, mid, upMid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFetchBlackList(t *testing.T) {
convey.Convey("FetchBlackList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
upMid, err := d.FetchBlackList(c, mid)
ctx.Convey("Then err should be nil.upMid should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(upMid, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFetchPartBlackList(t *testing.T) {
convey.Convey("FetchPartBlackList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
cursor model.CursorValue
size = int(10)
)
cursor.CursorID = model.MaxInt64
cursor.CursorTime = xtime.Time(time.Now().Unix())
ctx.Convey("When everything goes positive", func(ctx convey.C) {
MID2IDMap, blackMIDs, err := d.FetchPartBlackList(c, mid, cursor, size)
ctx.Convey("Then err should be nil.MID2IDMap,blackMIDs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(blackMIDs, convey.ShouldNotBeNil)
ctx.So(MID2IDMap, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoIsBlack(t *testing.T) {
convey.Convey("IsBlack", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
candidateMIDs = []int64{88895105}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
MIDMap := d.IsBlack(c, mid, candidateMIDs)
ctx.Convey("Then MIDMap should not be nil.", func(ctx convey.C) {
ctx.So(MIDMap, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,86 @@
package dao
import (
"context"
"go-common/app/service/bbq/user/internal/model"
acc "go-common/app/service/main/account/api"
"go-common/library/log"
"go-common/library/net/metadata"
)
// RawUserCard 从主站获取用户基础信息
func (d *Dao) RawUserCard(c context.Context, mid int64) (userCard *model.UserCard, err error) {
req := &acc.MidReq{
Mid: mid,
RealIp: metadata.String(c, metadata.RemoteIP),
}
res, err := d.accountClient.Card3(c, req)
if err != nil {
log.Error("user card rpc error(%v)", err)
return
}
vipInfo := model.VIPInfo{
Type: res.Card.Vip.Type,
Status: res.Card.Vip.Status,
DueDate: res.Card.Vip.DueDate,
}
userCard = &model.UserCard{
MID: res.Card.Mid,
Name: res.Card.Name,
Sex: res.Card.Sex,
Rank: res.Card.Rank,
Face: res.Card.Face,
Sign: res.Card.Sign,
Level: res.Card.Level,
VIPInfo: vipInfo,
}
return
}
// RawUserCards 从主站获取用户基础信息
func (d *Dao) RawUserCards(c context.Context, mids []int64) (userCards map[int64]*model.UserCard, err error) {
req := &acc.MidsReq{
Mids: mids,
RealIp: metadata.String(c, metadata.RemoteIP),
}
res, err := d.accountClient.Cards3(c, req)
if err != nil {
log.Error("user card rpc error(%v)", err)
return
}
userCards = make(map[int64]*model.UserCard, len(mids))
for _, card := range res.Cards {
vipInfo := model.VIPInfo{
Type: card.Vip.Type,
Status: card.Vip.Status,
DueDate: card.Vip.DueDate,
}
userCard := &model.UserCard{
MID: card.Mid,
Name: card.Name,
Sex: card.Sex,
Rank: card.Rank,
Face: card.Face,
Sign: card.Sign,
Level: card.Level,
VIPInfo: vipInfo,
}
userCards[card.Mid] = userCard
}
return
}
// RawUserAccCards 批量获取账号信息
func (d *Dao) RawUserAccCards(c context.Context, mids []int64) (res *acc.CardsReply, err error) {
req := &acc.MidsReq{
Mids: mids,
RealIp: metadata.String(c, metadata.RemoteIP),
}
res, err = d.accountClient.Cards3(c, req)
if err != nil {
log.Error("d.accountClient.Cards3 err [%v]", err)
}
return
}

View File

@@ -0,0 +1,56 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRawUserCard(t *testing.T) {
convey.Convey("RawUserCard", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
userCard, err := d.RawUserCard(c, mid)
ctx.Convey("Then err should be nil.userCard should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(userCard, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawUserCards(t *testing.T) {
convey.Convey("RawUserCards", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{88895104}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
userCards, err := d.RawUserCards(c, mids)
ctx.Convey("Then err should be nil.userCards should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(userCards, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawUserAccCards(t *testing.T) {
convey.Convey("RawUserAccCards", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{88895104}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.RawUserAccCards(c, mids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,50 @@
package dao
import (
"context"
xsql "database/sql"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/database/sql"
"go-common/library/time"
)
const (
_addUserFan = "insert into %s (`mid`,`fan_mid`) values (?,?) on duplicate key update state=0"
_cancelUserFan = "update %s set state=1 where mid = ? and fan_mid = ?"
_getUserPartFans = "select fan_mid, mtime from user_fan_%02d where mid=? and state=0 and mtime<=? order by mtime desc, fan_mid desc limit %d"
_isFan = "select fan_mid from user_fan_%02d where mid = ? and state=0 and fan_mid in (%s)"
)
//TxAddUserFan .
func (d *Dao) TxAddUserFan(tx *sql.Tx, mid, fanMid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(d.userFanSQL(mid, _addUserFan), mid, fanMid); err != nil {
return
}
return res.LastInsertId()
}
//TxCancelUserFan .
func (d *Dao) TxCancelUserFan(tx *sql.Tx, mid, fanMid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(d.userFanSQL(mid, _cancelUserFan), mid, fanMid); err != nil {
return
}
return res.RowsAffected()
}
// IsFan 获取mid的粉丝
func (d *Dao) IsFan(c context.Context, mid int64, candidateMIDs []int64) (MIDMap map[int64]bool) {
if len(candidateMIDs) == 0 {
return
}
MIDMap = d.isMidIn(c, mid, candidateMIDs, _isFan)
return
}
// FetchPartFanList 获取mid的粉丝列表
func (d *Dao) FetchPartFanList(c context.Context, mid int64, cursor model.CursorValue, size int) (
MID2IDMap map[int64]time.Time, followedMIDs []int64, err error) {
MID2IDMap, followedMIDs, err = d.fetchPartRelationUserList(c, mid, cursor, _getUserPartFans)
return
}

View File

@@ -0,0 +1,89 @@
package dao
import (
"context"
"github.com/smartystreets/goconvey/convey"
"go-common/app/service/bbq/user/internal/model"
xtime "go-common/library/time"
"testing"
"time"
)
func TestDaoTxAddUserFan(t *testing.T) {
convey.Convey("TxAddUserFan", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
fanMid = int64(88895105)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxAddUserFan(tx, mid, fanMid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxCancelUserFan(t *testing.T) {
convey.Convey("TxCancelUserFan", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
fanMid = int64(88895105)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxCancelUserFan(tx, mid, fanMid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoIsFan(t *testing.T) {
convey.Convey("IsFan", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
candidateMIDs = []int64{88895105}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
MIDMap := d.IsFan(c, mid, candidateMIDs)
ctx.Convey("Then MIDMap should not be nil.", func(ctx convey.C) {
ctx.So(MIDMap, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFetchPartFanList(t *testing.T) {
convey.Convey("FetchPartFanList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
cursor model.CursorValue
size = int(10)
)
cursor.CursorID = model.MaxInt64
cursor.CursorTime = xtime.Time(time.Now().Unix())
ctx.Convey("When everything goes positive", func(ctx convey.C) {
MID2IDMap, followedMIDs, err := d.FetchPartFanList(c, mid, cursor, size)
ctx.Convey("Then err should be nil.MID2IDMap,followedMIDs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(followedMIDs, convey.ShouldNotBeNil)
ctx.So(MID2IDMap, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,78 @@
package dao
import (
"context"
xsql "database/sql"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/time"
"go-common/app/service/bbq/user/internal/model"
)
const (
_addUserFollow = "insert into %s (`mid`,`followed_mid`)values(?,?) on duplicate key update state=0"
_cancelUserFollow = "update %s set state=1 where mid=? and followed_mid=?"
_getUserFollows = "select followed_mid from %s where mid = ? and state=0"
_getUserPartFollows = "select followed_mid, mtime from user_follow_%02d where mid=? and state=0 and mtime<=? order by mtime desc, followed_mid desc limit %d"
_isFollow = "select followed_mid from user_follow_%02d where mid=? and state=0 and followed_mid in (%s)"
)
//TxAddUserFollow .
func (d *Dao) TxAddUserFollow(c context.Context, tx *sql.Tx, mid, upMid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(d.userFollowSQL(mid, _addUserFollow), mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "user_follow"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid))
return
}
return res.RowsAffected()
}
//TxCancelUserFollow .
func (d *Dao) TxCancelUserFollow(c context.Context, tx *sql.Tx, mid, upMid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(d.userFollowSQL(mid, _cancelUserFollow), mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "cancel_user_follow"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid))
return
}
return res.RowsAffected()
}
// FetchFollowList 获取mid的所有关注up主
func (d *Dao) FetchFollowList(c context.Context, mid int64) (upMid []int64, err error) {
querySQL := d.userFollowSQL(mid, _getUserFollows)
rows, err := d.db.Query(c, querySQL, mid)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_select"), log.KV("table", "user_follow"))
return
}
defer rows.Close()
for rows.Next() {
var m int64
if err = rows.Scan(&m); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("table", "user_follow"))
return
}
upMid = append(upMid, m)
}
log.Infov(c, log.KV("event", "mysql_select"), log.KV("table", "user_follow"),
log.KV("mid", mid), log.KV("follow_num", len(upMid)))
return
}
// FetchPartFollowList 获取mid的关注up主
// cursor_id代表相应的up_midcursor_time表示mtime
func (d *Dao) FetchPartFollowList(c context.Context, mid int64, cursor model.CursorValue, size int) (
MID2IDMap map[int64]time.Time, followedMIDs []int64, err error) {
MID2IDMap, followedMIDs, err = d.fetchPartRelationUserList(c, mid, cursor, _getUserPartFollows)
return
}
// IsFollow 获取mid的关注up主
func (d *Dao) IsFollow(c context.Context, mid int64, candidateMIDs []int64) (MIDMap map[int64]bool) {
if len(candidateMIDs) == 0 {
return
}
MIDMap = d.isMidIn(c, mid, candidateMIDs, _isFollow)
return
}

View File

@@ -0,0 +1,105 @@
package dao
import (
"context"
"github.com/smartystreets/goconvey/convey"
"go-common/app/service/bbq/user/internal/model"
xtime "go-common/library/time"
"testing"
"time"
)
func TestDaoTxAddUserFollow(t *testing.T) {
convey.Convey("TxAddUserFollow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
upMid = int64(88895105)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxAddUserFollow(c, tx, mid, upMid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxCancelUserFollow(t *testing.T) {
convey.Convey("TxCancelUserFollow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
upMid = int64(88895105)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxCancelUserFollow(c, tx, mid, upMid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFetchFollowList(t *testing.T) {
convey.Convey("FetchFollowList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
upMid, err := d.FetchFollowList(c, mid)
ctx.Convey("Then err should be nil.upMid should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(upMid, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFetchPartFollowList(t *testing.T) {
convey.Convey("FetchPartFollowList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
cursor model.CursorValue
size = int(10)
)
cursor.CursorID = model.MaxInt64
cursor.CursorTime = xtime.Time(time.Now().Unix())
ctx.Convey("When everything goes positive", func(ctx convey.C) {
MID2IDMap, followedMIDs, err := d.FetchPartFollowList(c, mid, cursor, size)
ctx.Convey("Then err should be nil.MID2IDMap,followedMIDs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(followedMIDs, convey.ShouldNotBeNil)
ctx.So(MID2IDMap, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoIsFollow(t *testing.T) {
convey.Convey("IsFollow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
candidateMIDs = []int64{88895105}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
MIDMap := d.IsFollow(c, mid, candidateMIDs)
ctx.Convey("Then MIDMap should not be nil.", func(ctx convey.C) {
ctx.So(MIDMap, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,111 @@
package dao
import (
"context"
xsql "database/sql"
"encoding/json"
"fmt"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/time"
"go-common/library/xstr"
)
//常量
const (
_addUserLike = "insert into %s (`mid`, `opid`) values (?,?) on duplicate key update state=0"
_cancelUserLike = "update %s set state = 1 where mid=? and opid=?"
_selectUserLike = "select opid from user_like_%02d where mid = ? and state = 0 and opid in (%s)"
_spaceUserLike = "select opid, mtime from user_like_%02d where mid=%d and state=0 and mtime %s ? order by mtime %s, opid %s limit %d"
)
//TxAddUserLike .
func (d *Dao) TxAddUserLike(tx *sql.Tx, mid, svid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(d.userLikeSQL(mid, _addUserLike), mid, svid); err != nil {
return
}
return res.RowsAffected()
}
//TxCancelUserLike .
func (d *Dao) TxCancelUserLike(tx *sql.Tx, mid, svid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(d.userLikeSQL(mid, _cancelUserLike), mid, svid); err != nil {
return
}
return res.RowsAffected()
}
// CheckUserLike 检测用户是否点赞
func (d *Dao) CheckUserLike(c context.Context, mid int64, svids []int64) (res []int64, err error) {
log.V(1).Info("user like mid(%d) svids(%v)", mid, svids)
ls := len(svids)
if ls == 0 || mid == 0 {
return
}
idStr := xstr.JoinInts(svids)
querySQL := fmt.Sprintf(_selectUserLike, d.getTableIndex(mid), idStr)
rows, err := d.db.Query(c, querySQL, mid)
log.V(1).Infov(c, log.KV("log", fmt.Sprintf("user like rows(%v) err(%v)", rows, err)))
if err != nil {
if err == sql.ErrNoRows {
err = nil
}
return
}
for rows.Next() {
opid := int64(0)
rows.Scan(&opid)
res = append(res, opid)
}
log.V(1).Infov(c, log.KV("log", fmt.Sprintf("user like res(%v)", res)))
return
}
// GetUserLikeList 返回用户点赞列表
// 当前cursorID表示opid
func (d *Dao) GetUserLikeList(c context.Context, mid int64, cursorNext bool, cursor model.CursorValue, size int) (
likeSvs []*api.LikeSv, err error) {
compareSymbol := string(">=")
orderDirection := "asc"
if cursorNext {
compareSymbol = "<="
orderDirection = "desc"
}
querySQL := fmt.Sprintf(_spaceUserLike, d.getTableIndex(mid), mid, compareSymbol, orderDirection, orderDirection, size)
log.V(1).Infov(c, log.KV("like_list_sql", querySQL))
rows, err := d.db.Query(c, querySQL, cursor.CursorTime)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_select"), log.KV("table", "user_like"),
log.KV("mid", mid), log.KV("sql", querySQL))
return
}
defer rows.Close()
var svID int64
var curMtime time.Time
conflict := bool(true)
for rows.Next() {
if err = rows.Scan(&svID, &curMtime); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("table", "user_like"),
log.KV("sql", querySQL))
return
}
// 为了解决同一个mtime的冲突问题
if curMtime == cursor.CursorTime && conflict {
if svID == cursor.CursorID {
conflict = false
}
continue
}
cursorValue := model.CursorValue{CursorID: svID, CursorTime: curMtime}
jsonStr, _ := json.Marshal(cursorValue) // marshal的时候相信库函数不做err判断
likeSvs = append(likeSvs, &api.LikeSv{Svid: svID, CursorValue: string(jsonStr)})
}
log.Infov(c, log.KV("event", "mysql_select"), log.KV("table", "user_like"),
log.KV("mid", mid), log.KV("id", cursor.CursorID), log.KV("size", len(likeSvs)))
return
}

View File

@@ -0,0 +1,75 @@
package dao
import (
"context"
"go-common/app/service/bbq/user/internal/model"
xtime "go-common/library/time"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoTxAddUserLike(t *testing.T) {
convey.Convey("TxAddUserLike", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
svid = int64(133)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxAddUserLike(tx, mid, svid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxCancelUserLike(t *testing.T) {
convey.Convey("TxCancelUserLike", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
svid = int64(133)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxCancelUserLike(tx, mid, svid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetUserLikeList(t *testing.T) {
convey.Convey("GetUserLikeList", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
cursorNext bool
cursor model.CursorValue
size = int(10)
)
cursorNext = true
cursor.CursorID = model.MaxInt64
cursor.CursorTime = xtime.Time(time.Now().Unix())
ctx.Convey("When everything goes positive", func(ctx convey.C) {
likeSvs, err := d.GetUserLikeList(c, mid, cursorNext, cursor, size)
ctx.Convey("Then err should be nil.likeSvs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(likeSvs, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,121 @@
package dao
import (
"context"
xsql "database/sql"
"fmt"
"go-common/app/service/bbq/user/api"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_batchUserStatistics = "select `mid`,`av_total` - `unshelf_av_total`,`follow_total`,`fan_total`,`like_total`,`rev_like_total`,`black_total`,`play_total` from user_statistics where mid in (%s)"
_incrUserStatisticsFollow = "update user_statistics set `follow_total` = `follow_total` + 1 where `mid` = ?"
_incrUserStatisticsFan = "update user_statistics set `fan_total` = `fan_total` + 1 where `mid` = ?"
_decrUserStatisticsFollow = "update user_statistics set `follow_total` = `follow_total` - 1 where `mid` = ? and `follow_total` > 0"
_decrUserStatisticsFan = "update user_statistics set `fan_total` = `fan_total` - 1 where `mid` = ? and `fan_total` > 0"
_incrUserStatisticField = "insert into user_statistics (`mid`, `%s`) values (?, 1) on duplicate key update `%s` = `%s` + 1"
_decrUserStatisticsField = "update user_statistics set `%s` = `%s` - 1 where `mid` = ? and `%s` > 0"
_updateUserVideoView = "update `user_statistics` set `play_total` = ? where `mid` = ?;"
)
// RawBatchUserStatistics 从数据库获取用户基础信息
func (d *Dao) RawBatchUserStatistics(c context.Context, mids []int64) (res map[int64]*api.UserStat, err error) {
if len(mids) == 0 {
return
}
res = make(map[int64]*api.UserStat)
midStr := xstr.JoinInts(mids)
querySQL := fmt.Sprintf(_batchUserStatistics, midStr)
rows, err := d.db.Query(c, querySQL)
if err != nil {
log.Errorv(c, log.KV("event", "mysql_query"), log.KV("error", err), log.KV("sql", querySQL))
return
}
defer rows.Close()
for rows.Next() {
var mid int64
stat := new(api.UserStat)
if err = rows.Scan(&mid, &stat.Sv, &stat.Follow, &stat.Fan, &stat.Like, &stat.Liked, &stat.Black, &stat.View); err != nil {
log.Errorv(c, log.KV("event", "mysql_scan"), log.KV("error", err), log.KV("sql", querySQL))
return
}
res[mid] = stat
}
log.Infov(c, log.KV("event", "mysql_query"), log.KV("row_num", len(res)), log.KV("sql", querySQL))
return
}
//TxIncrUserStatisticsFollow .
func (d *Dao) TxIncrUserStatisticsFollow(tx *sql.Tx, mid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(_incrUserStatisticsFollow, mid); err != nil {
log.Error("incr user Video follow err(%v)", err)
return
}
return res.RowsAffected()
}
//TxIncrUserStatisticsFan .
func (d *Dao) TxIncrUserStatisticsFan(tx *sql.Tx, mid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(_incrUserStatisticsFan, mid); err != nil {
log.Error("incr user Video fan err(%v)", err)
return
}
return res.RowsAffected()
}
//TxDecrUserStatisticsFollow .
func (d *Dao) TxDecrUserStatisticsFollow(tx *sql.Tx, mid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(_decrUserStatisticsFollow, mid); err != nil {
log.Error("decr user Video follow err(%v)", err)
return
}
return res.RowsAffected()
}
//TxDecrUserStatisticsFan .
func (d *Dao) TxDecrUserStatisticsFan(tx *sql.Tx, mid int64) (num int64, err error) {
var res xsql.Result
if res, err = tx.Exec(_decrUserStatisticsFan, mid); err != nil {
log.Error("decr user Video fan err(%v)", err)
return
}
return res.RowsAffected()
}
//TxIncrUserStatisticsField .
func (d *Dao) TxIncrUserStatisticsField(c context.Context, tx *sql.Tx, mid int64, field string) (rowsAffected int64, err error) {
var res xsql.Result
querySQL := fmt.Sprintf(_incrUserStatisticField, field, field, field)
if res, err = tx.Exec(querySQL, mid); err != nil {
log.Errorv(c, log.KV("event", "incr_user_statistic"), log.KV("field", field), log.KV("mid", mid), log.KV("err", err))
return
}
return res.RowsAffected()
}
//TxDescUserStatisticsField .
func (d *Dao) TxDescUserStatisticsField(c context.Context, tx *sql.Tx, mid int64, field string) (rowsAffected int64, err error) {
var res xsql.Result
querySQL := fmt.Sprintf(_decrUserStatisticsField, field, field, field)
if res, err = tx.Exec(querySQL, mid); err != nil {
log.Errorv(c, log.KV("event", "incr_user_statistic"), log.KV("field", field), log.KV("mid", mid), log.KV("err", err))
return
}
return res.RowsAffected()
}
// UpdateUserVideoView .
func (d *Dao) UpdateUserVideoView(c context.Context, mid int64, views int64) error {
_, err := d.db.Exec(c, _updateUserVideoView, views, mid)
return err
}

View File

@@ -0,0 +1,146 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRawBatchUserStatistics(t *testing.T) {
convey.Convey("RawBatchUserStatistics", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{88895104}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.RawBatchUserStatistics(c, mids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxIncrUserStatisticsFollow(t *testing.T) {
convey.Convey("TxIncrUserStatisticsFollow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxIncrUserStatisticsFollow(tx, mid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxIncrUserStatisticsFan(t *testing.T) {
convey.Convey("TxIncrUserStatisticsFan", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxIncrUserStatisticsFan(tx, mid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxDecrUserStatisticsFollow(t *testing.T) {
convey.Convey("TxDecrUserStatisticsFollow", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxDecrUserStatisticsFollow(tx, mid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxDecrUserStatisticsFan(t *testing.T) {
convey.Convey("TxDecrUserStatisticsFan", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num, err := d.TxDecrUserStatisticsFan(tx, mid)
ctx.Convey("Then err should be nil.num should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(num, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxIncrUserStatisticsField(t *testing.T) {
convey.Convey("TxIncrUserStatisticsField", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
field = "like_total"
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rowsAffected, err := d.TxIncrUserStatisticsField(c, tx, mid, field)
ctx.Convey("Then err should be nil.rowsAffected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rowsAffected, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTxDescUserStatisticsField(t *testing.T) {
convey.Convey("TxDescUserStatisticsField", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(88895104)
field = "like_total"
)
tx, _ := d.BeginTran(c)
defer func() {
tx.Rollback()
}()
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rowsAffected, err := d.TxDescUserStatisticsField(c, tx, mid, field)
ctx.Convey("Then err should be nil.rowsAffected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rowsAffected, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"model.go",
"user.go",
"user_face.go",
],
importpath = "go-common/app/service/bbq/user/internal/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//library/time: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,53 @@
package model
import (
"encoding/json"
"go-common/library/ecode"
xtime "go-common/library/time"
"time"
)
const (
// MaxInt64 用于最大int64
MaxInt64 = int64(^uint64(0) >> 1)
// UserListLen 空间长度
UserListLen = 20
)
// Cache
const (
CacheKeyUserBase = "user_base:%d" //用户基本信息缓存key
CacheExpireUserBase = 600
)
// CursorValue 用于cursor的定位这里可以当做通用结构使用使用者自己根据需求定义cursor_id的含义
type CursorValue struct {
CursorID int64 `json:"cursor_id"`
CursorTime xtime.Time `json:"cursor_time"`
}
// ParseCursor 从cursor_prev和cursor_next判断请求的方向以及生成cursor
func ParseCursor(cursorPrev string, cursorNext string) (cursor CursorValue, directionNext bool, err error) {
// 判断是向前还是向后查询
directionNext = true
cursorStr := cursorNext
if len(cursorNext) == 0 && len(cursorPrev) > 0 {
directionNext = false
cursorStr = cursorPrev
}
// 解析cursor中的cursor_id
if len(cursorStr) != 0 {
var cursorData = []byte(cursorStr)
err = json.Unmarshal(cursorData, &cursor)
if err != nil {
err = ecode.ReqParamErr
return
}
}
// 第一次请求的时候携带的svid=0需要转成max传给dao层
if directionNext && cursor.CursorID == 0 {
cursor.CursorID = MaxInt64
cursor.CursorTime = xtime.Time(time.Now().Unix())
}
return
}

View File

@@ -0,0 +1,108 @@
package model
import (
"regexp"
"strings"
)
const (
//UserTypeUp up主
UserTypeUp = int8(1)
//UserTypeBili b站用户
UserTypeBili = int8(2)
//UserTypeNew 新注册用户
UserTypeNew = int8(3)
//DegreeUncomp 未完成状态
DegreeUncomp = int8(0)
//DegreeComp 完成状态
DegreeComp = int8(1)
//SexMan 男
SexMan = int8(1)
//SexWoman 女
SexWoman = int8(2)
//SexAnimal 不明生物
SexAnimal = int8(0)
)
// UserListType 用于指定列表类型
type UserListType int8
// UserListType的列表类型
const (
FollowListType UserListType = 1
FanListType UserListType = 2
BlackListType UserListType = 4
//ForbiddenStatus .
ForbiddenStatus = 1
//NormalStatus .
NormalStatus = 0
)
const (
// SpaceListLen 空间长度
SpaceListLen = 20
// BatchUserLen 批量请求用户信息时最大数量
BatchUserLen = 50
// MaxBlacklistLen 黑名单最大长度
MaxBlacklistLen = 200
// MaxFollowListLen 关注最大数
MaxFollowListLen = 1000
)
// UserCard 主站返回的用户信息
type UserCard struct {
MID int64 `json:"mid"`
Name string `json:"name"`
Uname string `json:"uname"` // TODO: to delete
Sex string `json:"sex"`
Rank int32 `json:"rank"`
Face string `json:"face"`
Sign string `json:"sign"`
Level int32 `json:"level"`
VIPInfo VIPInfo `json:"vip_info"`
}
// UserInfoConfig 用于请求UserInfo的时候携带的参数
type UserInfoConfig struct {
//needBase bool // 必须基于UserBase信息
NeedDesc bool // 注意desc和region_name一起可能被降级因为用户统计信息被认为是不重要信息
NeedStatistic bool // 注意:可能被降级,因为用户统计信息被认为是不重要信息
NeedFollowState bool // 注意:可能被降级,因为关注关系信息被认为是不重要信息
}
//UpUserInfoRes account服务返回信息
type UpUserInfoRes struct {
MID int64 `json:"mid"`
Name string `json:"name"`
Sex string `json:"sex"`
Face string `json:"face"`
Sign string `json:"sign"`
Rank int64 `json:"rank"`
}
//VIPInfo .
type VIPInfo struct {
Type int32 `json:"type"`
Status int32 `json:"status"`
DueDate int64 `json:"due_date"`
}
// CheckUnameSpecial 验证是否含有特殊字符
func CheckUnameSpecial(uname string) (matched bool) {
matched, _ = regexp.MatchString("^[A-Za-z0-9\uAC00-\uD788\u3041-\u309E\u30A1-\u30FE\u3131-\u3163\u4E00-\u9FA5\uF92C-\uFA29_-]{1,}$", uname)
return
}
//CheckUnameLength 验证长度
func CheckUnameLength(uname string) (matched bool) {
lu := strings.Count(uname, "") - 1
if lu < 3 || lu > 16 {
return false
}
bt := []byte(uname)
if len(bt) < 3 || len(bt) > 30 {
return false
}
return true
}

View File

@@ -0,0 +1,15 @@
package model
// UserFaceBFS .
type UserFaceBFS struct {
URL string `json:"url,omitempty"`
FileName string `json:"file_name,omitempty"`
Bucket string `json:"bucket,omitempty"`
Sex float32 `json:"sex,omitempty"`
Violent float32 `json:"violent,omitempty"`
Blood float32 `json:"blood,omitempty"`
Politics float32 `json:"politics,omitempty"`
IsYellow bool `json:"is_yellow,omitempty"`
ErrCode int32 `json:"error_code,omitempty"`
ErrMsg string `json:"error_msg,omitempty"`
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "go-common/app/service/bbq/user/internal/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/user/internal/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"],
)

View File

@@ -0,0 +1,18 @@
package grpc
import (
pb "go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/service"
"go-common/library/net/rpc/warden"
)
// New new warden rpc server
func New(c *warden.ServerConfig, svc *service.Service) *warden.Server {
ws := warden.NewServer(c)
pb.RegisterUserServer(ws.Server(), svc)
ws, err := ws.Start()
if err != nil {
panic(err)
}
return ws
}

View File

@@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"http.go",
"user.go",
],
importpath = "go-common/app/service/bbq/user/internal/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/user/internal/conf:go_default_library",
"//app/service/bbq/user/internal/service:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify: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,54 @@
package http
import (
"net/http"
"go-common/app/service/bbq/user/internal/conf"
"go-common/app/service/bbq/user/internal/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
vfy *verify.Verify
svc *service.Service
)
// Init init
func Init(c *conf.Config, s *service.Service) {
svc = s
vfy = verify.New(c.Verify)
engine := bm.DefaultServer(c.BM)
route(engine)
if err := engine.Start(); err != nil {
log.Error("bm Start error(%v)", err)
panic(err)
}
}
func route(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
g := e.Group("/x/user")
{
g.GET("/black/list", userBlackList)
g.GET("/info/list", userInfoList)
g.POST("/modify", auditModifyUser)
g.POST("/modify/cms_tag", auditModifyCmsTag)
g.POST("/forbid", forbidUser)
g.POST("/release", releaseUser)
g.POST("/video/view", updateUserVideoView)
}
}
func ping(ctx *bm.Context) {
if err := svc.Ping(ctx); err != nil {
log.Error("ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,280 @@
package http
import (
"go-common/app/service/bbq/user/api"
bm "go-common/library/net/http/blademaster"
"github.com/pkg/errors"
)
//
////userBase .
//func userBase(c *bm.Context) {
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// c.JSON(srv.UserBase(c, mid.(int64)))
//}
//
////spaceUserProfile ...
//func spaceUserProfile(c *bm.Context) {
// arg := new(v1.SpaceUserProfileRequest)
// mid := int64(0)
// midValue, exists := c.Get("mid")
// if exists {
// mid = midValue.(int64)
// }
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// if arg.Upmid == 0 {
// c.JSON(nil, ecode.ReqParamErr)
// return
// }
// c.JSON(srv.SpaceUserProfile(c, mid, arg))
//}
//
////userEdit
//func userBaseEdit(c *bm.Context) {
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// arg := new(v1.UserBaseEditRequest)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// c.JSON(srv.UserEdit(c, mid.(int64), arg))
//}
//
////addUserLike .
//func addUserLike(c *bm.Context) {
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// arg := new(v1.UserLikeAddRequest)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// resp, err := srv.AddUserLike(c, mid.(int64), arg.SVID)
// c.JSON(resp, err)
//
// // 埋点
// if err == nil {
// uiLog(c, model.ActionLike, nil)
// }
//}
//
////cancelUserLike .
//func cancelUserLike(c *bm.Context) {
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// arg := new(v1.UserLikeCancelRequest)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// resp, err := srv.CancelUserLike(c, mid.(int64), arg.SVID)
// c.JSON(resp, err)
//
// // 埋点
// if err == nil {
// uiLog(c, model.ActionCancelLike, nil)
// }
//}
//
////userLikeList .
//func userLikeList(c *bm.Context) {
// arg := &v1.SpaceSvListRequest{}
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// mid, exists := c.Get("mid")
// if exists {
// arg.MID = mid.(int64)
// }
// dev, _ := c.Get("device")
// arg.Device = dev.(*bm.Device)
// arg.Size = model.SpaceListLen
//
// c.JSON(srv.UserLikeList(c, arg))
//}
//
//
////userFollowList .
//func userFollowList(c *bm.Context) {
// arg := new(api.ListRelationUserInfoReq)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// c.JSON(svc.ListFollowUserInfo(c, arg))
//}
//
////userFanList .
//func userFanList(c *bm.Context) {
// arg := new(api.ListRelationUserInfoReq)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// c.JSON(svc.ListFanUserInfo(c, arg))
//}
//
////userInfoBlackList .
//func userInfoBlackList(c *bm.Context) {
// arg := new(api.ListRelationUserInfoReq)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// c.JSON(svc.ListBlackUserInfo(c, arg))
//}
//userBlackList .
func userBlackList(c *bm.Context) {
arg := new(api.ListRelationReq)
if err := c.Bind(arg); err != nil {
errors.Wrap(err, "参数验证失败")
return
}
c.JSON(svc.ListBlack(c, arg))
}
// userInfo used for audit
func userInfoList(c *bm.Context) {
arg := new(api.ListUserInfoReq)
if err := c.Bind(arg); err != nil {
errors.Wrap(err, "参数验证失败")
return
}
c.JSON(svc.ListUserInfo(c, arg))
}
// userInfo used for audit
func auditModifyUser(c *bm.Context) {
arg := new(api.UserBase)
if err := c.Bind(arg); err != nil {
errors.Wrap(err, "参数验证失败")
return
}
c.JSON(svc.UserFieldEdit(c, arg))
}
// userInfo used for audit
func auditModifyCmsTag(c *bm.Context) {
arg := new(api.CmsTagRequest)
if err := c.Bind(arg); err != nil {
errors.Wrap(err, "参数验证失败")
return
}
c.JSON(svc.UserCmsTagEdit(c, arg))
}
func forbidUser(c *bm.Context) {
arg := new(api.ForbidRequest)
if err := c.Bind(arg); err != nil {
errors.Wrap(err, "参数校验失败")
return
}
c.JSON(svc.ForbidUser(c, arg))
}
func releaseUser(c *bm.Context) {
arg := new(api.ReleaseRequest)
if err := c.Bind(arg); err != nil {
errors.Wrap(err, "参数校验失败")
return
}
c.JSON(svc.ReleaseUser(c, arg))
}
////userRelationModify .
//func userRelationModify(c *bm.Context) {
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// arg := new(v1.UserRelationRequest)
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// var res *v1.RelationResponse
// var err error
// var reportAction int
// switch arg.Action {
// case v1.FollowAdd:
// res, err = srv.AddUserFollow(c, mid.(int64), arg.UPMID)
// reportAction = model.ActionFollow
// case v1.FollowCancel:
// res, err = srv.CancelUserFollow(c, mid.(int64), arg.UPMID)
// reportAction = model.ActionCancelFollow
// case v1.BlackAdd:
// res, err = srv.AddUserBlack(c, mid.(int64), arg.UPMID)
// case v1.BlackCancel:
// res, err = srv.CancelUserBlack(c, mid.(int64), arg.UPMID)
// default:
// errors.Wrap(ecode.ReqParamErr, "参数验证失败")
// return
// }
// c.JSON(res, err)
//
// // 埋点
// if err == nil && reportAction != 0 {
// ext := struct {
// UPMID int64 `json:"up_mid"`
// }{
// UPMID: arg.UPMID,
// }
// uiLog(c, reportAction, ext)
// }
//}
//
////login 登陆
//func login(c *bm.Context) {
// arg := new(v1.LoginRequest)
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// if err := c.Bind(arg); err != nil {
// errors.Wrap(err, "参数验证失败")
// return
// }
// c.JSON(srv.Login(c, arg.NewTag, mid.(int64)))
//}
//
////手机验证
//func phoneCheck(c *bm.Context) {
// mid, exists := c.Get("mid")
// if !exists {
// c.JSON(nil, ecode.NoLogin)
// return
// }
// c.JSON(srv.PhoneCheck(c, mid.(int64)))
//}
// updateUserVideoView 更新用户作品播放量
func updateUserVideoView(c *bm.Context) {
args := &api.UserVideoView{}
if err := c.Bind(args); err != nil {
errors.Wrap(err, "参数校验失败")
return
}
c.JSON(svc.UpdateUserVideoView(c, args))
}

View File

@@ -0,0 +1,69 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"like.go",
"relation.go",
"service.go",
"user.go",
"user_face.go",
],
importpath = "go-common/app/service/bbq/user/internal/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/bbq/notice-service/api/v1:go_default_library",
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/user/internal/conf:go_default_library",
"//app/service/bbq/user/internal/dao:go_default_library",
"//app/service/bbq/user/internal/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
],
)
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 = [
"like_test.go",
"relation_test.go",
"service_test.go",
"user_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/user/internal/conf:go_default_library",
"//app/service/bbq/user/internal/model:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,180 @@
package service
import (
"context"
"fmt"
notice "go-common/app/service/bbq/notice-service/api/v1"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/ecode"
"go-common/library/log"
)
// AddLike 添加点赞
func (s *Service) AddLike(c context.Context, in *api.LikeReq) (res *api.LikeReply, err error) {
res = new(api.LikeReply)
mid := in.Mid
svid := in.Opid
upMid := in.UpMid
// 0. 前期up主校验
userReply, err := s.ListUserInfo(c, &api.ListUserInfoReq{UpMid: []int64{mid, upMid}})
if err != nil {
log.Errorw(c, "log", "get user info fail when adding like", "mid", mid, "up_mid", upMid, "err", err)
return
}
if len(userReply.List) != 2 {
log.Errorw(c, "log", "user error when adding like", "mid", mid, "up_mid", upMid, "user_info_reply", userReply.String())
return
}
var num int64
// TODO:考虑消息队列解耦等
tx, err := s.dao.BeginTran(c)
if err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("add user like begin tran err(%v) mid(%d) svid(%d)", err, mid, svid)))
err = ecode.AddUserLikeErr
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if num, err = s.dao.TxAddUserLike(tx, mid, svid); err != nil {
err = ecode.AddUserLikeErr
return
}
res.AffectedNum = num
if num == 0 {
return
}
if _, err = s.dao.TxIncrUserStatisticsField(c, tx, mid, "like_total"); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user statistics like incr err(%v) mid(%d) svid(%d)", err, mid, svid)))
return
}
if _, err = s.dao.TxIncrUserStatisticsField(c, tx, upMid, "rev_like_total"); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user statistics rev_like incr err(%v) mid(%d) svid(%d)", err, mid, svid)))
return
}
// TODO: 推送点赞给通知中心
if upMid == mid {
log.V(1).Infov(c, log.KV("log", "action_mid=mid"), log.KV("mid", mid))
} else {
notice := &notice.NoticeBase{Mid: upMid, ActionMid: mid, SvId: svid, Title: "点赞了你的作品", NoticeType: notice.NoticeTypeLike, BizType: notice.NoticeBizTypeSv}
tmpErr := s.dao.CreateNotice(c, notice)
if tmpErr != nil {
log.Error("create like notice fail: notice_msg=%s", notice.String())
}
}
return
}
// CancelLike 取消点赞
func (s *Service) CancelLike(c context.Context, in *api.LikeReq) (res *api.LikeReply, err error) {
res = new(api.LikeReply)
mid := in.Mid
svid := in.Opid
upMid := in.UpMid
// 0. 前期up主校验
userReply, err := s.ListUserInfo(c, &api.ListUserInfoReq{UpMid: []int64{mid, upMid}})
if err != nil {
log.Errorw(c, "log", "get user info fail when cancelling like", "mid", mid, "up_mid", upMid, "err", err)
return
}
if len(userReply.List) != 2 {
log.Errorw(c, "log", "user error when cancelling like", "mid", mid, "up_mid", upMid, "user_info_reply", userReply.String())
return
}
var num int64
// TODO:考虑消息队列解耦等
tx, err := s.dao.BeginTran(c)
if err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("cancel user like begin tran err(%v) mid(%d) svid(%d)", err, mid, svid)))
err = ecode.AddUserLikeErr
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if num, err = s.dao.TxCancelUserLike(tx, mid, svid); err != nil {
err = ecode.AddUserLikeErr
return
}
res.AffectedNum = num
if num == 0 {
return
}
if num, err = s.dao.TxDescUserStatisticsField(c, tx, mid, "like_total"); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user statistics like incr err(%v) mid(%d) svid(%d)", err, mid, svid)))
return
} else if num == 0 {
// 没能-1也当做成功
log.Errorv(c, log.KV("log", fmt.Sprintf("desc user statistics like incr err(%v) mid(%d) svid(%d)", err, mid, svid)))
return
}
if num, err = s.dao.TxDescUserStatisticsField(c, tx, upMid, "rev_like_total"); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user statistics rev_like incr err(%v) mid(%d) svid(%d)", err, mid, svid)))
return
} else if num == 0 {
// 没能-1也当做成功
log.Errorv(c, log.KV("log", fmt.Sprintf("desc user statistics rev_like incr err(%v) mid(%d) svid(%d)", err, mid, svid)))
return
}
return
}
// ListUserLike 用户的点赞列表
func (s *Service) ListUserLike(ctx context.Context, req *api.ListUserLikeReq) (res *api.ListUserLikeReply, err error) {
res = new(api.ListUserLikeReply)
// 0. 解析cursor
cursor, cursorNext, err := model.ParseCursor(req.CursorPrev, req.CursorNext)
if err != nil {
return
}
// 1. 获取svid列表
res.List, err = s.dao.GetUserLikeList(ctx, req.UpMid, cursorNext, cursor, model.UserListLen)
if err != nil {
log.Errorv(ctx, log.KV("event", "user_like_list"), log.KV("error", err))
return
}
// 这里之所以跟len/2是因为有可能同一时间多个点赞这种情况会出现回包数量<len但是这不代表has_more=false
// TODO: 同样情况还有用户列表,后续考虑优化
if len(res.List) < model.UserListLen/2 {
res.HasMore = false
if len(res.List) == 0 {
return
}
} else {
res.HasMore = true
}
return
}
// IsLike 返回是否点赞,点赞的才会返回
func (s *Service) IsLike(ctx context.Context, req *api.IsLikeReq) (res *api.IsLikeReply, err error) {
res = new(api.IsLikeReply)
res.List, err = s.dao.CheckUserLike(ctx, req.Mid, req.Svids)
if err != nil {
log.Warnv(ctx, log.KV("log", fmt.Sprintf("check user is like fail: req=%s", req.String())))
return
}
return
}

View File

@@ -0,0 +1,75 @@
package service
import (
"context"
"go-common/app/service/bbq/user/api"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceAddLike(t *testing.T) {
convey.Convey("AddLike", t, func(ctx convey.C) {
var (
c = context.Background()
in = &api.LikeReq{Mid: 88895104, UpMid: 88895134, Opid: 2233}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := s.AddLike(c, in)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceCancelLike(t *testing.T) {
convey.Convey("CancelLike", t, func(ctx convey.C) {
var (
c = context.Background()
in = &api.LikeReq{Mid: 88895104, UpMid: 88895134, Opid: 2233}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
s.AddLike(c, &api.LikeReq{Mid: 88895104, UpMid: 88895134, Opid: 2233})
res, err := s.CancelLike(c, in)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceListUserLike(t *testing.T) {
convey.Convey("ListUserLike", t, func(ctx convey.C) {
var (
c = context.Background()
req = &api.ListUserLikeReq{UpMid: 88895104}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := s.ListUserLike(c, req)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceIsLike(t *testing.T) {
convey.Convey("IsLike", t, func(ctx convey.C) {
var (
c = context.Background()
req = &api.IsLikeReq{Mid: 88895104, Svids: []int64{2233}}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
s.AddLike(c, &api.LikeReq{Mid: 88895104, UpMid: 88895134, Opid: 2233})
res, err := s.IsLike(c, req)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,446 @@
package service
import (
"context"
"encoding/json"
"fmt"
notice "go-common/app/service/bbq/notice-service/api/v1"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/time"
)
// ModifyRelation .
func (s *Service) ModifyRelation(c context.Context, arg *api.ModifyRelationReq) (res *api.ModifyRelationReply, err error) {
mid := arg.Mid
upMid := arg.UpMid
res = new(api.ModifyRelationReply)
if mid == upMid {
err = ecode.FollowMyselfErr
return
}
// 0. 前期up主校验
userReply, err := s.ListUserInfo(c, &api.ListUserInfoReq{UpMid: []int64{mid, upMid}})
if err != nil {
log.Errorw(c, "log", "get user info fail when modifying relation", "mid", mid, "up_mid", upMid, "err", err)
return
}
if len(userReply.List) != 2 {
log.Errorw(c, "log", "user error when modifying relation", "mid", mid, "up_mid", upMid, "user_info_reply", userReply.String())
return
}
// 1. 执行relation修改
switch arg.Action {
case api.FollowAdd:
res.FollowState, err = s.addUserFollow(c, mid, upMid)
case api.FollowCancel:
res.FollowState, err = s.cancelUserFollow(c, mid, upMid)
case api.BlackAdd:
res.FollowState, err = s.addUserBlack(c, mid, upMid)
case api.BlackCancel:
res.FollowState, err = s.cancelUserBlack(c, mid, upMid)
default:
err = ecode.ReqParamErr
return
}
return
}
// ListFollowUserInfo 关注列表
// 注意这里出现3种midvisitor/host/host关注的mid按顺序缩写为V、H、HF其中根据H获取HF然后计算V和HF的关注关系
func (s *Service) ListFollowUserInfo(ctx context.Context, in *api.ListRelationUserInfoReq) (res *api.ListUserInfoReply, err error) {
res, err = s.relationUserInfoList(ctx, in, model.FollowListType)
return
}
// ListFanUserInfo .
func (s *Service) ListFanUserInfo(ctx context.Context, in *api.ListRelationUserInfoReq) (res *api.ListUserInfoReply, err error) {
res, err = s.relationUserInfoList(ctx, in, model.FanListType)
return
}
// ListBlackUserInfo .
func (s *Service) ListBlackUserInfo(ctx context.Context, in *api.ListRelationUserInfoReq) (res *api.ListUserInfoReply, err error) {
// 强制只能访问自己的拉黑名单
in.UpMid = in.Mid
res, err = s.relationUserInfoList(ctx, in, model.BlackListType)
return
}
// ListFollow 返回全部关注数组
func (s *Service) ListFollow(ctx context.Context, in *api.ListRelationReq) (res *api.ListRelationReply, err error) {
res = new(api.ListRelationReply)
res.List, err = s.dao.FetchFollowList(ctx, in.Mid)
return
}
// ListBlack 返回全部黑名单数组
func (s *Service) ListBlack(ctx context.Context, in *api.ListRelationReq) (res *api.ListRelationReply, err error) {
res = new(api.ListRelationReply)
res.List, err = s.dao.FetchBlackList(ctx, in.Mid)
return
}
// relationUserInfoList 关注列表
// 注意这里出现3种midvisitor/host/host关注的mid按顺序缩写为V、H、HF其中根据H获取HF然后计算V和HF的关注关系
// @Param
// relationType: 1-follow; 2-fan; 4-black
func (s *Service) relationUserInfoList(c context.Context, req *api.ListRelationUserInfoReq, listType model.UserListType) (res *api.ListUserInfoReply, err error) {
res = new(api.ListUserInfoReply)
visitorMID := req.Mid
hostMID := req.UpMid
// 0.前期校验
// 这里就不校验up主是否存在
if hostMID == 0 {
err = ecode.ReqParamErr
log.Errorv(c, log.KV("event", "req_param"), log.KV("up_mid", 0))
return
}
// parseCursor该接口不支持向前查找所以cursorPrev填空
cursor, _, err := model.ParseCursor("", req.CursorNext)
if err != nil {
return
}
// 1. 获取用户列表
var MID2TimeMap map[int64]time.Time
var followedMIDs []int64
// 关注列表和粉丝列表的区别只在于获取mid列表
switch listType {
case model.FollowListType:
MID2TimeMap, followedMIDs, err = s.dao.FetchPartFollowList(c, hostMID, cursor, model.UserListLen)
case model.FanListType:
MID2TimeMap, followedMIDs, err = s.dao.FetchPartFanList(c, hostMID, cursor, model.UserListLen)
case model.BlackListType:
MID2TimeMap, followedMIDs, err = s.dao.FetchPartBlackList(c, hostMID, cursor, model.UserListLen)
default:
err = ecode.BBQSystemErr
log.Errorv(c, log.KV("event", "error_list_type"), log.KV("list_type", listType))
return
}
if err != nil {
log.Errorv(c, log.KV("event", "fetch_relation_list"), log.KV("error", err))
return
}
log.V(1).Infov(c, log.KV("event", "get_relation_list"), log.KV("rsp_size", len(followedMIDs)))
// 这里之所以跟len/2是因为有可能同一时间多个关注这种情况会出现回包数量<len但是这不代表has_more=false
// TODO: like也有该情况后续考虑优化能出意外的就在粉丝列表这里
if len(followedMIDs) < model.UserListLen/2 {
res.HasMore = false
if len(followedMIDs) == 0 {
return
}
} else {
res.HasMore = true
}
// 2. 获取user info
userInfos, err := s.batchUserInfo(c, visitorMID, followedMIDs, &api.ListUserInfoConf{NeedDesc: false, NeedStat: false, NeedFollowState: true})
if err != nil {
log.Errorv(c, log.KV("event", "batch_user_info"), log.KV("err", err))
return
}
// 3. form rsp
for _, mid := range followedMIDs {
userInfo, exists := userInfos[mid]
if exists {
res.List = append(res.List, userInfo)
} else {
log.Errorv(c, log.KV("event", "get_use_info"), log.KV("mid", mid))
}
}
// 4. 后处理为每个item添加cursor值
var itemCursor model.CursorValue
for _, item := range res.List {
var mtime time.Time
mtime, exists := MID2TimeMap[item.UserBase.Mid]
if !exists {
log.Errorv(c, log.KV("event", "relation_id_not_found"), log.KV("relation_mid", item.UserBase.Mid))
} else {
itemCursor.CursorID = item.UserBase.Mid
itemCursor.CursorTime = mtime
}
jsonStr, _ := json.Marshal(itemCursor) // marshal的时候相信库函数不做err判断
item.CursorValue = string(jsonStr)
}
return
}
//addUserFollow 关注
func (s *Service) addUserFollow(c context.Context, mid, upMid int64) (followState int8, err error) {
// TODO: 后续改成Userbase
userBases, err := s.dao.UserBase(c, []int64{mid, upMid})
if err != nil {
log.Errorv(c, log.KV("event", "user_base"))
return
}
// 两个人的mid是否都存在
if len(userBases) != 2 {
log.Errorv(c, log.KV("event", "mid_not_found"), log.KV("mid", mid), log.KV("up_mid", upMid), log.KV("rsp_size", len(userBases)))
err = ecode.BBQSystemErr
return
}
// 关注是否达到上限
userStats, err := s.dao.RawBatchUserStatistics(c, []int64{mid})
if err != nil {
log.Errorv(c, log.KV("event", "user_statistics"), log.KV("mid", mid), log.KV("err", err))
return
}
// 这种情况下为user_statistics中没有该条记录可以选择继续下面流程
if userStat, exist := userStats[mid]; !exist {
log.Errorv(c, log.KV("event", "user_statistics_not_found"), log.KV("mid", mid))
} else if userStat.Follow >= model.MaxFollowListLen {
err = ecode.UserFollowLimitErr
return
}
tx, err := s.dao.BeginTran(c)
if err != nil {
err = ecode.AddUserFollowErr
log.Error("add user follow begin tran err(%v)", err)
return
}
defer func() {
if err != nil {
tx.Rollback()
log.V(1).Infov(c, log.KV("event", "rollback"))
} else {
tx.Commit()
log.V(1).Infov(c, log.KV("event", "commit"))
// 相互关系
followState = 1
MIDMap := s.dao.IsFollow(c, upMid, []int64{mid})
if MIDMap != nil {
_, exists := MIDMap[mid]
if exists {
followState |= 2
}
}
notice := &notice.NoticeBase{
Mid: upMid, ActionMid: mid, NoticeType: notice.NoticeTypeFan, Title: "关注了你",
BizType: notice.NoticeBizTypeUser}
tmpErr := s.dao.CreateNotice(c, notice)
if tmpErr != nil {
log.Error("create follow notice fail: notice_msg=%s", notice.String())
}
}
}()
var num1, num2, cancelBlackNum int64
if cancelBlackNum, err = s.dao.TxCancelUserBlack(c, tx, mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "cancel_user_black"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid))
err = ecode.UserBlackErr
return
}
if cancelBlackNum == 1 {
err = ecode.UserAlreadyBlackFollowErr
return
}
if num1, err = s.dao.TxAddUserFollow(c, tx, mid, upMid); err != nil {
err = ecode.AddUserFollowErr
return
}
if num2, err = s.dao.TxAddUserFan(tx, upMid, mid); err != nil {
err = ecode.AddUserFollowErr
return
}
if num1 == 0 && num2 == 0 {
log.Infov(c, log.KV("log", fmt.Sprintf("already follow: mid=%d, up_mid=%d", mid, upMid)))
return
}
// 关注和粉丝不匹配的情况,当做关注成功,但是统计数计不变化吧
if num1 == 0 || num2 == 0 {
log.Errorv(c, log.KV("log", "consistency error with follow and fan"), log.KV("event", "fatal"), log.KV("up_mid", upMid))
return
}
// 更新关注数和粉丝数
if _, err = s.dao.TxIncrUserStatisticsField(c, tx, mid, "follow_total"); err != nil {
log.Errorv(c, log.KV("event", "incr_user_statistic"), log.KV("field", "follow_total"), log.KV("mid", mid), log.KV("err", err))
err = ecode.AddUserFollowErr
return
}
if _, err = s.dao.TxIncrUserStatisticsField(c, tx, upMid, "fan_total"); err != nil {
log.Errorv(c, log.KV("event", "incr_user_statistic"), log.KV("field", "fan_total"), log.KV("up_mid", upMid), log.KV("err", err))
err = ecode.AddUserFollowErr
}
return
}
//CancelUserFollow 取消关注
func (s *Service) cancelUserFollow(c context.Context, mid, upMid int64) (followState int8, err error) {
tx, err := s.dao.BeginTran(c)
if err != nil {
err = ecode.CancelUserFollowErr
log.Error("cancel user follow begin tran err(%v)", err)
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
var num1, num2 int64
if num1, err = s.dao.TxCancelUserFollow(c, tx, mid, upMid); err != nil {
log.Error("cancel user follow err(%v)", err)
err = ecode.CancelUserFollowErr
return
}
if num2, err = s.dao.TxCancelUserFan(tx, upMid, mid); err != nil {
log.Error("cancel user fan err(%v)", err)
err = ecode.CancelUserFollowErr
return
}
if num1 == 0 && num2 == 0 {
return
}
if num1 == 0 || num2 == 0 {
log.Errorv(c, log.KV("log", "consistency error with follow and fan"), log.KV("event", "fatal"), log.KV("up_mid", upMid))
return
}
if _, err = s.dao.TxDecrUserStatisticsFollow(tx, mid); err != nil {
log.Error("update user statistics follow decr err(%v) mid(%d) upmid(%d)", err, mid, upMid)
return
}
if _, err = s.dao.TxDecrUserStatisticsFan(tx, upMid); err != nil {
log.Error("update user statistics fan decr err(%v) mid(%d) upmid(%d)", err, mid, upMid)
return
}
return
}
//AddUserBlack 拉黑
func (s *Service) addUserBlack(c context.Context, mid, upMid int64) (followState int8, err error) {
// 0. 前期校验
// 是否存在mid和up_mid
// TODO: 后续改成Userbase
userBases, err := s.dao.UserBase(c, []int64{mid, upMid})
if err != nil {
log.Errorv(c, log.KV("event", "user_base"))
return
}
// 两个人的mid是否都存在
if len(userBases) != 2 {
log.Errorv(c, log.KV("event", "mid_not_found"), log.KV("mid", mid), log.KV("up_mid", upMid), log.KV("rsp_size", len(userBases)))
err = ecode.BBQSystemErr
return
}
// 黑名单数量是否满足要求
userStats, err := s.dao.RawBatchUserStatistics(c, []int64{mid})
if err != nil {
log.Errorv(c, log.KV("event", "user_statistics"), log.KV("mid", mid), log.KV("err", err))
return
}
// 这种情况下为user_statistics中没有该条记录可以选择继续下面流程
if userStat, exist := userStats[mid]; !exist {
log.Errorv(c, log.KV("event", "user_statistics_not_found"), log.KV("mid", mid))
} else if userStat.Black >= model.MaxBlacklistLen {
err = ecode.UserBlackLimitErr
return
}
// 若存在关注,则直接取消
_, err = s.cancelUserFollow(c, mid, upMid)
if err != nil {
log.Errorv(c, log.KV("log", "black fail due to cancel user follow fail"))
err = ecode.UserBlackErr
return
}
// 1. 插入黑名单
tx, err := s.dao.BeginTran(c)
if err != nil {
err = ecode.BBQSystemErr
log.Errorv(c, log.KV("event", "begin_transaction"), log.KV("mid", mid), log.KV("err", err))
return
}
defer func() {
if err != nil {
tx.Rollback()
log.V(1).Infov(c, log.KV("event", "rollback"))
} else {
tx.Commit()
log.V(1).Infov(c, log.KV("event", "commit"))
}
}()
var addBlackNum int64
if addBlackNum, err = s.dao.TxAddUserBlack(c, tx, mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "add_user_black"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid))
err = ecode.UserBlackErr
return
}
if addBlackNum == 0 {
log.Infov(c, log.KV("log", fmt.Sprintf("already black: mid=%d, up_mid=%d", mid, upMid)))
return
}
if _, err = s.dao.TxIncrUserStatisticsField(c, tx, mid, "black_total"); err != nil {
log.Errorv(c, log.KV("event", "incr_user_statistic"), log.KV("field", "black_total"), log.KV("mid", mid), log.KV("err", err))
err = ecode.UserBlackErr
}
return
}
//CancelUserBlack 取消拉黑
func (s *Service) cancelUserBlack(c context.Context, mid, upMid int64) (followState int8, err error) {
// 0. 前期校验
// 是否存在mid和up_mid
// TODO: 后续改成Userbase
userBases, err := s.dao.UserBase(c, []int64{mid, upMid})
if err != nil {
log.Errorv(c, log.KV("event", "user_base"))
return
}
// 两个人的mid是否都存在
if len(userBases) != 2 {
log.Errorv(c, log.KV("event", "mid_not_found"), log.KV("mid", mid), log.KV("up_mid", upMid), log.KV("rsp_size", len(userBases)))
err = ecode.BBQSystemErr
return
}
// 1. 取消黑名单
tx, err := s.dao.BeginTran(c)
if err != nil {
err = ecode.BBQSystemErr
log.Errorv(c, log.KV("event", "begin_transaction"), log.KV("mid", mid), log.KV("err", err))
return
}
defer func() {
if err != nil {
tx.Rollback()
log.V(1).Infov(c, log.KV("event", "rollback"))
} else {
tx.Commit()
log.V(1).Infov(c, log.KV("event", "commit"))
}
}()
var cancelBlackNum int64
if cancelBlackNum, err = s.dao.TxCancelUserBlack(c, tx, mid, upMid); err != nil {
log.Errorv(c, log.KV("event", "cancel_user_black"), log.KV("err", err), log.KV("mid", mid), log.KV("up_mid", upMid))
err = ecode.UserBlackErr
return
}
if cancelBlackNum == 0 {
return
}
if _, err := s.dao.TxDescUserStatisticsField(c, tx, mid, "black_total"); err != nil {
log.Errorv(c, log.KV("event", "desc_user_statistic"), log.KV("field", "black_total"), log.KV("mid", mid), log.KV("err", err))
err = ecode.UserBlackErr
}
return
}

View File

@@ -0,0 +1,282 @@
package service
import (
"context"
"go-common/app/service/bbq/user/api"
"go-common/library/ecode"
"go-common/library/log"
"testing"
"time"
)
func TestService_AddUserFollow(t *testing.T) {
ctx := context.Background()
// 初始化状态
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
originStat1 := getUserStat(88895104)
originStat2 := getUserStat(88895134)
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
// 关注是否生效
res := s.dao.IsFollow(ctx, 88895104, []int64{88895134})
log.Info("res: %v", res)
if len(res) == 0 {
t.Errorf("user follow fail")
}
res = s.dao.IsFan(ctx, 88895134, []int64{88895104})
log.Info("res: %v", res)
if len(res) == 0 {
t.Errorf("user follow fail")
}
// 关注对统计数据的影响
curStat1 := getUserStat(88895104)
if originStat1.Follow+1 != curStat1.Follow {
t.Errorf("follow stat fail: origin=%d, cur=%d", originStat1.Follow, curStat1.Follow)
}
curStat2 := getUserStat(88895134)
if originStat2.Fan+1 != curStat2.Fan {
t.Errorf("fan stat fail: origin=%d, cur=%d", originStat2.Fan, curStat2.Fan)
}
// 拉黑状态下不能关注
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
_, err := s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
if err != ecode.UserAlreadyBlackFollowErr {
t.Errorf("follow error when up_mid in black: err=%v", err)
}
}
func TestService_CancelUserFollow(t *testing.T) {
ctx := context.Background()
// 初始化状态
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
originStat1 := getUserStat(88895104)
originStat2 := getUserStat(88895134)
// 取关后的效果
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
res := s.dao.IsFollow(ctx, 88895104, []int64{88895134})
log.Info("res: %v", res)
if len(res) != 0 {
t.Errorf("cancel user follow fail")
}
res = s.dao.IsFan(ctx, 88895134, []int64{88895104})
log.Info("res: %v", res)
if len(res) != 0 {
t.Errorf("cancel user follow fail")
}
// 对统计数据的影响
curStat1 := getUserStat(88895104)
if originStat1.Follow-1 != curStat1.Follow {
t.Errorf("follow stat fail: origin=%d, cur=%d", originStat1.Follow, curStat1.Follow)
}
curStat2 := getUserStat(88895134)
if originStat2.Fan-1 != curStat2.Fan {
t.Errorf("fan stat fail: origin=%d, cur=%d", originStat2.Fan, curStat2.Fan)
}
}
func getUserStat(mid int64) (stat *api.UserStat) {
ctx := context.Background()
res, _ := s.dao.RawBatchUserStatistics(ctx, []int64{mid})
stat = res[mid]
return
}
// 测试多次重复的关注取关效果
func TestDuplicateFollow(t *testing.T) {
ctx := context.Background()
// 清除
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
originStat1 := getUserStat(88895104)
originStat2 := getUserStat(88895134)
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
curStat1 := getUserStat(88895104)
curStat2 := getUserStat(88895134)
if curStat1.Follow != originStat1.Follow {
t.Errorf("follow stat fail: cur=%d, origin=%d", curStat1.Follow, originStat1.Follow)
}
if curStat2.Fan != originStat2.Fan {
t.Errorf("fan stat fail: cur=%d, origin=%d", curStat1.Follow, originStat1.Follow)
}
}
func TestUserStatistics(t *testing.T) {
ctx := context.Background()
// 初始化
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
res, _ := s.dao.RawBatchUserStatistics(ctx, []int64{88895104, 88895134})
stat := res[88895104]
follow := stat.Follow
stat = res[88895134]
fan := stat.Fan
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 1})
time.Sleep(time.Second)
res, _ = s.dao.RawBatchUserStatistics(ctx, []int64{88895104, 88895134})
stat = res[88895104]
if follow+1 != stat.Follow {
t.Errorf("follow statistics fail")
}
stat = res[88895134]
if fan+1 != stat.Fan {
t.Errorf("fan statistics fail")
}
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
time.Sleep(time.Second)
res, _ = s.dao.RawBatchUserStatistics(ctx, []int64{88895104, 88895134})
log.Info("stat: %v", res)
stat = res[88895104]
if follow != stat.Follow {
t.Error("follow statistics fail", follow, stat.Follow)
}
stat = res[88895134]
if fan != stat.Fan {
t.Error("fan statistics fail", fan, stat.Fan)
}
}
func TestAddUserBlack(t *testing.T) {
ctx := context.Background()
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
res := s.dao.IsBlack(ctx, 88895104, []int64{88895134})
log.Info("res: %v", res)
if len(res) == 0 {
t.Errorf("user follow fail")
}
}
func TestCancelUserBlack(t *testing.T) {
ctx := context.Background()
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
res := s.dao.IsBlack(ctx, 88895104, []int64{88895134})
log.Info("res: %v", res)
if len(res) != 0 {
t.Errorf("cancel user follow fail")
}
}
func TestUserBlack(t *testing.T) {
ctx := context.Background()
// 初始化清除关系
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 2})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
// 关注时拉黑,会清楚拉黑状态
TestService_AddUserFollow(t)
_, err := s.addUserBlack(ctx, 88895104, 88895134)
if err != nil {
t.Errorf("follow state black case fail")
}
res1 := s.dao.IsBlack(ctx, 88895104, []int64{88895134})
if len(res1) == 0 {
t.Errorf("Black fail")
}
res1 = s.dao.IsFollow(ctx, 88895104, []int64{88895134})
if len(res1) != 0 {
t.Errorf("Black fail")
}
TestService_CancelUserFollow(t)
// 拉黑不能关注
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
_, err = s.addUserFollow(ctx, 88895104, 88895134)
if err == nil {
t.Errorf("black stat follow case fail")
}
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
// 拉黑数量
res, _ := s.dao.RawBatchUserStatistics(ctx, []int64{88895104})
stat := res[88895104]
black := stat.Black
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
res, _ = s.dao.RawBatchUserStatistics(ctx, []int64{88895104})
stat = res[88895104]
if black+1 != stat.Black {
t.Error("black stat fail")
}
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
res, _ = s.dao.RawBatchUserStatistics(ctx, []int64{88895104})
stat = res[88895104]
if black != stat.Black {
t.Error("black stat fail")
}
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 3})
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895104, UpMid: 88895134, Action: 4})
stat = getUserStat(88895104)
if black != stat.Black {
t.Error("black stat fail")
}
}
func TestService_ListFollowUserInfo(t *testing.T) {
ctx := context.Background()
s.ListFollowUserInfo(ctx, &api.ListRelationUserInfoReq{Mid: 88895134, UpMid: 88895104})
// TODO:
}
func TestService_ListFanUserInfo(t *testing.T) {
ctx := context.Background()
s.ListFanUserInfo(ctx, &api.ListRelationUserInfoReq{Mid: 88895104, UpMid: 88895134})
// TODO:
}
func TestService_ListBlackUserInfo(t *testing.T) {
ctx := context.Background()
s.ListBlackUserInfo(ctx, &api.ListRelationUserInfoReq{Mid: 88895134, UpMid: 88895104})
}
func TestService_ListFollow(t *testing.T) {
ctx := context.Background()
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895204, UpMid: 88895134, Action: 1})
stat := getUserStat(88895204)
reply, _ := s.ListFollow(ctx, &api.ListRelationReq{Mid: 88895204})
if stat.Follow != int64(len(reply.List)) {
t.Errorf("get follow list fail")
}
}
func TestService_ListBlack(t *testing.T) {
ctx := context.Background()
s.ModifyRelation(ctx, &api.ModifyRelationReq{Mid: 88895204, UpMid: 88895134, Action: 3})
stat := getUserStat(88895204)
reply, _ := s.ListBlack(ctx, &api.ListRelationReq{Mid: 88895204})
if stat.Black != int64(len(reply.List)) {
t.Errorf("get follow list fail")
}
}

View File

@@ -0,0 +1,42 @@
package service
import (
"context"
"go-common/app/service/bbq/user/internal/conf"
"go-common/app/service/bbq/user/internal/dao"
"go-common/library/queue/databus"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
userFaceSub *databus.Databus
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
}
if databusConfig, exists := c.Databus["bfs"]; exists {
s.userFaceSub = databus.New(databusConfig)
// 监听BFS databus
go s.subBfsUserFace()
}
return s
}
// Ping Service
func (s *Service) Ping(ctx context.Context) (err error) {
return s.dao.Ping(ctx)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,41 @@
package service
import (
"flag"
"go-common/app/service/bbq/user/internal/conf"
"go-common/library/conf/paladin"
"go-common/library/log"
"os"
"testing"
)
var (
s *Service
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "")
flag.Set("conf_token", "")
flag.Set("tree_id", "")
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")
} else {
flag.Set("conf", "../../cmd/test.toml")
flag.Set("deploy.env", "uat")
}
flag.Parse()
if err := paladin.Init(); err != nil {
panic(err)
}
if err := paladin.Watch("test.toml", conf.Conf); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
s = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,344 @@
package service
import (
"context"
"fmt"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/ecode"
"go-common/library/log"
"strconv"
"github.com/golang/protobuf/ptypes/empty"
)
// Login 登录
func (s *Service) Login(c context.Context, in *api.UserBase) (res *api.UserBase, err error) {
res = &api.UserBase{}
mid := in.Mid
newTag := in.NewTag
//默认参数
defaultUserBase := api.UserBase{
Mid: mid,
Uname: "Qing_" + strconv.FormatInt(mid, 10),
Face: "http://i0.hdslb.com/bfs/bbq/video-image/userface/1558868601542006937.png",
Sex: model.SexAnimal,
Signature: "",
UserType: model.UserTypeNew,
CompleteDegree: model.DegreeComp,
}
addUserBase := &defaultUserBase
*res = defaultUserBase
var rawUserBases map[int64]*api.UserBase
if rawUserBases, err = s.dao.RawUserBase(c, []int64{mid}); err != nil {
log.Warnw(c, "log", "get raw user base fail", "mid", mid)
return
}
// 是否在bbq数据库中存在
if rawUserBase, ok := rawUserBases[mid]; ok {
*res = *rawUserBase
if rawUserBase.CompleteDegree == model.DegreeUncomp {
addUserBase = rawUserBase
addUserBase.CompleteDegree = model.DegreeComp
s.dao.UpdateUserBase(c, mid, addUserBase)
}
return
}
// 获取主站的账号信息
var userCard *model.UserCard
userCard, err = s.dao.RawUserCard(c, mid)
// 获取失败则插入默认
if err != nil || userCard == nil {
log.V(10).Infow(c, "log", "get raw user card from main fail")
if _, err = s.dao.AddUserBase(c, addUserBase); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("Login inset default info err,err:%v", err)))
return
}
*res = defaultUserBase
} else {
// 变换主站信息到bbq并插入
if userCard.Sex == "男" {
addUserBase.Sex = model.SexMan
} else if userCard.Sex == "女" {
addUserBase.Sex = model.SexWoman
} else {
addUserBase.Sex = model.SexAnimal
}
addUserBase.Face = userCard.Face
// 这里默认用生成的Qing_{{mid}}存到mysql不使用主站昵称
addUserBase.CompleteDegree = model.DegreeComp
addUserBase.UserType = model.UserTypeBili
if _, err = s.dao.AddUserBase(c, addUserBase); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("Login inset bili info err,err:%v", err)))
}
*res = *addUserBase
// 返回的时候把主站的数据吐给前端展示把名字是否重复、屏蔽词等等逻辑都放给edit接口去判断
res.Uname = userCard.Name
}
// 返回一次提示
res.CompleteDegree = model.DegreeUncomp
//返回客户端同学要求
if newTag > 0 {
defaultUserBase.NewTag = newTag
}
return
}
// ListUserInfo 用户信息列表
func (s *Service) ListUserInfo(c context.Context, in *api.ListUserInfoReq) (res *api.ListUserInfoReply, err error) {
res = new(api.ListUserInfoReply)
upMIDs := in.UpMid
if len(upMIDs) == 0 {
return
}
if len(upMIDs) > model.BatchUserLen {
err = ecode.BatchUserTooLong
return
}
userInfos, err := s.batchUserInfo(c, in.Mid, upMIDs, &api.ListUserInfoConf{NeedDesc: in.NeedDesc, NeedStat: in.NeedStat, NeedFollowState: in.NeedFollowState})
if err != nil {
log.Warnv(c, log.KV("log", "batch user info fail"))
return
}
for _, mid := range upMIDs {
userInfo, exists := userInfos[mid]
if !exists {
log.Warnv(c, log.KV("log", fmt.Sprintf("get user info fail: mid=%d", mid)))
continue
}
res.List = append(res.List, userInfo)
}
return
}
// UserEdit 完善用户信息
// 该请求需要保证请求的mid已经存在如果不存在该接口会返回失败
func (s *Service) UserEdit(c context.Context, in *api.UserBase) (res *empty.Empty, err error) {
res = new(empty.Empty)
// 0. 参数正确性校验
if err = s.dao.CheckUname(c, in.Mid, in.Uname); err != nil {
return
}
// 1. 更新 编辑接口不关心用户信息完成度,直接更新为完善
in.CompleteDegree = model.DegreeComp
if _, err = s.dao.UpdateUserBase(c, in.Mid, in); err != nil {
err = ecode.EditUserBaseErr
return nil, err
}
return
}
// UserCmsTagEdit 修改cms_tag
func (s *Service) UserCmsTagEdit(c context.Context, in *api.CmsTagRequest) (res *empty.Empty, err error) {
// TODO: 这里就不要用事务了后面改掉同时UserFieldEdit也改掉
tx, err := s.dao.BeginTran(c)
if err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("edit user field begin tran err(%v) mid(%d)", err, in.Mid)))
err = ecode.BBQSystemErr
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if _, err = s.dao.UpdateUserField(c, tx, in.Mid, "cms_tag", in.CmsTag); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user cms_tag fail: mid=%d, cms_tag=%d", in.Mid, in.CmsTag)))
return
}
return
}
// UserFieldEdit 仅提供给审核使用
func (s *Service) UserFieldEdit(c context.Context, in *api.UserBase) (res *empty.Empty, err error) {
res = new(empty.Empty)
tx, err := s.dao.BeginTran(c)
if err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("edit user field begin tran err(%v) mid(%d)", err, in.Mid)))
err = ecode.BBQSystemErr
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if len(in.Uname) != 0 {
if _, err = s.dao.UpdateUserField(c, tx, in.Mid, "uname", in.Uname); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user uname fail: mid=%d, uname=%s", in.Mid, in.Uname)))
return
}
}
if len(in.Face) != 0 {
if _, err = s.dao.UpdateUserField(c, tx, in.Mid, "face", in.Face); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update user uname fail: mid=%d, uname=%s", in.Mid, in.Uname)))
return
}
}
return
}
func (s *Service) batchUserInfo(c context.Context, visitorMID int64, upMIDs []int64, conf *api.ListUserInfoConf) (res map[int64]*api.UserInfo, err error) {
res = make(map[int64]*api.UserInfo)
if len(upMIDs) == 0 {
return
}
// 1. 请求user base信息
userBases, err := s.dao.UserBase(c, upMIDs)
if err != nil {
log.Errorv(c, log.KV("event", "get_user_base"), log.KV("error", err))
return
}
log.V(1).Infov(c, log.KV("event", "get_user_base"), log.KV("req_size", len(upMIDs)),
log.KV("rsp_size", len(userBases)))
// 2. 请求user statistics信息
var userStatistics map[int64]*api.UserStat
if conf.NeedStat {
userStatistics, err = s.dao.RawBatchUserStatistics(c, upMIDs)
if err != nil {
log.Errorv(c, log.KV("event", "user_statistics"))
err = nil
userStatistics = make(map[int64]*api.UserStat)
}
}
// 3. 请求社交关系,是否关注,是否粉丝
var visitorFollowedMID map[int64]bool
var visitorFanMID map[int64]bool
if conf.NeedFollowState && visitorMID != 0 {
visitorFollowedMID = s.dao.IsFollow(c, visitorMID, upMIDs)
visitorFanMID = s.dao.IsFan(c, visitorMID, upMIDs)
}
// 4. 组装回包
for mid, userBase := range userBases {
userInfo := new(api.UserInfo)
res[mid] = userInfo
userInfo.UserBase = userBase
if len(userBase.Face) == 0 {
userBase.Face = "http://i0.hdslb.com/bfs/bbq/video-image/userface/1558868601542006937.png"
}
if conf.NeedDesc {
userInfo.UserBase.UserDesc, userInfo.UserBase.RegionName = s.genUserDesc(c, userBase)
}
if conf.NeedStat && userStatistics != nil {
userInfo.UserStat = userStatistics[mid]
}
if conf.NeedFollowState && visitorMID != 0 {
// mid和visitorMID的关系
_, follow := visitorFollowedMID[mid]
_, fan := visitorFanMID[mid]
if follow {
userInfo.FollowState |= 1
}
if fan {
userInfo.FollowState |= 2
}
}
}
return
}
// genUserDesc form tags from birthday, sex and region
func (s *Service) genUserDesc(c context.Context, base *api.UserBase) (userDesc []string, regionName string) {
var (
birthday string
sex string
)
if len(base.Birthday) != 8 {
birthday = "其他"
} else {
if base.Birthday > "20100000" {
birthday = "10后"
} else if base.Birthday > "20000000" {
birthday = "00后"
} else if base.Birthday > "19900000" {
birthday = "90后"
} else if base.Birthday > "19800000" {
birthday = "80后"
} else if base.Birthday > "19700000" {
birthday = "70后"
} else {
birthday = "其他"
}
}
userDesc = append(userDesc, birthday)
switch base.Sex {
case 1:
sex = "男"
case 2:
sex = "女"
default:
sex = "不明生物"
}
userDesc = append(userDesc, sex)
if base.Region == 0 {
return
}
region := int32(base.Region / 100 * 100)
if location, err := s.dao.GetLocation(c, region); err != nil {
log.Errorv(c, log.KV("event", "get_location"))
} else if location == nil {
log.Errorv(c, log.KV("event", "get_location"), log.KV("reason", "no_location"), log.KV("loc_id", base.Region))
} else {
userDesc = append(userDesc, location.Name)
regionName = location.Name
}
return
}
//PhoneCheck ..
func (s *Service) PhoneCheck(c context.Context, in *api.PhoneCheckReq) (res *api.PhoneCheckReply, err error) {
res = &api.PhoneCheckReply{}
userProfile, err := s.dao.GetUserBProfile(c, in)
if err != nil || userProfile == nil {
log.Warn("PhoneCheck get userb profile err:%v,res:%v", err, userProfile)
}
res.TelStatus = userProfile.Profile.GetTelStatus()
return
}
//ForbidUser ...
func (s *Service) ForbidUser(c context.Context, req *api.ForbidRequest) (res *empty.Empty, err error) {
if err = s.dao.ForbidUser(c, req.MID, req.ExpireTime); err != nil {
log.Warnv(c, log.KV("event", "Service ForbidUser"))
return
}
return
}
//ReleaseUser ...
func (s *Service) ReleaseUser(c context.Context, req *api.ReleaseRequest) (res *empty.Empty, err error) {
if err = s.dao.ReleaseUser(c, req.MID); err != nil {
log.Warnv(c, log.KV("event", "Service ForbidUser"))
return
}
return
}
// UpdateUserVideoView .
func (s *Service) UpdateUserVideoView(c context.Context, req *api.UserVideoView) (res *empty.Empty, err error) {
err = s.dao.UpdateUserVideoView(c, req.Mid, req.Views)
return
}

View File

@@ -0,0 +1,77 @@
package service
import (
"io/ioutil"
"net/http"
"net/url"
"strings"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/log"
"go-common/library/queue/databus"
"github.com/json-iterator/go"
)
func (s *Service) subBfsUserFace() {
mch := s.userFaceSub.Messages()
for {
msg, ok := <-mch
if !ok {
continue
}
s.bfsUserFaceConsumer(msg)
}
}
func (s *Service) bfsUserFaceConsumer(msg *databus.Message) {
if msg == nil {
return
}
defer msg.Commit()
inst := new(model.UserFaceBFS)
err := jsoniter.Unmarshal(msg.Value, inst)
if err != nil {
log.Error("subBfsUserFace error: %+v", err)
return
}
if inst.Bucket != "bbq" || !inst.IsYellow {
return
}
// TODO: remove debug info
log.Info("subBfsUserFace instance: %+v", inst)
if inst.URL == "" {
return
}
s1 := strings.TrimPrefix(inst.FileName, "video-image/userface/")
if s1 == inst.FileName {
log.Error("subBfsUserFace mid parse failed filename: %s", inst.FileName)
return
}
midInfo := strings.Split(s1, "_")
if len(midInfo) != 2 {
log.Error("subBfsUserFace mid parse failed filename: %s", inst.FileName)
return
}
// 推送到审核后台
params := url.Values{
"mid": {midInfo[0]},
"origin_face": {"https://i0.hdslb.com/bfs/bbq/video-image/userface/1558868601542006937.png"},
"face": {inst.URL},
}
resp, err := http.DefaultClient.PostForm("http://bbq-mng.bilibili.co/bbq/cms/user/setface", params)
if err != nil {
log.Error("subBfsUserFace post user_face to cms error: %+v", err)
return
}
response, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
log.Info("subBfsUserFace result: %s, error: %+v", string(response), err)
}

View File

@@ -0,0 +1,195 @@
package service
import (
"context"
"encoding/json"
"go-common/app/service/bbq/user/api"
"go-common/app/service/bbq/user/internal/model"
"go-common/library/log"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceLogin(t *testing.T) {
convey.Convey("Login", t, func(convCtx convey.C) {
var (
c = context.Background()
in = &api.UserBase{Mid: 88895104}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
guard := s.dao.MockRawUserCard(&model.UserCard{Sex: "女", Name: "哈哈"}, nil)
defer guard.Unpatch()
res, err := s.Login(c, in)
data, _ := json.Marshal(res)
log.V(1).Infow(c, "res", string(data))
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
//
//func TestServiceListUserInfo(t *testing.T) {
// convey.Convey("ListUserInfo", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// in = &api.ListUserInfoReq{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.ListUserInfo(c, in)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceUserEdit(t *testing.T) {
// convey.Convey("UserEdit", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// in = &api.UserBase{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.UserEdit(c, in)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceUserCmsTagEdit(t *testing.T) {
// convey.Convey("UserCmsTagEdit", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// in = &api.CmsTagRequest{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.UserCmsTagEdit(c, in)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceUserFieldEdit(t *testing.T) {
// convey.Convey("UserFieldEdit", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// in = &api.UserBase{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.UserFieldEdit(c, in)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServicebatchUserInfo(t *testing.T) {
// convey.Convey("batchUserInfo", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// visitorMID = int64(0)
// upMIDs = []int64{}
// conf = &api.ListUserInfoConf{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.batchUserInfo(c, visitorMID, upMIDs, conf)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServicegenUserDesc(t *testing.T) {
// convey.Convey("genUserDesc", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// base = &api.UserBase{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// userDesc, regionName := s.genUserDesc(c, base)
// convCtx.Convey("Then userDesc,regionName should not be nil.", func(convCtx convey.C) {
// convCtx.So(regionName, convey.ShouldNotBeNil)
// convCtx.So(userDesc, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServicePhoneCheck(t *testing.T) {
// convey.Convey("PhoneCheck", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// in = &api.PhoneCheckReq{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.PhoneCheck(c, in)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceForbidUser(t *testing.T) {
// convey.Convey("ForbidUser", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// req = &api.ForbidRequest{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.ForbidUser(c, req)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceReleaseUser(t *testing.T) {
// convey.Convey("ReleaseUser", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// req = &api.ReleaseRequest{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.ReleaseUser(c, req)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestServiceUpdateUserVideoView(t *testing.T) {
// convey.Convey("UpdateUserVideoView", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// req = &api.UserVideoView{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// res, err := s.UpdateUserVideoView(c, req)
// convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(res, convey.ShouldNotBeNil)
// })
// })
// })
//}