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

10
app/interface/main/playlist/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.idea/*
.project
.buildpath
.externalToolBuilders
.settings
.DS_Store
.a
*.iml
.vscode/*
playlist

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/interface/main/playlist/cmd:all-srcs",
"//app/interface/main/playlist/conf:all-srcs",
"//app/interface/main/playlist/dao:all-srcs",
"//app/interface/main/playlist/http:all-srcs",
"//app/interface/main/playlist/model:all-srcs",
"//app/interface/main/playlist/rpc/client:all-srcs",
"//app/interface/main/playlist/rpc/server:all-srcs",
"//app/interface/main/playlist/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,105 @@
### playlist
#### Version v1.6.3
##### Features
> 1.del PlaylistStat-T
#### Version v1.6.2
##### Features
> 1.forbid add ugc pay archive.
#### Version v1.6.1
##### Features
> 1.add internal
#### Version v1.6.0
##### Features
> 1.archive,account grpc
#### Version v1.5.0
##### Features
> 1.修改播单排序bug
#### Version v1.4.2
##### Features
> 1.使用bm.Start和bm.ServerConfig
> 2.改用metadata.RemoteIP方法
#### Version v1.4.1
##### Features
> 1.fix range out
#### Version v1.4.0
##### Features
> 1.identify使用grpc
> 2.在mysql中添加rows error
#### Version 1.3.2
##### Features
> 1.common conf
#### Version 1.3.1
##### Features
> 1.HTTPServer
> 2.del default conf
#### Version 1.3.0
##### Features
> 1.迁移main目录
#### Version 1.2.1
##### Features
> 1.使用account-service v7
#### Version 1.2.0
##### Features
> 1.Stat-T拆分 pid to id
#### Version 1.1.2
##### Features
> 1.bm update
#### Version 1.1.1
##### Features
> 1.http use bm
#### Version 1.1.0
##### Features
> 1.add share report
> 2.上报计数改为绝对值
> 3.计数信息流双写统一的topic
#### Version 1.0.7
##### Features
> 1.add video sort
#### Version 1.0.6
##### Bug Fixes
> 1.add video panic fix
#### Version 1.0.5
##### Bug Fixes
> 1.cache expire bug fix
#### Version 1.0.4
##### Features
> 1.上报接口加aid参数
> 2.rpc 更新缓存加状态判断
#### Version 1.0.3
##### Features
> 1.代码优化
#### Version 1.0.2
##### Bug Fixes
> 1.del playlist and del arc use trans
#### Version 1.0.1
##### Bug Fixes
> 1.rpc server add auth
> 2.playlist sort fix
#### Version 1.0.0
##### Features
> 1.初始化项目
> 2.新增播单相关接口

View File

@@ -0,0 +1,8 @@
# Owner
liweijia
# Author
all
# Reviewer
all

View File

@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- liweijia
labels:
- interface
- interface/main/playlist
- main
options:
no_parent_owners: true

View File

@@ -0,0 +1,18 @@
#### play-list
##### 项目简介
> 1.提供播单接口
##### 编译环境
> 请使用golang v1.8.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common
##### 编译执行
> 在主目录执行go build。
> 编译后可执行 ./cmd/cmd -conf play-list-test.toml 使用项目本地配置文件启动服务。
> 也可执行 ./play-list -conf_appid=play-list -conf_version=v2.1.0 -conf_host=172.16.33.134:9051 -conf_path=/data/conf/play-list -conf_env=10 -conf_token=SEHXM8x1vYhIUaZvQUmyWnMYJrF9jHJY 使用配置中心测试环境配置启动服务如无法启动可检查token是否正确。
##### 特别说明
> http接口文档可参考 http://info.bilibili.co/pages/viewpage.action?pageId=2490548

View File

@@ -0,0 +1,45 @@
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 = ["playlist-test.toml"],
importpath = "go-common/app/interface/main/playlist/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/http:go_default_library",
"//app/interface/main/playlist/rpc/server:go_default_library",
"//app/interface/main/playlist/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,6 @@
#!/bin/bash
command -v goconvey >/dev/null 2>&1 || { echo >&2 "required goconvey but it's not installed."; echo "Aborting."; echo "Please run commond: go get github.com/smartystreets/goconvey"; exit 1; }
cd ../
goconvey -excludedDirs "vendor" -packages 1

View File

@@ -0,0 +1,54 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/http"
rpc "go-common/app/interface/main/playlist/rpc/server"
"go-common/app/interface/main/playlist/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 {
log.Error("conf.Init error(%v)", err)
panic(err)
}
log.Init(conf.Conf.Log)
trace.Init(conf.Conf.Tracer)
defer trace.Close()
defer log.Close()
log.Info("playlist start")
// ecode
ecode.Init(conf.Conf.Ecode)
//server init
svr := service.New(conf.Conf)
rpcSvr := rpc.New(conf.Conf, svr)
http.Init(conf.Conf, svr)
// signal handler
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("playlist get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT:
rpcSvr.Close()
log.Info("playlist exit")
time.Sleep(time.Second)
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,157 @@
[identify]
whiteAccessKey = ""
whiteMid = 0
[identify.app]
key = "b1014d7c339a5649"
secret = "75b74b612aa792b112e6504cae44c319"
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 5
idle = 1
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.com"
secret = "http://open.bilibili.com"
[identify.httpClient]
key = "7c7ac0db1aa05587"
secret = "9a6d62d93290c5f771ad381e9ca23f26"
dial = "30ms"
timeout = "150ms"
keepAlive = "60s"
[identify.httpClient.breaker]
window ="10s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.httpClient.url]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[log]
dir = "/data/log/playlist/"
[app]
key = "b1014d7c339a5649"
secret = "75b74b612aa792b112e6504cae44c319"
[HTTPServer]
addr = "0.0.0.0:7151"
timeout = "1s"
[rpcServer2]
[[rpcServer2.servers]]
proto = "tcp"
addr = ":7159"
weight = 10
[rpcServer2.zookeeper]
root = "/microservice/playlist-service/"
addrs = ["172.18.33.172:2181"]
timeout = "60s"
[viewDatabus]
key = "9765cdac5894f2ba"
secret= "1448f5f2cd6029f6af6c5d438cd31edd"
group= "StatView-MainWebSvr-P"
topic= "StatView-T"
action="pub"
name = "playlist/playlist-pub/view"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[shareDatabus]
key = "9765cdac5894f2ba"
secret= "1448f5f2cd6029f6af6c5d438cd31edd"
group= "StatShare-MainWebSvr-P"
topic= "StatShare-T"
action="pub"
name = "playlist/playlist-pub/share"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[favoriteRPC]
timeout = "1s"
[archiveRPC]
timeout = "1s"
[accountRPC]
timeout = "200ms"
[filterRPC]
timeout = "1s"
[mysql]
addr = "172.16.33.205"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_playlist?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout = "4h"
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
name = "playlist"
proto = "tcp"
addr = "172.18.33.60:6894"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
statExpire = "24h"
plExpire = "24h"
[httpClient]
key = "b1014d7c339a5649"
secret = "75b74b612aa792b112e6504cae44c319"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[rule]
maxNameLimit = 80
maxPlDescLimit = 250
maxVideoDescLimit = 80
maxArcChangeLimit = 100
maxVideoCnt = 100
maxPlCnt = 100
maxPlsPageSize = 30
maxPlArcsPs = 100
sortStep = 100
minSort = 0
beginSort = 1000
maxSearchArcPs = 30
maxSearchLimit = 30
powerMids = [101, 27515235, 2089809, 27515232]
[host]
search = "http://s.search.bilibili.co"

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 = ["conf.go"],
importpath = "go-common/app/interface/main/playlist/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/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/auth:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,153 @@
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"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
)
// global var
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config config set
type Config struct {
// base
// elk
Log *log.Config
// App
App *bm.App
// rpc server2
RPCServer *rpc.ServerConfig
// tracer
Tracer *trace.Config
// auth
Auth *auth.Config
// verify
Verify *verify.Config
// HTTPServer
HTTPServer *bm.ServerConfig
// Ecode
Ecode *ecode.Config
// rpc
FavoriteRPC *rpc.ClientConfig
ArchiveRPC *rpc.ClientConfig
AccountRPC *rpc.ClientConfig
FilterRPC *rpc.ClientConfig
// databus
ViewDatabus *databus.Config
ShareDatabus *databus.Config
// Mysql
Mysql *sql.Config
// Redis
Redis *Redis
// HTTP client
HTTPClient *bm.ClientConfig
// Rule
Rule *Rule
// Host
Host *Host
// Warden Client
ArcClient *warden.ClientConfig
AccClient *warden.ClientConfig
}
// Host hosts.
type Host struct {
Search string
}
// Redis redis struct
type Redis struct {
*redis.Config
PlExpire time.Duration
StatExpire time.Duration
}
// Rule playlist config
type Rule struct {
MaxNameLimit int
MaxPlDescLimit int
MaxVideoDescLimit int
MaxArcChangeLimit int
MaxVideoCnt int
MaxPlCnt int
MaxPlArcsPs int
MaxPlsPageSize int
SortStep int64
MinSort int64
MaxSearchArcPs int
MaxSearchLimit int
BeginSort int64
PowerMids []int64
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,73 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"databus_test.go",
"mysql_test.go",
"redis_test.go",
"reply_test.go",
"search_test.go",
"stat_redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"databus.go",
"mysql.go",
"redis.go",
"reply.go",
"search.go",
"stat_redis.go",
],
importpath = "go-common/app/interface/main/playlist/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//app/job/main/playlist/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/stat/prom:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,80 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/app/interface/main/playlist/conf"
"go-common/library/cache/redis"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
"go-common/library/stat/prom"
)
// Dao dao struct.
type Dao struct {
// config
c *conf.Config
// db
db *sql.DB
// redis
redis *redis.Pool
statExpire int32
plExpire int32
// http client
http *bm.Client
// stmt
videosStmt map[string]*sql.Stmt
// databus
viewDbus *databus.Databus
shareDbus *databus.Databus
// search video URL
searchURL string
}
// New new dao.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
// config
c: c,
db: sql.NewMySQL(c.Mysql),
redis: redis.NewPool(c.Redis.Config),
statExpire: int32(time.Duration(c.Redis.StatExpire) / time.Second),
plExpire: int32(time.Duration(c.Redis.PlExpire) / time.Second),
http: bm.NewClient(c.HTTPClient),
viewDbus: databus.New(c.ViewDatabus),
shareDbus: databus.New(c.ShareDatabus),
searchURL: c.Host.Search + _searchURL,
}
d.videosStmt = make(map[string]*sql.Stmt, _plArcSub)
for i := 0; i < _plArcSub; i++ {
key := fmt.Sprintf("%02d", i)
d.videosStmt[key] = d.db.Prepared(fmt.Sprintf(_plArcsSQL, key))
}
return
}
// Ping ping dao
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
return
}
err = d.pingRedis(c)
return
}
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
// PromError stat and log.
func PromError(name string, format string, args ...interface{}) {
prom.BusinessErrCount.Incr(name)
log.Error(format, args...)
}

View File

@@ -0,0 +1,36 @@
package dao
import (
"flag"
"os"
"testing"
"go-common/app/interface/main/playlist/conf"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", " main.web-svr.playlist")
flag.Set("conf_token", "050b58400f4204509146d9b0a63e0cec")
flag.Set("tree_id", "6215")
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/playlist-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,55 @@
package dao
import (
"context"
"strconv"
"time"
"go-common/app/interface/main/playlist/model"
pjmdl "go-common/app/job/main/playlist/model"
"go-common/library/log"
"go-common/library/net/metadata"
xtime "go-common/library/time"
)
var _defaultAdd = int64(1)
// PubView adds a view count.
func (d *Dao) PubView(c context.Context, pid, aid, view int64) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
view += _defaultAdd
msg := &pjmdl.StatM{
Type: model.PlDBusType,
ID: pid,
Aid: aid,
IP: ip,
Count: &view,
Timestamp: xtime.Time(time.Now().Unix()),
}
if err = d.viewDbus.Send(c, strconv.FormatInt(pid, 10), msg); err != nil {
PromError("databus:发送浏览量", "d.viewDbus.Send(%+v) error(%v)", msg, err)
return
}
log.Info("s.PubView( pid: %d, aid: %d, ip: %s, view: %d)", msg.ID, msg.Aid, msg.IP, *msg.Count)
return
}
// PubShare adds a share count.
func (d *Dao) PubShare(c context.Context, pid, aid, share int64) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
share += _defaultAdd
msg := &pjmdl.StatM{
Type: model.PlDBusType,
ID: pid,
Aid: aid,
IP: ip,
Count: &share,
Timestamp: xtime.Time(time.Now().Unix()),
}
if err = d.shareDbus.Send(c, strconv.FormatInt(pid, 10), msg); err != nil {
PromError("databus:发送分享数", "d.shareDbus.Send(%+v) error(%v)", msg, err)
return
}
log.Info("s.PubShare( pid: %d, aid: %d, ip: %s, share: %d)", msg.ID, msg.Aid, msg.IP, *msg.Count)
return
}

View File

@@ -0,0 +1,38 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPubView(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
view = int64(1000)
)
convey.Convey("PubView", t, func(ctx convey.C) {
err := d.PubView(c, pid, aid, view)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoPubShare(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
share = int64(50)
)
convey.Convey("PubShare", t, func(ctx convey.C) {
err := d.PubShare(c, pid, aid, share)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,267 @@
package dao
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"go-common/app/interface/main/playlist/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_plArcSub = 100
_plArcSQL = "SELECT aid,sort,`desc` FROM playlist_archive_%s WHERE pid = ? AND aid = ?"
_plArcsSQL = "SELECT aid,sort,`desc` FROM playlist_archive_%s WHERE pid = ? ORDER BY sort"
_plArcAddSQL = "INSERT INTO playlist_archive_%s (pid,aid,sort,`desc`) VALUES (?,?,?,?)"
_plArcBatchAddSQL = "INSERT INTO playlist_archive_%s (pid,aid,sort,`desc`) VALUES %s"
_plArcDelSQL = "DELETE FROM playlist_archive_%s WHERE pid = ? AND aid = ?"
_plArcDelByPidSQL = "DELETE FROM playlist_archive_%s WHERE pid = ?"
_plArcBatchDelSQL = "DELETE FROM playlist_archive_%s WHERE pid = ? AND aid in (%s)"
_plArcDescEditSQL = "UPDATE playlist_archive_%s SET `desc` = ? WHERE pid = ? AND aid = ?"
_plArcSortEditSQL = "UPDATE playlist_archive_%s SET sort = ? WHERE pid = ? AND aid = ?"
_plArcSortBatchEditSQL = "UPDATE playlist_archive_%s SET sort = CASE %s END WHERE pid = ? AND aid IN (%s)"
_plAddSQL = "INSERT INTO playlist_stat (mid,fid,is_deleted,view,reply,fav,share) VALUES (?,?,0,0,0,0,0) ON DUPLICATE KEY UPDATE mid=?,fid=?,is_deleted=0,view=0,reply=0,fav=0,share=0"
_plEditSQL = "UPDATE playlist_stat SET mtime = ? WHERE id = ?"
_plDelSQL = "UPDATE playlist_stat set is_deleted = 1 WHERE id = ?"
_plByMidSQL = "SELECT id,mid,fid,view,reply,fav,`share`,mtime FROM playlist_stat WHERE is_deleted = 0 AND mid = ?"
_plByPidsSQL = "SELECT id,mid,fid,view,reply,fav,`share`,mtime FROM playlist_stat WHERE id in (%s)"
_plByPidSQL = "SELECT id,mid,fid,view,reply,fav,`share`,mtime FROM playlist_stat WHERE id = ?"
)
func plArcHit(pid int64) string {
return fmt.Sprintf("%02d", pid%_plArcSub)
}
// Video get video by pid and aid
func (d *Dao) Video(c context.Context, pid, aid int64) (res *model.ArcSort, err error) {
res = &model.ArcSort{}
row := d.db.QueryRow(c, fmt.Sprintf(_plArcSQL, plArcHit(pid)), pid, aid)
if err = row.Scan(&res.Aid, &res.Sort, &res.Desc); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("Video:row.Scan error(%v)", err)
}
}
return
}
// Videos get playlist videos.
func (d *Dao) Videos(c context.Context, pid int64) (res []*model.ArcSort, err error) {
var rows *xsql.Rows
if rows, err = d.videosStmt[plArcHit(pid)].Query(c, pid); err != nil {
log.Error("d.videosStmt[%s].Query(%d) error(%v)", plArcHit(pid), pid, err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.ArcSort)
if err = rows.Scan(&r.Aid, &r.Sort, &r.Desc); err != nil {
log.Error("row.Scan() error(%v)", err)
return
}
res = append(res, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.Err() error(%v)", err)
}
return
}
// AddArc add archive to playlist.
func (d *Dao) AddArc(c context.Context, pid, aid, sort int64, desc string) (lastID int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcAddSQL, plArcHit(pid)), pid, aid, desc, sort); err != nil {
log.Error("AddArc: db.Exec(%d,%d,%d,%s) error(%v)", pid, aid, sort, desc, err)
return
}
return res.LastInsertId()
}
// BatchAddArc add archives to playlist.
func (d *Dao) BatchAddArc(c context.Context, pid int64, arcSorts []*model.ArcSort) (lastID int64, err error) {
var (
res sql.Result
values []string
)
for _, v := range arcSorts {
values = append(values, fmt.Sprintf("(%d,%d,%d,'%s')", pid, v.Aid, v.Sort, v.Desc))
}
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcBatchAddSQL, plArcHit(pid), strings.Join(values, ","))); err != nil {
log.Error("BatchAddArc: db.Exec(%d) error(%v)", pid, err)
return
}
return res.LastInsertId()
}
// DelArc delete playlist archive.
func (d *Dao) DelArc(c context.Context, pid, aid int64) (affected int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcDelSQL, plArcHit(pid)), pid, aid); err != nil {
log.Error("DelArc: db.Exec(%d,%d) error(%v)", pid, aid, err)
return
}
return res.RowsAffected()
}
// BatchDelArc delete archives from playlist.
func (d *Dao) BatchDelArc(c context.Context, pid int64, aids []int64) (affected int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcBatchDelSQL, plArcHit(pid), xstr.JoinInts(aids)), pid); err != nil {
log.Error("BatchDelArc: db.Exec(%d,%v) error(%v)", pid, aids, err)
return
}
return res.RowsAffected()
}
// UpdateArcDesc update playlist arc desc.
func (d *Dao) UpdateArcDesc(c context.Context, pid, aid int64, desc string) (affected int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcDescEditSQL, plArcHit(pid)), desc, pid, aid); err != nil {
log.Error("UpdateArcDesc: db.Exec(%d,%d,%s) error(%v)", pid, aid, desc, err)
return
}
return res.RowsAffected()
}
// UpdateArcSort update playlist arc sort.
func (d *Dao) UpdateArcSort(c context.Context, pid, aid, sort int64) (affected int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcSortEditSQL, plArcHit(pid)), sort, pid, aid); err != nil {
log.Error("UpdateArcSort: db.Exec(%d,%d,%d) error(%v)", pid, aid, sort, err)
return
}
return res.RowsAffected()
}
// BatchUpdateArcSort batch update playlist arc sort.
func (d *Dao) BatchUpdateArcSort(c context.Context, pid int64, arcSorts []*model.ArcSort) (affected int64, err error) {
var (
caseStr string
aids []int64
res sql.Result
)
for _, v := range arcSorts {
caseStr = fmt.Sprintf("%s WHEN aid = %d THEN %d", caseStr, v.Aid, v.Sort)
aids = append(aids, v.Aid)
}
if res, err = d.db.Exec(c, fmt.Sprintf(_plArcSortBatchEditSQL, plArcHit(pid), caseStr, xstr.JoinInts(aids)), pid); err != nil {
log.Error("BatchUpdateArcSort: db.Exec(%d,%s,%v) error(%v)", pid, caseStr, aids, err)
return
}
return res.RowsAffected()
}
//Add playlist stat.
func (d *Dao) Add(c context.Context, mid, fid int64) (lastID int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, _plAddSQL, mid, fid, mid, fid); err != nil {
log.Error("Add:db.Exec(%d,%d) error(%v)", mid, fid, err)
return
}
return res.LastInsertId()
}
// Del playlist stat.
func (d *Dao) Del(c context.Context, pid int64) (affected int64, err error) {
var (
res sql.Result
tx *xsql.Tx
)
if tx, err = d.db.Begin(c); err != nil {
log.Error("d.db.Begin error(%v)", err)
return
}
if res, err = tx.Exec(_plDelSQL, pid); err != nil {
tx.Rollback()
log.Error("DelPlaylist: db.Exec(%d) error(%v)", pid, err)
return
}
if _, err = d.db.Exec(c, fmt.Sprintf(_plArcDelByPidSQL, plArcHit(pid)), pid); err != nil {
tx.Rollback()
log.Error("DelArc: db.Exec(%d) error(%v)", pid, err)
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
return res.RowsAffected()
}
// Update playlist stat.
func (d *Dao) Update(c context.Context, pid int64) (affected int64, err error) {
var res sql.Result
if res, err = d.db.Exec(c, _plEditSQL, time.Now(), pid); err != nil {
log.Error("Update mtime: db.Exec(%d) error(%v)", pid, err)
return
}
return res.RowsAffected()
}
// PlsByMid get playlist by mid.
func (d *Dao) PlsByMid(c context.Context, mid int64) (res []*model.PlStat, err error) {
var (
rows *xsql.Rows
)
if rows, err = d.db.Query(c, _plByMidSQL, mid); err != nil {
log.Error("PlsByMid:d.db.Query(%d) error(%v)", mid, err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.PlStat)
if err = rows.Scan(&r.ID, &r.Mid, &r.Fid, &r.View, &r.Reply, &r.Fav, &r.Share, &r.MTime); err != nil {
log.Error("PlsByMid:row.Scan() error(%v)", err)
return
}
res = append(res, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.Err() error(%v)", err)
}
return
}
// PlsByPid get playlist stat by pids.
func (d *Dao) PlsByPid(c context.Context, pids []int64) (res []*model.PlStat, err error) {
var (
rows *xsql.Rows
)
if rows, err = d.db.Query(c, fmt.Sprintf(_plByPidsSQL, xstr.JoinInts(pids))); err != nil {
log.Error("PlsByPid: db.Exec(%s) error(%v)", xstr.JoinInts(pids), err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.PlStat)
if err = rows.Scan(&r.ID, &r.Mid, &r.Fid, &r.View, &r.Reply, &r.Fav, &r.Share, &r.MTime); err != nil {
log.Error("PlsByPid:row.Scan() error(%v)", err)
return
}
res = append(res, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.Err() error(%v)", err)
}
return
}
// PlByPid get playlist by pid.
func (d *Dao) PlByPid(c context.Context, pid int64) (res *model.PlStat, err error) {
res = &model.PlStat{}
row := d.db.QueryRow(c, _plByPidSQL, pid)
if err = row.Scan(&res.ID, &res.Mid, &res.Fid, &res.View, &res.Reply, &res.Fav, &res.Share, &res.MTime); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("PlByPid:row.Scan error(%v)", err)
}
}
return
}

View File

@@ -0,0 +1,245 @@
package dao
import (
"context"
"testing"
"go-common/app/interface/main/playlist/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoplArcHit(t *testing.T) {
var (
pid = int64(1)
)
convey.Convey("plArcHit", t, func(ctx convey.C) {
p1 := plArcHit(pid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoVideo(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
)
convey.Convey("Video", t, func(ctx convey.C) {
res, err := d.Video(c, pid, aid)
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 TestDaoVideos(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
)
convey.Convey("Videos", t, func(ctx convey.C) {
_, err := d.Videos(c, pid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoAddArc(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(3)
sort = int64(0)
desc = "abc"
)
convey.Convey("AddArc", t, func(ctx convey.C) {
lastID, err := d.AddArc(c, pid, aid, sort, desc)
ctx.Convey("Then err should be nil.lastID should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(lastID, convey.ShouldNotBeNil)
})
})
}
func TestDaoBatchAddArc(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
arcSorts = []*model.ArcSort{}
)
convey.Convey("BatchAddArc", t, func(ctx convey.C) {
arcSorts = append(arcSorts, &model.ArcSort{Aid: 13825646, Desc: "abc", Sort: 100})
lastID, err := d.BatchAddArc(c, pid, arcSorts)
ctx.Convey("Then err should be nil.lastID should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(lastID, convey.ShouldNotBeNil)
})
})
}
func TestDaoDelArc(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
)
convey.Convey("DelArc", t, func(ctx convey.C) {
affected, err := d.DelArc(c, pid, aid)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoBatchDelArc(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aids = []int64{13825646, 11, 22, 33}
)
convey.Convey("BatchDelArc", t, func(ctx convey.C) {
affected, err := d.BatchDelArc(c, pid, aids)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoUpdateArcDesc(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
desc = "abc"
)
convey.Convey("UpdateArcDesc", t, func(ctx convey.C) {
affected, err := d.UpdateArcDesc(c, pid, aid, desc)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoUpdateArcSort(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
sort = int64(0)
)
convey.Convey("UpdateArcSort", t, func(ctx convey.C) {
affected, err := d.UpdateArcSort(c, pid, aid, sort)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoBatchUpdateArcSort(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
arcSorts = []*model.ArcSort{}
)
convey.Convey("BatchUpdateArcSort", t, func(ctx convey.C) {
arcSorts = append(arcSorts, &model.ArcSort{Aid: 1, Desc: "abc"})
affected, err := d.BatchUpdateArcSort(c, pid, arcSorts)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoAdd(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
fid = int64(1)
)
convey.Convey("Add", t, func(ctx convey.C) {
lastID, err := d.Add(c, mid, fid)
ctx.Convey("Then err should be nil.lastID should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(lastID, convey.ShouldNotBeNil)
})
})
}
func TestDaoDel(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
)
convey.Convey("Del", t, func(ctx convey.C) {
affected, err := d.Del(c, pid)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoUpdate(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
)
convey.Convey("Update", t, func(ctx convey.C) {
affected, err := d.Update(c, pid)
ctx.Convey("Then err should be nil.affected should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(affected, convey.ShouldNotBeNil)
})
})
}
func TestDaoPlsByMid(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
)
convey.Convey("PlsByMid", t, func(ctx convey.C) {
res, err := d.PlsByMid(c, mid)
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 TestDaoPlsByPid(t *testing.T) {
var (
c = context.Background()
pids = []int64{1, 2, 3}
)
convey.Convey("PlsByPid", t, func(ctx convey.C) {
_, err := d.PlsByPid(c, pids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoPlByPid(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
)
convey.Convey("PlByPid", t, func(ctx convey.C) {
res, err := d.PlByPid(c, pid)
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,286 @@
package dao
import (
"context"
"fmt"
"go-common/app/interface/main/playlist/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_plArcKey = "pla_%d"
_plArcDescKey = "plad_%d"
)
func keyPlArc(pid int64) string {
return fmt.Sprintf(_plArcKey, pid)
}
func keyPlArcDesc(pid int64) string {
return fmt.Sprintf(_plArcDescKey, pid)
}
// ArcsCache get playlist archives cache.
func (d *Dao) ArcsCache(c context.Context, pid int64, start, end int) (arcs []*model.ArcSort, err error) {
var (
plakey = keyPlArc(pid)
pladKey = keyPlArcDesc(pid)
conn = d.redis.Get(c)
aids []int64
descs []string
)
defer conn.Close()
values, err := redis.Values(conn.Do("ZRANGE", plakey, start, end, "WITHSCORES"))
if err != nil {
log.Error("conn.Do(ZREVRANGE, %s) error(%v)", plakey, err)
return
}
if len(values) == 0 {
return
}
arcMap := make(map[int64]*model.ArcSort)
args := redis.Args{}.Add(pladKey)
for len(values) > 0 {
arc := &model.ArcSort{}
if values, err = redis.Scan(values, &arc.Aid, &arc.Sort); err != nil {
log.Error("redis.Scan(%v) error(%v)", values, err)
return
}
arcMap[arc.Aid] = arc
aids = append(aids, arc.Aid)
args = args.Add(arc.Aid)
}
if len(aids) > 0 {
descs, err = redis.Strings(conn.Do("HMGET", args...))
if err != nil {
log.Error("conn.Do(HMGET %v) error(%v)", args, err)
err = nil
}
descLen := len(descs)
for k, aid := range aids {
if arc, ok := arcMap[aid]; ok {
if descLen >= k+1 {
if desc := descs[k]; desc != "" {
arc.Desc = desc
}
}
arcs = append(arcs, arc)
}
}
}
return
}
// AddArcCache add playlist archive cache.
func (d *Dao) AddArcCache(c context.Context, pid int64, arc *model.ArcSort) (err error) {
var (
plakey = keyPlArc(pid)
pladKey = keyPlArcDesc(pid)
conn = d.redis.Get(c)
count int
)
defer conn.Close()
if _, err = redis.Bool(conn.Do("EXPIRE", plakey, d.plExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", plakey, err)
return
}
if _, err = redis.Bool(conn.Do("EXPIRE", pladKey, d.plExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", pladKey, err)
return
}
args1 := redis.Args{}.Add(plakey)
args1 = args1.Add(arc.Sort).Add(arc.Aid)
if err = conn.Send("ZADD", args1...); err != nil {
log.Error("conn.Send(ZADD, %s, %v) error(%v)", plakey, args1, err)
return
}
count++
if arc.Desc != "" {
args2 := redis.Args{}.Add(pladKey).Add(arc.Aid).Add(arc.Desc)
if err = conn.Send("HSET", args2...); err != nil {
log.Error("conn.Send(ZADD, %s, %v) error(%v)", plakey, args2, err)
return
}
count++
if err = conn.Send("EXPIRE", pladKey, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s) error(%v)", pladKey, err)
return
}
count++
}
if err = conn.Send("EXPIRE", plakey, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s) error(%v)", pladKey, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// SetArcsCache set playlist archives cache.
func (d *Dao) SetArcsCache(c context.Context, pid int64, arcs []*model.ArcSort) (err error) {
var (
plaKey = keyPlArc(pid)
pladKey = keyPlArcDesc(pid)
conn = d.redis.Get(c)
addDesc bool
count int
)
defer conn.Close()
if _, err = redis.Bool(conn.Do("EXPIRE", plaKey, d.plExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", plaKey, err)
return
}
if _, err = redis.Bool(conn.Do("EXPIRE", pladKey, d.plExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", plaKey, err)
return
}
args1 := redis.Args{}.Add(plaKey)
args2 := redis.Args{}.Add(pladKey)
for _, arc := range arcs {
args1 = args1.Add(arc.Sort).Add(arc.Aid)
if arc.Desc != "" {
addDesc = true
args2 = args2.Add(arc.Aid).Add(arc.Desc)
}
}
if err = conn.Send("ZADD", args1...); err != nil {
log.Error("conn.Send(ZADD, %s, %v) error(%v)", plaKey, args1, err)
return
}
count++
if addDesc {
if err = conn.Send("HMSET", args2...); err != nil {
log.Error("conn.Send(ZADD, %s, %v) error(%v)", pladKey, args2, err)
return
}
count++
}
if err = conn.Send("EXPIRE", pladKey, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s) error(%v)", pladKey, err)
return
}
count++
if err = conn.Send("EXPIRE", plaKey, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s) error(%v)", plaKey, err)
return
}
count++
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// SetArcDescCache set playlist archive desc cache.
func (d *Dao) SetArcDescCache(c context.Context, pid, aid int64, desc string) (err error) {
var (
key = keyPlArcDesc(pid)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = redis.Bool(conn.Do("EXPIRE", key, d.plExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", key, err)
return
}
if err = conn.Send("HSET", key, aid, desc); err != nil {
log.Error("conn.Send(HSET, %s, %d, %s) error(%v)", key, aid, desc, err)
return
}
if err = conn.Send("EXPIRE", key, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// DelArcsCache delete playlist archives cache.
func (d *Dao) DelArcsCache(c context.Context, pid int64, aids []int64) (err error) {
var (
plaKey = keyPlArc(pid)
pladKey = keyPlArcDesc(pid)
conn = d.redis.Get(c)
)
defer conn.Close()
arg1 := redis.Args{}.Add(plaKey)
arg2 := redis.Args{}.Add(pladKey)
for _, aid := range aids {
arg1 = arg1.Add(aid)
arg2 = arg2.Add(aid)
}
if err = conn.Send("ZREM", arg1...); err != nil {
log.Error("conn.Send(ZREM %s) error(%v)", plaKey, err)
return
}
if err = conn.Send("HDEL", arg2...); err != nil {
log.Error("conn.Send(HDEL %s) error(%v)", pladKey, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// DelCache del all cache .
func (d *Dao) DelCache(c context.Context, pid int64) (err error) {
var (
plaKey = keyPlArc(pid)
pladKey = keyPlArcDesc(pid)
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("DEL", plaKey); err != nil {
log.Error("conn.Send(DEL plaKey(%s) error(%v))", plaKey, err)
return
}
if err = conn.Send("DEL", pladKey); err != nil {
log.Error("conn.Send(DEL pladKey(%s) error(%v))", pladKey, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}

View File

@@ -0,0 +1,120 @@
package dao
import (
"context"
"testing"
"go-common/app/interface/main/playlist/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaokeyPlArc(t *testing.T) {
var (
pid = int64(1)
)
convey.Convey("keyPlArc", t, func(ctx convey.C) {
p1 := keyPlArc(pid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaokeyPlArcDesc(t *testing.T) {
var (
pid = int64(1)
)
convey.Convey("keyPlArcDesc", t, func(ctx convey.C) {
p1 := keyPlArcDesc(pid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoArcsCache(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
start = int(1)
end = int(20)
)
convey.Convey("ArcsCache", t, func(ctx convey.C) {
_, err := d.ArcsCache(c, pid, start, end)
ctx.Convey("Then err should be nil.arcs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoAddArcCache(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
arc = &model.ArcSort{Aid: 13825646, Sort: 100, Desc: "abc"}
)
convey.Convey("AddArcCache", t, func(ctx convey.C) {
err := d.AddArcCache(c, pid, arc)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetArcsCache(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
arcs = []*model.ArcSort{}
)
convey.Convey("SetArcsCache", t, func(ctx convey.C) {
arcs = append(arcs, &model.ArcSort{Aid: 13825646, Sort: 100, Desc: "abc"})
err := d.SetArcsCache(c, pid, arcs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetArcDescCache(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aid = int64(13825646)
desc = "abc"
)
convey.Convey("SetArcDescCache", t, func(ctx convey.C) {
err := d.SetArcDescCache(c, pid, aid, desc)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelArcsCache(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
aids = []int64{13825646, 11, 100}
)
convey.Convey("DelArcsCache", t, func(ctx convey.C) {
err := d.DelArcsCache(c, pid, aids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelCache(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
)
convey.Convey("DelCache", t, func(ctx convey.C) {
err := d.DelCache(c, pid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,36 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/library/ecode"
)
const (
_replyType = "18"
_replyState = "0" // 0: open, 1: close
_replyURL = "http://api.bilibili.co/x/internal/v2/reply/subject/regist"
)
// RegReply opens playlist's reply.
func (d *Dao) RegReply(c context.Context, pid, mid int64) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
params.Set("oid", strconv.FormatInt(pid, 10))
params.Set("type", _replyType)
params.Set("state", _replyState)
var res struct {
Code int `json:"code"`
}
if err = d.http.Post(c, _replyURL, "", params, &res); err != nil {
PromError("reply:打开评论", "d.http.Post(%s) error(%v)", _replyURL+"?"+params.Encode(), err)
return
}
if res.Code != ecode.OK.Code() {
PromError("reply:打开评论状态码异常", "d.http.Post(%s) error(%v)", _replyURL+"?"+params.Encode(), err)
err = ecode.Int(res.Code)
}
return
}

View File

@@ -0,0 +1,22 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRegReply(t *testing.T) {
var (
c = context.Background()
pid = int64(1)
mid = int64(2)
)
convey.Convey("RegReply", t, func(ctx convey.C) {
err := d.RegReply(c, pid, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,52 @@
package dao
import (
"context"
"go-common/app/interface/main/playlist/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"net/url"
"strconv"
)
const (
_searchURL = "/main/search"
_searchVer = "v3"
_platform = "web"
_searchType = "video"
_searchFrom = "web_playlist"
)
// SearchVideo get search video.
func (d *Dao) SearchVideo(c context.Context, pn, ps int, query string) (res []*model.SearchArc, count int, err error) {
var (
params = url.Values{}
ip = metadata.String(c, metadata.RemoteIP)
)
params.Set("main_ver", _searchVer)
params.Set("platform", _platform)
params.Set("search_type", _searchType)
params.Set("from_source", _searchFrom)
params.Set("pagesize", strconv.Itoa(ps))
params.Set("page", strconv.Itoa(pn))
params.Set("keyword", query)
params.Set("clientip", ip)
var rs struct {
Code int `json:"code"`
Result []*model.SearchArc `json:"result"`
NumResults int `json:"numResults"`
}
if err = d.http.Get(c, d.searchURL, ip, params, &rs); err != nil {
log.Error("d.http.Get(%s,%v) error(%v)", d.searchURL, params, err)
return
}
if rs.Code != ecode.OK.Code() {
log.Error("d.http.Get(%s,%v) code error(%v)", d.searchURL, params, rs.Code)
return
}
res = rs.Result
count = rs.NumResults
return
}

View File

@@ -0,0 +1,25 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoSearchVideo(t *testing.T) {
var (
c = context.Background()
pn = int(1)
ps = int(10)
query = "aaaaa"
)
convey.Convey("SearchVideo", t, func(ctx convey.C) {
res, count, err := d.SearchVideo(c, pn, ps, query)
ctx.Convey("Then err should be nil.res,count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,304 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"go-common/app/interface/main/playlist/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_statKey = "st_%d"
_plKey = "pl_%d"
)
func keyStat(mid int64) string {
return fmt.Sprintf(_statKey, mid)
}
func keyPl(pid int64) string {
return fmt.Sprintf(_plKey, pid)
}
// PlStatCache get stat from cache.
func (d *Dao) PlStatCache(c context.Context, mid, pid int64) (stat *model.PlStat, err error) {
var (
bs []byte
key = keyStat(mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if bs, err = redis.Bytes(conn.Do("HGET", key, pid)); err != nil {
if err == redis.ErrNil {
err = nil
stat = nil
} else {
log.Error("conn.Do(HGET,%s,%d) error(%v)", key, pid, err)
}
return
}
stat = new(model.PlStat)
if err = json.Unmarshal(bs, stat); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", string(bs), err)
}
return
}
// SetPlStatCache set playlist stat to cache.
func (d *Dao) SetPlStatCache(c context.Context, mid, pid int64, stat *model.PlStat) (err error) {
var (
bs []byte
ok bool
keyMid = keyStat(mid)
keyPid = keyPl(pid)
conn = d.redis.Get(c)
)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", keyMid, d.statExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", keyMid, err)
return
}
if ok {
if bs, err = json.Marshal(stat); err != nil {
log.Error("json.Marshal() error(%v)", err)
return
}
if err = conn.Send("HSET", keyMid, pid, bs); err != nil {
log.Error("conn.Send(HSET,%s,%d) error(%v)", keyMid, pid, err)
return
}
if err = conn.Send("EXPIRE", keyMid, d.statExpire); err != nil {
log.Error("conn.Send(EXPIRE,%s) error(%v)", keyMid, err)
return
}
if err = conn.Send("SET", keyPid, bs); err != nil {
log.Error("conn.Send(SET,%s,%s) error(%v)", keyPid, string(bs), err)
return
}
if err = conn.Send("EXPIRE", keyPid, d.plExpire); err != nil {
log.Error("conn.Send(EXPIRE,%s) error(%v)", keyPid, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("add conn.Flush error(%v)", err)
return
}
for i := 0; i < 4; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("add conn.Receive()%d error(%v)", i+1, err)
return
}
}
}
return
}
// SetStatsCache set playlist stat list to cache.
func (d *Dao) SetStatsCache(c context.Context, mid int64, plStats []*model.PlStat) (err error) {
var (
bs []byte
keyPid string
keyPids []string
argsPid = redis.Args{}
)
keyMid := keyStat(mid)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = redis.Bool(conn.Do("EXPIRE", keyMid, d.statExpire)); err != nil {
log.Error("conn.Do(EXPIRE %s) error(%v)", keyMid, err)
return
}
argsMid := redis.Args{}.Add(keyMid)
for _, v := range plStats {
if bs, err = json.Marshal(v); err != nil {
log.Error("json.Marshal err(%v)", err)
continue
}
argsMid = argsMid.Add(v.ID).Add(string(bs))
keyPid = keyPl(v.ID)
keyPids = append(keyPids, keyPid)
argsPid = argsPid.Add(keyPid).Add(string(bs))
}
if err = conn.Send("HMSET", argsMid...); err != nil {
log.Error("conn.Send(HMSET, %s) error(%v)", keyMid, err)
return
}
if err = conn.Send("EXPIRE", keyMid, d.statExpire); err != nil {
log.Error("conn.Send(Expire, %s, %d) error(%v)", keyMid, d.statExpire, err)
return
}
if err = conn.Send("MSET", argsPid...); err != nil {
log.Error("conn.Send(MSET) error(%v)", err)
return
}
count := 3
for _, v := range keyPids {
count++
if err = conn.Send("EXPIRE", v, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s, %d) error(%v)", v, d.plExpire, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// PlsCache get playlist by pids from cache.
func (d *Dao) PlsCache(c context.Context, pids []int64) (res []*model.PlStat, err error) {
var (
key string
args = redis.Args{}
)
for _, pid := range pids {
key = keyPl(pid)
args = args.Add(key)
}
conn := d.redis.Get(c)
defer conn.Close()
var (
bss [][]byte
)
if bss, err = redis.ByteSlices(conn.Do("MGET", args...)); err != nil {
if err == redis.ErrNil {
err = nil
} else {
log.Error("PlsCache conn.Do(MGET,%s) error(%v)", key, err)
}
return
}
for _, bs := range bss {
stat := new(model.PlStat)
if bs == nil {
continue
}
if err = json.Unmarshal(bs, stat); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", string(bs), err)
err = nil
continue
}
res = append(res, stat)
}
return
}
// SetPlCache set playlist to cache.
func (d *Dao) SetPlCache(c context.Context, plStats []*model.PlStat) (err error) {
var (
bs []byte
keyPid string
keyPids []string
argsPid = redis.Args{}
)
conn := d.redis.Get(c)
defer conn.Close()
for _, v := range plStats {
if bs, err = json.Marshal(v); err != nil {
log.Error("json.Marshal err(%v)", err)
continue
}
keyPid = keyPl(v.ID)
keyPids = append(keyPids, keyPid)
argsPid = argsPid.Add(keyPid).Add(string(bs))
}
if err = conn.Send("MSET", argsPid...); err != nil {
log.Error("conn.Send(MSET) error(%v)", err)
return
}
count := 1
for _, v := range keyPids {
count++
if err = conn.Send("EXPIRE", v, d.plExpire); err != nil {
log.Error("conn.Send(Expire, %s, %d) error(%v)", v, d.plExpire, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// DelPlCache delete playlist from redis.
func (d *Dao) DelPlCache(c context.Context, mid, pid int64) (err error) {
var (
key = keyPl(pid)
plaKey = keyPlArc(pid)
pladKey = keyPlArcDesc(pid)
keyStat = keyStat(mid)
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", key, err)
return
}
if err = conn.Send("DEL", plaKey); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", plaKey, err)
return
}
if err = conn.Send("DEL", pladKey); err != nil {
log.Error("conn.Send(DEL %s) error(%v)", pladKey, err)
return
}
if err = conn.Send("HDEL", keyStat, pid); err != nil {
log.Error("conn.Send(HDEL,%s,%d) error(%v)", keyStat, pid, err)
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 4; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// StatsCache get playlist stats from cache.
func (d *Dao) StatsCache(c context.Context, mid int64) (res []*model.PlStat, err error) {
key := keyStat(mid)
conn := d.redis.Get(c)
defer conn.Close()
var (
bss [][]byte
)
if bss, err = redis.ByteSlices(conn.Do("HGETALL", key)); err != nil {
if err == redis.ErrNil {
err = nil
} else {
log.Error("StatCache conn.Do(HGETALL,%s) error(%v)", key, err)
}
return
}
for i := 1; i <= len(bss); i += 2 {
stat := new(model.PlStat)
if err = json.Unmarshal(bss[i], stat); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", string(bss[i]), err)
continue
}
res = append(res, stat)
}
return
}

View File

@@ -0,0 +1,133 @@
package dao
import (
"context"
"testing"
"go-common/app/interface/main/playlist/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaokeyStat(t *testing.T) {
var (
mid = int64(2)
)
convey.Convey("keyStat", t, func(ctx convey.C) {
p1 := keyStat(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaokeyPl(t *testing.T) {
var (
pid = int64(1)
)
convey.Convey("keyPl", t, func(ctx convey.C) {
p1 := keyPl(pid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoPlStatCache(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
pid = int64(1)
)
convey.Convey("PlStatCache", t, func(ctx convey.C) {
_, err := d.PlStatCache(c, mid, pid)
ctx.Convey("Then err should be nil.stat should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetPlStatCache(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
pid = int64(1)
stat = &model.PlStat{}
)
convey.Convey("SetPlStatCache", t, func(ctx convey.C) {
err := d.SetPlStatCache(c, mid, pid, stat)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoSetStatsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
plStats = []*model.PlStat{}
)
plStats = append(plStats, &model.PlStat{ID: 1, Share: 1})
convey.Convey("SetStatsCache", t, func(ctx convey.C) {
err := d.SetStatsCache(c, mid, plStats)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoPlsCache(t *testing.T) {
var (
c = context.Background()
pids = []int64{1, 2, 3}
)
convey.Convey("PlsCache", t, func(ctx convey.C) {
res, err := d.PlsCache(c, pids)
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 TestDaoSetPlCache(t *testing.T) {
var (
c = context.Background()
plStats = []*model.PlStat{}
)
plStats = append(plStats, &model.PlStat{ID: 1, View: 100})
convey.Convey("SetPlCache", t, func(ctx convey.C) {
err := d.SetPlCache(c, plStats)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelPlCache(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
pid = int64(1)
)
convey.Convey("DelPlCache", t, func(ctx convey.C) {
err := d.DelPlCache(c, mid, pid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoStatsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(2)
)
convey.Convey("StatsCache", t, func(ctx convey.C) {
_, err := d.StatsCache(c, mid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

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 = [
"favorite.go",
"http.go",
"video.go",
],
importpath = "go-common/app/interface/main/playlist/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//app/interface/main/playlist/service:go_default_library",
"//app/service/main/favorite/model: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/auth:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,360 @@
package http
import (
"strconv"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func whiteList(c *bm.Context) {
var (
err error
vmid, mid int64
)
params := c.Request.Form
if midStr, ok := c.Get("mid"); ok {
mid = midStr.(int64)
}
vmidStr := params.Get("vmid")
if vmidStr != "" {
if vmid, err = strconv.ParseInt(vmidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if mid == 0 && vmid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if vmid > 0 {
mid = vmid
}
c.JSON(plSvc.White(c, mid))
}
func add(c *bm.Context) {
var (
err error
mid, pid, public int64
name, description, cover string
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
publicStr := params.Get("public")
if publicStr != "" {
if public, err = strconv.ParseInt(publicStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
name = params.Get("name")
if name == "" || len([]rune(name)) > conf.Conf.Rule.MaxNameLimit {
c.JSON(nil, ecode.RequestErr)
return
}
description = params.Get("description")
if description != "" {
if len([]rune(description)) > conf.Conf.Rule.MaxPlDescLimit {
c.JSON(nil, ecode.PlDescTooLong)
return
}
}
cover = params.Get("cover")
if pid, err = plSvc.Add(c, mid, int8(public), name, description, cover, c.Request.Header.Get("Cookie"), params.Get("access_key")); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
data := make(map[string]interface{}, 1)
data["pid"] = pid
c.JSON(data, nil)
}
func del(c *bm.Context) {
var (
err error
mid, pid int64
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, switchCode(plSvc.Del(c, mid, pid), favmdl.TypePlayVideo))
}
func update(c *bm.Context) {
var (
err error
mid, pid, public int64
name, description, cover string
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
publicStr := params.Get("public")
if publicStr != "" {
if public, err = strconv.ParseInt(publicStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
name = params.Get("name")
if name == "" || len([]rune(name)) > conf.Conf.Rule.MaxNameLimit {
c.JSON(nil, ecode.RequestErr)
return
}
description = params.Get("description")
if description != "" {
if len([]rune(description)) > conf.Conf.Rule.MaxPlDescLimit {
c.JSON(nil, ecode.RequestErr)
return
}
}
cover = params.Get("cover")
c.JSON(nil, switchCode(plSvc.Update(c, mid, pid, int8(public), name, description, cover, c.Request.Header.Get("Cookie"), params.Get("access_key")), favmdl.TypePlayVideo))
}
func info(c *bm.Context) {
var (
err error
pid, mid int64
list *model.Playlist
)
params := c.Request.Form
if midStr, ok := c.Get("mid"); ok {
mid = midStr.(int64)
}
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if list, err = plSvc.Info(c, mid, pid); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
c.JSON(list, nil)
}
func report(c *bm.Context) {
var (
err error
pid, aid int64
)
params := c.Request.Form
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
aidStr := params.Get("aid")
if aidStr != "" {
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
c.JSON(true, plSvc.PubView(c, pid, aid))
}
func reportShare(c *bm.Context) {
var (
err error
pid, aid int64
)
params := c.Request.Form
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
aidStr := params.Get("aid")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(true, plSvc.PubShare(c, pid, aid))
}
func list(c *bm.Context) {
var (
err error
vmid, mid int64
pn, ps, sort, total int
list []*model.Playlist
)
params := c.Request.Form
if midStr, ok := c.Get("mid"); ok {
mid = midStr.(int64)
}
vmidStr := params.Get("vmid")
if vmidStr != "" {
if vmid, err = strconv.ParseInt(vmidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if mid == 0 && vmid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if vmid > 0 {
mid = vmid
}
pnStr := params.Get("pn")
if pn, err = strconv.Atoi(pnStr); err != nil || pn < 1 {
pn = 1
}
psStr := params.Get("ps")
if ps, err = strconv.Atoi(psStr); err != nil || ps < 1 || ps > conf.Conf.Rule.MaxPlsPageSize {
ps = conf.Conf.Rule.MaxPlsPageSize
}
sortStr := params.Get("sort")
if sortStr != "" {
if sort, err = strconv.Atoi(sortStr); err != nil || sort < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if list, total, err = plSvc.List(c, mid, pn, ps, sort); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
data := make(map[string]interface{}, 2)
page := map[string]int{
"num": pn,
"size": ps,
"total": total,
}
data["page"] = page
data["list"] = list
c.JSON(data, nil)
}
func addFavorite(c *bm.Context) {
var (
err error
mid, pid int64
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, switchCode(plSvc.AddFavorite(c, mid, pid), favmdl.TypePlayList))
}
func delFavorite(c *bm.Context) {
var (
err error
mid, pid int64
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, switchCode(plSvc.DelFavorite(c, mid, pid), favmdl.TypePlayList))
}
func listFavorite(c *bm.Context) {
var (
err error
mid, vmid int64
pn, ps, sort, total int
list []*model.Playlist
)
params := c.Request.Form
if midStr, ok := c.Get("mid"); ok {
mid = midStr.(int64)
}
vmidStr := params.Get("vmid")
if vmidStr != "" {
if vmid, err = strconv.ParseInt(vmidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
pnStr := params.Get("pn")
if pn, err = strconv.Atoi(pnStr); err != nil || pn < 1 {
pn = 1
}
psStr := params.Get("ps")
if ps, err = strconv.Atoi(psStr); err != nil || ps < 1 || ps > conf.Conf.Rule.MaxPlsPageSize {
ps = conf.Conf.Rule.MaxPlsPageSize
}
sortStr := params.Get("sort")
if sortStr != "" {
if sort, err = strconv.Atoi(sortStr); err != nil || sort < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if list, total, err = plSvc.ListFavorite(c, mid, vmid, pn, ps, sort); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayList))
return
}
data := make(map[string]interface{}, 2)
page := map[string]int{
"num": pn,
"size": ps,
"total": total,
}
data["page"] = page
data["list"] = list
c.JSON(data, nil)
}
func switchCode(err error, tp int8) error {
if err == nil {
return err
}
switch ecode.Cause(err) {
case ecode.FavNameTooLong:
err = ecode.PlNameTooLong
case ecode.FavFolderExist:
err = ecode.PlExist
case ecode.FavMaxFolderCount:
err = ecode.PlMaxCount
case ecode.FavCanNotDelDefault:
err = ecode.PlCanNotDelDefault
case ecode.FavFloderAlreadyDel:
err = ecode.PlAlreadyDel
case ecode.FavResourceOverflow:
err = ecode.PlVideoOverflow
case ecode.FavResourceAlreadyDel:
if tp == favmdl.TypePlayVideo {
err = ecode.PlVideoAlreadyDel
} else if tp == favmdl.TypePlayList {
err = ecode.PlFavAlreadyDel
}
case ecode.FavResourceExist:
if tp == favmdl.TypePlayVideo {
err = ecode.PlVideoExist
} else if tp == favmdl.TypePlayList {
err = ecode.PlFavExist
}
case ecode.FavFolderNotExist:
err = ecode.PlNotExist
}
return err
}

View File

@@ -0,0 +1,92 @@
package http
import (
"net/http"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
plSvc *service.Service
authSvr *auth.Auth
vfySvr *verify.Verify
)
// Init init http server
func Init(c *conf.Config, s *service.Service) {
authSvr = auth.New(c.Auth)
vfySvr = verify.New(c.Verify)
plSvc = s
engine := bm.DefaultServer(c.HTTPServer)
outerRouter(engine)
internalRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
// outerRouter init outer router api path.
func outerRouter(e *bm.Engine) {
// init api
e.Ping(ping)
group := e.Group("/x/playlist", bm.CORS())
{
group.GET("/whitelist", authSvr.Guest, whiteList)
group.GET("/report", report)
group.GET("/share/report", reportShare)
group.GET("", authSvr.Guest, list)
group.GET("/info", authSvr.Guest, info)
group.POST("/add", authSvr.User, add)
group.POST("/del", authSvr.User, del)
group.POST("/update", authSvr.User, update)
videoGroup := group.Group("/video")
{
videoGroup.GET("", videoList)
videoGroup.POST("/check", authSvr.User, check)
videoGroup.GET("/toview", authSvr.Guest, toView)
videoGroup.POST("/add", authSvr.User, addVideo)
videoGroup.POST("/del", authSvr.User, delVideo)
videoGroup.POST("/sort", authSvr.User, sortVideo)
videoGroup.POST("/desc/edit", authSvr.User, editVideoDesc)
videoGroup.GET("/search", authSvr.User, searchVideo)
}
favGroup := group.Group("/fav")
{
favGroup.GET("", authSvr.Guest, listFavorite)
favGroup.POST("/add", authSvr.User, addFavorite)
favGroup.POST("/del", authSvr.User, delFavorite)
}
}
}
func internalRouter(e *bm.Engine) {
group := e.Group("/x/internal/playlist")
{
group.GET("/whitelist", vfySvr.Verify, whiteList)
group.GET("/report", vfySvr.Verify, report)
group.GET("/share/report", vfySvr.Verify, reportShare)
group.GET("", vfySvr.Verify, list)
group.GET("/info", vfySvr.Verify, info)
videoGroup := group.Group("/video")
{
videoGroup.GET("", vfySvr.Verify, videoList)
videoGroup.GET("/toview", vfySvr.Verify, toView)
videoGroup.GET("/search", vfySvr.Verify, searchVideo)
}
favGroup := group.Group("/fav")
{
favGroup.GET("", vfySvr.Verify, listFavorite)
}
}
}
func ping(c *bm.Context) {
if err := plSvc.Ping(c); err != nil {
log.Error("playlist interface ping error")
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

@@ -0,0 +1,267 @@
package http
import (
"strconv"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/xstr"
)
func videoList(c *bm.Context) {
var (
pid int64
pn, ps int
err error
list *model.ArcList
)
params := c.Request.Form
pidStr := params.Get("pid")
pnStr := params.Get("pn")
psStr := params.Get("ps")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if pn, err = strconv.Atoi(pnStr); err != nil || pn < 1 {
pn = 1
}
if ps, err = strconv.Atoi(psStr); err != nil || ps < 1 || ps > conf.Conf.Rule.MaxPlArcsPs {
ps = conf.Conf.Rule.MaxPlArcsPs
}
if list, err = plSvc.Videos(c, pid, pn, ps); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
c.JSON(list, nil)
}
func toView(c *bm.Context) {
var (
pid, mid int64
err error
list *model.ToView
)
params := c.Request.Form
if midStr, ok := c.Get("mid"); ok {
mid = midStr.(int64)
}
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if list, err = plSvc.ToView(c, mid, pid); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
c.JSON(list, nil)
}
func check(c *bm.Context) {
var (
err error
mid, pid int64
aids []int64
videos model.Videos
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
aidStr := params.Get("aids")
if aidStr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if aids, err = xstr.SplitInts(aidStr); err != nil || len(aids) == 0 || len(aids) > conf.Conf.Rule.MaxArcChangeLimit {
c.JSON(nil, ecode.RequestErr)
return
}
aidMap := make(map[int64]int64, len(aids))
for _, aid := range aids {
aidMap[aid] = aid
}
if len(aidMap) < len(aids) {
c.JSON(nil, ecode.RequestErr)
return
}
pidStr := params.Get("pid")
if pidStr != "" {
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if videos, err = plSvc.CheckVideo(c, mid, pid, aids); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
c.JSON(videos, nil)
}
func addVideo(c *bm.Context) {
var (
err error
mid, pid int64
aids []int64
videos model.Videos
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
aidStr := params.Get("aids")
if aidStr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if aids, err = xstr.SplitInts(aidStr); err != nil || len(aids) == 0 || len(aids) > conf.Conf.Rule.MaxArcChangeLimit {
c.JSON(nil, ecode.RequestErr)
return
}
aidMap := make(map[int64]int64, len(aids))
for _, aid := range aids {
aidMap[aid] = aid
}
if len(aidMap) < len(aids) {
c.JSON(nil, ecode.RequestErr)
return
}
pidStr := params.Get("pid")
if pidStr != "" {
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if videos, err = plSvc.AddVideo(c, mid, pid, aids); err != nil {
c.JSON(nil, switchCode(err, favmdl.TypePlayVideo))
return
}
c.JSON(videos, nil)
}
func delVideo(c *bm.Context) {
var (
err error
mid, pid int64
aids []int64
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
aidStr := params.Get("aids")
if aidStr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if aids, err = xstr.SplitInts(aidStr); err != nil || len(aids) == 0 || len(aids) > conf.Conf.Rule.MaxArcChangeLimit {
c.JSON(nil, ecode.RequestErr)
return
}
aidMap := make(map[int64]int64, len(aids))
for _, aid := range aids {
aidMap[aid] = aid
}
if len(aidMap) < len(aids) {
c.JSON(nil, ecode.RequestErr)
return
}
pidStr := params.Get("pid")
if pidStr != "" {
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
c.JSON(nil, switchCode(plSvc.DelVideo(c, mid, pid, aids), favmdl.TypePlayVideo))
}
func sortVideo(c *bm.Context) {
var (
mid, pid, aid, sort int64
err error
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
aidStr := params.Get("aid")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
sortStr := params.Get("sort")
if sort, err = strconv.ParseInt(sortStr, 10, 64); err != nil || sort <= 0 || sort > int64(conf.Conf.Rule.MaxVideoCnt) {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, switchCode(plSvc.SortVideo(c, mid, pid, aid, sort), favmdl.TypePlayVideo))
}
func editVideoDesc(c *bm.Context) {
var (
err error
mid, pid, aid int64
)
params := c.Request.Form
midStr, _ := c.Get("mid")
mid = midStr.(int64)
pidStr := params.Get("pid")
if pid, err = strconv.ParseInt(pidStr, 10, 64); err != nil || pid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
aidStr := params.Get("aid")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
desc := params.Get("desc")
if len([]rune(desc)) > conf.Conf.Rule.MaxVideoDescLimit {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, plSvc.EditVideoDesc(c, mid, pid, aid, desc))
}
func searchVideo(c *bm.Context) {
var (
err error
pn, ps, count int
list []*model.SearchArc
)
params := c.Request.Form
pnStr := params.Get("pn")
if pn, err = strconv.Atoi(pnStr); err != nil || pn < 1 {
pn = 1
}
psStr := params.Get("ps")
if ps, err = strconv.Atoi(psStr); err != nil || ps < 1 || ps > conf.Conf.Rule.MaxSearchArcPs {
ps = conf.Conf.Rule.MaxSearchArcPs
}
query := params.Get("keyword")
if query == "" || len([]rune(query)) > conf.Conf.Rule.MaxSearchLimit {
c.JSON(nil, ecode.RequestErr)
return
}
if list, count, err = plSvc.SearchVideos(c, pn, ps, query); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
data := make(map[string]interface{}, 2)
page := map[string]int{
"num": pn,
"size": ps,
"count": count,
}
data["page"] = page
data["list"] = list
c.JSON(data, nil)
}

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 = [
"playlist.go",
"rpc.go",
],
importpath = "go-common/app/interface/main/playlist/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/archive/api:go_default_library",
"//app/service/main/favorite/model: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,94 @@
package model
import (
"go-common/library/time"
arcmdl "go-common/app/service/main/archive/api"
favmdl "go-common/app/service/main/favorite/model"
xtime "go-common/library/time"
)
// PlDBusType databus type playlist
const PlDBusType = "playlist"
// ArcSort struct.
type ArcSort struct {
Aid int64 `json:"aid"`
Sort int64 `json:"sort"`
Desc string `json:"desc"`
}
// Videos add video result.
type Videos struct {
RightAids []int64 `json:"right_aids"`
WrongAids []int64 `json:"wrong_aids"`
}
// Playlist struct.
type Playlist struct {
Pid int64 `json:"pid"`
*favmdl.Folder
Stat *Stat `json:"stat,omitempty"`
Author *arcmdl.Author `json:"owner,omitempty"`
FavoriteTime time.Time `json:"favorite_time,omitempty"`
IsFavorite bool `json:"is_favorite"`
}
// Stat playlist stat.
type Stat struct {
Pid int64 `json:"pid"`
View int64 `json:"view"`
Fav int64 `json:"favorite"`
Reply int64 `json:"reply"`
Share int64 `json:"share"`
}
// PlStat playlist stat
type PlStat struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Fid int64 `json:"fid"`
View int64 `json:"view"`
Reply int64 `json:"reply"`
Fav int64 `json:"favorite"`
Share int64 `json:"share"`
MTime xtime.Time `json:"mtime"`
}
// View arc view.
type View struct {
*arcmdl.Arc
Pages []*arcmdl.Page `json:"pages"`
}
// PlView playlist view struct
type PlView struct {
*View
PlayDesc string `json:"play_desc"`
}
// ArcList playlist archive list.
type ArcList struct {
List []*PlView `json:"list"`
}
// ToView to view page struct.
type ToView struct {
*Playlist
List []*View `json:"list"`
Favorite bool `json:"favorite"`
}
// SearchArc search archive struct
type SearchArc struct {
Aid int64 `json:"aid"`
Title string `json:"title"`
Pic string `json:"pic"`
Duration string `json:"duration"`
Mid int64 `json:"mid"`
Author string `json:"author"`
Play int64 `json:"play"`
Review int64 `json:"review"`
VideoReview int64 `json:"video_review"`
Favorites int64 `json:"favorites"`
}

View File

@@ -0,0 +1,6 @@
package model
// ArgStats struct.
type ArgStats struct {
*PlStat
}

View File

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

View File

@@ -0,0 +1,36 @@
package client
import (
"context"
"go-common/app/interface/main/playlist/model"
"go-common/library/net/rpc"
)
const (
_setStat = "RPC.SetStat"
_appid = "community.service.playlist"
)
var (
_noReply = &struct{}{}
)
// Service struct info.
type Service struct {
client *rpc.Client2
}
// New new servcie instance and return.
func New(c *rpc.ClientConfig) (s *Service) {
s = &Service{}
s.client = rpc.NewDiscoveryCli(_appid, c)
return
}
// SetStat updates playlist stat cache.
func (s *Service) SetStat(c context.Context, arg *model.ArgStats) (err error) {
err = s.client.Call(c, _setStat, arg, _noReply)
return
}

View File

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

View File

@@ -0,0 +1,40 @@
package server
import (
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/model"
"go-common/app/interface/main/playlist/service"
"go-common/library/net/rpc"
"go-common/library/net/rpc/context"
)
// RPC struct info.
type RPC struct {
s *service.Service
}
// New new rpc server.
func New(c *conf.Config, s *service.Service) (svr *rpc.Server) {
r := &RPC{s: s}
svr = rpc.NewServer(c.RPCServer)
if err := svr.Register(r); err != nil {
panic(err)
}
return
}
// Auth check connection success.
func (r *RPC) Auth(c context.Context, arg *rpc.Auth, res *struct{}) (err error) {
return
}
// Ping check connection success
func (r *RPC) Ping(c context.Context, arg *struct{}, res *struct{}) (err error) {
return
}
// SetStat set all stat cache(redis)
func (r *RPC) SetStat(c context.Context, arg *model.ArgStats, res *struct{}) (err error) {
err = r.s.SetStat(c, arg.PlStat)
return
}

View File

@@ -0,0 +1,69 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"favorite_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"favorite.go",
"service.go",
"stat.go",
"video.go",
],
importpath = "go-common/app/interface/main/playlist/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/dao:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/account/rpc/client:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/favorite/api/gorpc:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//app/service/main/filter/rpc/client:go_default_library",
"//library/cache:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/sync/errgroup: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,320 @@
package service
import (
"context"
"sort"
"strconv"
"time"
"go-common/app/interface/main/playlist/dao"
"go-common/app/interface/main/playlist/model"
accwarden "go-common/app/service/main/account/api"
arcmdl "go-common/app/service/main/archive/api"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
xtime "go-common/library/time"
)
const (
_first = 1
_sortDefault = 0
_sortByMTime = 1
_sortByView = 2
)
var _empPlaylists = make([]*model.Playlist, 0)
// White playlist white list.
func (s *Service) White(c context.Context, mid int64) (res map[string]bool, err error) {
_, power := s.allowMids[mid]
res = make(map[string]bool, 1)
res["power"] = power
return
}
// Add add playlist.
func (s *Service) Add(c context.Context, mid int64, public int8, name, description, cover, cookie, accessKey string) (pid int64, err error) {
var (
fid int64
ts = time.Now()
ip = metadata.String(c, metadata.RemoteIP)
)
if _, ok := s.allowMids[mid]; !ok {
err = ecode.PlDenied
return
}
arg := &favmdl.ArgAddFolder{Type: favmdl.TypePlayVideo, Mid: mid, Name: name, Description: description, Cover: cover, Public: public, Cookie: cookie, AccessKey: accessKey, RealIP: ip}
if fid, err = s.fav.AddFolder(c, arg); err != nil {
dao.PromError("添加播单rpc错误", "s.fav.AddFolder(%v) error(%v)", arg, err)
return
}
if pid, err = s.dao.Add(c, mid, fid); err != nil {
log.Error("s.dao.Add(%d,%d) error(%v)", mid, fid, err)
} else if pid > 0 {
s.cache.Save(func() {
stat := &model.PlStat{ID: pid, Mid: mid, Fid: fid, MTime: xtime.Time(ts.Unix())}
s.dao.SetPlStatCache(context.Background(), mid, pid, stat)
})
if err = s.dao.RegReply(c, pid, mid); err != nil {
err = nil
}
}
return
}
// Del delete playlist.
func (s *Service) Del(c context.Context, mid, pid int64) (err error) {
var (
affected int64
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
log.Error("s.plByPid(%d,%d) error(%v)", mid, pid, err)
return
}
arg := &favmdl.ArgDelFolder{Type: favmdl.TypePlayVideo, Mid: mid, Fid: stat.Fid, RealIP: ip}
if err = s.fav.DelFolder(c, arg); err != nil {
dao.PromError("删除播单rpc错误", "s.fav.DelFolder(%+v) error(%v)", arg, err)
return
}
if affected, err = s.dao.Del(c, pid); err != nil {
log.Error("s.dao.Del(%d) error(%v)", pid, err)
return
} else if affected > 0 {
s.dao.DelPlCache(c, mid, pid)
}
return
}
// Update update playlist.
func (s *Service) Update(c context.Context, mid, pid int64, public int8, name, description, cover, cookie, accessKey string) (err error) {
var (
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
log.Error("s.plByPid(%d) error(%v)", pid, err)
return
}
arg := &favmdl.ArgUpdateFolder{Type: favmdl.TypePlayVideo, Fid: stat.Fid, Mid: mid, Name: name, Description: description, Cover: cover, Public: public, Cookie: cookie, AccessKey: accessKey, RealIP: ip}
if err = s.fav.UpdateFolder(c, arg); err != nil {
dao.PromError("更新播单rpc错误", "s.fav.UpdateFolder(%+v) error(%v)", arg, err)
return
}
s.updatePlTime(c, mid, pid)
return
}
func (s *Service) updatePlTime(c context.Context, mid, pid int64) (err error) {
var (
affected int64
ts = time.Now()
stat *model.PlStat
)
if affected, err = s.dao.Update(c, pid); err != nil {
err = nil
log.Error("s.dao.Update(%d) error(%v)", pid, err)
return
} else if affected > 0 {
s.cache.Save(func() {
if stat, err = s.plByPid(context.Background(), pid); err != nil {
err = nil
} else {
stat.MTime = xtime.Time(ts.Unix())
s.dao.SetPlStatCache(context.Background(), mid, pid, stat)
}
})
}
return
}
// Info playlist stat info.
func (s *Service) Info(c context.Context, mid, pid int64) (res *model.Playlist, err error) {
var (
fav *favmdl.Folder
stat *model.PlStat
infoReply *accwarden.InfoReply
isFav bool
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
return
}
if stat == nil || stat.ID == 0 {
err = ecode.PlNotExist
dao.PromError("Info:播单不存在", "s.plByPid(%d) error(%v)", pid, stat)
return
}
arg := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: stat.Fid, Mid: stat.Mid, RealIP: ip}
if fav, err = s.fav.Folder(c, arg); err != nil || fav == nil {
dao.PromError("Info Forder:rpc错误", "s.fav.Folder(%+v) error(%v)", arg, err)
return
}
if fav.State == favmdl.StateIsDel {
err = ecode.PlNotExist
dao.PromError("InfoFav:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
// author
if infoReply, err = s.accClient.Info3(c, &accwarden.MidReq{Mid: fav.Mid, RealIp: ip}); err != nil {
dao.PromError("账号Info:grpc错误", "s.accClient.Info3 error(%v)", err)
return
}
if mid > 0 {
if isFav, err = s.fav.IsFav(c, &favmdl.ArgIsFav{Type: favmdl.TypePlayList, Mid: mid, Oid: pid, RealIP: ip}); err != nil {
log.Error("s.fav.IsFav(%d,%d) error(%d)", mid, pid, err)
err = nil
}
}
owner := &arcmdl.Author{Mid: fav.Mid, Name: infoReply.Info.Name, Face: infoReply.Info.Face}
fav.MTime = stat.MTime
res = &model.Playlist{Pid: pid, Folder: fav, Stat: &model.Stat{Pid: stat.ID, View: stat.View, Reply: stat.Reply, Fav: stat.Fav, Share: stat.Share}, Author: owner, IsFavorite: isFav}
return
}
func (s *Service) plInfo(c context.Context, mid, pid int64, ip string) (res *model.PlStat, err error) {
var fav *favmdl.Folder
if res, err = s.plByPid(c, pid); err != nil {
return
}
if res == nil || res.ID == 0 {
err = ecode.PlNotExist
log.Error("s.plByPid(%d) res(%v)", pid, res)
return
}
arg := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: res.Fid, Mid: res.Mid, RealIP: ip}
if fav, err = s.fav.Folder(c, arg); err != nil || fav == nil {
log.Error("s.fav.Folder(%+v) error(%v)", arg, err)
return
}
if fav.State == favmdl.StateIsDel {
err = ecode.PlNotExist
log.Error("s.fav.Folder(%d) state(%d)", pid, fav.State)
return
}
if mid > 0 && mid != res.Mid {
err = ecode.PlNotUser
}
return
}
// List playlist.
func (s *Service) List(c context.Context, mid int64, pn, ps, sortType int) (res []*model.Playlist, count int, err error) {
var (
start = (pn - 1) * ps
end = start + ps - 1
plStats []*model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if plStats, err = s.plsByMid(c, mid); err != nil {
return
}
count = len(plStats)
if count == 0 || count < start {
res = _empPlaylists
return
}
switch sortType {
case _sortDefault, _sortByMTime:
sort.Slice(plStats, func(i, j int) bool { return plStats[i].MTime > plStats[j].MTime })
case _sortByView:
sort.Slice(plStats, func(i, j int) bool { return plStats[i].View > plStats[j].View })
}
if count > end {
plStats = plStats[start : end+1]
} else {
plStats = plStats[start:]
}
res, err = s.batchFav(c, mid, plStats, ip)
return
}
//AddFavorite add playlist to favorite.
func (s *Service) AddFavorite(c context.Context, mid, pid int64) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
if _, err = s.Info(c, 0, pid); err != nil {
return
}
arg := &favmdl.ArgAdd{Type: favmdl.TypePlayList, Mid: mid, Oid: pid, Fid: 0, RealIP: ip}
if err = s.fav.Add(c, arg); err != nil {
dao.PromError("rpc:添加播单收藏", "s.fav.Add(%+v) error(%v)", arg, err)
}
return
}
// DelFavorite del playlist from favorite.
func (s *Service) DelFavorite(c context.Context, mid, pid int64) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
arg := &favmdl.ArgDel{Type: favmdl.TypePlayList, Mid: mid, Oid: pid, Fid: 0, RealIP: ip}
if err = s.fav.Del(c, arg); err != nil {
dao.PromError("rpc:删除播单收藏", "s.fav.Del(%+v) error(%v)", arg, err)
}
return
}
//ListFavorite playlist list.
func (s *Service) ListFavorite(c context.Context, mid, vmid int64, pn, ps, sortType int) (res []*model.Playlist, count int, err error) {
var (
plStats []*model.PlStat
favRes *favmdl.Favorites
pids []int64
tmpFavs map[int64]*favmdl.Favorite
tmpRs []*model.Playlist
ip = metadata.String(c, metadata.RemoteIP)
)
arg := &favmdl.ArgFavs{Type: favmdl.TypePlayList, Mid: mid, Vmid: vmid, Fid: 0, Pn: pn, Ps: ps, RealIP: ip}
if favRes, err = s.fav.Favorites(c, arg); err != nil {
dao.PromError("rpc:播单收藏列表", "s.fav.Favorites(%+v) error(%v)", arg, err)
return
}
if favRes == nil || len(favRes.List) == 0 {
res = _empPlaylists
return
}
tmpFavs = make(map[int64]*favmdl.Favorite)
for _, fav := range favRes.List {
pids = append(pids, fav.Oid)
tmpFavs[fav.Oid] = fav
}
if plStats, err = s.plsByPid(c, pids); err != nil {
return
}
count = favRes.Page.Count
tmpRs, err = s.batchFav(c, mid, plStats, ip)
for _, v := range tmpRs {
v.FavoriteTime = tmpFavs[v.Pid].MTime
res = append(res, v)
}
return
}
func (s *Service) batchFav(c context.Context, uid int64, plStats []*model.PlStat, ip string) (res []*model.Playlist, err error) {
var (
fVMids []*favmdl.ArgFVmid
tmpStats map[string]*model.PlStat
favRes []*favmdl.Folder
stat *model.Stat
)
tmpStats = make(map[string]*model.PlStat)
for _, v := range plStats {
statKey := strconv.FormatInt(v.Mid, 10) + "_" + strconv.FormatInt(v.Fid, 10)
tmpStats[statKey] = &model.PlStat{ID: v.ID, Mid: v.Mid, Fid: v.Fid, View: v.View, Reply: v.Reply, Fav: v.Fav, Share: v.Share, MTime: v.MTime}
fVMids = append(fVMids, &favmdl.ArgFVmid{Fid: v.Fid, Vmid: v.Mid})
}
arg := &favmdl.ArgFolders{Type: favmdl.TypePlayVideo, Mid: uid, FVmids: fVMids, RealIP: ip}
if favRes, err = s.fav.Folders(c, arg); err != nil {
dao.PromError("rpc:批量获取播单列表", "s.fav.Folders(%+v) error(%v)", arg, err)
return
}
for _, fav := range favRes {
statKey := strconv.FormatInt(fav.Mid, 10) + "_" + strconv.FormatInt(fav.ID, 10)
plStat := tmpStats[statKey]
stat = &model.Stat{Pid: plStat.ID, View: plStat.View, Fav: plStat.Fav, Reply: plStat.Reply, Share: plStat.Share}
fav.MTime = plStat.MTime
res = append(res, &model.Playlist{Pid: plStat.ID, Folder: fav, Stat: stat})
}
return
}

View File

@@ -0,0 +1,148 @@
package service
import (
"context"
"testing"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_Add(t *testing.T) {
var (
mid = int64(88888929)
plPublic = int8(0)
plName = "播单名称1"
plDescripton = "播单描述1"
cover = "http://image1.jpg"
)
Convey("Add", t, WithService(func(s *Service) {
res, err := s.Add(context.Background(), mid, plPublic, plName, plDescripton, cover, "", "")
So(err, ShouldBeNil)
So(res, ShouldBeGreaterThan, 0)
}))
}
func TestService_Del(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
)
Convey("Add", t, WithService(func(s *Service) {
err := s.Del(context.Background(), mid, pid)
So(err, ShouldBeNil)
}))
}
func TestService_Update(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
plPublic = int8(0)
plName = "播单名称1"
plDescripton = "播单描述1"
cover = "http://image1.jpg"
)
Convey("Add", t, WithService(func(s *Service) {
err := s.Update(context.Background(), mid, pid, plPublic, plName, plDescripton, cover, "", "")
So(err, ShouldBeNil)
}))
}
func TestService_AddVideo(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
aids = []int64{11, 22, 33, 44, 55}
)
Convey("Add", t, WithService(func(s *Service) {
videos, err := s.AddVideo(context.Background(), mid, pid, aids)
So(videos, ShouldNotBeNil)
So(err, ShouldBeNil)
}))
}
func TestService_DelVideo(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
aids = []int64{11, 22, 33, 44, 55}
)
Convey("Add", t, WithService(func(s *Service) {
err := s.DelVideo(context.Background(), mid, pid, aids)
So(err, ShouldBeNil)
}))
}
func TestService_SortVideo(t *testing.T) {
Convey("sort", t, func() {
var (
arcs []*model.ArcSort
aidSort, preSort, afSort, orderNum int64
start, end int
top, bottom bool
)
aid := int64(6)
sort := int64(1)
arcs = []*model.ArcSort{
{Aid: 1, Sort: 100},
{Aid: 2, Sort: 200},
{Aid: 3, Sort: 300},
{Aid: 4, Sort: 400},
{Aid: 5, Sort: 500},
{Aid: 6, Sort: 600},
}
if sort == _first {
top = true
} else if sort == int64(len(arcs)) {
bottom = true
}
for k, v := range arcs {
if k == 0 && top {
afSort = v.Sort
}
if k == len(arcs)-1 && bottom {
preSort = v.Sort
}
if aid == v.Aid {
if sort == int64(k+1) {
return
}
aidSort = v.Sort
}
if sort == int64(k+1) {
if !top && !bottom {
if aidSort > sort {
preSort = arcs[k].Sort
afSort = arcs[k+1].Sort
} else {
preSort = arcs[k-1].Sort
afSort = arcs[k].Sort
}
}
}
}
if top {
println("top")
orderNum = afSort / 2
} else if bottom {
println("bottom")
orderNum = preSort + int64(conf.Conf.Rule.SortStep)
} else {
println("else")
orderNum = preSort + (afSort-preSort)/2
}
println(start, end, preSort, afSort)
for _, v := range arcs {
if v.Aid == aid {
v.Sort = orderNum
}
}
for _, v := range arcs {
Printf("%+v", v)
}
})
}

View File

@@ -0,0 +1,75 @@
package service
import (
"context"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/dao"
accclient "go-common/app/service/main/account/api"
accwarden "go-common/app/service/main/account/api"
accrpc "go-common/app/service/main/account/rpc/client"
arcrpc "go-common/app/service/main/archive/api/gorpc"
arcclient "go-common/app/service/main/archive/api"
favrpc "go-common/app/service/main/favorite/api/gorpc"
"go-common/app/service/main/filter/rpc/client"
"go-common/library/cache"
"go-common/library/log"
)
// Service service struct.
type Service struct {
c *conf.Config
dao *dao.Dao
// rpc
fav *favrpc.Service
arc *arcrpc.Service2
acc *accrpc.Service3
filter *filter.Service
// cache proc
cache *cache.Cache
// playlist power mids
allowMids map[int64]struct{}
maxSort int64
arcClient arcclient.ArchiveClient
accClient accwarden.AccountClient
}
// New new service.
func New(c *conf.Config) *Service {
s := &Service{
c: c,
dao: dao.New(c),
fav: favrpc.New2(c.FavoriteRPC),
arc: arcrpc.New2(c.ArchiveRPC),
acc: accrpc.New3(c.AccountRPC),
filter: filter.New(c.FilterRPC),
cache: cache.New(1, 1024),
maxSort: c.Rule.MinSort + 4*c.Rule.SortStep*int64(c.Rule.MaxVideoCnt),
}
var err error
if s.arcClient, err = arcclient.NewClient(c.ArcClient); err != nil {
panic(err)
}
if s.accClient, err = accclient.NewClient(c.AccClient); err != nil {
panic(err)
}
s.initMids()
return s
}
func (s *Service) initMids() {
tmp := make(map[int64]struct{}, len(s.c.Rule.PowerMids))
for _, id := range s.c.Rule.PowerMids {
tmp[id] = struct{}{}
}
s.allowMids = tmp
}
// Ping ping service.
func (s *Service) Ping(c context.Context) (err error) {
if err = s.dao.Ping(c); err != nil {
log.Error("s.dao.Ping error(%v)", err)
}
return
}

View File

@@ -0,0 +1,28 @@
package service
import (
"flag"
"path/filepath"
"time"
"go-common/app/interface/main/playlist/conf"
)
var (
svr *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/playlist-test.toml")
flag.Set("conf", dir)
conf.Init()
svr = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
f(svr)
}
}

View File

@@ -0,0 +1,118 @@
package service
import (
"context"
"go-common/app/interface/main/playlist/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/log"
"go-common/library/net/metadata"
)
func (s *Service) plsByMid(c context.Context, mid int64) (res []*model.PlStat, err error) {
if res, err = s.dao.StatsCache(c, mid); err != nil || len(res) == 0 {
err = nil
if res, err = s.dao.PlsByMid(c, mid); err != nil {
log.Error("s.dao.PlsByMid(%d) error(%v)", mid, err)
return
}
if len(res) > 0 {
s.cache.Save(func() {
s.dao.SetStatsCache(context.Background(), mid, res)
})
}
}
return
}
func (s *Service) plByPid(c context.Context, pid int64) (res *model.PlStat, err error) {
var pls []*model.PlStat
pids := []int64{pid}
if pls, err = s.dao.PlsCache(c, pids); err != nil {
err = nil
} else if len(pls) != 0 {
res = pls[0]
return
}
if res, err = s.dao.PlByPid(c, pid); err != nil {
log.Error("s.dao.PlByPid(%d) error(%v)", pid, err)
}
return
}
func (s *Service) plsByPid(c context.Context, pids []int64) (res []*model.PlStat, err error) {
var (
tmpRs []*model.PlStat
rsMap map[int64]*model.PlStat
)
if tmpRs, err = s.dao.PlsCache(c, pids); err != nil || len(pids) != len(tmpRs) || len(tmpRs) == 0 {
err = nil
if tmpRs, err = s.dao.PlsByPid(c, pids); err != nil {
log.Error("s.dao.PlsByPid(%+v) error(%v)", pids, err)
}
if len(tmpRs) > 0 {
s.cache.Save(func() {
s.dao.SetPlCache(context.Background(), tmpRs)
})
}
}
rsMap = make(map[int64]*model.PlStat)
for _, v := range tmpRs {
rsMap[v.ID] = v
}
for _, v := range pids {
if rsMap[v] == nil {
continue
}
res = append(res, rsMap[v])
}
return
}
// SetStat set playlist stat cache.
func (s *Service) SetStat(c context.Context, arg *model.PlStat) (err error) {
var fav *favmdl.Folder
argFav := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: arg.Fid, Mid: arg.Mid, RealIP: ""}
if fav, err = s.fav.Folder(c, argFav); err != nil || fav == nil {
log.Error("SetStat s.fav.Folder(%+v) error(%v)", argFav, err)
return
}
log.Info("service SetStat(%v) favState(%d)", arg, fav.State)
if fav.State == favmdl.StateNormal {
if err = s.dao.SetPlStatCache(c, arg.Mid, arg.ID, arg); err != nil {
log.Error("SetStat s.dao.SetPlStatCache(%d,%d) error(%v)", arg.Mid, arg.ID, err)
}
}
return
}
// PubView pub playlist view.
func (s *Service) PubView(c context.Context, pid, aid int64) (err error) {
var (
pls *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if pls, err = s.plInfo(c, 0, pid, ip); err != nil {
return
}
//TODO aid in playlist
err = s.cache.Save(func() {
s.dao.PubView(context.Background(), pid, aid, pls.View)
})
return
}
// PubShare pub playlist share.
func (s *Service) PubShare(c context.Context, pid, aid int64) (err error) {
var (
pls *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if pls, err = s.plInfo(c, 0, pid, ip); err != nil {
return
}
//TODO aid in playlist
err = s.cache.Save(func() {
s.dao.PubShare(context.Background(), pid, aid, pls.Share)
})
return
}

View File

@@ -0,0 +1,529 @@
package service
import (
"context"
"html/template"
"sync"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/dao"
"go-common/app/interface/main/playlist/model"
arcmdl "go-common/app/service/main/archive/api"
"go-common/app/service/main/archive/model/archive"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
)
const (
_vUpload = "vupload"
_aidBulkSize = 50
)
var _empAids = make([]int64, 0)
// Videos get playlist video list by pid.
func (s *Service) Videos(c context.Context, pid int64, pn, ps int) (res *model.ArcList, err error) {
var (
aids []int64
arcSorts []*model.ArcSort
arcs map[int64]*arcmdl.ViewReply
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
res = &model.ArcList{List: make([]*model.PlView, 0)}
if stat, err = s.plByPid(c, pid); err != nil || stat == nil {
return
}
if _, err = s.fav.Folder(c, &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Mid: stat.Mid, Fid: stat.Fid, RealIP: ip}); err != nil {
dao.PromError("Folder接口错误", "s.fav.Folder(%d,%d) error(%v)", stat.Mid, stat.Fid, err)
return
}
start := (pn - 1) * ps
end := start + ps - 1
if arcSorts, err = s.videos(c, pid, start, end); err != nil {
return
}
//TODO check aids from fav
for _, v := range arcSorts {
aids = append(aids, v.Aid)
}
if arcs, err = s.views(c, aids, ip); err != nil {
log.Error("s.arc.Views3(%v) error(%v)", aids, err)
return
}
for _, v := range arcSorts {
if arc, ok := arcs[v.Aid]; ok {
view := &model.PlView{View: &model.View{Arc: arc.Arc, Pages: arc.Pages}, PlayDesc: template.HTMLEscapeString(v.Desc)}
res.List = append(res.List, view)
}
}
return
}
// ToView get playlist view page data.
func (s *Service) ToView(c context.Context, mid, pid int64) (res *model.ToView, err error) {
var (
aids []int64
arcSorts []*model.ArcSort
views map[int64]*arcmdl.ViewReply
info *model.Playlist
ip = metadata.String(c, metadata.RemoteIP)
)
if info, err = s.Info(c, mid, pid); err != nil {
return
}
res = &model.ToView{Playlist: info}
if arcSorts, err = s.videos(c, pid, 0, info.Count-1); err != nil {
return
}
//TODO check aids from fav
for _, v := range arcSorts {
aids = append(aids, v.Aid)
}
if views, err = s.views(c, aids, ip); err != nil {
log.Error("s.views(%v) error(%v)", aids, err)
return
}
res.List = make([]*model.View, 0)
for _, v := range arcSorts {
if arc, ok := views[v.Aid]; ok {
view := &model.View{Arc: arc.Arc, Pages: arc.Pages}
res.List = append(res.List, view)
}
}
return
}
// CheckVideo add video to playlist.
func (s *Service) CheckVideo(c context.Context, mid, pid int64, aids []int64) (videos model.Videos, err error) {
var (
stat *model.PlStat
fav *favmdl.Folder
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
return
}
if stat == nil {
err = ecode.PlNotExist
dao.PromError("CheckVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
argFolder := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: stat.Fid, Mid: stat.Mid, Vmid: 0, RealIP: ip}
if fav, err = s.fav.Folder(c, argFolder); err != nil || fav == nil {
dao.PromError("CheckVideo收藏Forder:rpc错误", "s.fav.Folder(%+v) error(%v)", argFolder, err)
return
}
if videos, _, _, err = s.filterArc(c, mid, pid, aids, ip); err != nil {
log.Error("s.filterArc(%v) error(%v)", aids, err)
}
return
}
// AddVideo add video to playlist.
func (s *Service) AddVideo(c context.Context, mid, pid int64, aids []int64) (videos model.Videos, err error) {
var (
lastID, sort, fid int64
arcSorts []*model.ArcSort
ip = metadata.String(c, metadata.RemoteIP)
)
if videos, sort, fid, err = s.filterArc(c, mid, pid, aids, ip); err != nil {
log.Error("s.filterArc(%v) error(%v)", aids, err)
return
}
if len(videos.RightAids) == 0 {
return
}
for _, aid := range videos.RightAids {
sort += conf.Conf.Rule.SortStep
arcSorts = append(arcSorts, &model.ArcSort{Aid: aid, Sort: sort, Desc: ""})
}
arg := &favmdl.ArgMultiAdd{Type: favmdl.TypePlayVideo, Mid: mid, Oids: videos.RightAids, Fid: fid, RealIP: ip}
if err = s.fav.MultiAdd(c, arg); err != nil {
dao.PromError("添加播单视频rpc错误", "s.fav.MultiAdd(%+v) error(%v)", arg, err)
return
}
if lastID, err = s.dao.BatchAddArc(c, pid, arcSorts); err != nil || lastID == 0 {
log.Error("s.dao.BatchAddArc(%d,%+v) error(%v)", pid, arcSorts, err)
return
}
if lastID > 0 {
s.cache.Save(func() {
s.dao.SetArcsCache(context.Background(), pid, arcSorts)
})
}
s.updatePlTime(c, mid, pid)
return
}
// DelVideo del video from playlist.
func (s *Service) DelVideo(c context.Context, mid, pid int64, aids []int64) (err error) {
var (
affected int64
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
return
}
if stat == nil {
err = ecode.PlNotExist
dao.PromError("DelVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
arg := &favmdl.ArgMultiDel{Type: favmdl.TypePlayVideo, Mid: mid, Oids: aids, Fid: stat.Fid, RealIP: ip}
if err = s.fav.MultiDel(c, arg); err != nil {
dao.PromError("删除播单视频rpc错误", "s.fav.MultiDel(%+v) error(%v)", arg, err)
return
}
if affected, err = s.dao.BatchDelArc(c, pid, aids); err != nil || affected == 0 {
log.Error("s.dao.BatchDelArc(%d,%v) error(%v)", mid, aids, err)
return
}
if affected > 0 {
s.cache.Save(func() {
s.dao.DelArcsCache(context.Background(), pid, aids)
})
}
s.updatePlTime(c, mid, pid)
return
}
// SortVideo sort playlist video.
func (s *Service) SortVideo(c context.Context, mid, pid, aid, sort int64) (err error) {
var (
info *favmdl.Favorites
aidSort, preSort, afSort, orderNum, affected int64
desc string
arcs []*model.ArcSort
plStat *model.PlStat
top, bottom, isPlaylist, reset bool
ip = metadata.String(c, metadata.RemoteIP)
)
if plStat, err = s.plByPid(c, pid); err != nil {
return
}
if plStat == nil {
err = ecode.PlNotExist
dao.PromError("SortVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
if plStat.ID == 0 {
err = ecode.PlNotExist
return
} else if mid != plStat.Mid {
err = ecode.PlNotUser
return
}
if info, err = s.fav.Favorites(c, &favmdl.ArgFavs{Type: favmdl.TypePlayVideo, Mid: mid, Fid: plStat.Fid, Pn: 1, Ps: 1, RealIP: ip}); err != nil {
dao.PromError("获取播单信息rpc错误", "s.fav.Favorites(%d,%d) error(%v)", mid, plStat.Fid, err)
return
} else if sort > int64(info.Page.Count) {
err = ecode.PlSortOverflow
return
}
if isPlaylist, err = s.fav.IsFavedByFid(c, &favmdl.ArgIsFavedByFid{Type: favmdl.TypePlayVideo, Mid: mid, Fid: plStat.Fid, Oid: aid, RealIP: ip}); err != nil {
dao.PromError("播单下视频rpc错误", "s.fav.IsFavedByFid(%d,%d,%d) error(%v)", mid, plStat.Fid, aid, err)
return
} else if !isPlaylist {
err = ecode.PlVideoAlreadyDel
return
}
if arcs, err = s.videos(c, pid, 0, info.Page.Count-1); err != nil {
return
}
if sort == _first {
top = true
} else if sort == int64(info.Page.Count) {
bottom = true
}
for k, v := range arcs {
if k == 0 && top {
afSort = v.Sort
}
if k == len(arcs)-1 && bottom {
preSort = v.Sort
}
if aid == v.Aid {
if sort == int64(k+1) {
return
}
aidSort = v.Sort
desc = v.Desc
}
if sort == int64(k+1) {
if !top && !bottom {
if aidSort > sort {
preSort = arcs[k].Sort
afSort = arcs[k+1].Sort
} else {
preSort = arcs[k-1].Sort
afSort = arcs[k].Sort
}
}
}
}
if top {
orderNum = afSort / 2
} else if bottom {
orderNum = preSort + conf.Conf.Rule.SortStep
} else {
orderNum = preSort + (afSort-preSort)/2
}
if orderNum == preSort || orderNum == afSort || orderNum <= conf.Conf.Rule.MinSort || orderNum > s.maxSort {
reset = true
if affected, err = s.resetArcSort(c, pid); err != nil {
dao.PromError("重置视频排序错误", "s.dao.UpdateArcSort(%d,%d) error(%v)", pid, aid, err)
return
}
} else {
if affected, err = s.dao.UpdateArcSort(c, pid, aid, orderNum); err != nil {
dao.PromError("更新视频排序错误", "s.dao.UpdateArcSort(%d,%d) error(%v)", pid, aid, err)
return
}
}
if affected > 0 {
s.cache.Save(func() {
if reset {
if err = s.dao.DelCache(context.Background(), pid); err != nil {
log.Error("s.dao.DelCache() pid(%d), error(%v)", pid, err)
return
}
s.videos(context.Background(), pid, 0, info.Page.Count-1)
} else {
s.dao.AddArcCache(context.Background(), pid, &model.ArcSort{Aid: aid, Sort: orderNum, Desc: desc})
}
})
}
return
}
// EditVideoDesc edit playlist video desc.
func (s *Service) EditVideoDesc(c context.Context, mid, pid, aid int64, desc string) (err error) {
var (
affected int64
plStat *model.PlStat
isPlaylist bool
ip = metadata.String(c, metadata.RemoteIP)
)
if plStat, err = s.plByPid(c, pid); err != nil {
return
}
if plStat == nil {
err = ecode.PlNotExist
dao.PromError("AddVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
if plStat.ID == 0 {
err = ecode.PlNotExist
return
} else if mid != plStat.Mid {
err = ecode.PlNotUser
return
}
if isPlaylist, err = s.fav.IsFavedByFid(c, &favmdl.ArgIsFavedByFid{Type: favmdl.TypePlayVideo, Mid: mid, Fid: plStat.Fid, Oid: aid, RealIP: ip}); err != nil {
dao.PromError("播单下视频rpc错误", "s.fav.IsFavedByFid(%d,%d,%d) error(%v)", mid, plStat.Fid, aid, err)
return
} else if !isPlaylist {
err = ecode.PlVideoAlreadyDel
return
}
if affected, err = s.dao.UpdateArcDesc(c, pid, aid, desc); err != nil {
log.Error("s.dao.UpdateArcDesc(%d,%d,%s) error(%v)", pid, aid, desc, err)
return
}
if affected > 0 {
s.cache.Save(func() {
s.dao.SetArcDescCache(context.Background(), pid, aid, desc)
})
}
s.updatePlTime(c, mid, pid)
return
}
// SearchVideos search add videos.
func (s *Service) SearchVideos(c context.Context, pn, ps int, query string) (res []*model.SearchArc, count int, err error) {
if res, count, err = s.dao.SearchVideo(c, pn, ps, query); err != nil {
log.Error("s.dao.SearchVideo(%s) error(%v)", query, err)
}
if len(res) == 0 {
res = make([]*model.SearchArc, 0)
}
return
}
func (s *Service) videos(c context.Context, pid int64, start, end int) (res []*model.ArcSort, err error) {
var (
arcs []*model.ArcSort
)
if arcs, err = s.dao.ArcsCache(c, pid, start, end); err != nil || len(arcs) == 0 {
err = nil
if arcs, err = s.dao.Videos(c, pid); err != nil {
log.Error("s.dao.Videos(%d) error(%v)", pid, err)
return
}
length := len(arcs)
if length > 0 {
s.cache.Save(func() {
s.dao.SetArcsCache(context.Background(), pid, arcs)
})
}
if length < start {
res = []*model.ArcSort{}
return
}
if length > end+1 {
res = arcs[start : end+1]
} else {
res = arcs[start:]
}
return
}
res = arcs
return
}
func (s *Service) resetArcSort(c context.Context, pid int64) (affected int64, err error) {
var (
arcs, afArcs []*model.ArcSort
)
if arcs, err = s.dao.Videos(c, pid); err != nil {
log.Error("s.dao.Videos(%d) error(%v)", pid, err)
return
}
sort := conf.Conf.Rule.BeginSort
for _, v := range arcs {
sort += s.c.Rule.SortStep
afArcs = append(afArcs, &model.ArcSort{Aid: v.Aid, Desc: v.Desc, Sort: sort})
}
affected, err = s.dao.BatchUpdateArcSort(c, pid, afArcs)
return
}
func (s *Service) filterArc(c context.Context, mid, pid int64, aids []int64, ip string) (res model.Videos, sort, fid int64, err error) {
var (
mutex = sync.Mutex{}
aidsLen = len(aids)
rightAids, rsRight, wrongAids []int64
group, errCtx = errgroup.WithContext(c)
tmpArc []*model.ArcSort
exists map[int64]bool
stat *model.Playlist
)
sort = conf.Conf.Rule.BeginSort
if stat, err = s.Info(c, 0, pid); err != nil {
return
}
if mid != stat.Mid {
err = ecode.PlNotUser
return
}
fid = stat.ID
exists = make(map[int64]bool, stat.Count)
if stat.Count > 0 {
if stat.Count > conf.Conf.Rule.MaxVideoCnt {
err = ecode.PlVideoOverflow
return
}
if tmpArc, err = s.videos(c, pid, 0, stat.Count-1); err != nil {
return
}
for _, v := range tmpArc {
exists[v.Aid] = true
}
if tmpLen := len(tmpArc); tmpLen < stat.Count {
sort = tmpArc[tmpLen-1].Sort
} else {
sort = tmpArc[stat.Count-1].Sort
}
}
tmpRight := make(map[int64]struct{})
for i := 0; i < aidsLen; i += _aidBulkSize {
var partAids []int64
if i+_aidBulkSize > aidsLen {
partAids = aids[i:]
} else {
partAids = aids[i : i+_aidBulkSize]
}
group.Go(func() (err error) {
var arcs *arcmdl.ViewsReply
arg := &arcmdl.ViewsRequest{Aids: partAids}
if arcs, err = s.arcClient.Views(errCtx, arg); err != nil {
log.Error("s.arcClient.Views(%v) error(%v)", partAids, err)
return
}
mutex.Lock()
for _, aid := range partAids {
if arcReply, ok := arcs.Views[aid]; !ok || arcs.Views[aid] == nil {
wrongAids = append(wrongAids, aid)
} else if !arcReply.Arc.IsNormal() ||
exists[aid] ||
arcReply.Arc.Rights.UGCPay == 1 ||
arcReply.Arc.AttrVal(archive.AttrBitIsBangumi) == archive.AttrYes ||
arcReply.Arc.AttrVal(archive.AttrBitIsMovie) == archive.AttrYes ||
(len(arcReply.Pages) > 0 && arcReply.Pages[0].From != _vUpload) {
wrongAids = append(wrongAids, aid)
} else {
rightAids = append(rightAids, aid)
tmpRight[aid] = struct{}{}
}
}
mutex.Unlock()
return
})
}
err = group.Wait()
if rightAids == nil {
rightAids = _empAids
rsRight = _empAids
} else if wrongAids == nil {
wrongAids = _empAids
}
if stat.Count+len(rightAids) > conf.Conf.Rule.MaxVideoCnt {
err = ecode.PlVideoOverflow
return
}
for _, aid := range aids {
if _, ok := tmpRight[aid]; ok {
rsRight = append(rsRight, aid)
}
}
res = model.Videos{RightAids: rsRight, WrongAids: wrongAids}
return
}
func (s *Service) views(c context.Context, aids []int64, ip string) (views map[int64]*arcmdl.ViewReply, err error) {
var (
mutex = sync.Mutex{}
aidsLen = len(aids)
group, errCtx = errgroup.WithContext(c)
)
views = make(map[int64]*arcmdl.ViewReply, aidsLen)
for i := 0; i < aidsLen; i += _aidBulkSize {
var partAids []int64
if i+_aidBulkSize > aidsLen {
partAids = aids[i:]
} else {
partAids = aids[i : i+_aidBulkSize]
}
group.Go(func() (err error) {
var arcs *arcmdl.ViewsReply
arg := &arcmdl.ViewsRequest{Aids: partAids}
if arcs, err = s.arcClient.Views(errCtx, arg); err != nil {
log.Error("s.arcClient.Views(%v) error(%v)", partAids, err)
return
}
mutex.Lock()
for _, v := range arcs.Views {
views[v.Arc.Aid] = v
}
mutex.Unlock()
return
})
}
err = group.Wait()
return
}