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,22 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/interface/bbq/bullet/api:all-srcs",
"//app/interface/bbq/bullet/cmd:all-srcs",
"//app/interface/bbq/bullet/internal/conf:all-srcs",
"//app/interface/bbq/bullet/internal/dao:all-srcs",
"//app/interface/bbq/bullet/internal/model:all-srcs",
"//app/interface/bbq/bullet/internal/server/http:all-srcs",
"//app/interface/bbq/bullet/internal/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,23 @@
# v1.0.7
1. 弹幕接口埋点字段fix
# v1.0.6
1. 弹幕接口埋点
# v1.0.5
1. 增加用户封禁
# v1.0.4
1. 增加全局的关闭开关
# v1.0.3
1. 参数校验失败提示
# v1.0.2
1. 增加接口参数校验
# v1.0.1
1. 添加弹幕的手机号绑定判定
### v1.0.0
1. 上线功能bullet和第一期功能相同

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "api_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "api_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
importpath = "go-common/app/interface/bbq/bullet/api",
proto = ":api_proto",
tags = ["automanaged"],
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [],
embed = [":api_go_proto"],
importpath = "go-common/app/interface/bbq/bullet/api",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto: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,48 @@
syntax = "proto3";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
package bbq.interface.bullet.v1;
option go_package = "api";
option (gogoproto.goproto_getters_all) = false;
message DataReport {
string app = 1 [(gogoproto.jsontag) = "mobi_app", (gogoproto.moretags)='form:"mobi_app"'];
string client = 2 [(gogoproto.jsontag) = "platform", (gogoproto.moretags)='form:"platform"'];
string version = 3 [(gogoproto.jsontag) = "version", (gogoproto.moretags)='form:"version"'];
string channel = 4 [(gogoproto.jsontag) = "channel", (gogoproto.moretags)='form:"channel"'];
string location = 5 [(gogoproto.jsontag) = "location", (gogoproto.moretags)='form:"location"'];
string query_id = 6 [(gogoproto.jsontag) = "query_id", (gogoproto.moretags)='form:"query_id"'];
string buvid = 7 [(gogoproto.jsontag) = "buvid", (gogoproto.moretags)='form:"buvid"'];
int64 svid = 8 [(gogoproto.jsontag) = "svid", (gogoproto.moretags)='form:"oid"'];
int32 total_duration = 9 [(gogoproto.jsontag) = "total_duration", (gogoproto.moretags)='form:"total_duration"'];
int32 play_duration = 10 [(gogoproto.jsontag) = "duration", (gogoproto.moretags)='form:"duration"'];
int32 data_type = 11 [(gogoproto.jsontag) = "data_type", (gogoproto.moretags)='form:"data_type"'];
int32 page = 12 [(gogoproto.jsontag) = "page_id", (gogoproto.moretags)='form:"page_id"'];
int32 module = 13 [(gogoproto.jsontag) = "module_id", (gogoproto.moretags)='form:"module_id"'];
}
message ListBulletReq {
int64 oid = 1 [(gogoproto.moretags)='form:"oid"'];
int32 start_ms = 2 [(gogoproto.moretags)='form:"start_ms"'];
int32 end_ms = 3 [(gogoproto.moretags)='form:"end_ms"'];
string cursor_next = 4 [(gogoproto.moretags)='form:"cursor_next"'];
int64 mid = 5;
}
message Bullet {
int64 id = 1;
int64 oid = 2[(gogoproto.moretags)='form:"oid"'];
int64 mid = 3[(gogoproto.jsontag) = "mid", (gogoproto.moretags)='form:"mid"'];
int32 offset_ms = 4[(gogoproto.jsontag) = "offset_ms", (gogoproto.moretags)='form:"offset_ms"'];
int32 offset = 5;
string content = 6[(gogoproto.jsontag) = "content", (gogoproto.moretags)='form:"content"'];
string cursor_value = 7[(gogoproto.jsontag) = "cursor_value,omitempty"];
}
message ListBulletReply {
bool has_more = 1 [(gogoproto.jsontag) = "has_more"];
repeated Bullet list = 2 [(gogoproto.jsontag) = "list,omitempty"];
}

View File

@@ -0,0 +1,44 @@
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/interface/bbq/bullet/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/bbq/bullet/internal/conf:go_default_library",
"//app/interface/bbq/bullet/internal/server/http:go_default_library",
"//app/interface/bbq/bullet/internal/service: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,47 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/interface/bbq/bullet/internal/conf"
"go-common/app/interface/bbq/bullet/internal/server/http"
"go-common/app/interface/bbq/bullet/internal/service"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/net/trace"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("bullet-interface start")
trace.Init(conf.Conf.Tracer)
defer trace.Close()
ecode.Init(conf.Conf.Ecode)
svc := service.New(conf.Conf)
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()
log.Info("bullet-interface exit")
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,77 @@
[log]
stdout = true
# dir = "./log/bbq"
v = 5
[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"
[onlineMysql]
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.filter]
addr = "discovery://default/filter.service"
[grpcClient.filter.wardenconf]
dial = "100ms"
timeout = "500ms"
[grpcClient.user]
addr = "discovery://default/bbq.service.user"
[grpcClient.user.wardenconf]
dial = "100ms"
timeout = "500ms"
[grpcClient.video]
addr = "discovery://default/bbq.service.video"
[grpcClient.video.wardenconf]
dial = "100ms"
timeout = "500ms"
[antiSpam]
[antiSpam.bullet]
on=true
second=10
n=8
hour=1
m=100
[bulletConfig]
closeWrite = false
closeRead = false
[infoc]
taskID = "001639"
proto = "tcp"
addr = "172.18.33.124:15140"
chanSize = 10240

View File

@@ -0,0 +1,43 @@
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/interface/bbq/bullet/internal/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/auth: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",
"//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,117 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
antispam "go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
Verify *verify.Config
Auth *auth.Config
Tracer *trace.Config
Redis *redis.Config
MySQL *sql.Config
OnlineMySQL *sql.Config
Ecode *ecode.Config
GRPCClient map[string]*GRPCConf
AntiSpam map[string]*antispam.Config
BulletConfig BulletConfig
Infoc *infoc.Config
}
// BulletConfig 弹幕的一些配置项
type BulletConfig struct {
CloseWrite bool
CloseRead bool
}
// GRPCConf .
type GRPCConf struct {
WardenConf *warden.ClientConfig
Addr string
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() (err error) {
if confPath != "" {
err = local()
} else {
err = remote()
}
if Conf.Redis != nil {
for _, anti := range Conf.AntiSpam {
anti.Redis = Conf.Redis
}
}
return
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"filter.go",
],
importpath = "go-common/app/interface/bbq/bullet/internal/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/bbq/bullet/api:go_default_library",
"//app/interface/bbq/bullet/internal/conf:go_default_library",
"//app/interface/bbq/bullet/internal/model:go_default_library",
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/video/api/grpc/v1: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/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,302 @@
package dao
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"go-common/app/interface/bbq/bullet/api"
"go-common/app/interface/bbq/bullet/internal/model"
"go-common/library/log"
"go-common/library/net/rpc/warden"
"go-common/app/interface/bbq/bullet/internal/conf"
user "go-common/app/service/bbq/user/api"
video "go-common/app/service/bbq/video/api/grpc/v1"
filter "go-common/app/service/main/filter/api/grpc/v1"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
)
// Dao dao
type Dao struct {
c *conf.Config
redis *redis.Pool
db *xsql.DB
filterClient filter.FilterClient
userClient user.UserClient
videoClient video.VideoClient
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
redis: redis.NewPool(c.Redis),
db: xsql.NewMySQL(c.MySQL),
filterClient: newFilterClient(c.GRPCClient["filter"]),
userClient: newUserClient(c.GRPCClient["user"]),
videoClient: newVideoClient(c.GRPCClient["video"]),
}
return
}
// newVideoClient .
func newVideoClient(cfg *conf.GRPCConf) video.VideoClient {
cc, err := warden.NewClient(cfg.WardenConf).Dial(context.Background(), cfg.Addr)
if err != nil {
panic(err)
}
return video.NewVideoClient(cc)
}
// newUserClient .
func newUserClient(cfg *conf.GRPCConf) user.UserClient {
cc, err := warden.NewClient(cfg.WardenConf).Dial(context.Background(), cfg.Addr)
if err != nil {
panic(err)
}
return user.NewUserClient(cc)
}
// newUserClient .
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)
}
// 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)
}
// ContentPost .
func (d *Dao) ContentPost(ctx context.Context, req *api.Bullet) (dmid int64, err error) {
result, err := d.db.Exec(ctx,
"insert into bullet_content (oid, mid, offset_ms, offset, content) values (?, ?, ?, ?, ?)",
req.Oid, req.Mid, req.OffsetMs, req.OffsetMs/1000, req.Content)
if err != nil {
log.Errorv(ctx, log.KV("log", "insert bullet fail: req=%s"+req.String()))
return
}
dmid, err = result.LastInsertId()
return
}
// ContentGet .
func (d *Dao) ContentGet(ctx context.Context, req *api.ListBulletReq) (res []*api.Bullet, err error) {
res = []*api.Bullet{}
mid := req.Mid
querySQL := fmt.Sprintf("select id, mid, offset, content from bullet_content where "+
"oid=%d and state=0 and offset>=%d and offset<%d order by offset, id desc",
req.Oid, req.StartMs/1000, req.EndMs/1000)
rows, err := d.db.Query(ctx, querySQL)
if err != nil {
return
}
defer rows.Close()
log.V(1).Infow(ctx, "sql", querySQL)
// 获取时间范围内的全量视频
var allBullet []*api.Bullet
midBullets := make(map[int32]*[]*api.Bullet)
for rows.Next() {
bullet := new(api.Bullet)
if err = rows.Scan(&bullet.Id, &bullet.Mid, &bullet.Offset, &bullet.Content); err != nil {
log.Errorv(ctx, log.KV("log", "scan mysql fail: sql="+querySQL))
return
}
bullet.OffsetMs = bullet.Offset * 1000
allBullet = append(allBullet, bullet)
// 先把访问者发过的弹幕按照秒级别进行汇总
if mid == bullet.Mid {
v, exists := midBullets[bullet.Offset]
if !exists {
v = new([]*api.Bullet)
midBullets[bullet.Offset] = v
}
*v = append(*v, bullet)
}
}
// 根据全量数据,选择满足条件的弹幕
currSecond := int32(-1)
currSecondCount := 0
for _, bullet := range allBullet {
if currSecond != bullet.Offset {
currSecond = bullet.Offset
currSecondCount = 0
if midBulletArray, exists := midBullets[currSecond]; exists {
log.V(10).Infow(ctx, "log", "current second user have published danmu", "offset", currSecond, "len", len(*midBulletArray))
for _, midBullet := range *midBulletArray {
currSecondCount++
res = append(res, midBullet)
if currSecondCount >= model.SecondMaxNum {
break
}
}
}
}
if currSecondCount >= model.SecondMaxNum {
continue
}
if bullet.Mid != mid {
currSecondCount++
bullet.OffsetMs = bullet.Offset * 1000
res = append(res, bullet)
}
}
if len(res) > 0 {
var cursor CursorValue
cursor.Offset = res[len(res)-1].Offset
b, _ := json.Marshal(cursor)
res[len(res)-1].CursorValue = string(b)
}
return
}
// ContentList 用于返回弹幕列表
/*
*/
func (d *Dao) ContentList(ctx context.Context, req *api.ListBulletReq) (res *api.ListBulletReply, err error) {
res = new(api.ListBulletReply)
// 0. 前期准备
// 获取当前oid的最大offset弹幕的offset
oidLastOffset, err := d.lastOffset(ctx, req.Oid)
if err != nil {
log.Warnv(ctx, log.KV("log", "get has more info fail"))
return
}
// 解析cursor
cursor, err := parseCursorValue(ctx, req.CursorNext)
if err != nil {
log.Warnv(ctx, log.KV("log", "parse cursor value fail"))
return
}
// 当两者相等,则说明已经到列表的最后了
if oidLastOffset <= cursor.Offset {
res.HasMore = false
log.Warnw(ctx, "log", "offset already end", "oid_last_offset", oidLastOffset, "cursor_offset", cursor.Offset)
return
}
// 1. 按照条数取SecondMaxNum条返回数据的offset范围start和end
// 这步是为了保证该次返回至少有条数
startS := cursor.Offset + 1
startS, endS, err := d.getNumBulletTs(ctx, req.Oid, startS, model.SecondMaxNum)
if err != nil {
log.Warnv(ctx, log.KV("log", "get num start bullet fail"))
return
}
log.V(1).Infow(ctx, "log", "get num bullet ts", "start_s", startS, "end_s", endS)
endS += 1
// 2. 根据选择的时间范围获取弹幕
newReq := &api.ListBulletReq{StartMs: startS * 1000, EndMs: endS * 1000, Oid: req.Oid, Mid: req.Mid}
bullets, err := d.ContentGet(ctx, newReq)
if err != nil {
log.Warnv(ctx, log.KV("log", "content get fail: req="+newReq.String()))
return
}
res.List = bullets
// 3. has_more设置如果offset和最后时间offset相等那么肯定没有更多弹幕了
if len(bullets) > 0 && oidLastOffset > bullets[len(bullets)-1].Offset {
res.HasMore = true
} else {
res.HasMore = false
}
return
}
func (d *Dao) getNumBulletTs(ctx context.Context, oid int64, startOffset, size int32) (startS, endS int32, err error) {
querySQL := fmt.Sprintf(
"select offset from bullet_content where oid=%d and state=0 and offset>=%d order by offset limit %d",
oid, startOffset, size)
rows, err := d.db.Query(ctx, querySQL)
if err != nil {
log.Errorv(ctx, log.KV("log", fmt.Sprintf("get num bullet from db fail: sql=%s", querySQL)))
return
}
log.V(1).Infow(ctx, "sql", querySQL)
var offset int32
var index int32
for rows.Next() {
if err = rows.Scan(&offset); err != nil {
log.Errorv(ctx, log.KV("log", "scan mysql fail: sql="+querySQL))
return
}
if index == 0 {
startS = offset
}
endS = offset
index++
}
return
}
func (d *Dao) lastOffset(ctx context.Context, oid int64) (lastOffset int32, err error) {
querySQL := fmt.Sprintf("select offset from bullet_content where oid=%d and state=0 order by offset desc limit 1", oid)
row := d.db.QueryRow(ctx, querySQL)
if err = row.Scan(&lastOffset); err != nil {
if err == sql.ErrNoRows {
err = nil
lastOffset = -1
} else {
log.Errorw(ctx, "log", "get has more from db fail", "sql", querySQL, "err", err)
return
}
}
return
}
// CursorValue .
type CursorValue struct {
Offset int32 `json:"offset"`
// level本来是想要用于避免一次选择太少的弹幕但后面修改策略进行二次查找之后就没这个必要了
//Level int32 `json:"level"`
//duration int32 `json:"duration"`
}
func parseCursorValue(ctx context.Context, cursorValue string) (cursor CursorValue, err error) {
if len(cursorValue) == 0 {
cursor.Offset = -1
//cursor.Level = 1
return
}
if err = json.Unmarshal([]byte(cursorValue), &cursor); err != nil {
log.Errorw(ctx, "log", "unmarshal fail: str="+cursorValue, "err", err)
return
}
return
}
//
//// 这里做了个优化当对于弹幕数较少的视频level等级定的高点在弹幕列表页中就可以选取更长范围的弹幕
//func getCursorLevel(duration int32, num int32) (level int32) {
// numPerSecond := num / duration
// if numPerSecond < 1 {
// level = 10
// } else if numPerSecond < 2 {
// level = 5
// } else if numPerSecond < 5 {
// level = 2
// } else {
// level = 1
// }
// return
//}

View File

@@ -0,0 +1,86 @@
package dao
import (
"context"
user "go-common/app/service/bbq/user/api"
video "go-common/app/service/bbq/video/api/grpc/v1"
filter "go-common/app/service/main/filter/api/grpc/v1"
"go-common/library/ecode"
"go-common/library/log"
)
// 用于filter
const (
FilterAreaAccount = "BBQ_account"
FilterAreaVideo = "BBQ_video"
FilterAreaReply = "BBQ_reply"
FilterAreaSearch = "BBQ_search"
FilterAreaDanmu = "BBQ_danmu"
FilterLevel = 20
)
// 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
}
// PhoneCheck .
func (d *Dao) PhoneCheck(c context.Context, mid int64) (telStatus int32, err error) {
req := &user.PhoneCheckReq{Mid: mid}
res, err := d.userClient.PhoneCheck(c, req)
if err != nil {
log.Errorw(c, "log", "call phone check fail", "err", err, "mid", mid)
return
}
telStatus = res.TelStatus
return
}
// batchVideoInfo .
func (d *Dao) batchVideoInfo(c context.Context, svids []int64) (res map[int64]*video.VideoInfo, err error) {
res = make(map[int64]*video.VideoInfo)
videoReq := &video.ListVideoInfoRequest{SvIDs: svids}
reply, err := d.videoClient.ListVideoInfo(c, videoReq)
if err != nil {
log.Errorw(c, "log", "call video service list vidoe info fail", "req", videoReq.String())
return
}
for _, videoInfo := range reply.List {
res[videoInfo.VideoBase.Svid] = videoInfo
}
log.V(1).Infow(c, "log", "batch video base", "video_info", res)
return
}
// VideoBase 获取单个svid的VideoBase不存在则会返回error
func (d *Dao) VideoBase(c context.Context, svid int64) (res *video.VideoBase, err error) {
videoInfos, err := d.batchVideoInfo(c, []int64{svid})
if err != nil {
log.Warnw(c, "log", "batch fetch video info fail", "svid", svid)
return
}
if len(videoInfos) == 0 {
err = ecode.VideoUnExists
return
}
videoInfo, exists := videoInfos[svid]
if !exists {
err = ecode.VideoUnExists
return
}
res = videoInfo.VideoBase
return
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["model.go"],
importpath = "go-common/app/interface/bbq/bullet/internal/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,32 @@
package model
// const 常量
const (
SecondMaxNum = 15
CommonDurationSecond = 10
BulletMaxLen = 16
)
// 接口Action定义
const (
ActionRecommend = iota
ActionPlay
ActionLike
ActionCancelLike
ActionFollow
ActionCancelFollow
ActionCommentAdd
ActionCommentLike
ActionCommentReport
ActionFeedList
ActionShare
ActionDanmaku
ActionPlayPause
ActionPushRegister
ActionPushSucced
ActionPushCallback
ActionBlack
ActionCancelBlack
ActionVideoSearch
ActionUserSearch
)

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/interface/bbq/bullet/internal/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/bbq/bullet/api:go_default_library",
"//app/interface/bbq/bullet/internal/conf:go_default_library",
"//app/interface/bbq/bullet/internal/model:go_default_library",
"//app/interface/bbq/bullet/internal/service:go_default_library",
"//app/interface/bbq/common/auth:go_default_library",
"//app/interface/bbq/common/http:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/verify: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"],
)

View File

@@ -0,0 +1,179 @@
package http
import (
"net/http"
"strings"
"go-common/app/interface/bbq/bullet/api"
"go-common/app/interface/bbq/bullet/internal/conf"
"go-common/app/interface/bbq/bullet/internal/model"
"go-common/app/interface/bbq/bullet/internal/service"
xauth "go-common/app/interface/bbq/common/auth"
chttp "go-common/app/interface/bbq/common/http"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
antispam "go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/verify"
"github.com/golang/protobuf/ptypes/empty"
)
var (
// TODO:verify
vfy *verify.Verify
svc *service.Service
authSrv *xauth.BannedAuth
logger *chttp.UILog
bulletAntiSpam *antispam.Antispam
)
// Init init
func Init(c *conf.Config, s *service.Service) {
svc = s
initAntiSpam(c)
vfy = verify.New(c.Verify)
authSrv = xauth.NewBannedAuth(c.Auth, c.OnlineMySQL)
engine := bm.DefaultServer(c.BM)
route(engine)
if err := engine.Start(); err != nil {
log.Error("bm Start error(%v)", err)
panic(err)
}
logger = chttp.New(c.Infoc)
}
func initAntiSpam(c *conf.Config) {
var antiConfig *antispam.Config
var exists bool
if antiConfig, exists = c.AntiSpam["bullet"]; !exists {
panic("lose bullet anti_spam config")
}
bulletAntiSpam = antispam.New(antiConfig)
}
func route(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
g := e.Group("/bbq/bullet", wrapBBQ)
{
g.GET("/content/list", authSrv.Guest, contentList)
g.GET("/content/get", authSrv.Guest, contentGet)
g.POST("/content/post", authSrv.User, phoneCheck, bulletAntiSpam.ServeHTTP, contentPost)
}
}
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)
}
//wrapRes 为返回头添加BBQ自定义字段
func wrapBBQ(ctx *bm.Context) {
chttp.WrapHeader(ctx)
}
func uiLog(ctx *bm.Context, action int, ext interface{}) {
logger.Infoc(ctx, action, ext)
}
func contentGet(c *bm.Context) {
if conf.Conf.BulletConfig.CloseRead {
c.JSON([]*api.Bullet{}, nil)
return
}
arg := &api.ListBulletReq{}
if err := c.Bind(arg); err != nil {
return
}
if arg.Oid == 0 {
res := new([]interface{})
c.JSON(res, ecode.DanmuGetErr)
return
}
if midValue, exists := c.Get("mid"); exists {
arg.Mid = midValue.(int64)
}
c.JSON(svc.ContentGet(c, arg))
}
func contentList(c *bm.Context) {
if conf.Conf.BulletConfig.CloseRead {
c.JSON(api.ListBulletReply{HasMore: false}, nil)
return
}
arg := &api.ListBulletReq{}
if err := c.Bind(arg); err != nil {
return
}
if arg.Oid == 0 {
res := new([]interface{})
c.JSON(res, ecode.DanmuGetErr)
return
}
if midValue, exists := c.Get("mid"); exists {
arg.Mid = midValue.(int64)
}
c.JSON(svc.ContentList(c, arg))
}
func contentPost(c *bm.Context) {
if conf.Conf.BulletConfig.CloseWrite {
c.JSON(struct{}{}, nil)
return
}
arg := &api.Bullet{}
if err := c.Bind(arg); err != nil {
return
}
// 这里客户端是有限制的所以这里就不单独给出toast了
if arg.Oid == 0 || arg.Content == "" || strings.Count(arg.Content, "") > model.BulletMaxLen {
log.Warnw(c, "log", "post error", "req", arg, "content_len", strings.Count(arg.Content, ""))
c.JSON(nil, ecode.DanmuPostErr)
return
}
if midValue, exists := c.Get("mid"); exists {
arg.Mid = midValue.(int64)
}
dmid, err := svc.ContentPost(c, arg)
c.JSON(new(empty.Empty), err)
uiLog(c, model.ActionDanmaku, struct {
DMID int64 `json:"dmid"`
}{
DMID: dmid,
})
}
// phoneCheck 进行手机校验
func phoneCheck(ctx *bm.Context) {
midValue, exists := ctx.Get("mid")
if !exists {
ctx.JSON(nil, ecode.NoLogin)
ctx.Abort()
return
}
mid := midValue.(int64)
err := svc.PhoneCheck(ctx, mid)
if err != nil {
ctx.JSON(nil, err)
ctx.Abort()
return
}
}

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 = ["service.go"],
importpath = "go-common/app/interface/bbq/bullet/internal/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/bbq/bullet/api:go_default_library",
"//app/interface/bbq/bullet/internal/conf:go_default_library",
"//app/interface/bbq/bullet/internal/dao:go_default_library",
"//app/service/bbq/video/api/grpc/v1:go_default_library",
"//library/ecode:go_default_library",
"//library/log: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,125 @@
package service
import (
"context"
"fmt"
"go-common/app/interface/bbq/bullet/api"
"go-common/app/interface/bbq/bullet/internal/conf"
"go-common/app/interface/bbq/bullet/internal/dao"
video "go-common/app/service/bbq/video/api/grpc/v1"
"go-common/library/ecode"
"go-common/library/log"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
}
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()
}
// ContentGet .
func (s *Service) ContentGet(ctx context.Context, req *api.ListBulletReq) (res []*api.Bullet, err error) {
videoBase, err := s.dao.VideoBase(ctx, req.Oid)
if err != nil {
log.Warnw(ctx, "log", "get video base fail", "err", err, "req", req)
return
}
if video.IsLimitSet(videoBase.Limits, video.VideoLimitBitBullet) {
log.Infow(ctx, "log", "video limit is set")
return []*api.Bullet{}, nil
}
if res, err = s.dao.ContentGet(ctx, req); err != nil {
log.Warnv(ctx, log.KV("log", "content get fail"))
return
}
return
}
// ContentList .
func (s *Service) ContentList(ctx context.Context, req *api.ListBulletReq) (res *api.ListBulletReply, err error) {
videoBase, err := s.dao.VideoBase(ctx, req.Oid)
if err != nil {
log.Warnw(ctx, "log", "get video base fail", "err", err, "req", req)
return
}
if video.IsLimitSet(videoBase.Limits, video.VideoLimitBitBullet) {
log.Infow(ctx, "log", "video limit is set")
return &api.ListBulletReply{}, nil
}
if res, err = s.dao.ContentList(ctx, req); err != nil {
log.Warnv(ctx, log.KV("log", "get content list fail"))
return
}
return
}
// ContentPost .
func (s *Service) ContentPost(ctx context.Context, req *api.Bullet) (dmid int64, err error) {
videoBase, err := s.dao.VideoBase(ctx, req.Oid)
if err != nil {
log.Warnw(ctx, "log", "get video base fail", "err", err, "req", req)
return
}
if video.IsLimitSet(videoBase.Limits, video.VideoLimitBitBullet) {
log.Infow(ctx, "log", "video limit is set")
err = ecode.DanmuLimitErr
return
}
// 屏蔽词
level, filterErr := s.dao.Filter(ctx, req.Content, dao.FilterAreaDanmu)
if filterErr != nil {
log.Errorv(ctx, log.KV("log", "filter fail"))
} else if level >= dao.FilterLevel {
err = ecode.FilterErr
log.Warnv(ctx, log.KV("log", fmt.Sprintf("content filter fail: content=%s, level=%d", req.Content, level)))
return
}
// 发布弹幕
dmid, err = s.dao.ContentPost(ctx, req)
if err != nil {
log.Warnv(ctx, log.KV("log", "publish danmu fail"))
return
}
return
}
//PhoneCheck ..
func (s *Service) PhoneCheck(c context.Context, mid int64) (err error) {
telStatus, err := s.dao.PhoneCheck(c, mid)
if err != nil {
log.Errorw(c, "log", "call phone check fail", "mid", mid)
return
}
if telStatus == 0 {
err = ecode.BBQNoBindPhone
log.Infow(c, "log", "no bind phone", "mid", mid)
return
}
return
}