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

29
app/job/main/reply/BUILD Normal file
View File

@@ -0,0 +1,29 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/reply/cmd:all-srcs",
"//app/job/main/reply/conf:all-srcs",
"//app/job/main/reply/dao/message:all-srcs",
"//app/job/main/reply/dao/notice:all-srcs",
"//app/job/main/reply/dao/reply:all-srcs",
"//app/job/main/reply/dao/search:all-srcs",
"//app/job/main/reply/dao/spam:all-srcs",
"//app/job/main/reply/dao/stat:all-srcs",
"//app/job/main/reply/http:all-srcs",
"//app/job/main/reply/model/activity:all-srcs",
"//app/job/main/reply/model/drawyoo:all-srcs",
"//app/job/main/reply/model/reply:all-srcs",
"//app/job/main/reply/model/topic:all-srcs",
"//app/job/main/reply/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,474 @@
#### reply-job
#### Version 6.0.1
> 1.修复err
#### Version 6.0.0
> 1.折叠评论,置顶及删除逻辑
#### Version 5.0.16
> 1.account grpc
#### Version 5.0.15
> 1. remove user action table
#### Version 5.0.14
> 1.CD数减半
#### Version 5.0.13
> 1.fix nil sub bug
#### Version 5.0.11
> 1.增加置顶和取消置顶的消息
#### Version 5.0.10
> 1.国际版消息推送
#### Version 5.0.9
>1.bbq
#### Version 5.0.8
> 1.ats 不要每次都从message里拿
#### Version 5.0.7
> 1.点赞限制放宽10->15
#### Version 5.0.5
> 1.评论cd调整删除cd code如果人证成功
#### Version 5.0.2
>1.修复mtime
#### Version 5.0.1
>1.reply dialog, 修复sql
#### Version 5.0.0
>1.reply dialog, 5.33
#### Version 4.20.8
>1.fix reply report update
#### Version 4.20.6
>like大于3
#### Version 4.20.3
>删除非必要日志
#### Version 4.20.2
>log.Warn
#### Version 4.20.1
>epid grpc
#### Version 4.19.11
> 1.增加feature flag
#### Version 4.19.9
> 修复回复通知到ats 中
#### Version 4.19.8
> 1.配置文件
> 2.xint move to model
#### Version 4.19.7
> 1.notify新增nativeJump
#### Version 4.19.6
> 1.修复job的golint问题
#### Version 4.19.4
> 1.点赞MaxLike去重
#### Version 4.19.3
> 1.去除点赞双写数据库
#### Version 4.19.2
> 1.去掉子评论老缓存index
#### Version 4.19.0
> 1.双写子评论缓存index
#### Version 4.18.8
> 1.修复通知评论内容长度
#### Version 4.18.7
> 1.回复通知标题换为根评论内容
#### Version 4.18.6
> 1.下线老Stat-T
#### Version 4.18.5
> 1.音频评论消息通知接入
#### Version 4.18.4
> 1.rebuild master
#### Version 4.18.3
> 1.删除非块级配置
#### Version 4.18.2
> 1.fix旧点赞冲突无法更新
#### Version 4.18.1
> 1.fix点赞新平台
#### Version 4.18.0
> 1.点赞新平台
#### Version 4.17.13
> 1.修复bug
#### Version 4.17.12
> 1.新增business支持
#### Version 4.17.11
> 1.修复添加评论时根评论已删除
#### Version 4.17.10
> 1.迁移到bm
#### Version 4.17.9
> 1.迁移到databus
#### Version 4.17.8
> 1.修复可能出现的deadlock
#### Version 4.17.7
> 1.增加subject mcount
#### Version 4.17.6
> 1.调整顺序,解决死锁问题
#### Version 4.17.5
> 1.回源分段
#### Version 4.17.4
> 1.删除子评论oid index
#### Version 4.17.3
> 1.新子评论index缓存key
#### Version 4.17.2
> 1.修复通知回复mc
#### Version 4.17.1
> 1.评论计数通知
#### Version 4.17.0
> 1.评论分段回源
#### Version 4.16.7
> 1.添加通知native跳转链接
#### Version 4.16.6
> 1.话题正则修改去掉emoji和link
#### Version 4.16.5
> 1.去掉稿件、动态、小视频、相簿评论数通知
> 2.切换账号v7 rpc
#### Version 4.16.4
> 1.添加稿件、动态、小视频、相簿评论数通知
#### Version 4.16.3
> 1.修复错误封禁通知链接
#### Version 4.16.2
> 1.修复reply_admin_log mid插入问题
#### Version 4.16.1
> 1.异步发送通知
#### Version 4.16.0
> 1.增加了对评论话题的支持
#### Version 4.15.8
> 1.在热门评论索引处添加根评论判断
#### Version 4.15.7
> 1.修改DynamicUrl
#### Version 4.15.6
> 1.修改封禁文案
#### Version 4.15.5
> 1.更换小黑屋通知标题接口
#### Version 4.15.4
> 1.优化置顶评论
##### Version 4.15.3
> 1.动态消息推送
##### Version 4.15.2
> 1.修改评论At的正则表达式
##### Version 4.15.1
> 1.添加StatReply-T计数databus
##### Version 4.15.0
> 1.添加一二审状态处理
##### Version 4.14.5
> 1.修复热评删除后计算时放到缓存中
##### Version 4.14.4
> 1.添加置顶缓存过期时间配置
##### Version 4.14.3
> 1.top评论回源时sql语句增加limit限制
##### Version 4.14.2
> 1.修复置顶时楼层index被删除评论
> 2.优化单次回源时加载好index缓存
> 3.置顶评论预加载
> 4.修复可能存在两个置顶的问题
##### Version 4.14.1
> 1.添加举报审核databus事件
##### Version 4.14.0
> 1.添加评论记录更新
##### Version 4.13.0
> 1.数据库回源优化
##### Version 4.13.0
> 1.业务计数配置化
##### Version 4.12.3
> 1.修改小黑屋通知链接
##### Version 4.12.2
> 1.切换archive3 rpc接口
##### Version 4.12.1
> 1.通知规避部分违规违禁内容
##### Version 4.12.0
> 1.添加评论计数通知
##### Version 4.11.0
> 1.databus实时数据流
##### Version 4.10.0
> 1.迁移大仓库更新memcache
##### Version 4.9.2
> 1.修复点赞数量为0通知
##### Version 4.9.1
> 1.修复点赞慢查询
##### Version 4.9.0
> 1.添加先审后发
##### Version 4.8.5
> 1.修复通知标题为空
##### Version 4.8.4
> 1.修复消息推送http头
##### Version 4.8.3
> 1.修改搜索通知URL
##### Version 4.8.2
> 1.修复mobile通知跳转
##### Version 4.8.1
> 1.修复稿件redirect通知跳转
##### Version 4.8.0
> 1.添加评论通知
##### Version 4.7.2
> 1.调整协管删除评论日志格式
##### Version 4.7.1
> 1.添加协管员删除日志详情(50个字符长度)
##### Version 4.7.0
> 1.新增协管员删除评论逻辑
##### Version 4.6.5
> 1.修复syslog奔溃
##### Version 4.6.4
> 1.修复syslog panic
> 2.修复admin log用户删除operation
##### Version 4.6.3
> 1.更新标准库
> 2.增加稿件类型"article"的消息通知
##### Version 4.6.2
> 1.优化搜索批量更新
##### Version 4.6.1
> 1.被拉黑名单屏蔽通知
##### Version 4.6.0
> 1.新搜索评论列表通知
> 2.添加活动稿件通知链接
> 3.文章计数发送databus
##### Version 4.5.6
> 1.去除老搜索通知
##### Version 4.5.5
> 1.修改热评Version 2.0逻辑
##### Version 4.5.4
> 1.添加ping资源
##### Version 4.5.3
> 1.修复审核移除评论默认状态值
##### Version 4.5.2
> 1.修复运营后台评论状态同步搜索索引
##### Version 4.5.1
> 1.更新健康检查端口
##### Version 4.5.0
> 1.更新lib
> 2.新配置中心
##### Version 4.4.1
> 1.修复举报通知
##### Version 4.4.0
> 1.添加举报用户反馈
##### Version 4.3.3
> 1.修复举报的热门评论
##### Version 4.3.2
> 1.修复点踩使用缓存不计数
##### Version 4.3.1
> 1.修复评论更新时间
> 2.修复监控评论没有推送通知
##### Version 4.3.0
> 1.热评算法调整
##### Version 4.2.2
> 1.举报去除通知索引更新
##### Version 4.2.1
> 1.修复评论删除状态
##### Version 4.2.0
> 1.添加举报一二审
##### Version 4.1.1
> 1.增加封禁时间
##### Version 4.1.0
> 1.小黑屋评论跳转
##### Version 4.0.11
> 1.修改先审后发为先发后审
> 2.番剧评论跳转
> 3.举报不加节操
##### Version 4.0.10
> 1.限制口节操发送消息长度
##### Version 4.0.9
> 1.删除先审后发根评论缓存
##### Version 4.0.8
> 1.删除番剧评论跳转
##### Version 4.0.7
> 1.删除小视频消息通知
##### Version 4.0.6
> 1.top主动添加置顶
##### v4.0.5
> 1.修改点赞过期时间
##### Version 4.0.4
> 1.踩不发通知。
> 2.从缓存获取reply
##### Version 4.0.3
> 1.修复content内容为空
##### Version 4.0.2
> 1.删除sql attr位操作
##### Version 4.0.1
> 1.修改top置顶为异步
##### Version 4.0.0
> 1.添加评论踩
> 2.添加up置顶评论
> 3.添加up删除评论
> 4.添加评论attr属性字段隔离属性和状态
> 5.添加subject主体attr字段记录是否存在置顶等属性
> 6.更新基础库依赖
##### Version 3.3.0
> 1.更新go-common依赖
##### Version 3.2.2
> 1.修复二级评论计数
##### Version 3.2.1
> 1.评论计数双写到databus
##### Version 3.2.0
> 1.更新活动搜索索引
> 2.更新楼层计数必须依赖于db
##### Version 3.1.0
> 1.增加电影区评论跳转
> 2.更新go-business至1.6.0
> 3.更新go-common至4.1.3
##### v3.0.1
> 1.更新基础库依赖包
##### v3.0.0
> 1.番剧评论跳转锚点处理
> 2.增加活动单页评论类型
> 3.修复置顶评论cache count不更新
> 4.govendor支持
##### v2.3.0
> 1.评论点赞通知
##### v2.2.4
> 1.允许隐藏filter状态
##### v2.2.3
> 1.疑似垃圾评论隐藏
##### v2.2.2
> 1.修复疑似垃圾评论按点赞数不显示
##### v2.2.1
> 1.修复置顶平路member为空
##### v2.2.0
> 1.修复评论删除及恢复
> 2.修复疑是垃圾评论不显示
##### v2.1.3
> 1.kafka消息同步推送失败重试
##### v2.1.2
> 1.修复无top评论稿件查询db
##### v2.1.0
> 1.添加jump跳转
> 2.支持画站类型
> 3.修复top点赞无效
##### v2.0.0
> reply-job初始化

View File

@@ -0,0 +1,12 @@
# Owner
chenzhihui
caoguoliang
zhapuyu
wangxu01
# Author
chenzhihui
caoguoliang
# Reviewer
chenzhihui

16
app/job/main/reply/OWNERS Normal file
View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
- chenzhihui
- wangxu01
- zhapuyu
labels:
- job
- job/main/reply
- main
options:
no_parent_owners: true
reviewers:
- caoguoliang
- chenzhihui

View File

@@ -0,0 +1,10 @@
#### reply-job
##### 项目简介
> reply-job
##### 编译环境
> 请只用golang v1.8.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["reply-job-test.toml"],
importpath = "go-common/app/job/main/reply/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//app/job/main/reply/http:go_default_library",
"//app/job/main/reply/service:go_default_library",
"//library/exp/feature: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,59 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/job/main/reply/conf"
"go-common/app/job/main/reply/http"
"go-common/app/job/main/reply/service"
"go-common/library/exp/feature"
"go-common/library/log"
"go-common/library/net/trace"
)
var (
s *service.Service
)
func main() {
feature.DefaultGate.AddFlag(flag.CommandLine)
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
log.Init(conf.Conf.XLog)
trace.Init(conf.Conf.Tracer)
defer trace.Close()
defer log.Close()
log.Info("reply_consumer start")
s = service.New(conf.Conf)
http.Init(conf.Conf, s)
signalHandler()
}
func signalHandler() {
var (
err error
ch = make(chan os.Signal, 1)
)
signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
si := <-ch
switch si {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("get a signal %s, stop the consume process", si.String())
if err = s.Close(); err != nil {
log.Error("close consumer error(%v)", err)
}
s.Wait()
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,246 @@
[job]
proc = 8
searchNum = 20
searchFlush = "100ms"
batchNumber = 20
[Xlog]
stdout=true
[bm]
addr = "0.0.0.0:10000"
timeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
[weight]
like = 2
hate = 4
[statTypes]
"archive" = 1
"article" = 12
"playlist" = 18
[host]
activity = "http://uat-www.bilibili.com"
message = "http://uat-message.bilibili.com"
drawYoo = "http://uat-h.bilibili.com"
search = "http://bili-search.bilibili.co"
blackroom = "http://uat-blocked.bilibili.co"
api = "http://uat-api.bilibili.co"
liveVC = "http://api.vc.bilibili.co"
liveAct = "http://api.live.bilibili.co"
bangumi = "http://bangumi.bilibili.co"
[httpClient]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
dial = "1s"
timeout = "3s"
keepAlive = "60s"
timer = 1000
[httpClient.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[drawyooHTTPClient]
key = "49df034922d68827"
secret = "92ce984f5c8b8415366838c6ee3e039f"
dial = "1s"
timeout = "3s"
keepAlive = "60s"
timer = 1000
[drawyooHTTPClient.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.1
request = 100
[app]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
[rpcClient2]
[rpcClient2.account]
timeout = "30s"
[rpcClient2.archive]
timeout = "30s"
[rpcClient2.assist]
timeout = "30s"
[rpcClient2.article]
timeout = "30s"
[mysql]
[mysql.reply]
addr= "172.22.34.101:3306"
dsn = "test_3306:UJPZaGKjpb2ylFx3HNhmLuwOYft4MCAi@tcp(172.22.34.101:3306)/bilibili_reply?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "200ms"
execTimeout = "200ms"
tranTimeout = "200ms"
[mysql.reply.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
proto = "tcp"
addr = "172.18.33.60:6889"
idle = 10
active = 10
dialTimeout = "500ms"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
indexExpire = "8h"
userActExpire="1h"
reportExpire = "24h"
userCntExpire = "30s"
notifyExpire = "24h"
[memcache]
proto = "tcp"
addr = "172.18.33.61:11213"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "80s"
expire = "1h"
[databus]
[databus.event]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group = "Reply-MainCommunity-P"
topic = "Reply-T"
action ="pub"
name = "reply/event"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 2
active = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[databus.stats]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group = "StatReply-MainCommunity-P"
topic = "StatReply-T"
action ="pub"
name = "reply/stats"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 2
active = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[databus.consumer]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group = "ReplyAdd-MainCommunity-S"
topic = "ReplyAdd-T"
action ="sub"
name = "reply/job"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 2
active = 5
dialTimeout = "1s"
readTimeout = "40s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[databus.consumer.discovery]
domain = "api.bilibili.co"
key = "0c4b8fe3ff35a4b6"
secret = "b370880d1aca7d3a289b9b9a7f4d6812"
[databus.like]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group = "StatLike-MainCommunity-S"
topic = "StatLike-T"
action ="sub"
name = "reply/job"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 2
active = 5
dialTimeout = "1s"
readTimeout = "40s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[databus.like.discovery]
domain = "api.bilibili.co"
key = "0c4b8fe3ff35a4b6"
secret = "b370880d1aca7d3a289b9b9a7f4d6812"
[[stats]]
key = "0Pub71WwEMKXu63qtztu"
secret = "0Pub71WwEMKXu63qtztv"
group = "Stat-UGC-P"
topic = "Stat-T"
action ="pub"
name = "archive-service/stat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
type = 1
field = "aid"
[[stats]]
key = "0QEO9F8JuuIxZzNDvklH"
secret = "0QEO9F8JuuIxZzNDvklI"
group = "ArticleStat-Article-P"
topic = "ArticleStat-T"
action ="pub"
name = "archive-service/stat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
type = 12
field = "aid"
[[stats]]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "PlaylistStat-Playlist-P"
topic= "PlaylistStat-T"
action="pub"
name = "playlist-service/stat"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
type = 18
field = "pid"

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/job/main/reply/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,158 @@
package conf
import (
"flag"
"go-common/library/database/elastic"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"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"
)
var (
// ConfPath config path.
ConfPath string
// Conf config.
Conf *Config
)
// Config config.
type Config struct {
XLog *log.Config
Tracer *trace.Config
// HTTP
HTTPClient *bm.ClientConfig
DrawyooHTTPClient *bm.ClientConfig
// databus
Databus *Databus
// rpc
RPCClient2 *RPCClient2
// bm
BM *bm.ServerConfig
// mysql
MySQL *MySQL
// redis
Redis *Redis
// mc
Memcache *Memcache
// Host
Host *Host
Weight *Weight
Job *Job
StatTypes map[string]int8
Es *elastic.Config
AccountClient *warden.ClientConfig
}
// Job job.
type Job struct {
Proc int
SearchNum int
SearchFlush time.Duration
MessageMids []int64
BatchNumber int
}
// Weight weight.
type Weight struct {
Like int
Hate int
}
// Host represents host info.
type Host struct {
Activity string
Message string
DrawYoo string
Search string
BlackRoom string
LiveVC string
LiveAct string
API string
Bangumi string
}
// MySQL mysql.
type MySQL struct {
Reply *sql.Config
}
// Redis redis.
type Redis struct {
*redis.Config
IndexExpire time.Duration
ReportExpire time.Duration
UserCntExpire time.Duration
StatCacheExpire time.Duration
UserActExpire time.Duration
NotifyExpire time.Duration
}
// Memcache mc.
type Memcache struct {
*memcache.Config
Expire time.Duration
TopExpire time.Duration
}
// RPCClient2 rpc client.
type RPCClient2 struct {
Account *rpc.ClientConfig
Archive *rpc.ClientConfig
Article *rpc.ClientConfig
Assist *rpc.ClientConfig
}
// Databus databus.
type Databus struct {
Event *databus.Config
Stats *databus.Config
Consumer *databus.Config
Like *databus.Config
}
// Stats stats.
type Stats struct {
*databus.Config
Type int8
Field string
}
func init() {
flag.StringVar(&ConfPath, "conf", "", "config path")
}
// Init init conf
func Init() (err error) {
if ConfPath == "" {
return configCenter()
}
_, err = toml.DecodeFile(ConfPath, &Conf)
return
}
func configCenter() (err error) {
var (
ok bool
value string
client *conf.Client
)
if client, err = conf.New(); err != nil {
return
}
if value, ok = client.Toml2(); !ok {
panic(err)
}
_, err = toml.Decode(value, &Conf)
return
}

View File

@@ -0,0 +1,48 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["message_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["message.go"],
importpath = "go-common/app/job/main/reply/dao/message",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,175 @@
package message
import (
"context"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"go-common/app/job/main/reply/conf"
"go-common/library/ecode"
"go-common/library/log"
xhttp "go-common/library/net/http/blademaster"
"go-common/library/xstr"
)
const (
// 1 main site related
// 1_1 reply related
// 1_2 at related
// 1_3 report related
//_codeReplyGet = "1_1_1"
_codeReplyDelete = "1_1_2"
_codeReplyLike = "1_1_3"
_codeAt = "1_2_1"
_codeReport = "1_3_1"
_dataTypeReply = 1
_dataTypeAt = 2
_dataTypeLike = 3
_dataTypeSystem = 4
_notifyTypeCnt = 2
)
// Dao message dao.
type Dao struct {
httpCli *xhttp.Client
apiURL string
}
// NewMessageDao new a message dao and return.
func NewMessageDao(c *conf.Config) *Dao {
return &Dao{
httpCli: xhttp.NewClient(c.HTTPClient),
apiURL: c.Host.Message + "/api/notify/send.user.notify.do",
}
}
// Like send a like message.
func (dao *Dao) Like(c context.Context, mid, tomid int64, title, msg, extraInfo string, now time.Time) (err error) {
return dao.send(c, _codeReplyLike, "", title, msg, _dataTypeLike, mid, []int64{tomid}, extraInfo, now.Unix())
}
// Reply send a reply message.
func (dao *Dao) Reply(c context.Context, mc, resID string, mid, tomid int64, title, msg, extraInfo string, now time.Time) (err error) {
return dao.send(c, mc, resID, title, msg, _dataTypeReply, mid, []int64{tomid}, extraInfo, now.Unix())
}
// DeleteReply send delete reply message.
func (dao *Dao) DeleteReply(c context.Context, mid int64, title, msg string, now time.Time) (err error) {
return dao.send(c, _codeReplyDelete, "", title, msg, _dataTypeSystem, 0, []int64{mid}, "", now.Unix())
}
// At send a at message.
func (dao *Dao) At(c context.Context, mid int64, mids []int64, title, msg, extraInfo string, now time.Time) (err error) {
if len(mids) == 0 {
return
}
return dao.send(c, _codeAt, "", title, msg, _dataTypeAt, mid, mids, extraInfo, now.Unix())
}
// AcceptReport send accept report message.
func (dao *Dao) AcceptReport(c context.Context, mid int64, title, msg string, now time.Time) (err error) {
return dao.send(c, _codeReport, "", title, msg, _dataTypeSystem, 0, []int64{mid}, "", now.Unix())
}
// System send a system message.
func (dao *Dao) System(c context.Context, mc, resID string, mid int64, title, msg, info string, now time.Time) (err error) {
return dao.send(c, mc, resID, title, msg, _dataTypeSystem, 0, []int64{mid}, info, now.Unix())
}
func (dao *Dao) send(c context.Context, mc, resID, title, msg string, tp int, pub int64, mids []int64, info string, ts int64) (err error) {
params := url.Values{}
params.Set("type", "json")
params.Set("source", "1")
params.Set("mc", mc)
params.Set("title", title)
params.Set("data_type", strconv.Itoa(tp))
params.Set("context", msg)
params.Set("mid_list", xstr.JoinInts(mids))
params.Set("publisher", strconv.FormatInt(pub, 10))
params.Set("ext_info", info)
if resID != "" {
params.Set("notify_type", fmt.Sprint(_notifyTypeCnt))
params.Set("res_id", resID)
}
var res struct {
Code int `json:"code"`
}
if err = dao.httpCli.Post(c, dao.apiURL, "", params, &res); err != nil {
log.Error("message url(%s) error(%v)", dao.apiURL+"?"+params.Encode(), err)
return
}
if res.Code != ecode.OK.Code() {
log.Error("message url(%s) error(%v)", dao.apiURL+"?"+params.Encode(), res.Code)
err = fmt.Errorf("message send failed")
return
}
log.Info("sendmessage success:%v;code:%d", params, res.Code)
if tp != _dataTypeSystem {
params.Set("mobi_app", "android_i")
if tp == _dataTypeAt {
params.Set("title", converAt(title))
} else if tp == _dataTypeLike {
params.Set("context", convertMsg(msg))
} else if tp == _dataTypeReply {
params.Set("title", convertMsg(title))
params.Set("context", convertMsg(msg))
}
var res1 struct {
Code int `json:"code"`
}
if err = dao.httpCli.Post(c, dao.apiURL, "", params, &res1); err != nil {
log.Error("message url(%s) error(%v)", dao.apiURL+"?"+params.Encode(), err)
return
}
if res1.Code != ecode.OK.Code() {
log.Error("message url(%s) error(%v)", dao.apiURL+"?"+params.Encode(), res1.Code)
err = fmt.Errorf("message send failed")
return
}
log.Info("send international message success:%v;code:%d", params, res1.Code)
}
return
}
func converAt(title string) string {
return strings.Replace(title, "评论中@了你", "評論中@了你", -1)
}
func convertMsg(msg string) string {
rmsg := []rune(msg)
for i, c := range rmsg {
switch c {
case '评':
rmsg[i] = '評'
case '论':
rmsg[i] = '論'
case '赞':
rmsg[i] = '讚'
case '条':
rmsg[i] = '條'
case '专':
rmsg[i] = '專'
case '栏':
rmsg[i] = '欄'
case '数':
rmsg[i] = '數'
case '达':
rmsg[i] = '達'
case '应':
rmsg[i] = '應'
case '点':
rmsg[i] = '點'
case '击':
rmsg[i] = '擊'
default:
continue
}
}
return string(rmsg)
}

View File

@@ -0,0 +1,229 @@
package message
import (
"context"
"flag"
"go-common/app/job/main/reply/conf"
"os"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-job")
flag.Set("conf_token", "5deea0665f8a7670b22a719337a39c7d")
flag.Set("tree_id", "2123")
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/reply-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = NewMessageDao(conf.Conf)
os.Exit(m.Run())
}
func TestMessageNewMessageDao(t *testing.T) {
convey.Convey("NewMessageDao", t, func(ctx convey.C) {
var (
c = conf.Conf
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := NewMessageDao(c)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestMessageLike(t *testing.T) {
convey.Convey("Like", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
tomid = int64(0)
title = ""
msg = ""
extraInfo = ""
now = time.Now()
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.Like(c, mid, tomid, title, msg, extraInfo, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessageReply(t *testing.T) {
convey.Convey("Reply", t, func(ctx convey.C) {
var (
c = context.Background()
mc = ""
resID = ""
mid = int64(0)
tomid = int64(0)
title = ""
msg = ""
extraInfo = ""
now = time.Now()
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.Reply(c, mc, resID, mid, tomid, title, msg, extraInfo, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessageDeleteReply(t *testing.T) {
convey.Convey("DeleteReply", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
title = ""
msg = ""
now = time.Now()
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.DeleteReply(c, mid, title, msg, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessageAt(t *testing.T) {
convey.Convey("At", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
mids = []int64{}
title = ""
msg = ""
extraInfo = ""
now = time.Now()
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.At(c, mid, mids, title, msg, extraInfo, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessageAcceptReport(t *testing.T) {
convey.Convey("AcceptReport", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
title = ""
msg = ""
now = time.Now()
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.AcceptReport(c, mid, title, msg, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessageSystem(t *testing.T) {
convey.Convey("System", t, func(ctx convey.C) {
var (
c = context.Background()
mc = ""
resID = ""
mid = int64(0)
title = ""
msg = ""
info = ""
now = time.Now()
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.System(c, mc, resID, mid, title, msg, info, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessagesend(t *testing.T) {
convey.Convey("send", t, func(ctx convey.C) {
var (
c = context.Background()
mc = ""
resID = ""
title = ""
msg = ""
tp = int(0)
pub = int64(0)
mids = []int64{}
info = ""
ts = int64(0)
err error
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err = d.send(c, mc, resID, title, msg, tp, pub, mids, info, ts)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(msg, convey.ShouldBeBlank)
})
})
})
}
func TestMessageconverAt(t *testing.T) {
convey.Convey("converAt", t, func(ctx convey.C) {
var (
title = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := converAt(title)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldBeBlank)
})
})
})
}
func TestMessageconvertMsg(t *testing.T) {
convey.Convey("convertMsg", t, func(ctx convey.C) {
var (
msg = "评"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := convertMsg(msg)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldEqual, "評")
})
})
})
}

View File

@@ -0,0 +1,66 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"audio_playlist_test.go",
"audio_test.go",
"bangumi_test.go",
"credit_test.go",
"dao_test.go",
"drawyoo_test.go",
"dynamic_test.go",
"live_test.go",
"topic_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"audio.go",
"audio_playlist.go",
"bangumi.go",
"credit.go",
"dao.go",
"drawyoo.go",
"dynamic.go",
"live.go",
"topic.go",
],
importpath = "go-common/app/job/main/reply/dao/notice",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,42 @@
package notice
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
)
const (
_urlAudio = "https://m.bilibili.com/audio/au%d"
)
type audio struct {
Title string `json:"title"`
}
// Audio is Audio
func (d *Dao) Audio(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
uri := d.urlAudio
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*audio `json:"data"`
}
if err = d.httpClient.Get(c, uri, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", uri, params.Encode(), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", uri, res.Code)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_urlAudio, oid)
return
}

View File

@@ -0,0 +1,36 @@
package notice
import (
"context"
"fmt"
"net/url"
"go-common/library/log"
)
const (
_urlAudioPlayList = "https://m.bilibili.com/audio/am%d"
)
// AudioPlayList is show AudioPlay list
func (d *Dao) AudioPlayList(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
uri := fmt.Sprintf(d.urlAudioPlaylist, oid)
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"title"`
} `json:"data"`
}
if err = d.httpClient.Get(c, uri, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", uri, params.Encode(), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", uri, res.Code)
return
}
title = res.Data.Title
link = fmt.Sprintf(_urlAudioPlayList, oid)
return
}

View File

@@ -0,0 +1,29 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeAudioPlayList(t *testing.T) {
convey.Convey("AudioPlayList", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.AudioPlayList(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,29 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeAudio(t *testing.T) {
convey.Convey("Audio", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Audio(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,41 @@
package notice
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
)
type bangumi struct {
EpisodeID int64 `json:"episode_id,string"`
SeasonID int64 `json:"season_id"`
Title string `json:"title"`
IndexTitle string `json:"index_title"`
}
// Bangumi return link.
func (d *Dao) Bangumi(c context.Context, oid int64) (title, link string, epid int64, err error) {
params := url.Values{}
params.Set("aids", strconv.FormatInt(oid, 10))
params.Set("platform", "reply")
params.Set("build", "0")
var res struct {
Code int `json:"code"`
Result map[int64]*bangumi `json:"result"`
}
if err = d.httpClient.Get(c, d.urlBangumi, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlBangumi, params.Encode(), err)
return
}
if res.Code != 0 || res.Result == nil {
err = fmt.Errorf("url:%s?%s code:%d", d.urlBangumi, params.Encode(), res.Code)
return
}
if r := res.Result[oid]; r != nil {
epid = r.EpisodeID
}
return
}

View File

@@ -0,0 +1,30 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeBangumi(t *testing.T) {
convey.Convey("Bangumi", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, epid, err := d.Bangumi(c, oid)
ctx.Convey("Then err should be nil.title,link,epid should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(epid, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,88 @@
package notice
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
)
const (
_urlBan = "https://www.bilibili.com/blackroom/ban/%d"
_urlNotice = "https://www.bilibili.com/blackroom/notice/%d"
_urlCreditLink = "https://www.bilibili.com/judgement/case/%d"
)
type notice struct {
Title string `json:"title"`
}
type ban struct {
Title string `json:"punishTitle"`
}
type credit struct {
Title string `json:"punishTitle"`
}
// Credit return link.
func (d *Dao) Credit(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*credit `json:"data"`
}
if err = d.httpClient.Get(c, d.urlCredit, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlCredit, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("url:%s?%s code:%d", d.urlCredit, params.Encode(), res.Code)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_urlCreditLink, oid)
return
}
// Notice get blackromm notice info.
func (d *Dao) Notice(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*notice `json:"data"`
}
if err = d.httpClient.Get(c, d.urlNotice, "", params, &res); err != nil {
log.Error("httpNotice(%s) error(%v)", d.urlNotice, err)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_urlNotice, oid)
return
}
// Ban get blackroom ban info.
func (d *Dao) Ban(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*ban `json:"data"`
}
if err = d.httpClient.Get(c, d.urlBan, "", params, &res); err != nil {
log.Error("httpNotice(%s) error(%v)", d.urlBan, err)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_urlBan, oid)
return
}

View File

@@ -0,0 +1,71 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestCreditBan(t *testing.T) {
convey.Convey("Audio", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Ban(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeCredit(t *testing.T) {
convey.Convey("Audio", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Credit(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeCreditNotice(t *testing.T) {
convey.Convey("Audio", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Notice(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,53 @@
package notice
import (
"go-common/app/job/main/reply/conf"
bm "go-common/library/net/http/blademaster"
)
// Dao activity dao.
type Dao struct {
c *conf.Config
urlLiveSmallVideo string
urlLiveActivity string
urlLiveNotice string
urlLivePicture string
urlCredit string
urlTopic string
urlActivity string
urlActivitySub string
urlDrwayoo string
urlDynamic string
urlNotice string
urlBan string
urlBangumi string
urlAudio string
urlAudioPlaylist string
httpClient *bm.Client
drawyooHTTPClient *bm.Client
}
// New new a dao and return.
func New(c *conf.Config) *Dao {
return &Dao{
c: c,
// http
urlLiveSmallVideo: c.Host.LiveVC + "/clip/v1/video/detail",
urlLiveActivity: c.Host.LiveAct + "/comment/v1/relation/get_by_id",
urlLiveNotice: c.Host.LiveVC + "/news/v1/notice/info",
urlLivePicture: c.Host.LiveVC + "/link_draw/v1/doc/detail",
urlCredit: c.Host.API + "/x/internal/credit/blocked/cases",
urlTopic: c.Host.Activity + "/activity/page/one/%d",
urlActivity: c.Host.Activity + "/activity/page/one/%d",
urlActivitySub: c.Host.Activity + "/activity/subject/url",
urlDrwayoo: c.Host.DrawYoo + "/api/pushS",
urlDynamic: c.Host.LiveVC + "/dynamic_repost/v0/dynamic_repost/ftch_rp_cont?dynamic_ids[]=%d",
urlNotice: c.Host.API + "/x/internal/credit/publish/infos",
urlBan: c.Host.API + "/x/internal/credit/blocked/infos",
urlBangumi: c.Host.Bangumi + "/api/inner/aid_episodes_v2",
urlAudio: c.Host.API + "/x/internal/v1/audio/songs/batch",
urlAudioPlaylist: c.Host.API + "/x/internal/v1/audio/menus/%d",
httpClient: bm.NewClient(c.HTTPClient),
drawyooHTTPClient: bm.NewClient(c.DrawyooHTTPClient),
}
}

View File

@@ -0,0 +1,34 @@
package notice
import (
"flag"
"go-common/app/job/main/reply/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-job")
flag.Set("conf_token", "5deea0665f8a7670b22a719337a39c7d")
flag.Set("tree_id", "2123")
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/reply-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,35 @@
package notice
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
)
// Drawyoo return link.
func (d *Dao) Drawyoo(c context.Context, hid int64) (title, link string, err error) {
params := url.Values{}
params.Set("hid", strconv.FormatInt(hid, 10))
params.Set("act", "getHidInfo")
var res struct {
State int `json:"state"`
Data []*struct {
Title string `json:"title"`
Link string `json:"link"`
} `json:"data"`
}
if err = d.drawyooHTTPClient.Post(c, d.urlDrwayoo, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlDrwayoo, params.Encode(), err)
return
}
if len(res.Data) == 0 {
err = fmt.Errorf("url:%s code:%d", d.urlDrwayoo, res.State)
return
}
title = res.Data[0].Title
link = res.Data[0].Link
return
}

View File

@@ -0,0 +1,29 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeDrawyoo(t *testing.T) {
convey.Convey("Drawyoo", t, func(ctx convey.C) {
var (
c = context.Background()
hid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Drawyoo(c, hid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,43 @@
package notice
import (
"context"
"fmt"
"net/url"
"go-common/library/log"
)
const (
_dynamicLink = "https://t.bilibili.com/%d"
)
// Dynamic return link and content.
func (d *Dao) Dynamic(c context.Context, oid int64) (content, link string, err error) {
params := url.Values{}
uri := fmt.Sprintf(d.urlDynamic, oid)
var res struct {
Code int `json:"code"`
Data *struct {
Pairs []struct {
DynamicID int64 `json:"dynamic_id"`
Content string `json:"rp_cont"`
Type int32 `json:"type"`
} `json:"pairs"`
TotalCount int64 `json:"total_count"`
} `json:"data,omitempty"`
Message string `json:"message"`
}
if err = d.httpClient.Get(c, uri, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", uri, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil || len(res.Data.Pairs) == 0 {
err = fmt.Errorf("get dynamic failed!url:%s?%s code:%d message:%s pairs:%v", uri, params.Encode(), res.Code, res.Message, res.Data.Pairs)
return
}
content = res.Data.Pairs[0].Content
link = fmt.Sprintf(_dynamicLink, oid)
return
}

View File

@@ -0,0 +1,29 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeDynamic(t *testing.T) {
convey.Convey("Dynamic", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
content, link, err := d.Dynamic(c, oid)
ctx.Convey("Then err should be nil.content,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(content, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,117 @@
package notice
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
)
const (
_liveSmallVideoLink = "http://vc.bilibili.com/video/%d"
_liveNoticeLink = "http://link.bilibili.com/p/eden/news#/newsdetail?id=%d"
_livePictureLink = "http://h.bilibili.com/ywh/%d"
)
// LiveSmallVideo return link.
func (d *Dao) LiveSmallVideo(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("video_id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Item *struct {
Description string `json:"description"`
} `json:"item"`
} `json:"data"`
}
if err = d.httpClient.Get(c, d.urlLiveSmallVideo, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlLiveSmallVideo, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil || res.Data.Item == nil {
err = fmt.Errorf("url:%s?%s code:%d", d.urlLiveSmallVideo, params.Encode(), res.Code)
return
}
title = res.Data.Item.Description
link = fmt.Sprintf(_liveSmallVideoLink, oid)
return
}
// LiveActivity return link.
func (d *Dao) LiveActivity(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, d.urlLiveActivity, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlLiveActivity, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("url:%s?%s code:%d", d.urlLiveActivity, params.Encode(), res.Code)
return
}
title = res.Data.Name
link = res.Data.URL
return
}
// LiveNotice return link.
func (d *Dao) LiveNotice(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"title"`
} `json:"data"`
}
if err = d.httpClient.Get(c, d.urlLiveNotice, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlLiveNotice, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("url:%s?%s code:%d", d.urlLiveNotice, params.Encode(), res.Code)
return
}
title = res.Data.Title
link = fmt.Sprintf(_liveNoticeLink, oid)
return
}
// LivePicture return link.
func (d *Dao) LivePicture(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("doc_id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Item *struct {
Title string `json:"title"`
Desc string `json:"description"`
} `json:"item"`
} `json:"data"`
}
if err = d.httpClient.Get(c, d.urlLivePicture, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlLivePicture, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil || res.Data.Item == nil {
err = fmt.Errorf("url:%s?%s code:%d", d.urlLivePicture, params.Encode(), res.Code)
return
}
title = res.Data.Item.Title
if title == "" {
title = res.Data.Item.Desc
}
link = fmt.Sprintf(_livePictureLink, oid)
return
}

View File

@@ -0,0 +1,92 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeLiveSmallVideo(t *testing.T) {
convey.Convey("LiveSmallVideo", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.LiveSmallVideo(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeLiveActivity(t *testing.T) {
convey.Convey("LiveActivity", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.LiveActivity(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeLiveNotice(t *testing.T) {
convey.Convey("LiveNotice", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.LiveNotice(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeLivePicture(t *testing.T) {
convey.Convey("LivePicture", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.LivePicture(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,90 @@
package notice
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
)
// Topic return topic link.
func (d *Dao) Topic(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
uri := fmt.Sprintf(d.urlTopic, oid)
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"name"`
PCLink string `json:"pc_url"`
H5Link string `json:"h5_url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, uri, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", uri, params.Encode(), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", uri, res.Code)
return
}
title = res.Data.Title
link = res.Data.PCLink
if link == "" {
link = res.Data.H5Link
}
return
}
// Activity return topic link.
func (d *Dao) Activity(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
uri := fmt.Sprintf(d.urlActivity, oid)
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"name"`
PCLink string `json:"pc_url"`
H5Link string `json:"h5_url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, uri, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", uri, params.Encode(), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", uri, res.Code)
return
}
title = res.Data.Title
link = res.Data.PCLink
if link == "" {
link = res.Data.H5Link
}
return
}
// ActivitySub return topic link.
func (d *Dao) ActivitySub(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("oid", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"name"`
Link string `json:"act_url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, d.urlActivitySub, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", d.urlActivitySub, params.Encode(), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", d.urlActivitySub, res.Code)
return
}
title = res.Data.Title
link = res.Data.Link
return
}

View File

@@ -0,0 +1,71 @@
package notice
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestNoticeTopic(t *testing.T) {
convey.Convey("Topic", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Topic(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeActivity(t *testing.T) {
convey.Convey("Activity", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.Activity(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestNoticeActivitySub(t *testing.T) {
convey.Convey("ActivitySub", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := d.ActivitySub(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,74 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"admin_test.go",
"business_test.go",
"content_test.go",
"dao_test.go",
"databus_test.go",
"memcache_test.go",
"redis_test.go",
"reply_test.go",
"report_test.go",
"subject_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//app/job/main/reply/model/reply:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"admin.go",
"business.go",
"content.go",
"dao.go",
"databus.go",
"fold.go",
"memcache.go",
"redis.go",
"reply.go",
"report.go",
"subject.go",
],
importpath = "go-common/app/job/main/reply/dao/reply",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//app/job/main/reply/model/reply:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,47 @@
package reply
import (
"context"
"time"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_inAdminSQL = "INSERT INTO reply_admin_log (oid,type,rpID,adminid,result,remark,isnew,isreport,state,ctime,mtime) VALUES(?,?,?,?,?,?,?,?,?,?,?)"
_upAdminSQL = "UPDATE reply_admin_log SET isnew=0,mtime=? WHERE rpID=? AND isnew=1"
)
// AdminDao define admin mysql info
type AdminDao struct {
mysql *sql.DB
}
// NewAdminDao new ReplyReportDao and return.
func NewAdminDao(db *sql.DB) (dao *AdminDao) {
dao = &AdminDao{
mysql: db,
}
return
}
// Insert insert reply report.
func (dao *AdminDao) Insert(c context.Context, adminid, oid, rpID int64, tp int8, result, remark string, isnew, isreport, state int8, now time.Time) (id int64, err error) {
res, err := dao.mysql.Exec(c, _inAdminSQL, oid, tp, rpID, adminid, result, remark, isnew, isreport, state, now, now)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.LastInsertId()
}
// UpIsNotNew update reply report.
func (dao *AdminDao) UpIsNotNew(c context.Context, rpID int64, now time.Time) (rows int64, err error) {
res, err := dao.mysql.Exec(c, _upAdminSQL, now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,51 @@
package reply
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyInsert(t *testing.T) {
convey.Convey("Insert", t, func(ctx convey.C) {
var (
c = context.Background()
adminid = int64(0)
oid = int64(0)
rpID = int64(0)
tp = int8(0)
result = ""
remark = ""
isnew = int8(0)
isreport = int8(0)
state = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
id, err := d.Admin.Insert(c, adminid, oid, rpID, tp, result, remark, isnew, isreport, state, now)
ctx.Convey("Then err should be nil.id should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(id, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyUpIsNotNew(t *testing.T) {
convey.Convey("UpIsNotNew", t, func(ctx convey.C) {
var (
c = context.Background()
rpID = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Admin.UpIsNotNew(c, rpID, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,44 @@
package reply
import (
"context"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
)
const (
_selBussinessSQL = "SELECT type, alias FROM business WHERE state=0"
)
// BusinessDao business dao.
type BusinessDao struct {
db *sql.DB
}
// NewBusinessDao new BusinessDao and return.
func NewBusinessDao(db *sql.DB) (dao *BusinessDao) {
dao = &BusinessDao{
db: db,
}
return
}
// ListBusiness gets all business records
func (dao *BusinessDao) ListBusiness(c context.Context) (business []*reply.Business, err error) {
rows, err := dao.db.Query(c, _selBussinessSQL)
if err != nil {
return
}
defer rows.Close()
business = make([]*reply.Business, 0)
for rows.Next() {
b := new(reply.Business)
if err = rows.Scan(&b.Type, &b.Alias); err != nil {
return
}
business = append(business, b)
}
err = rows.Err()
return
}

View File

@@ -0,0 +1,23 @@
package reply
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyListBusiness(t *testing.T) {
convey.Convey("ListBusiness", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
business, err := d.Business.ListBusiness(c)
ctx.Convey("Then err should be nil.business should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(business, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,66 @@
package reply
import (
"context"
"fmt"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_contSharding int64 = 200
)
const (
_inContSQL = "INSERT IGNORE INTO reply_content_%d (rpid,message,ats,ip,plat,device,version,ctime,mtime,topics) VALUES(?,?,?,?,?,?,?,?,?,?)"
_selContSQL = "SELECT rpid,message,ats,ip,plat,device,topics FROM reply_content_%d WHERE rpid=?"
)
// ContentDao define content mysql stmt
type ContentDao struct {
selContStmts []*sql.Stmt
mysql *sql.DB
}
// NewContentDao new contentDao and return.
func NewContentDao(db *sql.DB) (dao *ContentDao) {
dao = &ContentDao{
mysql: db,
selContStmts: make([]*sql.Stmt, _contSharding),
}
for i := int64(0); i < _contSharding; i++ {
dao.selContStmts[i] = dao.mysql.Prepared(fmt.Sprintf(_selContSQL, i))
}
return
}
func (dao *ContentDao) hit(oid int64) int64 {
return oid % int64(_contSharding)
}
// TxInsert insert reply content by transaction.
func (dao *ContentDao) TxInsert(tx *sql.Tx, oid int64, rc *reply.Content) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_inContSQL, dao.hit(oid)), rc.RpID, rc.Message, rc.Ats, rc.IP, rc.Plat, rc.Device, rc.Version, rc.CTime, rc.MTime, rc.Topics)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// Get get reply content.
func (dao *ContentDao) Get(c context.Context, oid int64, rpID int64) (rc *reply.Content, err error) {
row := dao.selContStmts[dao.hit(oid)].QueryRow(c, rpID)
rc = &reply.Content{}
if err = row.Scan(&rc.RpID, &rc.Message, &rc.Ats, &rc.IP, &rc.Plat, &rc.Device, &rc.Topics); err != nil {
if err == sql.ErrNoRows {
err = nil
rc = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}

View File

@@ -0,0 +1,43 @@
package reply
import (
"context"
"go-common/app/job/main/reply/model/reply"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyTxInsert1(t *testing.T) {
convey.Convey("TxInsert", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rc = &reply.Content{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Content.TxInsert(tx, oid, rc)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyGet1(t *testing.T) {
convey.Convey("Get", t, func(ctx convey.C) {
var (
oid = int64(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rc, err := d.Content.Get(context.Background(), oid, rpID)
ctx.Convey("Then err should be nil.rc should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rc, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,75 @@
package reply
import (
"context"
"go-common/app/job/main/reply/conf"
"go-common/library/database/sql"
"go-common/library/queue/databus"
)
// Dao define mysql info
type Dao struct {
// memcache
Mc *MemcacheDao
// mysql
mysql *sql.DB
Admin *AdminDao
Content *ContentDao
Report *ReportDao
Reply *RpDao
Subject *SubjectDao
Business *BusinessDao
// redis
Redis *RedisDao
// databus
eventBus *databus.Databus
}
// New new a db and return
func New(c *conf.Config) (d *Dao) {
d = &Dao{
// memchache
Mc: NewMemcacheDao(c.Memcache),
// mysql
mysql: sql.NewMySQL(c.MySQL.Reply),
// redis
Redis: NewRedisDao(c.Redis),
// databus
eventBus: databus.New(c.Databus.Event),
}
d.Admin = NewAdminDao(d.mysql)
d.Content = NewContentDao(d.mysql)
d.Reply = NewReplyDao(d.mysql)
d.Report = NewReportDao(d.mysql)
d.Subject = NewSubjectDao(d.mysql)
d.Business = NewBusinessDao(d.mysql)
return
}
// Ping check db is alive
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.mysql.Ping(c); err != nil {
return
}
if err = d.Redis.Ping(c); err != nil {
return
}
return d.Mc.Ping(c)
}
// Close close all db connection
func (d *Dao) Close() {
if d.Mc.mc != nil {
d.Mc.mc.Close()
}
if d.Redis.redis != nil {
d.Redis.redis.Close()
}
d.mysql.Close()
}
// BeginTran begin mysql transaction
func (d *Dao) BeginTran(c context.Context) (*sql.Tx, error) {
return d.mysql.Begin(c)
}

View File

@@ -0,0 +1,34 @@
package reply
import (
"flag"
"go-common/app/job/main/reply/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-job")
flag.Set("conf_token", "5deea0665f8a7670b22a719337a39c7d")
flag.Set("tree_id", "2123")
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/reply-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,33 @@
package reply
import (
"context"
"fmt"
model "go-common/app/job/main/reply/model/reply"
"go-common/library/log"
)
type event struct {
Action string `json:"action"`
Mid int64 `json:"mid"`
Subject *model.Subject `json:"subject"`
Reply *model.Reply `json:"reply"`
Report *model.Report `json:"report,omitempty"`
}
// PubEvent pub reply event.
func (d *Dao) PubEvent(c context.Context, action string, mid int64, sub *model.Subject, rp *model.Reply, report *model.Report) error {
e := &event{
Action: action,
Mid: mid,
Subject: sub,
Reply: rp,
Report: report,
}
if sub == nil {
log.Error("PubEvent failed,sub is nil!value: %v %v %v %v", action, mid, rp, report)
return nil
}
return d.eventBus.Send(c, fmt.Sprint(sub.Oid), &e)
}

View File

@@ -0,0 +1,27 @@
package reply
import (
"context"
model "go-common/app/job/main/reply/model/reply"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyPubEvent(t *testing.T) {
convey.Convey("PubEvent", t, func(ctx convey.C) {
var (
action = ""
mid = int64(0)
sub = &model.Subject{}
rp = &model.Reply{}
report = &model.Report{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.PubEvent(context.Background(), action, mid, sub, rp, report)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,44 @@
package reply
import (
"context"
"fmt"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
)
const (
_foldedReplies = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,floor,state,attr,ctime,mtime FROM reply_%d WHERE oid=? AND type=? AND root=? AND state=12"
_countFoldedReplies = "SELECT COUNT(*) FROM reply_%d WHERE oid=? AND type=? AND root=? AND state=12"
)
// TxCountFoldedReplies ...
func (dao *RpDao) TxCountFoldedReplies(tx *sql.Tx, oid int64, tp int8, root int64) (count int, err error) {
if err = tx.QueryRow(fmt.Sprintf(_countFoldedReplies, dao.hit(oid)), oid, tp, root).Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
}
return
}
return
}
// FoldedReplies ...
func (dao *RpDao) FoldedReplies(ctx context.Context, oid int64, tp int8, root int64) (rps []*reply.Reply, err error) {
rows, err := dao.mysql.Query(ctx, fmt.Sprintf(_foldedReplies, dao.hit(oid)), oid, tp, root)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
r := new(reply.Reply)
if err = rows.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
return
}
rps = append(rps, r)
}
if err = rows.Err(); err != nil {
return
}
return
}

View File

@@ -0,0 +1,241 @@
package reply
import (
"context"
"fmt"
"strconv"
"time"
"go-common/app/job/main/reply/conf"
model "go-common/app/job/main/reply/model/reply"
"go-common/library/cache/memcache"
"go-common/library/log"
)
const (
_prefixSub = "s_"
_prefixRp = "r_"
_prefixAdminTop = "at_"
_prefixUpperTop = "ut_"
)
// MemcacheDao define memcache info
type MemcacheDao struct {
mc *memcache.Pool
expire int32
topExpire int32
}
// NewMemcacheDao return a new mc dao
func NewMemcacheDao(c *conf.Memcache) *MemcacheDao {
return &MemcacheDao{
mc: memcache.NewPool(c.Config),
expire: int32(time.Duration(c.Expire) / time.Second),
topExpire: int32(time.Duration(c.TopExpire) / time.Second),
}
}
func keyAdminTop(oid int64, tp int8) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d", _prefixAdminTop, oid, tp)
}
return _prefixAdminTop + strconv.FormatInt((oid<<8)|int64(tp), 10)
}
func keyUpperTop(oid int64, tp int8) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d", _prefixUpperTop, oid, tp)
}
return _prefixUpperTop + strconv.FormatInt((oid<<8)|int64(tp), 10)
}
func keySub(oid int64, tp int8) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d", _prefixSub, oid, tp)
}
return _prefixSub + strconv.FormatInt((oid<<8)|int64(tp), 10)
}
func keyRp(rpID int64) string {
return _prefixRp + strconv.FormatInt(rpID, 10)
}
// Ping check connection success.
func (dao *MemcacheDao) Ping(c context.Context) (err error) {
conn := dao.mc.Get(c)
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: dao.expire}
err = conn.Set(&item)
conn.Close()
return
}
// AddSubject add subject into memcache.
func (dao *MemcacheDao) AddSubject(c context.Context, subs ...*model.Subject) (err error) {
if len(subs) == 0 {
return
}
conn := dao.mc.Get(c)
for _, sub := range subs {
key := keySub(sub.Oid, sub.Type)
item := &memcache.Item{Key: key, Object: sub, Expiration: dao.expire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s,%v) error(%v)", key, sub, err)
}
}
conn.Close()
return
}
// GetSubject get subject from memcache.
func (dao *MemcacheDao) GetSubject(c context.Context, oid int64, tp int8) (sub *model.Subject, err error) {
key := keySub(oid, tp)
conn := dao.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
sub = new(model.Subject)
if err = conn.Scan(item, sub); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
sub = nil
}
return
}
// AddReply add reply into memcache.
func (dao *MemcacheDao) AddReply(c context.Context, rs ...*model.Reply) (err error) {
if len(rs) == 0 {
return
}
conn := dao.mc.Get(c)
for _, r := range rs {
key := keyRp(r.RpID)
item := &memcache.Item{Key: key, Object: r, Expiration: dao.expire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s,%v) error(%v)", key, r, err)
}
}
conn.Close()
return
}
// GetTop get subject top reply from memcache
func (dao *MemcacheDao) GetTop(c context.Context, oid int64, tp int8, top uint32) (rp *model.Reply, err error) {
var key string
if top == model.ReplyAttrUpperTop {
key = keyUpperTop(oid, tp)
} else if top == model.ReplyAttrAdminTop {
key = keyAdminTop(oid, tp)
} else {
return
}
conn := dao.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
rp = new(model.Reply)
if err = conn.Scan(item, &rp); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
rp = nil
}
return
}
// AddTop add top reply into memcache.
func (dao *MemcacheDao) AddTop(c context.Context, rp *model.Reply) (err error) {
if rp == nil {
return
}
var key string
if rp.AttrVal(model.ReplyAttrAdminTop) == 1 {
key = keyAdminTop(rp.Oid, rp.Type)
} else if rp.AttrVal(model.ReplyAttrUpperTop) == 1 {
key = keyUpperTop(rp.Oid, rp.Type)
} else {
return
}
conn := dao.mc.Get(c)
defer conn.Close()
item := &memcache.Item{Key: key, Object: rp, Expiration: dao.topExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s,%v) error(%v)", key, rp, err)
}
return
}
// DeleteTop delete topreply from memcache.
func (dao *MemcacheDao) DeleteTop(c context.Context, rp *model.Reply, tp uint32) (err error) {
var key string
if tp == model.SubAttrAdminTop {
key = keyAdminTop(rp.Oid, rp.Type)
} else if tp == model.SubAttrUpperTop {
key = keyUpperTop(rp.Oid, rp.Type)
}
conn := dao.mc.Get(c)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
conn.Close()
return
}
// DeleteSub delete sub from memcache.
func (dao *MemcacheDao) DeleteSub(c context.Context, oid int64, tp int8) (err error) {
conn := dao.mc.Get(c)
if err = conn.Delete(keySub(oid, tp)); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete error(%v)", err)
}
}
conn.Close()
return
}
// DeleteReply delete reply from memcache.
func (dao *MemcacheDao) DeleteReply(c context.Context, rpID int64) (err error) {
conn := dao.mc.Get(c)
if err = conn.Delete(keyRp(rpID)); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete error(%v)", err)
}
}
conn.Close()
return
}
// GetReply get reply from memcache.
func (dao *MemcacheDao) GetReply(c context.Context, rpID int64) (rp *model.Reply, err error) {
key := keyRp(rpID)
conn := dao.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
rp = new(model.Reply)
if err = conn.Scan(item, rp); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
rp = nil
}
return
}

View File

@@ -0,0 +1,209 @@
package reply
import (
"context"
model "go-common/app/job/main/reply/model/reply"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestReplykeyAdminTop(t *testing.T) {
convey.Convey("keyAdminTop", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyAdminTop(oid, tp)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyUpperTop(t *testing.T) {
convey.Convey("keyUpperTop", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyUpperTop(oid, tp)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeySub(t *testing.T) {
convey.Convey("keySub", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keySub(oid, tp)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyRp(t *testing.T) {
convey.Convey("keyRp", t, func(ctx convey.C) {
var (
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyRp(rpID)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyPing1(t *testing.T) {
convey.Convey("Ping", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Ping(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddSubject(t *testing.T) {
convey.Convey("AddSubject", t, func(ctx convey.C) {
var (
c = context.Background()
subs = &model.Subject{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Mc.AddSubject(c, subs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetSubject(t *testing.T) {
convey.Convey("GetSubject", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
sub, err := d.Mc.GetSubject(c, oid, tp)
ctx.Convey("Then err should be nil.sub should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(sub, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyAddReply(t *testing.T) {
convey.Convey("AddReply", t, func(ctx convey.C) {
var (
c = context.Background()
rs = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Mc.AddReply(c, rs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetTop1(t *testing.T) {
convey.Convey("GetTop", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
top = uint32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rp, err := d.Mc.GetTop(c, oid, tp, top)
ctx.Convey("Then err should be nil.rp should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rp, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddTop(t *testing.T) {
convey.Convey("AddTop", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Mc.AddTop(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDeleteTop(t *testing.T) {
convey.Convey("DeleteTop", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
tp = uint32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Mc.DeleteTop(c, rp, tp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDeleteReply(t *testing.T) {
convey.Convey("DeleteReply", t, func(ctx convey.C) {
var (
c = context.Background()
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Mc.DeleteReply(c, rpID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetReply(t *testing.T) {
convey.Convey("GetReply", t, func(ctx convey.C) {
var (
c = context.Background()
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rp, err := d.Mc.GetReply(c, rpID)
ctx.Convey("Then err should be nil.rp should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rp, convey.ShouldBeNil)
})
})
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,862 @@
package reply
import (
"context"
"testing"
"time"
"go-common/app/job/main/reply/model/reply"
"github.com/smartystreets/goconvey/convey"
)
func TestReplykeyDialogIdx(t *testing.T) {
convey.Convey("keyDialogIdx", t, func(ctx convey.C) {
var (
dialogID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyDialogIdx(dialogID)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyIdx(t *testing.T) {
convey.Convey("keyIdx", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
sort = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyIdx(oid, tp, sort)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyNewRtIdx(t *testing.T) {
convey.Convey("keyNewRtIdx", t, func(ctx convey.C) {
var (
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyNewRtIdx(rpID)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyAuditIdx(t *testing.T) {
convey.Convey("keyAuditIdx", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyAuditIdx(oid, tp)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyRpt(t *testing.T) {
convey.Convey("keyRpt", t, func(ctx convey.C) {
var (
mid = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyRpt(mid, now)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyLike(t *testing.T) {
convey.Convey("keyLike", t, func(ctx convey.C) {
var (
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyLike(rpID)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyUAct(t *testing.T) {
convey.Convey("keyUAct", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyUAct(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeySpamRpRec(t *testing.T) {
convey.Convey("keySpamRpRec", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keySpamRpRec(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeySpamRpDaily(t *testing.T) {
convey.Convey("keySpamRpDaily", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keySpamRpDaily(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeySpamActRec(t *testing.T) {
convey.Convey("keySpamActRec", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keySpamActRec(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyTopOid(t *testing.T) {
convey.Convey("keyTopOid", t, func(ctx convey.C) {
var (
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyTopOid(tp)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyNotifyCnt(t *testing.T) {
convey.Convey("keyNotifyCnt", t, func(ctx convey.C) {
var (
oid = int64(0)
typ = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyNotifyCnt(oid, typ)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplykeyMaxLikeCnt(t *testing.T) {
convey.Convey("keyMaxLikeCnt", t, func(ctx convey.C) {
var (
rpid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyMaxLikeCnt(rpid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyDelAuditIndexs(t *testing.T) {
convey.Convey("DelAuditIndexs", t, func(ctx convey.C) {
var (
rs = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.DelAuditIndexs(context.Background(), rs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddAuditIndex(t *testing.T) {
convey.Convey("AddAuditIndex", t, func(ctx convey.C) {
var (
rp = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddAuditIndex(context.Background(), rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddFloorIndexEnd(t *testing.T) {
convey.Convey("AddFloorIndexEnd", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddFloorIndexEnd(context.Background(), oid, tp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddFloorIndex(t *testing.T) {
convey.Convey("AddFloorIndex", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
rs = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddFloorIndex(context.Background(), oid, tp, rs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddCountIndexBatch(t *testing.T) {
convey.Convey("AddCountIndexBatch", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
rs = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddCountIndexBatch(context.Background(), oid, tp, rs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddCountIndex(t *testing.T) {
convey.Convey("AddCountIndex", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
rp = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddCountIndex(context.Background(), oid, tp, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddLikeIndexBatch(t *testing.T) {
convey.Convey("AddLikeIndexBatch", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rpts map[int64]*reply.Report
rs = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddLikeIndexBatch(c, oid, tp, rpts, rs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddLikeIndex(t *testing.T) {
convey.Convey("AddLikeIndex", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rpts map[int64]*reply.Report
r = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddLikeIndex(c, oid, tp, rpts, r)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddIndex(t *testing.T) {
convey.Convey("AddIndex", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rpt = &reply.Report{}
rp = &reply.Reply{}
isRecover bool
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddIndex(c, oid, tp, rpt, rp, isRecover)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDelIndexBySortType(t *testing.T) {
convey.Convey("DelIndexBySortType", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &reply.Reply{}
sortType = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.DelIndexBySortType(c, rp, sortType)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDelIndex(t *testing.T) {
convey.Convey("DelIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.DelIndex(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddNewChildIndex(t *testing.T) {
convey.Convey("AddNewChildIndex", t, func(ctx convey.C) {
var (
c = context.Background()
root = int64(0)
rs = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddNewChildIndex(c, root, rs)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddTopOid(t *testing.T) {
convey.Convey("AddTopOid", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddTopOid(c, oid, tp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDelTopOid(t *testing.T) {
convey.Convey("DelTopOid", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.DelTopOid(c, oid, tp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyAddLike(t *testing.T) {
convey.Convey("AddLike", t, func(ctx convey.C) {
var (
c = context.Background()
rpID = int64(0)
ras = &reply.Action{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddLike(c, rpID, ras)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDelLike(t *testing.T) {
convey.Convey("DelLike", t, func(ctx convey.C) {
var (
c = context.Background()
rpID = int64(0)
ra = &reply.Action{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.DelLike(c, rpID, ra)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyExpireLike(t *testing.T) {
convey.Convey("ExpireLike", t, func(ctx convey.C) {
var (
c = context.Background()
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.Redis.ExpireLike(c, rpID)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyRange(t *testing.T) {
convey.Convey("Range", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
sort = int8(0)
start = int(0)
end = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rpIds, err := d.Redis.Range(c, oid, tp, sort, start, end)
ctx.Convey("Then err should be nil.rpIds should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rpIds, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyFloorEnd(t *testing.T) {
convey.Convey("FloorEnd", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
score, found, err := d.Redis.FloorEnd(c, oid, tp)
ctx.Convey("Then err should be nil.score,found should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(found, convey.ShouldNotBeNil)
ctx.So(score, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyMinScore(t *testing.T) {
convey.Convey("MinScore", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
sort = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
score, err := d.Redis.MinScore(c, oid, tp, sort)
ctx.Convey("Then err should be nil.score should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(score, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyCountReplies(t *testing.T) {
convey.Convey("CountReplies", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
sort = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := d.Redis.CountReplies(c, oid, tp, sort)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyExpireDialogIndex(t *testing.T) {
convey.Convey("ExpireDialogIndex", t, func(ctx convey.C) {
var (
c = context.Background()
dialogID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.Redis.ExpireDialogIndex(c, dialogID)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyExpireIndex(t *testing.T) {
convey.Convey("ExpireIndex", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
sort = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.Redis.ExpireIndex(c, oid, tp, sort)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyExpireNewChildIndex(t *testing.T) {
convey.Convey("ExpireNewChildIndex", t, func(ctx convey.C) {
var (
c = context.Background()
root = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.Redis.ExpireNewChildIndex(c, root)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyAddDialogIndex(t *testing.T) {
convey.Convey("AddDialogIndex", t, func(ctx convey.C) {
var (
c = context.Background()
dialogID = int64(0)
rps = []*reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddDialogIndex(c, dialogID, rps)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplySetUserReportCnt(t *testing.T) {
convey.Convey("SetUserReportCnt", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
count = int(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.SetUserReportCnt(c, mid, count, now)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetUserReportCnt(t *testing.T) {
convey.Convey("GetUserReportCnt", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := d.Redis.GetUserReportCnt(c, mid, now)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyGetUserReportTTL(t *testing.T) {
convey.Convey("GetUserReportTTL", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ttl, err := d.Redis.GetUserReportTTL(c, mid, now)
ctx.Convey("Then err should be nil.ttl should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ttl, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyRankIndex(t *testing.T) {
convey.Convey("RankIndex", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rpID = int64(0)
sort = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rank, err := d.Redis.RankIndex(c, oid, tp, rpID, sort)
ctx.Convey("Then err should be nil.rank should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rank, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyExpireUserAct(t *testing.T) {
convey.Convey("ExpireUserAct", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := d.Redis.ExpireUserAct(c, mid)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyAddUserActs(t *testing.T) {
convey.Convey("AddUserActs", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
actions map[int64]int8
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.AddUserActs(c, mid, actions)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyDelUserAct(t *testing.T) {
convey.Convey("DelUserAct", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.DelUserAct(c, mid, rpID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyUserAct(t *testing.T) {
convey.Convey("UserAct", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
act, err := d.Redis.UserAct(c, mid, rpID)
ctx.Convey("Then err should be nil.act should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(act, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyUserActs(t *testing.T) {
convey.Convey("UserActs", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
rpids = []int64{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
acts, err := d.Redis.UserActs(c, mid, rpids)
ctx.Convey("Then err should be nil.acts should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(acts, convey.ShouldNotBeNil)
})
})
})
}
func TestReplySpamReply(t *testing.T) {
convey.Convey("SpamReply", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rec, daily, err := d.Redis.SpamReply(c, mid)
ctx.Convey("Then err should be nil.rec,daily should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(daily, convey.ShouldNotBeNil)
ctx.So(rec, convey.ShouldNotBeNil)
})
})
})
}
func TestReplySpamAction(t *testing.T) {
convey.Convey("SpamAction", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
code, err := d.Redis.SpamAction(c, mid)
ctx.Convey("Then err should be nil.code should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(code, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyNotifyCnt(t *testing.T) {
convey.Convey("NotifyCnt", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
typ = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
cnt, err := d.Redis.NotifyCnt(c, oid, typ)
ctx.Convey("Then err should be nil.cnt should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(cnt, convey.ShouldNotBeNil)
})
})
})
}
func TestReplySetNotifyCnt(t *testing.T) {
convey.Convey("SetNotifyCnt", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
typ = int8(0)
cnt = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.SetNotifyCnt(c, oid, typ, cnt)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestReplyMaxLikeCnt(t *testing.T) {
convey.Convey("MaxLikeCnt", t, func(ctx convey.C) {
var (
c = context.Background()
rpid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
cnt, err := d.Redis.MaxLikeCnt(c, rpid)
ctx.Convey("Then err should be nil.cnt should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(cnt, convey.ShouldNotBeNil)
})
})
})
}
func TestReplySetMaxLikeCnt(t *testing.T) {
convey.Convey("SetMaxLikeCnt", t, func(ctx convey.C) {
var (
c = context.Background()
rpid = int64(0)
cnt = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Redis.SetMaxLikeCnt(c, rpid, cnt)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,544 @@
package reply
import (
"context"
"fmt"
"strings"
"time"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_replySharding int64 = 200
)
const (
_inSQL = "INSERT IGNORE INTO reply_%d (id,oid,type,mid,root,parent,dialog,floor,state,attr,ctime,mtime) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"
_incrCntSQL = "UPDATE reply_%d SET count=count+1,rcount=rcount+1,mtime=? WHERE id=?"
_incrFCntSQL = "UPDATE reply_%d SET count=count+1,mtime=? WHERE id=?"
_incrRCntSQL = "UPDATE reply_%d SET rcount=rcount+1,mtime=? WHERE id=?"
_decrCntSQL = "UPDATE reply_%d SET rcount=rcount-1,mtime=? WHERE id=? AND rcount > 0"
_upStateSQL = "UPDATE reply_%d SET state=?,mtime=? WHERE id=?"
_upAttrSQL = "UPDATE reply_%d SET attr=?,mtime=? WHERE id=?"
_upLikeSQL = "UPDATE reply_%d SET `like`=?,hate=?,mtime=? WHERE id=?"
_selSQLForUpdate = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,attr,ctime,mtime FROM reply_%d WHERE id=? for update"
_selSQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,attr,ctime,mtime FROM reply_%d WHERE id=?"
_selAllSQL = "SELECT id,rcount,`like`,hate,floor,attr FROM reply_%d WHERE oid=? AND type=? AND root=0 AND state in (0,1,2,5,6)"
_selAllByRtSQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,ctime,mtime FROM reply_%d WHERE oid=? AND type=? AND root=? AND state in (0,1,2,5,6)"
_selIncrByDialogSQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,attr,ctime,mtime FROM reply_%d WHERE oid=? AND type=? AND root=? AND state IN (0,1,2,5,6) AND dialog=? and id>? limit 10000"
_selByRootSQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,ctime,mtime FROM reply_%d WHERE oid=? AND type=? AND root=? AND state=?"
_selTopSQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,floor,state,attr,ctime,mtime FROM reply_%d WHERE oid=? AND type=? AND root=0 AND attr&(1<<?) limit 1"
_selAllByFloorSQL = "SELECT id,rcount,`like`,hate,floor,attr FROM reply_%d WHERE oid=? AND type=? AND root=0 AND state in (0,1,2,5,6) and floor>=? and floor<?"
_selByFloorLimitSQL = "SELECT id,rcount,`like`,hate,floor,attr FROM reply_%d WHERE oid=? AND type=? AND root=0 AND state in (0,1,2,5,6) and floor<? order by floor desc limit ?"
_selByLikeLimitSQL = "SELECT id,rcount,`like`,hate,floor,attr FROM reply_%d WHERE oid=? AND type=? AND root=0 AND state in (0,1,2,5,6) order by `like` desc limit ?"
_selByCountLimitSQL = "SELECT id,rcount,`like`,hate,floor,attr FROM reply_%d WHERE oid=? AND type=? AND root=0 AND state in (0,1,2,5,6) order by `rcount` desc limit ?"
_fixDialogSelSQL = "select id, parent, floor from reply_%d where oid=? and type=? and root=? and id>? limit ?"
_fixDialogSetSQL = "update reply_%d set dialog=?, mtime=? where id in (%s)"
)
// RpDao define reply db sqlStmt
type RpDao struct {
// stmt
upStateStmt []*sql.Stmt
upLikeStmt []*sql.Stmt
selStmt []*sql.Stmt
selAllStmt []*sql.Stmt
selAllByRtStmt []*sql.Stmt
selByDialogStmt []*sql.Stmt
selByRootStmt []*sql.Stmt
selTopStmt []*sql.Stmt
fixDialogStmt []*sql.Stmt
mysql *sql.DB
}
// NewReplyDao new replyDao and return.
func NewReplyDao(db *sql.DB) (dao *RpDao) {
dao = &RpDao{
mysql: db,
upStateStmt: make([]*sql.Stmt, _replySharding),
upLikeStmt: make([]*sql.Stmt, _replySharding),
selTopStmt: make([]*sql.Stmt, _repSharding),
selStmt: make([]*sql.Stmt, _replySharding),
selAllStmt: make([]*sql.Stmt, _replySharding),
selByDialogStmt: make([]*sql.Stmt, _replySharding),
selAllByRtStmt: make([]*sql.Stmt, _replySharding),
selByRootStmt: make([]*sql.Stmt, _replySharding),
fixDialogStmt: make([]*sql.Stmt, _replySharding),
}
for i := int64(0); i < _replySharding; i++ {
dao.upStateStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_upStateSQL, i))
dao.upLikeStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_upLikeSQL, i))
dao.selStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selSQL, i))
dao.selAllStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selAllSQL, i))
dao.selTopStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selTopSQL, i))
dao.selByDialogStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selIncrByDialogSQL, i))
dao.selAllByRtStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selAllByRtSQL, i))
dao.selByRootStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selByRootSQL, i))
dao.fixDialogStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_fixDialogSelSQL, i))
}
return
}
func (dao *RpDao) hit(oid int64) int64 {
return oid % _replySharding
}
// TxInsert insert reply by transaction.
func (dao *RpDao) TxInsert(tx *sql.Tx, r *reply.Reply) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_inSQL, dao.hit(r.Oid)), r.RpID, r.Oid, r.Type, r.Mid, r.Root, r.Parent, r.Dialog, r.Floor, r.State, r.Attr, r.CTime, r.MTime)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrCount incr count and rcount of reply by transaction.
func (dao *RpDao) TxIncrCount(tx *sql.Tx, oid, rpID int64, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrCntSQL, dao.hit(oid)), now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrFCount incr rcount of reply by transaction
func (dao *RpDao) TxIncrFCount(tx *sql.Tx, oid, rpID int64, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrFCntSQL, dao.hit(oid)), now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrRCount incr rcount of reply by transaction
func (dao *RpDao) TxIncrRCount(tx *sql.Tx, oid, rpID int64, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrRCntSQL, dao.hit(oid)), now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxDecrCount decr rcount of reply by transaction.
func (dao *RpDao) TxDecrCount(tx *sql.Tx, oid, rpID int64, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrCntSQL, dao.hit(oid)), now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// GetForUpdate decr rcount of reply by transaction.
func (dao *RpDao) GetForUpdate(tx *sql.Tx, oid, rpID int64) (r *reply.Reply, err error) {
r = new(reply.Reply)
row := tx.QueryRow(fmt.Sprintf(_selSQLForUpdate, dao.hit(oid)), rpID)
if err = row.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
if err == sql.ErrNoRows {
r = nil
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// TxUpState update reply state by transaction.
func (dao *RpDao) TxUpState(tx *sql.Tx, oid, rpID int64, state int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upStateSQL, dao.hit(oid)), state, now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// UpState update reply state.
func (dao *RpDao) UpState(c context.Context, oid, rpID int64, state int8, now time.Time) (rows int64, err error) {
res, err := dao.upStateStmt[dao.hit(oid)].Exec(c, state, now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxUpAttr update reply state.
func (dao *RpDao) TxUpAttr(tx *sql.Tx, oid, rpID int64, attr uint32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upAttrSQL, dao.hit(oid)), attr, now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// UpLike incr or decr reply like.
func (dao *RpDao) UpLike(c context.Context, oid, rpID int64, like, hate int, now time.Time) (rows int64, err error) {
res, err := dao.upLikeStmt[dao.hit(oid)].Exec(c, like, hate, now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// Get get reply.
func (dao *RpDao) Get(c context.Context, oid, rpID int64) (r *reply.Reply, err error) {
r = &reply.Reply{}
row := dao.selStmt[dao.hit(oid)].QueryRow(c, rpID)
if err = row.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
if err == sql.ErrNoRows {
r = nil
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// GetTop get top reply
func (dao *RpDao) GetTop(c context.Context, oid int64, tp int8, bit uint32) (r *reply.Reply, err error) {
r = &reply.Reply{}
row := dao.selTopStmt[dao.hit(oid)].QueryRow(c, oid, tp, bit)
if err = row.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
if err == sql.ErrNoRows {
r = nil
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// GetByDialog get all reply by dialog
func (dao *RpDao) GetByDialog(c context.Context, oid int64, typ int8, root int64, dialog int64) (rps []*reply.Reply, err error) {
var minID int64
for {
count := 0
rows, err := dao.selByDialogStmt[dao.hit(oid)].Query(c, oid, typ, root, dialog, minID)
if err != nil {
log.Error("mysql.QueryGetByDialog error(%v)", err)
return nil, err
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
log.Error("row.Scan() error(%v)", err)
return nil, err
}
rps = append(rps, r)
count++
if r.RpID > minID {
minID = r.RpID
}
}
if err = rows.Err(); err != nil {
log.Error("mysql rows.Err() error(%v)", err)
return nil, err
}
// 每次查10000个直到某一次查出来count小于10000
if count < 10000 {
break
}
}
return
}
// GetAllInSlice GetAllInSlice
func (dao *RpDao) GetAllInSlice(c context.Context, oid int64, typ int8, maxFloor int, shard int) (rs []*reply.Reply, err error) {
if shard < 1 {
log.Error("shard(%d) is too small", shard)
return nil, fmt.Errorf("shard(%d) is too small", shard)
}
start := 1
startTs := time.Now()
for {
if start > maxFloor {
nowTs := time.Now()
if nowTs.Sub(startTs) > time.Second*3 {
log.Warn("GetAllInSlice (%d,%d,%d,%d) running over 3 secs,total time is:%v", oid, typ, maxFloor, shard, nowTs.Sub(startTs))
}
return
}
end := start + shard
if end > maxFloor {
end = maxFloor + 1
}
var result []*reply.Reply
result, err = dao.GetAllByFloor(c, oid, typ, start, end)
if err != nil {
//try again
result, err = dao.GetAllByFloor(c, oid, typ, start, end)
if err != nil {
return
}
}
rs = append(rs, result...)
start += shard
}
}
// GetByFloorLimit GetByFloorLimit
func (dao *RpDao) GetByFloorLimit(ctx context.Context, oid int64, typ int8, floor int, limit int) (rs []*reply.Reply, err error) {
lastFloor := floor
for lastFloor > 1 && limit > 0 {
count := _maxCount
if limit <= _maxCount {
count = limit
}
limit -= _maxCount
var temp []*reply.Reply
temp, err = dao.getByFloorLimit(ctx, oid, typ, lastFloor, count)
if err != nil {
return
}
if len(temp) > 0 {
lastFloor = temp[len(temp)-1].Floor
rs = append(rs, temp...)
}
if len(temp) < count {
break
}
}
return
}
func (dao *RpDao) getByFloorLimit(ctx context.Context, oid int64, typ int8, floor int, limit int) (rs []*reply.Reply, err error) {
rows, err := dao.mysql.Query(ctx, fmt.Sprintf(_selByFloorLimitSQL, dao.hit(oid)), oid, typ, floor, limit)
if err != nil {
log.Error("mysql.Query %s error(%v)", _selByFloorLimitSQL, err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.Attr); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetByLikeLimit GetByLikeLimit
func (dao *RpDao) GetByLikeLimit(ctx context.Context, oid int64, typ int8, limit int) (rs []*reply.Reply, err error) {
rows, err := dao.mysql.Query(ctx, fmt.Sprintf(_selByLikeLimitSQL, dao.hit(oid)), oid, typ, limit)
if err != nil {
log.Error("mysql.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.Attr); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetByCountLimit GetByCountLimit
func (dao *RpDao) GetByCountLimit(ctx context.Context, oid int64, typ int8, limit int) (rs []*reply.Reply, err error) {
rows, err := dao.mysql.Query(ctx, fmt.Sprintf(_selByCountLimitSQL, dao.hit(oid)), oid, typ, limit)
if err != nil {
log.Error("mysql.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.Attr); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetAllByFloor GetAllByFloor
func (dao *RpDao) GetAllByFloor(ctx context.Context, oid int64, typ int8, start int, end int) (rs []*reply.Reply, err error) {
rows, err := dao.mysql.Query(ctx, fmt.Sprintf(_selAllByFloorSQL, dao.hit(oid)), oid, typ, start, end)
if err != nil {
log.Error("mysql.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.Attr); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetAll get all replies.
func (dao *RpDao) GetAll(c context.Context, oid int64, tp int8) (rs []*reply.Reply, err error) {
rows, err := dao.selAllStmt[dao.hit(oid)].Query(c, oid, tp)
if err != nil {
log.Error("mysql.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.Attr); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetAllByRoot get all replies by root reply.
func (dao *RpDao) GetAllByRoot(c context.Context, oid, rpID int64, tp int8) (rs []*reply.Reply, err error) {
rows, err := dao.selAllByRtStmt[dao.hit(oid)].Query(c, oid, tp, rpID)
if err != nil {
log.Error("mysql.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.CTime, &r.MTime); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetsByRoot get all replies by root reply.
func (dao *RpDao) GetsByRoot(c context.Context, oid, rpID int64, tp, state int8) (rs []*reply.Reply, err error) {
rows, err := dao.selByRootStmt[dao.hit(oid)].Query(c, oid, tp, rpID, state)
if err != nil {
log.Error("mysql.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := &reply.Reply{}
if err = rows.Scan(&r.RpID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.CTime, &r.MTime); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// FixDialogGetRepliesByRoot ...
func (dao *RpDao) FixDialogGetRepliesByRoot(c context.Context, oid int64, tp int8, rootID int64) (rps []*reply.RpItem, err error) {
var (
minID int64
)
for {
count := 0
rows, err := dao.fixDialogStmt[dao.hit(oid)].Query(c, oid, tp, rootID, minID, 10000)
if err == sql.ErrNoRows {
err = nil
break
} else if err != nil {
log.Error("stmt.Query() error(%v)", err)
return nil, err
}
defer rows.Close()
for rows.Next() {
rp := &reply.RpItem{}
if err = rows.Scan(&rp.ID, &rp.Parent, &rp.Floor); err != nil {
log.Error("rows.Scan() error(%v)", err)
return nil, err
}
rps = append(rps, rp)
count++
if rp.ID > minID {
minID = rp.ID
}
}
if err = rows.Err(); err != nil {
log.Error("rows.Err() error(%v)", err)
return nil, err
}
if count < 10000 {
break
}
}
return
}
// FixDialogSetDialogBatch ...
func (dao *RpDao) FixDialogSetDialogBatch(c context.Context, oid, dialog int64, rpIDs []int64) {
length := len(rpIDs)
if length == 0 {
return
}
ids := strings.Trim(strings.Join(strings.Split(fmt.Sprint(rpIDs), " "), ","), "[]")
setQuery := fmt.Sprintf(_fixDialogSetSQL, dao.hit(oid), ids)
res, err := dao.mysql.Exec(c, setQuery, dialog, time.Now())
if err != nil {
log.Error("db.exec(query: %s, dialog: %d, oid: %d, rpids:%v, error(%v))", setQuery, dialog, oid, rpIDs, err)
return
}
rows, err := res.RowsAffected()
if rows != int64(length) || err != nil {
log.Error("s.dao.SetDialogBatch RowsAffected(%d) actual length(%d) error(%v)", rows, length, err)
return
}
}

View File

@@ -0,0 +1,448 @@
package reply
import (
"context"
"go-common/app/job/main/reply/model/reply"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyTxInsert(t *testing.T) {
convey.Convey("TxInsert", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
r = &reply.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxInsert(tx, r)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrCount2(t *testing.T) {
convey.Convey("TxIncrCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxIncrCount(tx, oid, rpID, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrFCount2(t *testing.T) {
convey.Convey("TxIncrFCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxIncrFCount(tx, oid, rpID, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrRCount2(t *testing.T) {
convey.Convey("TxIncrRCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxIncrRCount(tx, oid, rpID, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxDecrCount2(t *testing.T) {
convey.Convey("TxDecrCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxDecrCount(tx, oid, rpID, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyGetForUpdate(t *testing.T) {
convey.Convey("GetForUpdate", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
r, err := d.Reply.GetForUpdate(tx, oid, rpID)
ctx.Convey("Then err should be nil.r should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(r, convey.ShouldBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxUpState(t *testing.T) {
convey.Convey("TxUpState", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
state = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxUpState(tx, oid, rpID, state, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyUpState(t *testing.T) {
convey.Convey("UpState", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpID = int64(0)
state = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.UpState(c, oid, rpID, state, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyTxUpAttr2(t *testing.T) {
convey.Convey("TxUpAttr", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
rpID = int64(0)
attr = uint32(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.TxUpAttr(tx, oid, rpID, attr, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyUpLike(t *testing.T) {
convey.Convey("UpLike", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpID = int64(0)
like = int(0)
hate = int(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Reply.UpLike(c, oid, rpID, like, hate, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyGet3(t *testing.T) {
convey.Convey("Get", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
r, err := d.Reply.Get(c, oid, rpID)
ctx.Convey("Then err should be nil.r should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(r, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetTop2(t *testing.T) {
convey.Convey("GetTop", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
bit = uint32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
r, err := d.Reply.GetTop(c, oid, tp, bit)
ctx.Convey("Then err should be nil.r should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(r, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetByDialog(t *testing.T) {
convey.Convey("GetByDialog", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
typ = int8(0)
root = int64(0)
dialog = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rps, err := d.Reply.GetByDialog(c, oid, typ, root, dialog)
ctx.Convey("Then err should be nil.rps should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rps, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetAllInSlice(t *testing.T) {
convey.Convey("GetAllInSlice", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
typ = int8(0)
maxFloor = int(0)
shard = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetAllInSlice(c, oid, typ, maxFloor, shard)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetByFloorLimit(t *testing.T) {
convey.Convey("GetByFloorLimit", t, func(ctx convey.C) {
var (
oid = int64(0)
typ = int8(0)
floor = int(0)
limit = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetByFloorLimit(context.Background(), oid, typ, floor, limit)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplygetByFloorLimit(t *testing.T) {
convey.Convey("getByFloorLimit", t, func(ctx convey.C) {
var (
oid = int64(0)
typ = int8(0)
floor = int(0)
limit = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.getByFloorLimit(context.Background(), oid, typ, floor, limit)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetByLikeLimit(t *testing.T) {
convey.Convey("GetByLikeLimit", t, func(ctx convey.C) {
var (
oid = int64(0)
typ = int8(0)
limit = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetByLikeLimit(context.Background(), oid, typ, limit)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetByCountLimit(t *testing.T) {
convey.Convey("GetByCountLimit", t, func(ctx convey.C) {
var (
oid = int64(0)
typ = int8(0)
limit = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetByCountLimit(context.Background(), oid, typ, limit)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetAllByFloor(t *testing.T) {
convey.Convey("GetAllByFloor", t, func(ctx convey.C) {
var (
oid = int64(0)
typ = int8(0)
start = int(0)
end = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetAllByFloor(context.Background(), oid, typ, start, end)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetAll(t *testing.T) {
convey.Convey("GetAll", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetAll(context.Background(), oid, tp)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetAllByRoot(t *testing.T) {
convey.Convey("GetAllByRoot", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpID = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetAllByRoot(c, oid, rpID, tp)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyGetsByRoot(t *testing.T) {
convey.Convey("GetsByRoot", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpID = int64(0)
tp = int8(0)
state = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rs, err := d.Reply.GetsByRoot(c, oid, rpID, tp, state)
ctx.Convey("Then err should be nil.rs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rs, convey.ShouldBeNil)
})
})
})
}
func TestReplyFixDialogGetRepliesByRoot(t *testing.T) {
convey.Convey("FixDialogGetRepliesByRoot", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rootID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rps, err := d.Reply.FixDialogGetRepliesByRoot(c, oid, tp, rootID)
ctx.Convey("Then err should be nil.rps should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rps, convey.ShouldBeNil)
})
})
})
}
func TestReplyFixDialogSetDialogBatch(t *testing.T) {
convey.Convey("FixDialogSetDialogBatch", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
dialog = int64(0)
rpIDs = []int64{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
d.Reply.FixDialogSetDialogBatch(c, oid, dialog, rpIDs)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}

View File

@@ -0,0 +1,139 @@
package reply
import (
"context"
"fmt"
"time"
model "go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_repSharding int64 = 200
)
const ( // report
_upRepSQL = "UPDATE reply_report_%d SET state=?,mtime=?,reason=?,content=?,attr=? WHERE rpid=?"
_selRepSQL = "SELECT oid,type,rpid,mid,reason,content,count,score,state,ctime,mtime,attr FROM reply_report_%d WHERE rpid=?"
_selRepByOidSQL = "SELECT oid,type,rpid,mid,reason,content,count,score,state,ctime,mtime,attr FROM reply_report_%d WHERE oid=? and type=?"
// report user
_getRptUsersSQL = "SELECT oid,type,rpid,mid,reason,content,state,ctime,mtime FROM reply_report_user_%d WHERE rpid=? and state=?"
_setRptUserStateSQL = "UPDATE reply_report_user_%d SET state=?,mtime=? WHERE rpid=?"
)
//ReportDao define report mysql stmt
type ReportDao struct {
upRepStmts []*sql.Stmt
selRepStmts []*sql.Stmt
getUsersStmts []*sql.Stmt
setUserStateStmts []*sql.Stmt
mysql *sql.DB
}
// NewReportDao new ReplyReportDao and return.
func NewReportDao(db *sql.DB) (dao *ReportDao) {
dao = &ReportDao{
mysql: db,
upRepStmts: make([]*sql.Stmt, _repSharding),
selRepStmts: make([]*sql.Stmt, _repSharding),
getUsersStmts: make([]*sql.Stmt, _repSharding),
setUserStateStmts: make([]*sql.Stmt, _repSharding),
}
for i := int64(0); i < _repSharding; i++ {
dao.upRepStmts[i] = dao.mysql.Prepared(fmt.Sprintf(_upRepSQL, i))
dao.selRepStmts[i] = dao.mysql.Prepared(fmt.Sprintf(_selRepSQL, i))
dao.getUsersStmts[i] = dao.mysql.Prepared(fmt.Sprintf(_getRptUsersSQL, i))
dao.setUserStateStmts[i] = dao.mysql.Prepared(fmt.Sprintf(_setRptUserStateSQL, i))
}
return
}
func (dao *ReportDao) hit(oid int64) int64 {
return oid % _repSharding
}
// Update update reply report.
func (dao *ReportDao) Update(c context.Context, rpt *model.Report) (rows int64, err error) {
res, err := dao.upRepStmts[dao.hit(rpt.Oid)].Exec(c, rpt.State, rpt.MTime, rpt.Reason, rpt.Content, rpt.Attr, rpt.RpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// Get get a reply report.
func (dao *ReportDao) Get(c context.Context, oid, rpID int64) (rpt *model.Report, err error) {
row := dao.selRepStmts[dao.hit(oid)].QueryRow(c, rpID)
rpt = &model.Report{}
err = row.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.Count, &rpt.Score, &rpt.State, &rpt.CTime, &rpt.MTime, &rpt.Attr)
if err != nil {
if err == sql.ErrNoRows {
rpt = nil
err = nil
} else {
log.Error("Mysql error(%v)", err)
}
}
return
}
// GetMapByOid return report map by oid.
func (dao *ReportDao) GetMapByOid(c context.Context, oid int64, typ int8) (res map[int64]*model.Report, err error) {
rows, err := dao.mysql.Query(c, fmt.Sprintf(_selRepByOidSQL, dao.hit(oid)), oid, typ)
if err != nil {
log.Error("db.Query(%s) error(%v)", _selRepByOidSQL, err)
return
}
defer rows.Close()
res = make(map[int64]*model.Report)
for rows.Next() {
rpt := &model.Report{}
if err = rows.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.Count, &rpt.Score, &rpt.State, &rpt.CTime, &rpt.MTime, &rpt.Attr); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
res[rpt.RpID] = rpt
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// GetUsers return a report users from mysql.
func (dao *ReportDao) GetUsers(c context.Context, oid int64, tp int8, rpID int64) (res map[int64]*model.ReportUser, err error) {
rows, err := dao.getUsersStmts[dao.hit(oid)].Query(c, rpID, model.ReportUserStateNew)
if err != nil {
log.Error("db.Query error(%v)", err)
return
}
defer rows.Close()
res = make(map[int64]*model.ReportUser)
for rows.Next() {
rpt := &model.ReportUser{}
if err = rows.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.State, &rpt.CTime, &rpt.MTime); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
res[rpt.Mid] = rpt
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// SetUserReported set a user report state by rpID.
func (dao *ReportDao) SetUserReported(c context.Context, oid int64, tp int8, rpID int64, now time.Time) (rows int64, err error) {
res, err := dao.setUserStateStmts[dao.hit(oid)].Exec(c, model.ReportUserStateReported, now, rpID)
if err != nil {
log.Error("db.Exec error(%v)", err)
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,97 @@
package reply
import (
"context"
model "go-common/app/job/main/reply/model/reply"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyUpdate(t *testing.T) {
convey.Convey("Update", t, func(ctx convey.C) {
var (
c = context.Background()
rpt = &model.Report{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Report.Update(c, rpt)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyGet(t *testing.T) {
convey.Convey("Get", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rpt, err := d.Report.Get(c, oid, rpID)
ctx.Convey("Then err should be nil.rpt should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rpt, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyGetMapByOid(t *testing.T) {
convey.Convey("GetMapByOid", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
typ = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.Report.GetMapByOid(c, oid, typ)
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 TestReplyGetUsers(t *testing.T) {
convey.Convey("GetUsers", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
res, err := d.Report.GetUsers(c, oid, tp, rpID)
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 TestReplySetUserReported(t *testing.T) {
convey.Convey("SetUserReported", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
rpID = int64(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Report.SetUserReported(c, oid, tp, rpID, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,187 @@
package reply
import (
"context"
"fmt"
"time"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_subSharding int64 = 50
)
const (
_incrSubCntSQL = "UPDATE reply_subject_%d SET count=count+1,rcount=rcount+1,acount=acount+1,mtime=? WHERE oid=? AND type=?"
_incrSubFCntSQL = "UPDATE reply_subject_%d SET count=count+1,mtime=? WHERE oid=? AND type=?"
_incrSubRCntSQL = "UPDATE reply_subject_%d SET rcount=rcount+1,mtime=? WHERE oid=? AND type=?"
_incrSubACntSQL = "UPDATE reply_subject_%d SET acount=acount+?,mtime=? WHERE oid=? AND type=?"
_incrSubMCntSQL = "UPDATE reply_subject_%d SET mcount=mcount+1,mtime=? WHERE oid=? AND type=?"
_decrSubMCntSQL = "UPDATE reply_subject_%d SET mcount=mcount-1,mtime=? WHERE oid=? AND type=? AND mcount>0"
_decrSubCntSQL = "UPDATE reply_subject_%d SET rcount=rcount-1,mtime=? WHERE oid=? AND type=?"
_upSubAttrSQL = "UPDATE reply_subject_%d SET attr=?,mtime=? WHERE oid=? AND type=?"
_decrSubACntSQL = "UPDATE reply_subject_%d SET acount=acount-?,mtime=? WHERE oid=? AND type=?"
_upSubMetaSQL = "UPDATE reply_subject_%d SET meta=?,mtime=? WHERE oid=? AND type=?"
_selSubjectSQL = "SELECT oid,type,mid,count,rcount,acount,state,attr,ctime,mtime,meta FROM reply_subject_%d WHERE oid=? AND type=?"
_selSubjectForUpdateSQL = "SELECT oid,type,mid,count,rcount,acount,state,attr,ctime,mtime,meta FROM reply_subject_%d WHERE oid=? AND type=? FOR UPDATE"
)
// SubjectDao define subject mysql stmt
type SubjectDao struct {
selSubjectStmt []*sql.Stmt
mysql *sql.DB
}
// NewSubjectDao new ReplySubjectDao and return.
func NewSubjectDao(db *sql.DB) (dao *SubjectDao) {
dao = &SubjectDao{
mysql: db,
selSubjectStmt: make([]*sql.Stmt, _subSharding),
}
for i := int64(0); i < _subSharding; i++ {
dao.selSubjectStmt[i] = dao.mysql.Prepared(fmt.Sprintf(_selSubjectSQL, i))
}
return
}
func (dao *SubjectDao) hit(oid int64) int64 {
return oid % _subSharding
}
// UpMeta update subject meta.
func (dao *SubjectDao) UpMeta(c context.Context, oid int64, tp int8, meta string, now time.Time) (rows int64, err error) {
res, err := dao.mysql.Exec(c, fmt.Sprintf(_upSubMetaSQL, dao.hit(oid)), meta, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxUpMeta update subject meta.
func (dao *SubjectDao) TxUpMeta(tx *sql.Tx, oid int64, tp int8, meta string, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upSubMetaSQL, dao.hit(oid)), meta, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxUpAttr update subject attr.
func (dao *SubjectDao) TxUpAttr(tx *sql.Tx, oid int64, tp int8, attr uint32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upSubAttrSQL, dao.hit(oid)), attr, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrCount incr subject count and rcount by transaction.
func (dao *SubjectDao) TxIncrCount(tx *sql.Tx, oid int64, tp int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubCntSQL, dao.hit(oid)), now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrFCount incr subject count and rcount by transaction.
func (dao *SubjectDao) TxIncrFCount(tx *sql.Tx, oid int64, tp int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubFCntSQL, dao.hit(oid)), now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrMCount incr subject mcount by transaction.
func (dao *SubjectDao) TxIncrMCount(tx *sql.Tx, oid int64, tp int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubMCntSQL, dao.hit(oid)), now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// TxDecrMCount decr subject mcount by transaction.
func (dao *SubjectDao) TxDecrMCount(tx *sql.Tx, oid int64, tp int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrSubMCntSQL, dao.hit(oid)), now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrRCount incr subject rcount by transaction
func (dao *SubjectDao) TxIncrRCount(tx *sql.Tx, oid int64, tp int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubRCntSQL, dao.hit(oid)), now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxDecrCount decr subject count by transaction.
func (dao *SubjectDao) TxDecrCount(tx *sql.Tx, oid int64, tp int8, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrSubCntSQL, dao.hit(oid)), now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// TxIncrACount incr subject acount by transaction.
func (dao *SubjectDao) TxIncrACount(tx *sql.Tx, oid int64, tp int8, count int, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubACntSQL, dao.hit(oid)), count, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// TxDecrACount decr subject rcount by transaction.
func (dao *SubjectDao) TxDecrACount(tx *sql.Tx, oid int64, tp int8, count int, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrSubACntSQL, dao.hit(oid)), count, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec() error(%v)", err)
return
}
return res.RowsAffected()
}
// Get get a subject.
func (dao *SubjectDao) Get(c context.Context, oid int64, tp int8) (sub *reply.Subject, err error) {
sub = &reply.Subject{}
row := dao.selSubjectStmt[dao.hit(oid)].QueryRow(c, oid, tp)
if err = row.Scan(&sub.Oid, &sub.Type, &sub.Mid, &sub.Count, &sub.RCount, &sub.ACount, &sub.State, &sub.Attr, &sub.CTime, &sub.MTime, &sub.Meta); err != nil {
if err == sql.ErrNoRows {
sub = nil
err = nil
} else {
log.Error("row.Scan error(%v)", err)
}
}
return
}
// GetForUpdate get a subject for update.
func (dao *SubjectDao) GetForUpdate(tx *sql.Tx, oid int64, tp int8) (sub *reply.Subject, err error) {
sub = &reply.Subject{}
if err = tx.QueryRow(fmt.Sprintf(_selSubjectForUpdateSQL, dao.hit(oid)), oid, tp).Scan(&sub.Oid, &sub.Type, &sub.Mid, &sub.Count, &sub.RCount, &sub.ACount, &sub.State, &sub.Attr, &sub.CTime, &sub.MTime, &sub.Meta); err != nil {
return
}
return
}

View File

@@ -0,0 +1,239 @@
package reply
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestReplyUpMeta(t *testing.T) {
convey.Convey("UpMeta", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
meta = ""
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.UpMeta(c, oid, tp, meta, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
})
}
func TestReplyTxUpMeta(t *testing.T) {
convey.Convey("TxUpMeta", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
meta = ""
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxUpMeta(tx, oid, tp, meta, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxUpAttr(t *testing.T) {
convey.Convey("TxUpAttr", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
attr = uint32(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxUpAttr(tx, oid, tp, attr, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrCount(t *testing.T) {
convey.Convey("TxIncrCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxIncrCount(tx, oid, tp, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrFCount(t *testing.T) {
convey.Convey("TxIncrFCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxIncrFCount(tx, oid, tp, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrMCount(t *testing.T) {
convey.Convey("TxIncrMCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxIncrMCount(tx, oid, tp, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxDecrMCount(t *testing.T) {
convey.Convey("TxDecrMCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxDecrMCount(tx, oid, tp, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrRCount(t *testing.T) {
convey.Convey("TxIncrRCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxIncrRCount(tx, oid, tp, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxDecrCount(t *testing.T) {
convey.Convey("TxDecrCount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxDecrCount(tx, oid, tp, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxIncrACount(t *testing.T) {
convey.Convey("TxIncrACount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
count = int(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxIncrACount(tx, oid, tp, count, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyTxDecrACount(t *testing.T) {
convey.Convey("TxDecrACount", t, func(ctx convey.C) {
var (
tx, _ = d.mysql.Begin(context.Background())
oid = int64(0)
tp = int8(0)
count = int(0)
now = time.Now()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rows, err := d.Subject.TxDecrACount(tx, oid, tp, count, now)
ctx.Convey("Then err should be nil.rows should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rows, convey.ShouldNotBeNil)
})
})
tx.Rollback()
})
}
func TestReplyGet2(t *testing.T) {
convey.Convey("Get", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
sub, err := d.Subject.Get(c, oid, tp)
ctx.Convey("Then err should be nil.sub should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(sub, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["search_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["search.go"],
importpath = "go-common/app/job/main/reply/dao/search",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,72 @@
package search
import (
"context"
"encoding/json"
"fmt"
"net/url"
"go-common/app/job/main/reply/conf"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
const (
_appIDRecord = "reply_record"
_updateURL = "/x/internal/search/reply/update"
)
// Dao Dao
type Dao struct {
searchHTTPClient *bm.Client
searchUpdateURI string
}
// New New
func New(c *conf.Config) *Dao {
return &Dao{
searchHTTPClient: bm.NewClient(c.HTTPClient),
searchUpdateURI: c.Host.API + _updateURL,
}
}
// DelReply DelReply
func (dao *Dao) DelReply(c context.Context, rpid, oid, mid int64, state int8) (err error) {
return dao.update(c, rpid, oid, mid, state)
}
func (dao *Dao) update(c context.Context, rpid, oid, mid int64, state int8) (err error) {
type updateRecord struct {
ID int64 `json:"id"`
Oid int64 `json:"oid"`
Mid int64 `json:"mid"`
State int8 `json:"state"`
}
var (
res struct {
Code int `json:"code"`
Msg string `json:"message"`
}
)
records := make([]*updateRecord, 0)
record := &updateRecord{}
record.ID = rpid
record.Oid = oid
record.Mid = mid
record.State = state
records = append(records, record)
recordsStr, _ := json.Marshal(records)
params := url.Values{}
params.Set("appid", _appIDRecord)
params.Set("data", string(recordsStr))
if err = dao.searchHTTPClient.Post(c, dao.searchUpdateURI, "", params, &res); err != nil {
log.Error("bm.Post(%s) failed error(%v)", dao.searchUpdateURI+"?"+params.Encode(), err)
return
}
if res.Code != ecode.OK.Code() {
err = fmt.Errorf("update reply es records failed")
}
log.Info("updateSearch: %s post:%s ret:%v", dao.searchUpdateURI, params.Encode(), res)
return
}

View File

@@ -0,0 +1,77 @@
package search
import (
"context"
"flag"
"go-common/app/job/main/reply/conf"
"os"
"testing"
"github.com/smartystreets/goconvey/convey"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-job")
flag.Set("conf_token", "5deea0665f8a7670b22a719337a39c7d")
flag.Set("tree_id", "2123")
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/reply-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
func TestSearchDelReply(t *testing.T) {
convey.Convey("DelReply", t, func(ctx convey.C) {
var (
c = context.Background()
rpid = int64(0)
oid = int64(0)
mid = int64(0)
state = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.DelReply(c, rpid, oid, mid, state)
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
})
})
}
func TestSearchupdate(t *testing.T) {
convey.Convey("update", t, func(ctx convey.C) {
var (
c = context.Background()
rpid = int64(0)
oid = int64(0)
mid = int64(0)
state = int8(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.update(c, rpid, oid, mid, state)
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
} else {
ctx.So(err, convey.ShouldBeNil)
}
})
})
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["redis_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["redis.go"],
importpath = "go-common/app/job/main/reply/dao/spam",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/redis:go_default_library",
"//library/log:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,166 @@
package spam
import (
"context"
"strconv"
"go-common/library/cache/redis"
"go-common/library/log"
)
// Cache Cache
type Cache struct {
redisPool *redis.Pool
expireRp int
expireAct int
}
// NewCache NewCache
func NewCache(c *redis.Config) *Cache {
return &Cache{
redisPool: redis.NewPool(c),
expireRp: 60, // 60s
expireAct: 20, // 20s
}
}
func (c *Cache) keyRcntCnt(mid int64) string {
return "rc_" + strconv.FormatInt(mid, 10)
}
func (c *Cache) keyUpRcntCnt(mid int64) string {
return "urc_" + strconv.FormatInt(mid, 10)
}
func (c *Cache) keyDailyCnt(mid int64) string {
return "rd_" + strconv.FormatInt(mid, 10)
}
func (c *Cache) keyActRec(mid int64) string {
return "ra_" + strconv.FormatInt(mid, 10)
}
func (c *Cache) keySpamRpRec(mid int64) string {
return "sr_" + strconv.FormatInt(mid, 10)
}
func (c *Cache) keySpamRpDaily(mid int64) string {
return "sd_" + strconv.FormatInt(mid, 10)
}
func (c *Cache) keySpamActRec(mid int64) string {
return "sa_" + strconv.FormatInt(mid, 10)
}
// IncrReply incr user reply count.
func (c *Cache) IncrReply(ctx context.Context, mid int64, isUp bool) (count int, err error) {
key := c.keyRcntCnt(mid)
if isUp {
key = c.keyUpRcntCnt(mid)
}
conn := c.redisPool.Get(ctx)
defer conn.Close()
conn.Send("INCR", key)
conn.Send("EXPIRE", key, c.expireRp)
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if count, err = redis.Int(conn.Receive()); err != nil {
log.Error("conn.Receive error(%v)", key, err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive error(%v)", key, err)
return
}
return
}
// IncrAct incr user action count.
func (c *Cache) IncrAct(ctx context.Context, mid int64) (count int, err error) {
key := c.keyActRec(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
conn.Send("INCR", key)
conn.Send("EXPIRE", key, c.expireAct)
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if count, err = redis.Int(conn.Receive()); err != nil {
log.Error("conn.Receive error(%v)", key, err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive error(%v)", key, err)
return
}
return
}
// IncrDailyReply IncrDailyReply
func (c *Cache) IncrDailyReply(ctx context.Context, mid int64) (count int, err error) {
key := c.keyDailyCnt(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
if count, err = redis.Int(conn.Do("INCR", key)); err != nil {
log.Error("conn.Do(INCRBY, %s), error(%v)", key, err)
}
return
}
// TTLDailyReply TTLDailyReply
func (c *Cache) TTLDailyReply(ctx context.Context, mid int64) (ttl int, err error) {
key := c.keyDailyCnt(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
if ttl, err = redis.Int(conn.Do("TTL", key)); err != nil {
log.Error("conn.Do(TTL, %s), error(%v)", key, err)
}
return
}
// ExpireDailyReply ExpireDailyReply
func (c *Cache) ExpireDailyReply(ctx context.Context, mid int64, exp int) (err error) {
key := c.keyDailyCnt(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
if _, err = conn.Do("EXPIRE", key, exp); err != nil {
log.Error("conn.Do(EXPIRE, %s), error(%v)", key, err)
}
return
}
// SetReplyRecSpam SetReplyRecSpam
func (c *Cache) SetReplyRecSpam(ctx context.Context, mid int64, code, exp int) (err error) {
key := c.keySpamRpRec(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
if _, err = conn.Do("SETEX", key, exp, code); err != nil {
log.Error("conn.Do error(%v)", err)
}
return
}
// SetReplyDailySpam SetReplyDailySpam
func (c *Cache) SetReplyDailySpam(ctx context.Context, mid int64, code, exp int) (err error) {
key := c.keySpamRpDaily(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
if _, err = conn.Do("SETEX", key, exp, code); err != nil {
log.Error("conn.Do error(%v)", err)
}
return
}
// SetActionRecSpam SetActionRecSpam
func (c *Cache) SetActionRecSpam(ctx context.Context, mid int64, code, exp int) (err error) {
key := c.keySpamActRec(mid)
conn := c.redisPool.Get(ctx)
defer conn.Close()
if _, err = conn.Do("SETEX", key, exp, code); err != nil {
log.Error("conn.Do error(%v)", err)
}
return
}

View File

@@ -0,0 +1,259 @@
package spam
import (
"context"
"flag"
"go-common/app/job/main/reply/conf"
"os"
"testing"
"github.com/smartystreets/goconvey/convey"
)
var (
d *Cache
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-job")
flag.Set("conf_token", "5deea0665f8a7670b22a719337a39c7d")
flag.Set("tree_id", "2123")
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/reply-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = NewCache(conf.Conf.Redis.Config)
os.Exit(m.Run())
}
func TestSpamkeyRcntCnt(t *testing.T) {
convey.Convey("keyRcntCnt", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keyRcntCnt(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamkeyUpRcntCnt(t *testing.T) {
convey.Convey("keyUpRcntCnt", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keyUpRcntCnt(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamkeyDailyCnt(t *testing.T) {
convey.Convey("keyDailyCnt", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keyDailyCnt(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamkeyActRec(t *testing.T) {
convey.Convey("keyActRec", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keyActRec(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamkeySpamRpRec(t *testing.T) {
convey.Convey("keySpamRpRec", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keySpamRpRec(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamkeySpamRpDaily(t *testing.T) {
convey.Convey("keySpamRpDaily", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keySpamRpDaily(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamkeySpamActRec(t *testing.T) {
convey.Convey("keySpamActRec", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.keySpamActRec(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamIncrReply(t *testing.T) {
convey.Convey("IncrReply", t, func(ctx convey.C) {
var (
mid = int64(0)
isUp bool
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := d.IncrReply(context.Background(), mid, isUp)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamIncrAct(t *testing.T) {
convey.Convey("IncrAct", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := d.IncrAct(context.Background(), mid)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamIncrDailyReply(t *testing.T) {
convey.Convey("IncrDailyReply", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := d.IncrDailyReply(context.Background(), mid)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamTTLDailyReply(t *testing.T) {
convey.Convey("TTLDailyReply", t, func(ctx convey.C) {
var (
mid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ttl, err := d.TTLDailyReply(context.Background(), mid)
ctx.Convey("Then err should be nil.ttl should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ttl, convey.ShouldNotBeNil)
})
})
})
}
func TestSpamExpireDailyReply(t *testing.T) {
convey.Convey("ExpireDailyReply", t, func(ctx convey.C) {
var (
mid = int64(0)
exp = int(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.ExpireDailyReply(context.Background(), mid, exp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestSpamSetReplyRecSpam(t *testing.T) {
convey.Convey("SetReplyRecSpam", t, func(ctx convey.C) {
var (
mid = int64(0)
code = int(0)
exp = int(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.SetReplyRecSpam(context.Background(), mid, code, exp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestSpamSetReplyDailySpam(t *testing.T) {
convey.Convey("SetReplyDailySpam", t, func(ctx convey.C) {
var (
mid = int64(0)
code = int(0)
exp = int(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.SetReplyDailySpam(context.Background(), mid, code, exp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestSpamSetActionRecSpam(t *testing.T) {
convey.Convey("SetActionRecSpam", t, func(ctx convey.C) {
var (
mid = int64(0)
code = int(0)
exp = int(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.SetActionRecSpam(context.Background(), mid, code, exp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["databus_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["databus.go"],
importpath = "go-common/app/job/main/reply/dao/stat",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,54 @@
package stat
import (
"context"
"strconv"
"time"
"go-common/app/job/main/reply/conf"
"go-common/library/log"
"go-common/library/queue/databus"
)
type statMsg struct {
ID int64 `json:"id"`
Timestamp int64 `json:"timestamp"`
Count int `json:"count"`
Type string `json:"type"`
}
// Dao stat dao.
type Dao struct {
// new databus stats
types map[int8]string
databus *databus.Databus
}
// New new a stat dao and return.
func New(c *conf.Config) *Dao {
d := new(Dao)
// new databus stats
d.types = make(map[int8]string)
for name, typ := range c.StatTypes {
d.types[typ] = name
}
d.databus = databus.New(c.Databus.Stats)
return d
}
// Send update stat.
func (d *Dao) Send(c context.Context, typ int8, oid int64, cnt int) (err error) {
// new databus stats
if name, ok := d.types[typ]; ok {
m := &statMsg{
ID: oid,
Type: name,
Count: cnt,
Timestamp: time.Now().Unix(),
}
if err = d.databus.Send(c, strconv.FormatInt(oid, 10), m); err != nil {
log.Error("d.databus.Send(%d,%d,%d) error(%v)", typ, oid, cnt, err)
}
}
return
}

View File

@@ -0,0 +1,54 @@
package stat
import (
"context"
"flag"
"go-common/app/job/main/reply/conf"
"os"
"testing"
"github.com/smartystreets/goconvey/convey"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-job")
flag.Set("conf_token", "5deea0665f8a7670b22a719337a39c7d")
flag.Set("tree_id", "2123")
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/reply-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
func TestStatSend(t *testing.T) {
convey.Convey("Send", t, func(ctx convey.C) {
var (
c = context.Background()
typ = int8(0)
oid = int64(0)
cnt = int(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.Send(c, typ, oid, cnt)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/main/reply/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//app/job/main/reply/service:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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 http
import (
"net/http"
"go-common/app/job/main/reply/conf"
"go-common/app/job/main/reply/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var rpSvc *service.Service
// Init init http
func Init(c *conf.Config, svc *service.Service) {
rpSvc = svc
engine := bm.DefaultServer(c.BM)
outerRouter(engine)
if err := engine.Start(); err != nil {
log.Error("bm.DefaultServer error(%v)", err)
panic(err)
}
}
// outerRouter init outer router api path.
func outerRouter(e *bm.Engine) {
e.GET("/monitor/ping", ping)
}
// ping check server ok.
func ping(c *bm.Context) {
if err := rpSvc.Ping(c); err != nil {
log.Error("reply job ping error")
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

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

View File

@@ -0,0 +1,15 @@
package activity
// Activity activity.
type Activity struct {
Aid int64 `json:"id"`
Title string `json:"name"`
Link string `json:"pc_url"`
Cover string `json:"pc_cover"`
}
// Subject subject.
type Subject struct {
Title string `json:"name"`
Link string `json:"act_url"`
}

View File

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

View File

@@ -0,0 +1,11 @@
package drawyoo
// Drawyoo Drawyoo
type Drawyoo struct {
Hid int64 `json:"hid"`
Mid int64 `json:"mid"`
Title string `json:"title"`
Link string `json:"link"`
Cover string `json:"cover"`
Msg string `json:"msg"`
}

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 = [
"reply.go",
"sql.go",
],
importpath = "go-common/app/job/main/reply/model/reply",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/account/model:go_default_library",
"//library/ecode:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,623 @@
package reply
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"text/template"
"database/sql/driver"
accmdl "go-common/app/service/main/account/model"
"go-common/library/ecode"
xtime "go-common/library/time"
)
// reply cnost
const (
AttrYes = uint32(1)
AttrNo = uint32(0)
FolderKindSub = "s"
FolderKindRoot = "r"
SortByFloor = int8(0)
SortByCount = int8(1)
SortByLike = int8(2)
//SubTypeVideo = define.TypeVideo
//SubTypeTopic = define.TypeTopic
SubTypeVideo = int8(1)
SubTypeTopic = int8(2)
SubTypeDrawyoo = int8(3)
SubTypeActivity = int8(4)
SubTypeLiveVideo = int8(5)
SubTypeForbiden = int8(6) // reply forbiden info
SubTypeNotice = int8(7) // reply notice info
SubTypeLiveAct = int8(8)
SubTypeActArc = int8(9)
SubTypeLiveNotice = int8(10)
SubTypeLivePicture = int8(11) // 文画
SubTypeArticle = int8(12) // 文章
SubTypeTicket = int8(13) // 票务
SubTypeAudio = int8(14) // 音乐
SubTypeCredit = int8(15) // 风纪委
SubTypeDynamic = int8(17) // 动态
SubTypePlaylist = int8(18) // 播单
SubTypeAudioPlaylist = int8(19) // 音乐播单
SubStateNormal = int8(0)
SubStateForbid = int8(1)
SubStateReplyAfterAudit = int8(2)
// Sub attr bit
SubAttrAdminTop = uint32(0)
SubAttrUpperTop = uint32(1)
SubAttrFolded = uint32(7)
ReplyStateNormal = int8(0) // normal
ReplyStateHidden = int8(1) // hidden by up
ReplyStateFiltered = int8(2) // filtered
ReplyStateAdminDel = int8(3) // delete by admin
ReplyStateUserDel = int8(4) // delele by user
ReplyStateMonitor = int8(5) // reply monitor
ReplyStateGarbage = int8(6) // bigdata filter reply
ReplyStateTop = int8(7) // top by admin
ReplyStateUpDel = int8(8) // delete by up
ReplyStateBlacklist = int8(9) // in a blacklist
ReplyStateAssistDel = int8(10) // delete by assistant
ReplyStateAudit = int8(11) // reply after audit
ReplyStateFolded = int8(12) // 被折叠
// reply attr bit
ReplyAttrAdminTop = uint32(0)
ReplyAttrUpperTop = uint32(1)
ReplyAttrGarbage = uint32(2)
ReplyAttrFolded = uint32(7)
ReportStateNew = int8(0) // 待一审
ReportStateDelete = int8(1) // 移除
ReportStateIgnore = int8(2) // 忽略
ReportStateDeleteOne = int8(3) // 一审移除
ReportStateNewTwo = int8(4) // 待二审
ReportStateDeleteTwo = int8(5) // 二审移除
ReportStateIgnoreTwo = int8(6) // 二审忽略
ReportStateIgnoreOne = int8(7) // 一审忽略
ReportStateTransferred = int8(8) // 举报转移风纪委
ReportAttrTransferred = uint(0) //State状态是否曾今从待一审\二审转换成待二审\一审
ReportUserStateNew = int8(0) // 新增
ReportUserStateReported = int8(1) // 已反馈
OpCancel = int8(0) // 取消赞踩
OpAdd = int8(1) // 添加赞踩
ActionNormal = int8(0) // 未踩赞
ActionLike = int8(1) // 赞
ActionHate = int8(2) // 踩
UpperOpHide = int8(1)
UpperOpShow = int8(2)
ReportReasonOther = int8(0) // 其他
ReportReasonAd = int8(1) // 广告
ReportReasonPorn = int8(2) // 色情
ReportReasonMeaningless = int8(3) // 刷屏
ReportReasonProvoke = int8(4) // 引站
ReportReasonSpoiler = int8(5) // 剧透
ReportReasonPolitic = int8(6) // 政治
ReportReasonAttack = int8(7) // 人身攻击
ReportReasonUnrelated = int8(8) // 视频不相关
ReportReasonProhibited = int8(9) // 违禁
ReportReasonVulgar = int8(10) // 低俗
ReportReasonIllegalWebsite = int8(11) // 非法网站
ReportReasonGamblingFraud = int8(12) // 赌博诈骗
ReportReasonRumor = int8(13) // 传播不实信息
ReportReasonAbetting = int8(14) // 怂恿教唆信息
ReportReasonPrivacyInvasion = int8(15) // 侵犯隐私
ReportReasonUnlimitedSign = int8(16) // 抢楼
ForbidReasonSpoiler = int8(10) // 发布剧透信息
ForbidReasonAd = int8(6) // 发布垃圾广告信息
ForbidReasonUnlimitedSign = int8(2) // 抢楼
ForbidReasonMeaningless = int8(1) // 刷屏
ForbidReasonProvoke = int8(9) // 发布引战言论
ForbidReasonVulgar = int8(14) // 发布低俗信息
ForbidReasonGamblingFraud = int8(4) // 发布赌博诈骗信息
ForbidReasonPorn = int8(13) // 发布色情信息
ForbidReasonRumor = int8(18) // 发布传播不实信息
ForbidReasonIllegalWebsite = int8(17) // 发布非法网站信息
ForbidReasonAbetting = int8(19) // 发布怂恿教唆信息
ForbidReasonProhibited = int8(5) // 发布违禁信息
ForbidReasonPrivacyInvasion = int8(8) // 涉及侵犯他人隐私
ForbidReasonAttack = int8(7) // 发布人身攻击言论
PlatUnknow = int8(0)
PlatWeb = int8(1)
PlatAndroid = int8(2)
PlatIPhone = int8(3)
PlatWpM = int8(4) // wp mobile
PlatIPad = int8(5)
PlatWpPc = int8(6) // wp win10
AdminOperDelete = int8(0) // admin delete
AdminOperDeleteByReport = int8(1) // admin delete by report
AdminOperIgnoreReport = int8(2) // admin ignore report
AdminOperRecover = int8(3) // admin recover
AdminOperEdit = int8(4) // admin edit reply content
AdminOperPass = int8(5) // admin pass
AdminOperSubState = int8(6) // admin change subject state
AdminOperSubTop = int8(7) // top reply
AdminOperSubMid = int8(8) // admin change subject mid
AdminOperRptIgnore1 = int8(9) // admin report ignore 1
AdminOperRptIgnore2 = int8(10) // admin report ignore 2
AdminOperRptDel1 = int8(11) // admin report del 1
AdminOperRptDel2 = int8(12) // admin report del 2
AdminOperRptRecover1 = int8(13) // admin report recover 1
AdminOperRptRecover2 = int8(14) // admin report recover 2
AdminOperActionSet = int8(15) // admin action set
AdminOperDeleteUp = int8(16) // admin delete by up
AdminOperDeleteUser = int8(17) // admin delete by user
AdminOperDeleteAssist = int8(18) // admin delete by assist
AdminOperRptTransfer1 = int8(20) // admin transfer report to 1
AdminOperRptTransfer2 = int8(21) // admin transfer report to 2
AdminOperRptTransferArbitration = int8(22) // admin transfer report to arbitration
AdminOperRptStateSet = int8(23) // admin set report state
AdminIsNotReport = int8(0)
AdminIsReport = int8(1)
AdminIsNotNew = int8(0)
AdminIsNew = int8(1)
AuditTypeOne = int8(1) // 一审
AuditTypeTwo = int8(2) // 二审
)
var (
// NotifyComRules 社区规则
NotifyComRules = fmt.Sprintf(`评论区是公众场所,而非私人场所,具体规范烦请参阅#{《社区规则》}{"%s"},良好的社区氛围需要大家一起维护!`, "http://www.bilibili.com/blackboard/blackroom.html")
// NotifyComRulesReport NotifyComRulesReport
NotifyComRulesReport = "感谢您对bilibili社区秩序的维护哔哩哔哩 (゜-゜)つロ 干杯~"
// NotifyComUnrelated NotifyComUnrelated
NotifyComUnrelated = "bilibili倡导发送与视频相关的评论希望大家尊重作品尊重UP主。良好的社区氛围需要大家一起维护"
// NotifyComProvoke NotifyComProvoke
NotifyComProvoke = "bilibili倡导平等友善的交流。良好的社区氛围需要大家一起维护"
// NofityComProhibited NofityComProhibited
NofityComProhibited = fmt.Sprintf(`请自觉遵守国家相关法律法规及#{《社区规则》}{"%s"}bilibili良好的社区氛围需要大家一起维护`, "http://www.bilibili.com/blackboard/blackroom.html")
// ReportReason 举报理由类型
ReportReason = map[int8]string{
ReportReasonAd: "内容涉及垃圾广告",
ReportReasonPorn: "内容涉及色情",
ReportReasonMeaningless: "刷屏",
ReportReasonProvoke: "内容涉及引战",
ReportReasonSpoiler: "内容涉及视频剧透",
ReportReasonPolitic: "内容涉及政治相关",
ReportReasonAttack: "内容涉及人身攻击",
ReportReasonUnrelated: "视频不相关",
ReportReasonProhibited: "内容涉及违禁相关",
ReportReasonVulgar: "内容涉及低俗信息",
ReportReasonIllegalWebsite: "内容涉及非法网站信息",
ReportReasonGamblingFraud: "内容涉及赌博诈骗信息",
ReportReasonRumor: "内容涉及传播不实信息",
ReportReasonAbetting: "内容不适宜",
ReportReasonPrivacyInvasion: "内容涉及侵犯他人隐私",
ReportReasonUnlimitedSign: "抢楼",
}
// ForbidReason 封禁理由类型
ForbidReason = map[int8]string{
ForbidReasonSpoiler: "发布剧透信息",
ForbidReasonAd: "发布垃圾广告信息",
ForbidReasonUnlimitedSign: "抢楼",
ForbidReasonMeaningless: "刷屏",
ForbidReasonProvoke: "发布引战言论",
ForbidReasonVulgar: "发布低俗信息",
ForbidReasonGamblingFraud: "发布赌博诈骗信息",
ForbidReasonPorn: "发布色情信息",
ForbidReasonRumor: "发布传播不实信息",
ForbidReasonIllegalWebsite: "发布非法网站信息",
ForbidReasonAbetting: "发布不适宜内容",
ForbidReasonProhibited: "发布违禁信息",
ForbidReasonPrivacyInvasion: "涉及侵犯他人隐私",
ForbidReasonAttack: "发布人身攻击言论",
}
)
// Subject is subject of reply
type Subject struct {
ID int64 `json:"-"`
Oid int64 `json:"oid"`
Type int8 `json:"type"`
Mid int64 `json:"mid"`
Count int `json:"count"`
RCount int `json:"rcount"`
ACount int `json:"acount"`
MCount int `json:"mcount"`
State int8 `json:"state"`
Attr uint32 `json:"attr"`
Meta string `json:"meta"`
CTime xtime.Time `json:"ctime"`
MTime xtime.Time `json:"-"`
}
// HasFolded ...
func (s *Subject) HasFolded() bool {
return s.AttrVal(SubAttrFolded) == AttrYes
}
// UnmarkHasFolded ...
func (s *Subject) UnmarkHasFolded() {
s.AttrSet(AttrNo, SubAttrFolded)
}
// SubjectMeta SubjectMeta
type SubjectMeta struct {
AdminTop int64 `json:"atop,omitempty"`
UpperTop int64 `json:"utop,omitempty"`
}
// TopCount TopCount
func (s *Subject) TopCount() int {
return int(s.AttrVal(SubAttrUpperTop) + s.AttrVal(SubAttrAdminTop))
}
// AttrVal return val of subject'attr
func (s *Subject) AttrVal(bit uint32) uint32 {
if s.Attr == 0 {
return uint32(0)
}
return (s.Attr >> bit) & uint32(1)
}
// TopSet TopSet
func (s *Subject) TopSet(top int64, typ uint32, act uint32) (err error) {
var meta SubjectMeta
if s.Meta != "" {
err = json.Unmarshal([]byte(s.Meta), &meta)
if err != nil {
return
}
}
if act == 1 {
if typ == 0 {
if meta.AdminTop == top {
return fmt.Errorf("subject already have the same top")
}
meta.AdminTop = top
} else {
if meta.UpperTop == top {
return fmt.Errorf("subject already have the same top")
}
meta.UpperTop = top
}
} else {
if typ == 0 {
meta.AdminTop = 0
} else {
meta.UpperTop = 0
}
}
var content []byte
content, err = json.Marshal(meta)
if err != nil {
return err
}
s.Meta = string(content)
return
}
// AttrSet set val of subject'attr
func (s *Subject) AttrSet(v uint32, bit uint32) {
s.Attr = s.Attr&(^(1 << bit)) | (v << bit)
}
// IsNormal check if reply subject normal
func (s *Subject) IsNormal() bool {
return s.State == SubStateNormal
}
// IsAudit check reply subject is in audit
func (s *Subject) IsAudit() bool {
return s.State == SubStateReplyAfterAudit
}
// CheckSubState check
func CheckSubState(state int8) (err error) {
if state < SubStateNormal || state > SubStateReplyAfterAudit {
err = ecode.ReplyIllegalSubState
}
return
}
// CheckSubForbid check if subject forbiden reply
func CheckSubForbid(state int8) (err error) {
if state == SubStateForbid {
err = ecode.ReplyForbidReply
}
return
}
// Reply define reply object
type Reply struct {
RpID int64 `json:"rpid"`
Oid int64 `json:"oid"`
Type int8 `json:"type"`
Mid int64 `json:"mid"`
Root int64 `json:"root"`
Parent int64 `json:"parent"`
Dialog int64 `json:"dialog"`
Count int `json:"count"`
RCount int `json:"rcount"`
Floor int `json:"floor"`
State int8 `json:"state"`
Attr uint32 `json:"attr"`
CTime xtime.Time `json:"ctime"`
MTime xtime.Time `json:"-"`
// string
RpIDStr string `json:"rpid_str,omitempty"`
RootStr string `json:"root_str,omitempty"`
ParentStr string `json:"parent_str,omitempty"`
// action count, from ReplyAction count
Like int `json:"like"`
Hate int `json:"hate"`
Action int8 `json:"action"`
// member info
Member *accmdl.Info `json:"member"`
// other
Content *Content `json:"content"`
Replies []*Reply `json:"replies"`
}
// IsFolded ...
func (r *Reply) IsFolded() bool {
return r.State == ReplyStateFolded
}
// HasFolded ...
func (r *Reply) HasFolded() bool {
return r.AttrVal(ReplyAttrFolded) == AttrYes
}
// UnmarkHasFolded ...
func (r *Reply) UnmarkHasFolded() {
r.AttrSet(AttrNo, ReplyAttrFolded)
}
// AttrVal return val of reply'attr
func (r *Reply) AttrVal(bit uint32) uint32 {
if r.Attr == 0 {
return uint32(0)
}
return (r.Attr >> bit) & uint32(1)
}
// AttrSet set attr of reply'attr
func (r *Reply) AttrSet(v uint32, bit uint32) {
r.Attr = r.Attr&(^(1 << bit)) | (v << bit)
}
// IsNormal check if reply normal
func (r *Reply) IsNormal() bool {
return r.State == ReplyStateNormal || r.State == ReplyStateHidden || r.State == ReplyStateFiltered || r.State == ReplyStateMonitor || r.State == ReplyStateGarbage || r.State == ReplyStateTop || r.State == ReplyStateFolded
}
// IsDeleted deleted.
func (r *Reply) IsDeleted() bool {
return r.State == ReplyStateUserDel || r.State == ReplyStateUpDel || r.State == ReplyStateAdminDel || r.State == ReplyStateAssistDel
}
// IsRoot IsRoot
func (r *Reply) IsRoot() bool {
return r.Root == 0 && r.Parent == 0
}
// IsTop top.
func (r *Reply) IsTop() bool {
if r.Attr != 0 && (r.AttrVal(ReplyAttrAdminTop) == 1 || r.AttrVal(ReplyAttrUpperTop) == 1) {
return true
}
return false
}
// IsAdminTop admin top.
func (r *Reply) IsAdminTop() bool {
return r.AttrVal(ReplyAttrAdminTop) == 1
}
// IsUpTop up top.
func (r *Reply) IsUpTop() bool {
return r.AttrVal(ReplyAttrUpperTop) == 1
}
// FillStr fill reply string info
func (r *Reply) FillStr(isEscape bool) {
r.RpIDStr = strconv.FormatInt(r.RpID, 10)
r.RootStr = strconv.FormatInt(r.Root, 10)
r.ParentStr = strconv.FormatInt(r.Parent, 10)
if r.Content != nil {
if isEscape {
r.Content.Message = template.HTMLEscapeString(r.Content.Message)
}
r.Content.IP = 0
r.Content.Version = ""
}
}
// CheckSort check sort type
func CheckSort(sort int8) bool {
return SortByFloor <= sort && sort <= SortByLike
}
// CheckPlat check plat type
func CheckPlat(plat int8) bool {
return PlatUnknow <= plat && plat <= PlatWpPc
}
// Content define reply content
type Content struct {
RpID int64 `json:"-"`
Message string `json:"message"`
Ats Int64Bytes `json:"ats,omitempty"`
Topics Mstr `json:"topics,omitempty"`
IP uint32 `json:"ipi,omitempty"`
Plat int8 `json:"plat"`
Device string `json:"device"`
Version string `json:"version,omitempty"`
CTime xtime.Time `json:"-"`
MTime xtime.Time `json:"-"`
// ats member info
// Members []*accmdl.Info `json:"members"`
}
// // FillAts fill user ifo of @
// func (rc *ReplyContent) FillAts(mis map[int64]*accmdl.Info) {
// rc.Members = make([]*accmdl.Info, 0, len(rc.Ats))
// for _, at := range rc.Ats {
// if mi, ok := mis[at]; ok {
// rc.Members = append(rc.Members, mi)
// }
// }
// rc.Ats = nil
// }
// Action reply action info
type Action struct {
ID int64 `json:"-"`
RpID int64 `json:"rpid"`
Action int8 `json:"action"`
Mid int64 `json:"mid"`
CTime xtime.Time `json:"-"`
}
// CheckAction check action operation
func CheckAction(act int8) (err error) {
if act != OpAdd && act != OpCancel {
err = ecode.ReplyIllegalAction
}
return
}
// Report define reply report
type Report struct {
ID int64 `json:"id,omitempty"`
RpID int64 `json:"rpid"`
Oid int64 `json:"oid"`
Type int8 `json:"type"`
Mid int64 `json:"mid"`
Reason int8 `json:"reason"`
Content string `json:"content"`
Count int `json:"count"`
Score int `json:"score"`
State int8 `json:"state"`
CTime xtime.Time `json:"ctime"`
MTime xtime.Time `json:"-"`
Attr int8 `json:"attr"`
}
// AttrVal AttrVal
func (rpt Report) AttrVal(bit uint) int8 {
return (rpt.Attr >> bit) & int8(1)
}
// IsTransferred AttrVal
func (rpt Report) IsTransferred() bool {
return rpt.AttrVal(ReportAttrTransferred) == 1
}
// AttrSet set attr of ReplyReport'attr
func (rpt *Report) AttrSet(v int8, bit uint) {
rpt.Attr = rpt.Attr&(^(1 << bit)) | (v << bit)
}
// SetTransferred SetTransferred
func (rpt *Report) SetTransferred() {
rpt.AttrSet(1, 0)
}
// ReportUser report user.
type ReportUser struct {
ID int64 `json:"id"`
Oid int64 `json:"oid"`
Type int8 `json:"type"`
RpID int64 `json:"rpid"`
Mid int64 `json:"mid"`
Reason int8 `json:"reason"`
Content string `json:"content"`
State int8 `json:"state"`
CTime xtime.Time `json:"ctime"`
MTime xtime.Time `json:"mtime"`
}
// CheckReportReason check if reprot reason illegal
func CheckReportReason(reason int8) (err error) {
if !(ReportReasonOther <= reason && reason <= ReportReasonUnrelated) {
err = ecode.ReplyIllegalReport
}
return
}
// Mstr Mstr
type Mstr []string
// Scan Scan
func (ms *Mstr) Scan(src interface{}) (err error) {
switch sc := src.(type) {
case []byte:
if len(sc) == 0 {
return
}
res := strings.Split(string(sc), ",")
for i := range res {
res[i] = strings.Replace(res[i], "%2c", ",", -1)
}
*ms = res
}
return
}
// Value Value
func (ms Mstr) Value() (driver.Value, error) {
return ms.Bytes(), nil
}
// Bytes Bytes
func (ms Mstr) Bytes() []byte {
var res string
for i := range ms {
str := strings.Replace(ms[i], ",", "%2c", -1)
res += str
if i != (len(ms) - 1) {
res += ","
}
}
return []byte(res)
}
// Business Business
type Business struct {
Type int32 `json:"type"`
Alias string `json:"alias"`
}
// RpItem fix dialog model
type RpItem struct {
ID int64
Parent int64
Floor int
Next *RpItem
}
type RpItems []*RpItem
func (c RpItems) Len() int {
return len(c)
}
func (c RpItems) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c RpItems) Less(i, j int) bool {
return c[i].ID < c[j].ID
}

View File

@@ -0,0 +1,64 @@
package reply
import (
"database/sql/driver"
"encoding/binary"
)
// Int64Bytes implements the Scanner interface.
type Int64Bytes []int64
// Scan parse the data into int64 slice
func (is *Int64Bytes) Scan(src interface{}) (err error) {
switch sc := src.(type) {
case []byte:
var res []int64
for i := 0; i < len(sc) && i+8 <= len(sc); i += 8 {
ui := binary.BigEndian.Uint64(sc[i : i+8])
res = append(res, int64(ui))
}
*is = res
}
return
}
// Value marshal int64 slice to driver.Value,each int64 will occupy Fixed 8 bytes
func (is Int64Bytes) Value() (driver.Value, error) {
return is.Bytes(), nil
}
// Bytes marshal int64 slice to bytes,each int64 will occupy Fixed 8 bytes
func (is Int64Bytes) Bytes() []byte {
res := make([]byte, 0, 8*len(is))
for _, i := range is {
bs := make([]byte, 8)
binary.BigEndian.PutUint64(bs, uint64(i))
res = append(res, bs...)
}
return res
}
// Evict get rid of the sepcified num from the slice
func (is *Int64Bytes) Evict(e int64) (ok bool) {
res := make([]int64, len(*is)-1)
for _, v := range *is {
if v != e {
res = append(res, v)
} else {
ok = true
}
}
*is = res
return
}
// Exist judge the sepcified num is in the slice or not
func (is Int64Bytes) Exist(i int64) (e bool) {
for _, v := range is {
if v == i {
e = true
return
}
}
return
}

View File

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

View File

@@ -0,0 +1,9 @@
package topic
// Topic Topic
type Topic struct {
TpID int64 `json:"tp_id"`
Title string `json:"title"`
Mid int64 `json:"mid"`
Cover string `json:"cover"`
}

View File

@@ -0,0 +1,85 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"index_test.go",
"reply_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/go-sql-driver/mysql:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"admin.go",
"business.go",
"fold.go",
"index.go",
"notify.go",
"reply.go",
"service.go",
"spam.go",
"upper.go",
],
importpath = "go-common/app/job/main/reply/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/openplatform/article/model:go_default_library",
"//app/interface/openplatform/article/rpc/client:go_default_library",
"//app/job/main/reply/conf:go_default_library",
"//app/job/main/reply/dao/message:go_default_library",
"//app/job/main/reply/dao/notice:go_default_library",
"//app/job/main/reply/dao/reply:go_default_library",
"//app/job/main/reply/dao/search:go_default_library",
"//app/job/main/reply/dao/spam:go_default_library",
"//app/job/main/reply/dao/stat:go_default_library",
"//app/job/main/reply/model/reply:go_default_library",
"//app/service/main/account/api: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/assist/model/assist:go_default_library",
"//app/service/main/assist/rpc/client:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/openplatform/pgc-season/api/grpc/episode/v1:go_default_library",
"//library/database/elastic: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/rpc/warden:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/pipeline/fanout: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,894 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"go-common/app/job/main/reply/model/reply"
model "go-common/app/job/main/reply/model/reply"
xsql "go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_eventReportAdd = "report_add"
_eventReportDel = "report_del"
_eventReportIgnore = "report_ignore"
_eventReportRecover = "report_recover"
)
func (s *Service) actionAdmin(c context.Context, msg *consumerMsg) {
var d struct {
Op string `json:"op"`
Adid int64 `json:"adid"`
AdName string `json:"adname"`
Oid int64 `json:"oid"`
RpID int64 `json:"rpid"`
Mid int64 `json:"mid"`
Tp int8 `json:"tp"`
Action uint32 `json:"action"`
Moral int `json:"moral"`
Notify bool `json:"notify"`
Remark string `json:"remark"`
MTime xtime.Time `json:"mtime"`
Ftime int64 `json:"ftime"`
Audit int8 `json:"audit"`
Reason int8 `json:"reason"`
Content string `json:"content"`
FReason int8 `json:"freason"`
Assist bool `json:"assist"`
State int8 `json:"state"`
}
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if d.Oid <= 0 || d.RpID <= 0 {
log.Error("The structure of msg.Data(%s) was wrong", msg.Data)
return
}
rp, err := s.getReply(c, d.Oid, d.RpID)
if err != nil {
log.Error("s.getReply failed , oid(%d), RpID(%d) err(%v)", d.Oid, d.RpID, err)
return
}
if rp == nil {
log.Error("getReply nil oid(%d) RpID(%d)", d.Oid, d.RpID)
return
}
switch {
case d.Op == "del":
s.adminDel(c, rp, d.Adid, d.Mid, d.Ftime, d.Moral, d.AdName, d.Remark, d.MTime, d.Notify, d.Reason, d.FReason)
case d.Op == "del_rpt":
s.reportDel(c, rp, d.Adid, d.Mid, d.Ftime, d.Moral, d.AdName, d.Remark, d.MTime, d.Notify, d.Audit, d.Reason, d.Content, d.FReason)
case d.Op == "del_up":
s.userDel(c, rp, d.Mid, d.MTime, d.Remark, d.Assist)
case d.Op == "re", d.Op == "pass":
s.passReply(c, rp, d.MTime, d.Adid, d.Remark, d.Op)
case d.Op == "edit":
s.addSearchUp(c, rp.State, rp, nil)
case d.Op == "ignore":
s.reportIgnore(c, rp, d.Audit, d.MTime, d.Adid, d.Remark)
case d.Op == "transfer":
s.reportTransfer(c, rp, d.Audit, d.MTime, d.Adid, d.Remark)
case d.Op == "stateset":
s.reportStateSet(c, rp, d.State, d.MTime, d.Adid, d.Remark)
case d.Op == "top_add":
err := s.topAdd(c, rp, d.MTime, d.Action, model.SubAttrAdminTop)
if err != nil {
log.Error("s.topAdd(oid:%d,tp:%d err(%v))", rp.Oid, rp.Type, err)
return
}
// s.dao.Redis.AddTopOid(c, rp.Oid, rp.Type)
s.adminLog(c, rp, d.Adid, model.AdminIsReport, model.AdminOperSubTop, "管理员置顶评论", d.Remark)
s.addSearchUp(c, rp.State, rp, nil)
case d.Op == "rpt_re":
s.reportRecover(c, rp, d.Audit, d.MTime, d.Adid, d.Remark)
}
}
func (s *Service) reportStateSet(c context.Context, rp *model.Reply, state int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
op int8
result string
oldState = rp.State
)
rpt, err := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
rpt.State = state
rpt.MTime = mtime
op = model.AdminOperRptTransferArbitration
if adid == 0 {
result = "系统自动移交至风纪委"
} else {
result = "管理员移交至风纪委"
}
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v)", rpt, err)
return
}
// admin log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
s.addSearchUp(c, oldState, rp, rpt)
return
}
func (s *Service) reportTransfer(c context.Context, rp *model.Reply, audit int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
op int8
result string
oldState = rp.State
)
rpt, err := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
if rpt.IsTransferred() {
log.Error("report(%v) has been transfferd before!", *rpt)
return
}
if audit == model.AuditTypeOne {
rpt.State = model.ReportStateNew
op = model.AdminOperRptTransfer1
result = "二审转一审"
} else if audit == model.AuditTypeTwo {
rpt.State = model.ReportStateNewTwo
op = model.AdminOperRptTransfer2
result = "一审转二审"
}
rpt.MTime = mtime
rpt.SetTransferred()
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v)", rpt, err)
return
}
// admin log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
s.addSearchUp(c, oldState, rp, rpt)
return
}
func (s *Service) reportIgnore(c context.Context, rp *model.Reply, audit int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
op int8
result string
oldState = rp.State
)
rpt, err := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
if audit == model.AuditTypeOne {
rpt.State = model.ReportStateIgnoreOne
op = model.AdminOperRptIgnore1
result = "一审忽略"
} else if audit == model.AuditTypeTwo {
rpt.State = model.ReportStateIgnoreTwo
op = model.AdminOperRptIgnore2
result = "二审忽略"
} else {
if rpt.State == model.ReportStateNew {
rpt.State = model.ReportStateIgnoreOne
} else {
rpt.State = model.ReportStateIgnoreTwo
}
op = model.AdminOperIgnoreReport
result = "已忽略举报"
}
rpt.MTime = mtime
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v)", rpt, err)
return
}
// admin log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
s.addSearchUp(c, oldState, rp, rpt)
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject(%v,%v) err(%v) or sub nil", sub.Oid, sub.Type, err)
return
}
if err = s.dao.PubEvent(c, _eventReportIgnore, rpt.Mid, sub, rp, rpt); err != nil {
return
}
return
}
func (s *Service) reportRecover(c context.Context, rp *model.Reply, audit int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
ok bool
op int8
result string
rootRp *model.Reply
oldState = rp.State
)
isRoot := rp.Root == 0 && rp.Parent == 0
rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject(%v,%v) err(%v) or sub nil", sub.Oid, sub.Type, err)
return
}
if audit == model.AuditTypeOne {
// 一审移除,恢复到待一审忽略,评论正常
rpt.State = model.ReportStateIgnoreOne
op = model.AdminOperRptRecover1
result = "一审恢复评论"
} else if audit == model.AuditTypeTwo {
// 二审移除,恢复到二审忽略,评论正常
rpt.State = model.ReportStateIgnoreTwo
op = model.AdminOperRptRecover2
result = "二审恢复评论"
} else {
log.Error("reportRecover unsupport audit: %d", audit)
return
}
rpt.MTime = mtime
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v) or row==0", rpt, err)
return
}
// 只恢复管理员删除的评论,不恢复用户删除的
if rp.State == model.ReplyStateAdminDel {
rp.MTime = mtime
rp.State = model.ReplyStateNormal
if err = s.tranRecover(c, rp, model.ReplyStateNormal, isRoot); err != nil {
log.Error("Transaction recover reply failed err(%v)", err)
return
}
}
// add cache
if isRoot {
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rp.RpID, err)
}
if err = s.dao.Redis.AddIndex(c, rp.Oid, rp.Type, rpt, rp, true); err != nil {
log.Error("s.dao.Redis.AddIndex(%d, %d) error(%v)", rp.Oid, rp.Type, err)
}
} else {
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, rp.Root); err == nil && ok {
if err = s.dao.Redis.AddNewChildIndex(c, rp.Root, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot failed , rproot(%d), err(%v)", rp.Root, err)
}
}
if rootRp, err = s.getReplyCache(c, rp.Oid, rp.Root); err != nil {
log.Error("s.getReply failed , oid(%d), root(%d) err(%v)", rp.Oid, rp.Root, err)
} else if rootRp != nil {
rootRp.RCount++
if err = s.dao.Mc.AddReply(c, rootRp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d) err(%v)", rootRp.RpID, err)
}
}
}
// log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
// notify
s.addSearchUp(c, oldState, rp, rpt)
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
if err = s.dao.PubEvent(c, _eventReportRecover, rpt.Mid, sub, rp, rpt); err != nil {
return
}
return
}
// topAdd add top reply
func (s *Service) topAdd(c context.Context, rp *model.Reply, ts xtime.Time, act uint32, tp uint32) (err error) {
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getsubject(%v,%v) err(%v) or sub nil", rp.Oid, rp.Type, err)
return
}
if act == 1 && sub.AttrVal(tp) == 1 {
log.Error("Repeat to add top reply(%d,%d,%d,%d) ", rp.RpID, rp.Oid, tp, sub.Attr)
return
}
err = sub.TopSet(rp.RpID, tp, act)
if err != nil {
log.Error("sub.TopSet(%d,%d,%d) failed!err:=%v ", rp.RpID, rp.Oid, tp, err)
return
}
sub.AttrSet(act, tp)
rp.AttrSet(act, tp)
tx, err := s.beginTran(c)
if err != nil {
log.Error("s.beginTran() err(%v)", err)
return
}
var rows int64
//rp.State = model.ReplyStateTop
if rows, err = s.dao.Reply.TxUpAttr(tx, rp.Oid, rp.RpID, rp.Attr, ts.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.UpState(%v, %d) error(%v) or row==0", rp, rp.State, err)
return
}
if rows, err = s.dao.Subject.TxUpMeta(tx, sub.Oid, sub.Type, sub.Meta, ts.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
return
}
if rows, err = s.dao.Subject.TxUpAttr(tx, sub.Oid, sub.Type, sub.Attr, ts.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Upattr(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
return
}
tx.Commit()
s.dao.Mc.AddSubject(c, sub)
if act == 1 {
s.dao.Redis.DelIndexBySortType(c, rp, reply.SortByCount)
s.dao.Redis.DelIndexBySortType(c, rp, reply.SortByLike)
} else if rp.IsNormal() {
if ok, err := s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByCount); err == nil && ok {
if err = s.dao.Redis.AddCountIndex(c, sub.Oid, sub.Type, rp); err != nil {
log.Error("s.dao.Redis.AddCountIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
if ok, err := s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, sub.Oid, sub.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
}
s.dao.Mc.AddReply(c, rp)
s.dao.Mc.AddTop(c, rp)
if act == 1 {
s.dao.PubEvent(c, "top", 0, sub, rp, nil)
} else if act == 0 {
s.dao.PubEvent(c, "untop", 0, sub, rp, nil)
}
// 折叠评论被置顶自动取消折叠
if rp.IsFolded() && act == 1 {
rp.State = model.ReplyStateNormal
s.marker.Do(c, func(ctx context.Context) {
if _, err := s.dao.Reply.UpState(ctx, rp.Oid, rp.RpID, rp.State, time.Now()); err == nil {
if ok, err := s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByFloor); err == nil && ok {
s.dao.Redis.AddFloorIndex(c, rp.Oid, rp.Type, rp)
}
s.handleFolded(ctx, rp)
}
})
}
return
}
func (s *Service) userDel(c context.Context, rp *model.Reply, mid int64, mtime xtime.Time, remark string, assist bool) (err error) {
var (
state int8
sub *model.Subject
oldState = rp.State
isRoot = rp.Root == 0 && rp.Parent == 0
isFolded = rp.IsFolded()
)
if rp.IsDeleted() {
s.addSearchUp(c, oldState, rp, nil)
return
}
if sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type); err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if rp.Mid == mid {
state = model.ReplyStateUserDel
} else if assist {
state = model.ReplyStateAssistDel
} else if mid > 0 {
state = model.ReplyStateUpDel
} else {
state = model.ReplyStateAdminDel
}
rp.MTime = mtime
if err = s.tranDel(c, rp, state, sub, isRoot); err != nil {
log.Error("reportDel tranDel(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if isFolded {
s.marker.Do(c, func(ctx context.Context) {
s.handleFolded(ctx, rp)
})
}
if err = s.clearReplyCache(c, rp); err != nil {
log.Error("reportDel clearReplyCache(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
}
rp.State = state
if rp.Mid == mid {
s.adminLog(c, rp, mid, model.AdminIsNotReport, model.AdminOperDeleteUser, "由本人删除", remark)
s.searchDao.DelReply(c, rp.RpID, sub.Oid, mid, state)
} else if assist {
s.adminLog(c, rp, mid, model.AdminIsNotReport, model.AdminOperDeleteAssist, "由UP主协管员删除", remark)
s.addAssistLog(c, sub.Mid, mid, sub.Oid, 1, 1, strconv.FormatInt(rp.RpID, 10), rp.Content.Message)
} else if mid > 0 {
s.adminLog(c, rp, mid, model.AdminIsNotReport, model.AdminOperDeleteUp, "由up主删除", remark)
} else {
s.adminLog(c, rp, 0, model.AdminIsNotReport, model.AdminOperDelete, "由系统删除", remark)
}
s.dao.PubEvent(c, "reply_del", rp.Mid, sub, rp, nil)
return
}
func (s *Service) adminDel(c context.Context, rp *model.Reply, adid, mid, ftime int64, moral int, adName, remark string, mtime xtime.Time, notify bool, reason, freason int8) (err error) {
var (
sub *model.Subject
report *model.Report
oldState = rp.State
isRoot = rp.Root == 0 && rp.Parent == 0
isFolded = rp.IsFolded()
)
if rp.IsDeleted() {
s.addSearchUp(c, oldState, rp, nil)
return
}
if sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type); err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
rp.MTime = mtime
if err = s.tranDel(c, rp, model.ReplyStateAdminDel, sub, isRoot); err != nil {
log.Error("reportDel tranDel(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if isFolded {
s.marker.Do(c, func(ctx context.Context) {
s.handleFolded(ctx, rp)
})
}
if err = s.clearReplyCache(c, rp); err != nil {
log.Error("reportDel clearReplyCache(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
}
if report, err = s.dao.Report.Get(c, rp.Oid, rp.RpID); err != nil {
log.Error("reportDel getReport(%d,%d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if report != nil {
if report.State == model.ReportStateNew || report.State == model.ReportStateNewTwo {
if report.State == model.ReportStateNew {
report.State = model.ReportStateDeleteOne
} else if report.State == model.ReportStateNewTwo {
report.State = model.ReportStateDeleteTwo
}
report.MTime = mtime
if _, err = s.dao.Report.Update(c, report); err != nil {
log.Error("reportDel updateReport(%d, %d) error(%v)", report.Oid, report.ID, err)
return
}
s.addSearchUp(c, oldState, rp, report)
s.dao.PubEvent(c, _eventReportDel, 0, sub, rp, report)
}
}
rp.State = model.ReplyStateAdminDel
// add moral and notify
s.moralAndNotify(c, rp, moral, notify, mid, adid, adName, remark, reason, freason, ftime, false)
// forbidden tip
forbidDay := strconv.FormatInt(ftime, 10) + "天"
if ftime == -1 {
forbidDay = "永久"
}
s.adminLog(c, rp, adid, model.AdminIsNotReport, model.AdminOperDelete, fmt.Sprintf("已删除并封禁%s/扣除%d节操", forbidDay, moral), remark)
s.addSearchUp(c, oldState, rp, nil)
if report == nil {
s.dao.PubEvent(c, "reply_del", 0, sub, rp, nil)
}
return
}
func (s *Service) reportDel(c context.Context, rp *model.Reply, adid, mid, ftime int64, moral int, adName, remark string, mtime xtime.Time, notify bool, audit int8, reason int8, content string, freason int8) (err error) {
var (
op int8
isPunish bool
sub *model.Subject
report *model.Report
oldState = rp.State
)
if sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type); err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if report, err = s.dao.Report.Get(c, rp.Oid, rp.RpID); err != nil || report == nil {
log.Error("reportDel getReport(%d,%d) error(%v)", rp.Oid, rp.RpID, err)
return
}
report.MTime = mtime
// 一审、二审操作
switch audit {
case model.AuditTypeOne:
report.State = model.ReportStateDeleteOne
op = model.AdminOperRptDel1
case model.AuditTypeTwo:
report.State = model.ReportStateDeleteTwo
op = model.AdminOperRptDel2
default:
report.State = model.ReportStateDelete
op = model.AdminOperDeleteByReport
}
report.Reason = reason
report.Content = content
if _, err = s.dao.Report.Update(c, report); err != nil {
log.Error("reportDel updateReport(%d, %d) error(%v)", report.Oid, report.ID, err)
return
}
if !rp.IsDeleted() {
isRoot := rp.Root == 0 && rp.Parent == 0
rp.MTime = mtime
if err = s.tranDel(c, rp, model.ReplyStateAdminDel, sub, isRoot); err != nil {
log.Error("reportDel tranDel(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if err = s.clearReplyCache(c, rp); err != nil {
log.Error("reportDel clearReplyCache(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
}
rp.State = model.ReplyStateAdminDel
} else {
isPunish = true
}
// add moral and notify
s.moralAndNotify(c, rp, moral, notify, mid, adid, adName, remark, reason, freason, ftime, isPunish)
// forbidden tip
forbidDay := strconv.FormatInt(ftime, 10) + "天"
if ftime == -1 {
forbidDay = "永久"
}
s.adminLog(c, rp, adid, model.AdminIsReport, op, fmt.Sprintf("已通过举报删除并封禁%s/扣除%d节操", forbidDay, moral), remark)
s.addSearchUp(c, oldState, rp, report)
if err = s.dao.PubEvent(c, _eventReportDel, report.Mid, sub, rp, report); err != nil {
return
}
return
}
func (s *Service) clearReplyCache(c context.Context, rp *model.Reply) (err error) {
var (
sub *model.Subject
rootRp *model.Reply
isRoot = rp.Root == 0 && rp.Parent == 0
)
if !isRoot && rp.IsNormal() {
// update root cache for count.
if rootRp, err = s.getReplyCache(c, rp.Oid, rp.Root); err != nil {
return
}
if rootRp != nil {
rootRp.RCount--
if err = s.addReplyCache(c, rootRp); err != nil {
log.Error("s.dao.Mc.addReplyCache(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
}
}
}
if rp.IsAdminTop() {
s.dao.Mc.DeleteTop(c, rp, model.ReplyAttrAdminTop)
}
if rp.IsUpTop() {
s.dao.Mc.DeleteTop(c, rp, model.ReplyAttrUpperTop)
}
if err = s.dao.Mc.DeleteReply(c, rp.RpID); err != nil {
log.Error("s.dao.Mc.DeleteReply failed , RpID(%d), err(%v)", rp.RpID, err)
}
if err = s.dao.Redis.DelIndex(c, rp); err != nil {
log.Error("s.dao.Redis.DelIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
if rp.State == model.ReplyStateAudit {
s.eraseAuditIndex(c, rp)
}
// update reply count
sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d), err(%v)", sub.Oid, err)
}
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
return
}
func (s *Service) passReply(c context.Context, rp *model.Reply, mtime xtime.Time, adid int64, remark, op string) {
var (
err error
ok bool
rootRp *model.Reply
oldState = rp.State
isRoot = rp.Root == 0 && rp.Parent == 0
)
sub, err := s.dao.Subject.Get(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if rp.State <= model.ReplyStateHidden {
s.addSearchUp(c, oldState, rp, nil)
return
}
rp.MTime = mtime
rp.State = model.ReplyStateNormal
if op == "re" || oldState == model.ReplyStateAudit {
if err = s.tranRecover(c, rp, model.ReplyStateNormal, isRoot); err != nil {
log.Error("Transaction recover reply failed err(%v)", err)
return
}
} else if op == "pass" {
var row int64
var tx *xsql.Tx
tx, err = s.dao.BeginTran(c)
if err != nil {
return
}
if row, err = s.dao.Reply.TxUpState(tx, rp.Oid, rp.RpID, model.ReplyStateNormal, mtime.Time()); err != nil || row == 0 {
tx.Rollback()
log.Error("dao.Reply.TxUpState(%v, %d) error(%v) or row==0", rp, model.ReplyStateNormal, err)
return
}
if rp.State == model.ReplyStateAudit || rp.State == model.ReplyStateMonitor {
if _, err = s.dao.Subject.TxDecrMCount(tx, rp.Oid, rp.Type, mtime.Time()); err != nil {
tx.Rollback()
log.Error("dao.Reply.TxDecrMCount(%v) error(%v)", rp, err)
return
}
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
}
if isRoot {
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByFloor); err == nil && ok {
var min int
min, err = s.dao.Redis.MinScore(c, rp.Oid, rp.Type, reply.SortByFloor)
if err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , oid(%d) type(%d) err(%v)", rp.Oid, rp.Type, err)
} else if rp.Floor > min {
if err = s.dao.Redis.AddFloorIndex(c, rp.Oid, rp.Type, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByCount); err == nil && ok {
if err = s.dao.Redis.AddCountIndex(c, rp.Oid, rp.Type, rp); err != nil {
log.Error("s.dao.Redis.AddCountIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, rp.Oid, rp.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
}
} else {
if ok, err = s.dao.Redis.ExpireDialogIndex(c, rp.Dialog); err == nil && ok {
if err = s.dao.Redis.AddDialogIndex(c, rp.Dialog, []*model.Reply{rp}); err != nil {
log.Error("s.dao.Redis.AddDialogINdex Error (%v)", err)
}
}
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, rp.Root); err == nil && ok {
if err = s.dao.Redis.AddNewChildIndex(c, rp.Root, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot failed , rproot(%d), err(%v)", rp.Root, err)
}
}
if op == "re" || oldState == model.ReplyStateAudit {
if rootRp, err = s.getReplyCache(c, rp.Oid, rp.Root); err != nil {
log.Error("s.getReply failed , oid(%d), root(%d) err(%v)", rp.Oid, rp.Root, err)
} else if rootRp != nil {
rootRp.RCount++
if err = s.addReplyCache(c, rootRp); err != nil {
log.Error("s.addReplyCache oid(%d), rpid(%d) err(%v)", rootRp.Oid, rootRp.RpID, err)
}
}
}
}
if err = s.addReplyCache(c, rp); err != nil {
log.Error("s.addReplyCache oid(%d), rpid(%d) err(%v)", rp.Oid, rp.RpID, err)
}
if oldState == model.ReplyStateAudit {
s.dao.Redis.DelAuditIndexs(c, rp)
}
// update reply count
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d) err(%v)", sub.Oid, err)
}
// admin log
if op == "re" {
s.adminLog(c, rp, adid, model.AdminIsNotReport, model.AdminOperRecover, "已恢复评论", remark)
} else if op == "pass" {
s.adminLog(c, rp, adid, model.AdminIsNotReport, model.AdminOperPass, "已通过评论", remark)
}
s.addSearchUp(c, oldState, rp, nil)
}
func (s *Service) tranRecover(c context.Context, rp *model.Reply, state int8, isRoot bool) error {
var (
err error
rootReply *model.Reply
count int
rows int64
tx *xsql.Tx
)
if tx, err = s.beginTran(c); err != nil {
return err
}
mtime := rp.MTime
if rp, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.RpID); err != nil || rp == nil {
tx.Rollback()
return fmt.Errorf("s.dao.Reply.GetForUpdate(%d,%d) error(%v) or is nil)", rp.Oid, rp.RpID, err)
}
if mtime != 0 {
rp.MTime = mtime
}
if rp.IsNormal() {
tx.Rollback()
return fmt.Errorf("reply(%d,%d) already is normal", rp.Oid, rp.RpID)
}
rows, err = s.dao.Reply.TxUpState(tx, rp.Oid, rp.RpID, state, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("TxUpState error(%v) or rows(%d)", err, rows)
}
if isRoot {
count = rp.RCount + 1
} else {
rootReply, err = s.dao.Reply.Get(c, rp.Oid, rp.Root)
if err != nil {
tx.Rollback()
return err
}
count = 1
}
if isRoot {
rows, err = s.dao.Subject.TxIncrRCount(tx, rp.Oid, rp.Type, rp.MTime.Time())
} else {
rows, err = s.dao.Reply.TxIncrRCount(tx, rp.Oid, rp.Root, rp.MTime.Time())
}
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("TxIncrRCount error(%v) or rows(%d)", err, rows)
}
if isRoot || rootReply != nil && rootReply.IsNormal() {
rows, err = s.dao.Subject.TxIncrACount(tx, rp.Oid, rp.Type, count, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("TxIncrACount error(%v) or rows(%d)", err, rows)
}
}
return tx.Commit()
}
func (s *Service) tranDel(c context.Context, rp *model.Reply, state int8, sub *model.Subject, isRoot bool) error {
var (
count int
rows int64
rootReply *model.Reply
err error
tx *xsql.Tx
)
if tx, err = s.beginTran(c); err != nil {
return err
}
mtime := rp.MTime
if rp, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.RpID); err != nil || rp == nil {
tx.Rollback()
return fmt.Errorf("s.dao.Reply.GetForUpdate(%d,%d) error(%v) or is nil)", rp.Oid, rp.RpID, err)
}
if mtime != 0 {
rp.MTime = mtime
}
if rp.IsDeleted() || rp.AttrVal(reply.ReplyAttrAdminTop) == 1 {
tx.Rollback()
return fmt.Errorf("reply(%d,%d) already deleted", rp.Oid, rp.RpID)
}
rows, err = s.dao.Reply.TxUpState(tx, rp.Oid, rp.RpID, state, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("error(%v) or rows(%d)", err, rows)
}
if rp.IsNormal() {
if isRoot {
count = rp.RCount + 1
rows, err = s.dao.Subject.TxDecrACount(tx, rp.Oid, rp.Type, count, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("error(%v) or rows(%d)", err, rows)
}
rows, err = s.dao.Subject.TxDecrCount(tx, rp.Oid, rp.Type, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("SubjectTxDecrCount error(%v) or rows(%d)", err, rows)
}
} else {
if rootReply, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.Root); err != nil {
tx.Rollback()
return err
}
if rootReply != nil {
if rootReply.IsNormal() {
rows, err = s.dao.Subject.TxDecrACount(tx, rp.Oid, rp.Type, 1, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("error(%v) or rows(%d)", err, rows)
}
}
rows, err = s.dao.Reply.TxDecrCount(tx, rp.Oid, rp.Root, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("ReplyTxDecrCount error(%v) or rows(%d)", err, rows)
}
}
}
}
if rp.AttrVal(model.ReplyAttrUpperTop) == 1 {
rp.AttrSet(0, model.ReplyAttrUpperTop)
sub.AttrSet(0, model.SubAttrUpperTop)
sub.TopSet(0, model.SubAttrUpperTop, 0)
if rows, err = s.dao.Subject.TxUpMeta(tx, sub.Oid, sub.Type, sub.Meta, rp.MTime.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
return fmt.Errorf("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
}
if rows, err = s.dao.Subject.TxUpAttr(tx, sub.Oid, sub.Type, sub.Attr, rp.MTime.Time()); err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("dao.Upattr(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
}
//rp.State = model.ReplyStateTop
if rows, err = s.dao.Reply.TxUpAttr(tx, rp.Oid, rp.RpID, rp.Attr, rp.MTime.Time()); err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("dao.Reply.UpState(%v, %d) error(%v) or rows(%d)", rp, rp.State, err, rows)
}
}
if rp.State == model.ReplyStateMonitor || rp.State == model.ReplyStateAudit {
if _, err = s.dao.Subject.TxDecrMCount(tx, rp.Oid, rp.Type, rp.MTime.Time()); err != nil {
tx.Rollback()
log.Error("dao.Reply.TxDecrMCount(%v) error(%v)", rp, err)
return fmt.Errorf("dao.Reply.TxDecrMCount error(%v)", err)
}
}
return tx.Commit()
}
func (s *Service) eraseAuditIndex(c context.Context, rp *model.Reply) (err error) {
var rs []*model.Reply
if rp.Root == 0 && rp.Parent == 0 {
if rs, err = s.dao.Reply.GetsByRoot(c, rp.Oid, rp.RpID, rp.Type, model.ReplyStateAudit); err != nil {
return
}
}
if err = s.dao.Redis.DelAuditIndexs(c, append(rs, rp)...); err != nil {
return
}
return
}
func (s *Service) addReplyCache(c context.Context, rp *model.Reply) (err error) {
var isRoot = rp.Root == 0 && rp.Parent == 0
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
if isRoot && rp.IsTop() {
if err = s.dao.Mc.AddTop(c, rp); err != nil {
log.Error("s.dao.Mc.AddTop(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
}
return
}

View File

@@ -0,0 +1,12 @@
package service
import (
"context"
"go-common/app/job/main/reply/model/reply"
)
// ListBusiness return all non-deleted business record.
func (s *Service) ListBusiness(c context.Context) (business []*reply.Business, err error) {
return s.dao.Business.ListBusiness(c)
}

View File

@@ -0,0 +1,121 @@
package service
import (
"context"
"encoding/json"
"time"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
"go-common/library/log"
)
func (s *Service) folderHanlder(ctx context.Context, msg *consumerMsg) {
var d struct {
Op string `json:"op"`
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Root int64 `json:"root"`
}
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
switch d.Op {
case "re_idx":
s.recoverFolderIdx(ctx, d.Oid, d.Tp, d.Root)
// case "marker":
// 删除折叠评论后需要查询是否需要取消标记
// s.marker.Do(ctx, func(ctx context.Context) {
// s.handleHasFoldedMark(ctx, d.Oid, d.Tp, d.Root)
// })
default:
return
}
}
// handleFolded ...
func (s *Service) handleFolded(ctx context.Context, rp *reply.Reply) {
sub, root, err := s.handleHasFoldedMark(ctx, rp.Oid, rp.Type, rp.Root)
if err != nil {
return
}
if sub != nil {
s.dao.Mc.DeleteSub(ctx, sub.Oid, sub.Type)
}
if root != nil {
s.dao.Mc.DeleteReply(ctx, root.RpID)
}
s.remFoldedCache(ctx, rp)
}
func (s *Service) recoverFolderIdx(ctx context.Context, oid int64, tp int8, root int64) {
rps, err := s.dao.Reply.FoldedReplies(ctx, oid, tp, root)
if err != nil || len(rps) == 0 {
return
}
// 折叠根评论
if root == 0 {
s.dao.Redis.AddFolderBatch(ctx, reply.FolderKindSub, oid, rps)
} else {
s.dao.Redis.AddFolderBatch(ctx, reply.FolderKindRoot, root, rps)
}
}
func (s *Service) handleHasFoldedMark(ctx context.Context, oid int64, tp int8, root int64) (sub *reply.Subject, reply *reply.Reply, err error) {
var (
tx *sql.Tx
count int
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
// 锁subject表
if sub, err = s.dao.Subject.GetForUpdate(tx, oid, tp); err != nil {
tx.Rollback()
return
}
if count, err = s.dao.Reply.TxCountFoldedReplies(tx, oid, tp, root); err != nil || count > 0 {
tx.Rollback()
return
}
// 折叠根评论
if root == 0 {
if !sub.HasFolded() {
tx.Rollback()
return
}
sub.UnmarkHasFolded()
if _, err = s.dao.Subject.TxUpAttr(tx, oid, tp, sub.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
} else {
if reply, err = s.dao.Reply.GetForUpdate(tx, oid, root); err != nil {
tx.Rollback()
return
}
if !reply.HasFolded() {
tx.Rollback()
return
}
reply.UnmarkHasFolded()
if _, err = s.dao.Reply.TxUpAttr(tx, oid, root, reply.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
return
}
return
}
// remFoldedCache ...
func (s *Service) remFoldedCache(ctx context.Context, rp *reply.Reply) {
if rp.IsRoot() {
s.dao.Redis.RemFolder(ctx, reply.FolderKindSub, rp.Oid, rp.RpID)
} else {
s.dao.Redis.RemFolder(ctx, reply.FolderKindRoot, rp.Root, rp.RpID)
}
}

View File

@@ -0,0 +1,379 @@
package service
import (
"context"
"encoding/json"
"sort"
"time"
model "go-common/app/job/main/reply/model/reply"
"go-common/library/log"
)
const (
_replySliceNum = 20000
)
func dialogMapByRoot(rootID int64, rps []*model.RpItem, oid int64, tp int8) (dialogMap map[int64][]*model.RpItem) {
length := len(rps)
dialogMap = make(map[int64][]*model.RpItem)
// 根评论下没有评论
if length == 0 {
return
}
// 这里由于种种原因, 可能子评论的ID比父评论大故按Floor排序
// 按Floor严格排序保证父评论也几乎是升序排列提高后续的命中率
sort.Slice(rps, func(i, j int) bool {
return rps[i].Floor < rps[j].Floor
})
for i := 0; i < length; i++ {
if rps[i] == nil {
return
}
if rps[i].Parent == rootID {
// 对话根评论
continue
}
// i-1 must >= 0, 因为第一个元素必然是对话根评论which parrent=root, 出现这种情况说明数据是脏的
if i-1 < 0 {
log.Error("invalid data, rootID(%d), rpID(%d), parrent(%d) oid(%d) type(%d)", rootID, rps[i].ID, rps[i].Parent, oid, tp)
return
}
if rps[i].Parent == rps[i-1].Parent {
// 和上一条评论回复同一条评论的情况按ID排序所以这种情况的概率会很高
rps[i].Next = rps[i-1].Next
} else {
var j int
if sort.IsSorted(model.RpItems(rps)) {
// 和上一条评论回复不同评论的情况, 其父评论一定在它之前
j = sort.Search(i, func(n int) bool {
return rps[n].ID >= rps[i].Parent
})
} else {
for index := range rps[:i] {
if rps[index].ID == rps[i].Parent {
j = index
break
}
}
}
// search 如果返回j==i说明没搜索,或者遍历到了最后一个, 这种情况说明数据是脏的
if j == i {
log.Error("invalid data, rootID(%d), rpID(%d), parrent(%d) oid(%d) type(%d)", rootID, rps[i].ID, rps[i].Parent, oid, tp)
return nil
}
rps[i].Next = rps[j]
}
}
tmp := new(struct {
ID int64
DiaglogID int64
})
for i := 0; i < length; i++ {
if rps[i] == nil {
return
}
next := rps[i].Next
if next == nil {
// 如果是对话根评论
dialogMap[rps[i].ID] = append(dialogMap[rps[i].ID], rps[i])
} else if next.ID == tmp.ID {
// 这里tmp缓存了上一个评论的父评论, 减少查找的次数
// 如果跟上一条评论评论的是同一条评论,则可以直接加进上一个dialog
dialogMap[tmp.DiaglogID] = append(dialogMap[tmp.DiaglogID], rps[i])
} else {
depth := 0
for next.Next != nil {
next = next.Next
depth++
if depth > 10000 {
for i := range rps {
log.Error("rp: %v", rps[i])
}
log.Error("recursive reach max depth")
return nil
}
}
}
}
return
}
func (s *Service) setDialogByRoot(c context.Context, oid int64, tp int8, rootID int64) (err error) {
// 循环获取某个根评论下的所有子评论
rps, err := s.dao.Reply.FixDialogGetRepliesByRoot(c, oid, tp, rootID)
if err != nil {
log.Error("fix dialog error (%v)", err)
return
}
//根据所有子评论构造 key为二级父评论(即对话根评论), value为二级父评论下的所有子评论的map
dialogMap := dialogMapByRoot(rootID, rps, oid, tp)
for k, v := range dialogMap {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
s.dao.Reply.FixDialogSetDialogBatch(c, oid, k, ids)
}
return
}
// actionRecoverFixDialog fix dialog
func (s *Service) actionRecoverFixDialog(c context.Context, msg *consumerMsg) {
var (
err error
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Root int64 `json:"root"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
s.setDialogByRoot(c, d.Oid, d.Tp, d.Root)
}
func (s *Service) actionRecoverDialog(c context.Context, msg *consumerMsg) {
var (
ok bool
err error
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Root int64 `json:"root"`
Dialog int64 `json:"dialog"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if ok, err = s.dao.Redis.ExpireDialogIndex(c, d.Dialog); err == nil && !ok {
rps, err := s.dao.Reply.GetByDialog(c, d.Oid, d.Tp, d.Root, d.Dialog)
if err != nil {
return
}
err = s.dao.Redis.AddDialogIndex(c, d.Dialog, rps)
if err != nil {
log.Error("s.dao.Redis.AddDialogIndex() error (%v)", err)
return
}
}
}
func (s *Service) acionRecoverFloorIdx(c context.Context, msg *consumerMsg) {
var (
err error
rCount int
limit int
ok bool
sub *model.Subject
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Count int `json:"count"`
Floor int `json:"floor"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
sub, err = s.getSubject(c, d.Oid, d.Tp)
if err != nil || sub == nil {
log.Error("s.getSubject(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
return
}
if sub.RCount == 0 {
return
}
startFloor := sub.Count + 1
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByFloor); err == nil && ok {
startFloor, err = s.dao.Redis.MinScore(c, d.Oid, d.Tp, model.SortByFloor)
if err != nil {
log.Error("s.dao.Redis.MinScore(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
return
}
if startFloor <= 1 {
if startFloor != -1 {
if err = s.dao.Redis.AddFloorIndexEnd(c, d.Oid, d.Tp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexEnd(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
}
return
}
if d.Count > 0 {
rCount, err = s.dao.Redis.CountReplies(c, d.Oid, d.Tp, model.SortByFloor)
if err != nil {
log.Error("s.dao.Redis.CountReplies(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
return
}
}
}
if d.Count > 0 {
limit = d.Count - rCount
} else if d.Floor > 0 {
limit = startFloor - d.Floor
} else {
log.Warn("RecoverFloorByCount(%d,%d) count(%d) or floor(%d) invalid!", d.Oid, d.Tp, d.Floor, d.Count)
return
}
limit += s.batchNumber
if limit < (s.batchNumber / 2) {
return
} else if limit < s.batchNumber {
limit = s.batchNumber
}
rs, err := s.dao.Reply.GetByFloorLimit(c, d.Oid, d.Tp, startFloor, limit)
if err != nil {
log.Error("s.dao.Reply.GetByFloorLimit(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
return
}
if err = s.dao.Redis.AddFloorIndex(c, d.Oid, d.Tp, rs...); err != nil {
log.Error("s.dao.Redis.AddFloorIndex(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
if len(rs) < limit {
if err = s.dao.Redis.AddFloorIndexEnd(c, d.Oid, d.Tp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexEnd(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
return
}
}
// actionRecoverIndex recover index of archive's reply
func (s *Service) actionRecoverIndex(c context.Context, msg *consumerMsg) {
var (
err error
ok bool
sub *model.Subject
rs []*model.Reply
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Sort int8 `json:"sort"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if d.Oid <= 0 || !model.CheckSort(d.Sort) {
log.Error("The structure of doActionRecoverIndex msg.Data(%s) was wrong", msg.Data)
return
}
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, d.Sort); err == nil && !ok {
sub, err = s.getSubject(c, d.Oid, d.Tp)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d,%d) err(%v)", d.Oid, d.Tp, err)
return
}
if d.Sort == model.SortByFloor {
rs, err = s.dao.Reply.GetAllInSlice(c, d.Oid, d.Tp, sub.Count, _replySliceNum)
if err != nil {
log.Error("dao.Reply.GetAllInSlice(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
// floor index
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByFloor); err == nil && !ok {
if err = s.dao.Redis.AddFloorIndex(c, d.Oid, d.Tp, rs...); err != nil {
log.Error("s.dao.Redis.AddFloorIndex(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
if err = s.dao.Redis.AddFloorIndexEnd(c, d.Oid, d.Tp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexEnd(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
}
} else if d.Sort == model.SortByLike {
rs, err = s.dao.Reply.GetByLikeLimit(c, d.Oid, d.Tp, 30000)
if err != nil {
log.Error("dao.Reply.GetAllInSlice(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
// like index
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByLike); err == nil && !ok {
rpts, _ := s.dao.Report.GetMapByOid(c, d.Oid, d.Tp)
if err = s.dao.Redis.AddLikeIndexBatch(c, d.Oid, d.Tp, rpts, rs...); err != nil {
log.Error("s.dao.Redis.AddLikeIndexBatch(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
}
} else if d.Sort == model.SortByCount {
rs, err = s.dao.Reply.GetByCountLimit(c, d.Oid, d.Tp, 20000)
if err != nil {
log.Error("dao.Reply.GetAllInSlice(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
// count index
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByCount); err == nil && !ok {
if err = s.dao.Redis.AddCountIndexBatch(c, d.Oid, d.Tp, rs...); err != nil {
log.Error("s.dao.Redis.AddCountIndex(%d, %d) error(%v)", d.Oid, d.Tp, err)
}
}
}
// 回源index时把top缓存初始化减少attr扫表慢查询
if n := sub.TopCount(); n > 0 {
for _, r := range rs {
if r.IsTop() {
top := model.SubAttrAdminTop
if r.IsUpTop() {
top = model.SubAttrUpperTop
}
err = sub.TopSet(r.RpID, top, 1)
if err == nil {
_, err = s.dao.Subject.UpMeta(c, d.Oid, d.Tp, sub.Meta, time.Now())
if err != nil {
log.Error("s.dao.Subject.UpMeta(%d,%d,%d) failed!err:=%v ", r.RpID, r.Oid, d.Tp, err)
}
s.dao.Mc.AddSubject(c, sub)
}
// get reply with content
var rp *model.Reply
rp, err = s.getReply(c, d.Oid, r.RpID)
if err == nil && rp != nil {
s.dao.Mc.AddTop(c, rp)
}
n--
}
if n == 0 {
break
}
}
}
}
}
// actionRecoverRootIndex recover index of root reply
func (s *Service) actionRecoverRootIndex(c context.Context, msg *consumerMsg) {
var (
err error
ok bool
rs []*model.Reply
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Root int64 `json:"root"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if d.Oid <= 0 || d.Root <= 0 {
log.Error("The structure of doActionRecoverRootIndex msg.Data(%s) was wrong", msg.Data)
return
}
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, d.Root); err == nil && !ok {
if rs, err = s.dao.Reply.GetAllByRoot(c, d.Oid, d.Root, d.Tp); err != nil {
log.Error("dao.Reply.GetAllReply(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
if err = s.dao.Redis.AddNewChildIndex(c, d.Root, rs...); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
}
}

View File

@@ -0,0 +1,26 @@
package service
import (
"context"
"encoding/json"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestRecoverIdx(t *testing.T) {
var c = context.Background()
Convey("recover idx", t, WithService(func(s *Service) {
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Count int `json:"count"`
Floor int `json:"floor"`
}
d.Oid = 78
d.Tp = 2
d.Count = 20
content, _ := json.Marshal(&d)
s.acionRecoverFloorIdx(c, &consumerMsg{Data: content})
}))
}

View File

@@ -0,0 +1,568 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
artmdl "go-common/app/interface/openplatform/article/model"
model "go-common/app/job/main/reply/model/reply"
accmdl "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
arcmdl "go-common/app/service/main/archive/model/archive"
epmdl "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
"go-common/library/log"
)
const (
_mcReply = "1_1_1"
_mcCntArticle = "1_1_4"
_mcCntDynamic = "1_1_5"
_mcCntClip = "1_1_6"
_mcCntAlbum = "1_1_7"
_mcCntArchive = "1_1_8"
_msgTitleSize = 40
_msgContentSize = 80
)
func (s *Service) notifyReply(c context.Context, sub *model.Subject, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if len(rp.Content.Ats) > 0 {
title, link, jump, nativeJump, msg := s.messageInfo(c, rp)
if link != "" {
atmt := fmt.Sprintf("#{%s}{\"%s\"}评论中@了你", title, link)
cont := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
if err := s.messageDao.At(c, rp.Mid, rp.Content.Ats, atmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.At failed , mid(%d) err(%v)", rp.Mid, err)
}
}
}
if err := s.notifyCnt(c, sub, rp); err != nil {
log.Error("s.notifyCnt(%v,%v) error(%v)", sub, rp, err)
}
})
}
func (s *Service) notifyReplyReply(c context.Context, sub *model.Subject, rootRp, parentRp, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if err := s.notifyCnt(c, sub, rp); err != nil {
log.Error("s.notifyCnt(%v,%v) error(%v)", sub, rp, err)
}
// notify parent reply
if rp.Mid == rootRp.Mid && rp.Root == rp.Parent && len(rp.Content.Ats) == 0 {
return
}
title, link, jump, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
// 5.29 改为根评论内容推送
//rpmt = fmt.Sprintf("#{%s}{\"%s\"}评论中回复了你", title, link)
rpmt := []rune(parentRp.Content.Message)
if len(rpmt) > _msgContentSize {
rpmt = rpmt[:_msgContentSize]
}
atmt := fmt.Sprintf("#{%s}{\"%s\"}评论中@了你", title, link)
cont := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
// notify
if rp.Mid != rootRp.Mid && !s.getBlackListRelation(c, rootRp.Mid, rp.Mid) {
if err := s.messageDao.Reply(c, _mcReply, "", rp.Mid, rootRp.Mid, string(rpmt), cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", rp.Mid, rootRp.Mid, err)
}
}
if rp.Root != rp.Parent {
if parentRp != nil && rootRp.Mid != parentRp.Mid && rp.Mid != parentRp.Mid && !s.getBlackListRelation(c, parentRp.Mid, rp.Mid) {
if err := s.messageDao.Reply(c, _mcReply, "", rp.Mid, parentRp.Mid, string(rpmt), cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", rp.Mid, parentRp.Mid, err)
}
}
}
var ats []int64
for _, mid := range rp.Content.Ats {
if mid != parentRp.Mid {
ats = append(ats, mid)
}
}
if len(ats) > 0 {
if err := s.messageDao.At(c, rp.Mid, ats, atmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.At failed , mid(%d), err(%v)", rp.Mid, err)
}
}
})
}
func (s *Service) notifyLike(c context.Context, mid int64, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if ok, num := s.notifyLikeNum(c, rp, mid); ok {
_, _, jump, nativeJump, msg := s.messageInfo(c, rp)
if jump == "" {
return
}
// NOTE content and title is opposite
cont := fmt.Sprintf("等%d人赞了你的回复", num)
rpmt := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
if err := s.messageDao.Like(c, mid, rp.Mid, rpmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", mid, rp.Mid, err)
}
} else {
log.Warn("Didn't satify notify condition, omit notify!")
}
})
}
// notifyLike check if need notify user when receive like
func (s *Service) notifyLikeNum(c context.Context, rp *model.Reply, mid int64) (ok bool, num int64) {
if rp.Mid == mid || rp.Like <= 0 {
ok = false
return
}
num = int64(rp.Like)
// NOTE if num >1000 send when num%1000==0
if num < 10 || (num < 100 && num%10 == 0) || (num < 1000 && num%100 == 0) || num%1000 == 0 {
ok = true
}
return
}
func (s *Service) notifyCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
max, err := s.dao.Redis.NotifyCnt(c, sub.Oid, sub.Type)
if err != nil {
log.Error("redis.NotifyCnt(%d,%d) error(%v)", sub.Oid, sub.Type, err)
return
}
if sub.ACount <= max {
log.Warn("notifyCnt ignore oid:%d type:%d current:%d max:%d", sub.Oid, sub.Type, sub.ACount, max)
return
}
if err = s.dao.Redis.SetNotifyCnt(c, sub.Oid, sub.Type, sub.ACount); err != nil {
log.Error("redis.SetNotifyCnt(%d,%d,%d) error(%v)", sub.Oid, sub.Type, sub.ACount, err)
return
}
switch sub.Type {
case model.SubTypeVideo:
return s.notifyArchiveCnt(c, sub, rp)
case model.SubTypeArticle:
return s.notifyArticleCnt(c, sub, rp)
case model.SubTypeDynamic:
return s.notifyDynamicCnt(c, sub, rp, _mcCntDynamic)
case model.SubTypeLiveVideo:
return s.notifyDynamicCnt(c, sub, rp, _mcCntClip)
case model.SubTypeLivePicture:
return s.notifyDynamicCnt(c, sub, rp, _mcCntAlbum)
default:
return
}
}
func (s *Service) notifyDynamicCnt(c context.Context, sub *model.Subject, rp *model.Reply, mc string) (err error) {
if !shouldNotifyLow(sub.ACount) {
return
}
title, link, _, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("#{%s}{\"%s\"}收到了第%d条评论", title, link, sub.ACount)
notifyContent := fmt.Sprintf("#{%s}{\"%s\"}", msg, link)
if err = s.messageDao.Reply(c, mc, resID, rp.Mid, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func (s *Service) notifyArchiveCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !shouldNotifyLow(sub.ACount) {
return
}
title, link, _, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("你的投稿收到了第%d条评论", sub.ACount)
notifyContent := fmt.Sprintf("你投稿的视频“#{%s}{\"%s\"}”收到了第%d条评论『%s』", title, link, sub.ACount, msg)
if err = s.messageDao.System(c, _mcCntArchive, resID, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.System(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func (s *Service) notifyArticleCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !shouldNotifyMiddle(sub.ACount) {
return
}
title, link, _, nativeJump, _ := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("你的专栏文章评论数达到了%d", sub.ACount)
notifyContent := fmt.Sprintf("你投稿的专栏文章“#{%s}{\"%s\"}”评论数达到了%d去回应一下大家的评论吧 #{点击前往}{\"%s\"}", title, link, sub.ACount, link)
if err = s.messageDao.System(c, _mcCntArticle, resID, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.System(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func shouldNotifyLow(n int) (ok bool) {
switch {
case n <= 0:
ok = false
case n == 1 || n == 10 || n == 30 || n == 50:
ok = true
case n <= 1000:
ok = (n%100 == 0)
default:
ok = (n%10000 == 0)
}
return
}
func shouldNotifyMiddle(n int) (ok bool) {
switch {
case n <= 0:
ok = false
case n <= 10:
ok = true
case n <= 100:
ok = (n%10 == 0)
case n <= 1000:
ok = (n%100 == 0)
default:
ok = (n%10000 == 0)
}
return
}
// filterViolationMsg every two characters, the third character processing for *.
func filterViolationMsg(msg string) string {
s := []rune(msg)
for i := 0; i < len(s); i++ {
if i%3 != 0 {
s[i] = '*'
}
}
return string(s)
}
// moralAndNotify del moral and notify user.
func (s *Service) moralAndNotify(c context.Context, rp *model.Reply, moral int, notify bool, rptMid, adid int64, adname, remark string, reason, freason int8, ftime int64, isPunish bool) (err error) {
title, link, _, _, msg := s.messageInfo(c, rp)
smsg := []rune(msg)
if len(smsg) > 50 {
smsg = smsg[:50]
}
if moral > 0 {
reason := "发布的评论违规并被管理员删除 - " + string(smsg)
if rptMid > 0 {
reason = "发布的评论被举报并被管理员删除 - " + string(smsg)
}
arg := &accmdl.MoralReq{
Mid: rp.Mid,
Moral: -float64(moral),
Oper: adname,
Reason: reason,
Remark: remark,
}
if _, err = s.accSrv.AddMoral3(c, arg); err != nil {
log.Error("s.accSrv.AddMoral3(%d) error(%v)", rp.Mid, err)
}
}
msg = filterViolationMsg(msg)
if title != "" && link != "" && rptMid > 0 {
if err = s.reportNotify(c, rp, title, link, msg, ftime, reason, freason, isPunish); err != nil {
log.Error("s.reportNotify(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
}
}
if !notify {
return
}
if title != "" && link != "" {
// notify message
mt := "评论违规处理通知"
mc := fmt.Sprintf("您好,您在#{%s}{\"%s\"}下的评论 『%s』 ", title, link, msg)
if rptMid > 0 {
mc = fmt.Sprintf("您好,根据用户举报,您在#{%s}{\"%s\"}下的评论 『%s』 ", title, link, msg)
}
if isPunish {
mc += ",已被处罚"
} else {
mc += ",已被移除"
}
// forbidden
if ftime > 0 {
mc += fmt.Sprintf(",并被封禁%d天。", ftime)
} else if ftime == -1 {
mc += ",并被永久封禁。"
} else {
mc += "。"
}
// forbid reason
if ar, ok := model.ForbidReason[freason]; ok {
mc += "理由:" + ar + "。"
// community rules
switch {
case freason == model.ForbidReasonSpoiler || freason == model.ForbidReasonAd || freason == model.ForbidReasonUnlimitedSign || freason == model.ForbidReasonMeaningless:
mc += model.NotifyComRules
case freason == model.ForbidReasonProvoke || freason == model.ForbidReasonAttack:
mc += model.NotifyComProvoke
default:
mc += model.NofityComProhibited
}
} else { // report reason
if ar, ok := model.ReportReason[reason]; ok {
mc += "理由:" + ar + "。"
}
// community rules
switch {
case reason == model.ReportReasonSpoiler || reason == model.ReportReasonAd || reason == model.ReportReasonUnlimitedSign || reason == model.ReportReasonMeaningless:
mc += model.NotifyComRules
case reason == model.ReportReasonUnrelated:
mc += model.NotifyComUnrelated
case reason == model.ReportReasonProvoke || reason == model.ReportReasonAttack:
mc += model.NotifyComProvoke
default:
mc += model.NofityComProhibited
}
}
// send the message
if err = s.messageDao.DeleteReply(c, rp.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.messageDao.DeleteReply failed, (%d) error(%v)", rp.Mid, err)
}
log.Info("notify oid:%d type:%d rpID:%d reason:%d content:%s", rp.Oid, rp.Type, rp.RpID, reason, mc)
} else {
log.Warn("no notify oid:%d type:%d rpid:%d", rp.Oid, rp.Type, rp.RpID)
}
return
}
func (s *Service) reportNotify(c context.Context, rp *model.Reply, title, link, msg string, ftime int64, reason, freason int8, isPunish bool) (err error) {
var (
rptUser *model.ReportUser
rptUsers map[int64]*model.ReportUser
)
mt := "举报处理结果通知"
mc := fmt.Sprintf("您好,您在#{%s}{\"%s\"}下举报的评论 『%s』 ", title, link, msg)
if isPunish {
mc += "已被处罚"
} else {
mc += "已被移除"
}
// forbidden
if ftime > 0 {
mc += fmt.Sprintf(",并被封禁%d天。", ftime)
} else if ftime == -1 {
mc += ",该用户已被永久封禁。"
} else {
mc += "。"
}
// forbid reason
if ar, ok := model.ForbidReason[freason]; ok {
mc += "理由:" + ar + "。"
} else if ar, ok := model.ReportReason[reason]; ok {
mc += "理由:" + ar + "。"
}
// community rules
mc += model.NotifyComRulesReport
if rptUsers, err = s.dao.Report.GetUsers(c, rp.Oid, rp.Type, rp.RpID); err != nil {
log.Error("reportUser.GetUsers(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
return
}
for _, rptUser = range rptUsers {
// send the message
if err = s.messageDao.AcceptReport(c, rptUser.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.messageDao.DeleteReply failed, (%d) error(%v)", rp.Mid, err)
}
}
if _, err = s.dao.Report.SetUserReported(c, rp.Oid, rp.Type, rp.RpID, rp.MTime.Time()); err != nil {
log.Error("s.dao.Report.SetUserReported(%d, %d, %d) error(%v)", rp.Oid, rp.Type, rp.RpID)
}
return
}
func (s *Service) messageInfo(c context.Context, rp *model.Reply) (title, link, jump, nativeJump, msg string) {
var (
err error
native bool
subType int
extraIntentID int64
)
switch rp.Type {
case model.SubTypeVideo:
var (
m *api.Arc
uri *url.URL
)
arg := &arcmdl.ArgAid2{
Aid: rp.Oid,
}
m, err = s.arcSrv.Archive3(c, arg)
if err != nil || m == nil {
log.Error("s.arcSrv.Archive3(%v) ret:%v error(%v)", arg, m, err)
return
}
if m.AttrVal(arcmdl.AttrBitIsBangumi) == 1 {
req := &epmdl.EpAidReq{
Aids: []int32{int32(rp.Oid)},
}
resp, err1 := s.bangumiSrv.ListByAids(c, req)
if err1 != nil {
log.Error("s.bangumiSrv.ListByAids(%v, %v) error(%v)", c, req, err1)
return
}
if resp.Infos[int32(rp.Oid)] != nil {
extraIntentID = int64(resp.Infos[int32(rp.Oid)].EpisodeId)
}
subType = 1
}
if m.RedirectURL != "" {
// NOTE mobile jump
if uri, err = url.Parse(m.RedirectURL); err == nil {
q := uri.Query()
q.Set("aid", strconv.FormatInt(rp.Oid, 10))
uri.RawQuery = q.Encode()
link = uri.String()
}
} else {
link = fmt.Sprintf("http://www.bilibili.com/video/av%d/", rp.Oid)
}
title = m.Title
native = true
case model.SubTypeTopic:
if title, link, err = s.noticeDao.Topic(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Topic(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeDrawyoo:
if title, link, err = s.noticeDao.Drawyoo(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Drawyoo(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeActivity:
if title, link, err = s.noticeDao.Activity(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Activity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeForbiden:
title, link, err = s.noticeDao.Ban(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeNotice:
title, link, err = s.noticeDao.Notice(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeActArc:
if title, link, err = s.noticeDao.ActivitySub(c, rp.Oid); err != nil {
log.Error("s.noticeDao.ActivitySub(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeArticle:
var m map[int64]*artmdl.Meta
arg := &artmdl.ArgAids{
Aids: []int64{rp.Oid},
}
m, err = s.articleSrv.ArticleMetas(c, arg)
if err != nil || m == nil {
log.Error("s.articleSrv.ArticleMetas(%v) ret:%v error(%v)", arg, m, err)
return
}
if meta, ok := m[rp.Oid]; ok {
title = meta.Title
link = fmt.Sprintf("http://www.bilibili.com/read/cv%d", rp.Oid)
}
case model.SubTypeLiveVideo:
if title, link, err = s.noticeDao.LiveSmallVideo(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveSmallVideo(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeLiveAct:
if title, link, err = s.noticeDao.LiveActivity(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveActivity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeLiveNotice:
//if title, link, err = s.noticeDao.LiveNotice(c, rp.Oid); err != nil {
// log.Error("s.noticeDao.LiveNotice(%d) error(%v)", rp.Oid, err)
// return
//}
// NOTE 忽略直播公告跳转链接
return
case model.SubTypeLivePicture:
if title, link, err = s.noticeDao.LivePicture(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LivePiture(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeCredit:
if title, link, err = s.noticeDao.Credit(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Credit(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeDynamic:
if title, link, err = s.noticeDao.Dynamic(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Dynamic(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeAudio:
if title, link, err = s.noticeDao.Audio(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Audio(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeAudioPlaylist:
if title, link, err = s.noticeDao.AudioPlayList(c, rp.Oid); err != nil {
log.Error("s.noticeDao.AudioPlayList(%d) error(%v)", rp.Oid, err)
return
}
native = true
default:
return
}
tmp := []rune(title)
if len(tmp) > _msgTitleSize {
title = string(tmp[:_msgTitleSize])
}
jump = fmt.Sprintf("%s#reply%d", link, rp.RpID)
tmp = []rune(rp.Content.Message)
if len(tmp) > _msgContentSize {
msg = string(tmp[:_msgContentSize])
} else {
msg = rp.Content.Message
}
if native {
rootID := rp.Root
if rootID == 0 {
rootID = rp.RpID
}
nativeJump = fmt.Sprintf("bilibili://comment/detail/%d/%d/%d/?subType=%d&anchor=%d&showEnter=1&extraIntentId=%d", rp.Type, rp.Oid, rootID, subType, rp.RpID, extraIntentID)
}
return
}
func extraInfo(newJump string) string {
var a = struct {
CmNewURL struct {
Title string `json:"title"`
Content string `json:"content"`
} `json:"cm_new_url"`
}{
CmNewURL: struct {
Title string `json:"title"`
Content string `json:"content"`
}{
Title: newJump,
Content: newJump,
},
}
b, _ := json.Marshal(a)
return string(b)
}

View File

@@ -0,0 +1,760 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"go-common/library/database/elastic"
"net/url"
"regexp"
"strings"
"time"
"go-common/app/job/main/reply/conf"
"go-common/app/job/main/reply/model/reply"
model "go-common/app/job/main/reply/model/reply"
accmdl "go-common/app/service/main/account/api"
assmdl "go-common/app/service/main/assist/model/assist"
relmdl "go-common/app/service/main/relation/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
xhttp "go-common/library/net/http/blademaster"
)
var (
_atReg = regexp.MustCompile(`@([^\s^:^,^@]+)`)
_topicReg = regexp.MustCompile(`#([^\n^@^#^\x{1F000}-\x{1F02F}^\x{1F0A0}-\x{1F0FF}^\x{1F100}-\x{1F64F}^\x{1F680}-\x{1F6FF}^\x{1F910}-\x{1F96B}^\x{1F980}-\x{1F9E0}]{1,32})#`)
_urlReg = regexp.MustCompile(`(((http:\/\/|https:\/\/)[a-z0-9A-Z]+\.(bilibili|biligame)\.com[a-z0-9A-Z\/\.\$\*\?~=#!%@&-]*)|((http:\/\/|https:\/\/)(acg|b23)\.tv[a-z0-9A-Z\/\.\$\*\?~=#!@&]*))`)
_avReg = regexp.MustCompile(`#(cv\d+)|#(av\d+)|#(vc\d+)`)
searchHTTPClient *xhttp.Client
errReplyContentNotFound = errors.New("reply content not found")
)
const (
_appIDReply = "reply"
_appIDReport = "replyreport"
timeFormat = "2006-01-02 15:03:04"
// event
_eventReply = "reply"
_eventHate = "hate"
_eventLike = "like"
_eventLikeCancel = "like_cancel"
_eventHateCancel = "hate_cancel"
)
func (s *Service) beginTran(c context.Context) (*xsql.Tx, error) {
return s.dao.BeginTran(c)
}
func (s *Service) actionAdd(c context.Context, msg *consumerMsg) {
var rp *model.Reply
if err := json.Unmarshal([]byte(msg.Data), &rp); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if rp.RpID == 0 || rp.Oid == 0 || rp.Content == nil {
log.Error("The structure of reply(%s) from rpCh was wrong", msg.Data)
return
}
if rp.Root == 0 && rp.Parent == 0 {
s.addReply(c, rp)
} else {
s.addReplyReply(c, rp)
}
}
func (s *Service) tranAdd(c context.Context, rp *model.Reply, is bool) (err error) {
tx, err := s.beginTran(c)
if err != nil {
log.Error("reply(%s) beginTran error(%v)", rp, err)
return
}
var rows int64
defer func() {
if err == nil && rows == 0 {
err = errors.New("sql: transaction add reply failed")
}
}()
if is {
if rp.IsNormal() {
rows, err = s.dao.Subject.TxIncrCount(tx, rp.Oid, rp.Type, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
} else {
rows, err = s.dao.Subject.TxIncrFCount(tx, rp.Oid, rp.Type, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
}
} else {
var rootReply *model.Reply
if rootReply, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.Root); err != nil {
tx.Rollback()
return err
}
if rootReply.IsDeleted() {
return fmt.Errorf("the root reply is deleted(%d,%d,%d)", rp.Oid, rp.Type, rp.Root)
}
if rp.IsNormal() {
rows, err = s.dao.Reply.TxIncrCount(tx, rp.Oid, rp.Root, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
rows, err = s.dao.Subject.TxIncrACount(tx, rp.Oid, rp.Type, 1, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrACount(%v) error(%v) or rows==0", rp, err)
return
}
} else {
rows, err = s.dao.Reply.TxIncrFCount(tx, rp.Oid, rp.Root, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
}
}
if rp.State == model.ReplyStateAudit || rp.State == model.ReplyStateMonitor {
if rows, err = s.dao.Subject.TxIncrMCount(tx, rp.Oid, rp.Type, rp.CTime.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrMCount(%v) error(%v) or rows==0", rp, err)
return
}
}
rows, err = s.dao.Content.TxInsert(tx, rp.Oid, rp.Content)
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Content.TxInContent(%v) error(%v) or rows==0", rp, err)
return
}
rows, err = s.dao.Reply.TxInsert(tx, rp)
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.TxInReply(%v) error(%v) or rows==0", rp, err)
return
}
return tx.Commit()
}
func (s *Service) regTopic(c context.Context, msg string) (topics []string) {
msg = _urlReg.ReplaceAllString(msg, "")
msg = _avReg.ReplaceAllString(msg, "#")
ss := _topicReg.FindAllStringSubmatch(msg, -1)
if len(ss) == 0 {
return
}
for _, nns := range ss {
if len(nns) == 2 {
topic := strings.TrimSpace(nns[1])
if len(topic) > 0 {
topics = append(topics, topic)
}
}
if len(topics) >= 5 {
break
}
}
return
}
func (s *Service) regAt(c context.Context, msg string, over, self int64) (ats []int64) {
var err error
ss := _atReg.FindAllStringSubmatch(msg, 10)
if len(ss) == 0 {
return
}
names := make([]string, 0, len(ss))
for _, nns := range ss {
if len(nns) == 2 {
names = append(names, nns[1])
}
}
if len(names) == 0 {
return
}
us, err := s.accSrv.InfosByName3(c, &accmdl.NamesReq{Names: names})
if err != nil {
log.Error("s.accSrv.InfosByName2 failed, err(%v)", err)
return
}
ats = make([]int64, 0, len(us.Infos))
for mid := range us.Infos {
if mid != over && mid != self {
ats = append(ats, mid)
}
}
if len(ats) == 0 {
return
}
ats = s.getFilterBlacklist(c, self, ats)
return
}
func (s *Service) addReply(c context.Context, rp *model.Reply) {
var (
err error
ok bool
)
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d,%d) err(%v)", rp.Oid, rp.Type, err)
return
}
if sub == nil {
log.Error("get subject is nil oid(%d) type(%d)", rp.Oid, rp.Type)
return
}
// init some field
if rp.IsNormal() {
sub.RCount = sub.RCount + 1
sub.ACount = sub.ACount + 1
}
sub.Count = sub.Count + 1
rp.Floor = sub.Count
rp.MTime = rp.CTime
rp.Content.RpID = rp.RpID
rp.Content.CTime = rp.CTime
rp.Content.MTime = rp.MTime
if len(rp.Content.Ats) == 0 {
rp.Content.Ats = s.regAt(c, rp.Content.Message, 0, rp.Mid)
}
rp.Content.Topics = s.regTopic(c, rp.Content.Message)
// begin transaction
if err = s.tranAdd(c, rp, true); err != nil {
log.Error("Transaction add reply(%v) error(%v)", rp, err)
return
}
// add cache
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d) err(%v)", sub.Oid, err)
}
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d) err(%v)", rp.RpID, err)
}
if rp.IsNormal() {
// update reply count
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
// add index cache
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByFloor); err == nil && ok {
if err = s.dao.Redis.AddFloorIndex(c, sub.Oid, sub.Type, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByCount); err == nil && ok {
if err = s.dao.Redis.AddCountIndex(c, sub.Oid, sub.Type, rp); err != nil {
log.Error("s.dao.Redis.AddCountIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, sub.Oid, sub.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
s.notifyReply(c, sub, rp)
} else if rp.State == model.ReplyStateAudit {
if err = s.dao.Redis.AddAuditIndex(c, rp); err != nil {
log.Error("s.dao.Redis.AddAUditIndex(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
}
if err = s.dao.PubEvent(c, _eventReply, rp.Mid, sub, rp, nil); err != nil {
return
}
}
func (s *Service) addReplyReply(c context.Context, rp *model.Reply) {
var (
err error
ok bool
)
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d,%d) err(%v)", rp.Oid, rp.Type, err)
return
}
// NOTE:depend on db,do not get from cache
rootRp, err := s.getReply(c, rp.Oid, rp.Root)
if err != nil {
log.Error("s.getReply failed , oid(%d), root(%d) err(%v)", rp.Oid, rp.Root, err)
return
}
if rootRp == nil {
log.Error("get reply is nil oid(%d) type(%d) rpid(%d)", rp.Oid, rp.Type, rp.Root)
return
}
var parentRp *model.Reply
if rp.Root != rp.Parent {
parentRp, err = s.getReply(c, rp.Oid, rp.Parent)
if err != nil {
log.Error("s.getReply failed , oid(%d), parent(%d) err(%v)", rp.Oid, rp.Parent, err)
return
}
if parentRp == nil {
log.Error("get reply is nil oid(%d) type(%d) rpid(%d)", rp.Oid, rp.Type, rp.Parent)
return
}
if parentRp.Dialog == 0 {
log.Warn("Dialog Need Migration oid(%d) type(%d) rootID(%d)", rp.Oid, rp.Type, rootRp.RpID)
// s.setDialogByRoot(context.Background(), rp.Oid, rp.Type, rp.Root)
}
rp.Dialog = parentRp.Dialog
} else {
parentRp = rootRp
if rp.Dialog != rp.RpID {
rp.Dialog = rp.RpID
}
}
// init some field
if rp.IsNormal() {
sub.ACount = sub.ACount + 1
rootRp.RCount = rootRp.RCount + 1
}
rootRp.Count = rootRp.Count + 1
rootRp.MTime = rp.CTime
rp.Floor = rootRp.Count
rp.MTime = rp.CTime
rp.Content.RpID = rp.RpID
rp.Content.CTime = rp.CTime
rp.Content.MTime = rp.MTime
if len(rp.Content.Ats) == 0 {
rp.Content.Ats = s.regAt(c, rp.Content.Message, 0, rp.Mid)
}
rp.Content.Topics = s.regTopic(c, rp.Content.Message)
// begin transaction
if err = s.tranAdd(c, rp, false); err != nil {
log.Error("Transaction add reply(%v) error(%v)", rp, err)
return
}
// add cache
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d), err(%v)", sub.Oid, err)
}
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rp.RpID, err)
}
if err = s.dao.Mc.AddReply(c, rootRp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rootRp.RpID, err)
}
if rootRp.IsTop() {
if err = s.dao.Mc.AddTop(c, rootRp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rootRp.RpID, err)
}
} else if rootRp.IsNormal() {
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByCount); err == nil && ok {
s.dao.Redis.AddCountIndex(c, rp.Oid, rp.Type, rootRp)
}
}
if rp.IsNormal() {
// update reply count
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
// add index cache
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, rootRp.RpID); err == nil && ok {
if err = s.dao.Redis.AddNewChildIndex(c, rootRp.RpID, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot failed , RpID(%d), err(%v)", rootRp.RpID, err)
}
}
// add dialog cache
if rp.Dialog != 0 {
if ok, err = s.dao.Redis.ExpireDialogIndex(c, rp.Dialog); err == nil && ok {
rps := []*model.Reply{rp}
if err = s.dao.Redis.AddDialogIndex(c, rp.Dialog, rps); err != nil {
log.Error("s.dao.Redis.AddDialogIndex failed , RpID(%d), Dialog(%d), Floor(%d) err(%v)", rp.RpID, rp.Dialog, rp.Floor, err)
}
}
}
s.notifyReplyReply(c, sub, rootRp, parentRp, rp)
} else if rp.State == model.ReplyStateAudit {
if err = s.dao.Redis.AddAuditIndex(c, rp); err != nil {
log.Error("s.dao.Redis.AddAUditIndex(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
}
if err = s.dao.PubEvent(c, _eventReply, rp.Mid, sub, rp, nil); err != nil {
return
}
}
func (s *Service) addTopCache(c context.Context, msg *consumerMsg) {
var (
err error
sub *model.Subject
rp *model.Reply
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Top uint32 `json:"top"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if rp, err = s.dao.Mc.GetTop(c, d.Oid, d.Tp, d.Top); err != nil {
log.Error("s.dao.Mc.GetTop(oid %v,top %v) err(%v)", d.Oid, d.Top, err)
return
} else if rp == nil {
if rp, err = s.dao.Reply.GetTop(c, d.Oid, d.Tp, d.Top); err != nil || rp == nil {
log.Error("s.dao.Reply.GetTop(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
if rp.Content, err = s.dao.Content.Get(c, d.Oid, rp.RpID); err != nil {
return
}
s.dao.Mc.AddTop(c, rp)
sub, err = s.dao.Subject.Get(c, d.Oid, d.Tp)
if err != nil {
log.Error("s.dao.Subject.Get(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
err = sub.TopSet(rp.RpID, d.Top, 1)
if err != nil {
return
}
_, err = s.dao.Subject.UpMeta(c, d.Oid, d.Tp, sub.Meta, time.Now())
if err != nil {
log.Error("s.dao.Subject.UpMeta(%d,%d,%d) failed!err:=%v ", rp.RpID, rp.Oid, d.Tp, err)
return
}
s.dao.Mc.AddSubject(c, sub)
}
}
func (s *Service) actionRpt(c context.Context, msg *consumerMsg) {
var (
err error
ok bool
)
var d struct {
Oid int64 `json:"oid"`
RpID int64 `json:"rpid"`
Tp int8 `json:"tp"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
rp, err := s.getReplyCache(c, d.Oid, d.RpID)
if err != nil {
log.Error("s.getReply failed , oid(%d), RpID(%d) err(%v)", d.Oid, d.RpID, err)
return
}
if rp == nil {
return
}
sub, err := s.getSubject(c, d.Oid, d.Tp)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d),tp(%d), RpID(%d) err(%v)", d.Oid, d.Tp, d.RpID, err)
return
}
// update like index
if rp.Root == 0 && rp.Parent == 0 && !rp.IsDeleted() {
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, d.Oid, rp.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex(%d, %d) error(%v)", d.Oid, rp.Type, err)
}
}
}
report, err := s.dao.Report.Get(c, d.Oid, d.RpID)
if err != nil || report == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
if err = s.dao.PubEvent(c, _eventReportAdd, report.Mid, sub, rp, report); err != nil {
return
}
}
func (s *Service) setLike(c context.Context, cmsg *StatMsg) {
var (
event string
)
rp, err := s.getReply(c, cmsg.Oid, cmsg.ID)
if err != nil || rp == nil || rp.Content == nil {
log.Error("s.getReply(%d, %d) reply:%+v error(%v)", cmsg.Oid, cmsg.ID, rp, err)
return
}
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d) type(%d) err(%v)", rp.Oid, rp.Type, err)
return
}
_, err = s.dao.Reply.UpLike(c, cmsg.Oid, cmsg.ID, cmsg.Count, cmsg.DislikeCount, time.Now())
if err != nil {
log.Error("s.dao.Reply.UpLike (%v) failed!err:=%v", cmsg, err)
return
}
if cmsg.Count > rp.Like {
event = _eventLike
var max int
if max, err = s.dao.Redis.MaxLikeCnt(c, rp.RpID); err == nil && cmsg.Count > max {
if err = s.dao.Redis.SetMaxLikeCnt(c, rp.RpID, int64(cmsg.Count)); err == nil {
rp.Like = cmsg.Count
rp.Hate = cmsg.DislikeCount
s.notifyLike(c, cmsg.Mid, rp)
}
}
} else if cmsg.DislikeCount > rp.Hate {
event = _eventHate
} else if cmsg.Count < rp.Like {
event = _eventLikeCancel
} else {
event = _eventHateCancel
}
rp.Like = cmsg.Count
rp.Hate = cmsg.DislikeCount
s.dao.Mc.AddReply(c, rp)
if rp.AttrVal(model.ReplyAttrAdminTop) == 1 || rp.AttrVal(model.ReplyAttrUpperTop) == 1 {
s.dao.Mc.AddTop(c, rp)
return
}
// if have root, then update root's index
if rp.Root == 0 && rp.IsNormal() {
var ok bool
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, rp.Oid, rp.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex(%d, %d) error(%v)", rp.Oid, rp.Type, err)
}
}
}
if err = s.dao.PubEvent(c, event, cmsg.Mid, sub, rp, nil); err != nil {
return
}
}
func (s *Service) adminLog(c context.Context, rp *model.Reply, adid int64, isreport, state int8, result, remark string) {
// admin log
s.dao.Admin.UpIsNotNew(c, rp.RpID, time.Now())
s.dao.Admin.Insert(c, adid, rp.Oid, rp.RpID, rp.Type, result, remark, model.AdminIsNew, isreport, state, time.Now())
}
// getSubject get reply subject from mysql .
// NOTE : note get from mc,count must depend on mysql
func (s *Service) getSubject(c context.Context, oid int64, tp int8) (sub *model.Subject, err error) {
if sub, err = s.dao.Subject.Get(c, oid, tp); err != nil {
log.Error("dao.Subject.Get(%d, %d) error(%v)", oid, tp, err)
}
return
}
func (s *Service) getReply(c context.Context, oid, RpID int64) (rp *model.Reply, err error) {
if rp, err = s.dao.Reply.Get(c, oid, RpID); err != nil {
log.Error("s.dao.Reply.Get(%d, %d) error(%v)", oid, RpID, err)
return
} else if rp == nil {
return
}
if rp.Content, err = s.dao.Content.Get(c, rp.Oid, rp.RpID); err != nil {
log.Error("s.dao.Content.Get(%d,%d) error(%v)", rp.Oid, rp.RpID, err)
} else if rp.Content == nil {
err = errReplyContentNotFound
}
return
}
func (s *Service) getReplyCache(c context.Context, oid, RpID int64) (rp *model.Reply, err error) {
if rp, err = s.dao.Mc.GetReply(c, RpID); err != nil {
log.Error("replyCacheDao.GetReply(%d, %d) error(%v)", oid, RpID, err)
}
if rp != nil {
return
}
if rp, err = s.dao.Reply.Get(c, oid, RpID); err != nil {
log.Error("dao.Reply.GetReply(%d, %d) error(%v)", oid, RpID, err)
}
if rp != nil {
rp.Content, _ = s.dao.Content.Get(c, rp.Oid, rp.RpID)
// NOTE not add member info to cache
}
return
}
func (s *Service) upAcount(c context.Context, oid int64, tp int8, count int, now time.Time) {
s.statDao.Send(c, tp, oid, count)
}
func (s *Service) callSearchUp(c context.Context, res map[string]*searchFlush) (err error) {
var (
rps []*searchFlush
rpts []*searchFlush
)
for _, r := range res {
if r.Report != nil {
rpts = append(rpts, r)
} else {
rps = append(rps, r)
}
}
if len(rps) > 0 {
err = s.callSearch(c, rps, false)
}
if len(rpts) > 0 {
err = s.callSearch(c, rpts, true)
}
return
}
// callSearch update reply or report info to ES search.
func (s *Service) callSearch(c context.Context, params []*searchFlush, isRpt bool) (err error) {
var (
b []byte
ms []map[string]interface{}
p = url.Values{}
urlStr = conf.Conf.Host.Search + "/api/reply/internal/update"
res struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
)
// 更新搜索ES数据字段
if isRpt {
// report
p.Set("appid", _appIDReport)
for _, p := range params {
m := make(map[string]interface{})
m["id"] = fmt.Sprintf("%d_%d_%d", p.Reply.RpID, p.Reply.Oid, p.Reply.Type)
m["reply_state"] = fmt.Sprintf("%d", p.Reply.State)
m["reason"] = fmt.Sprintf("%d", p.Report.Reason)
m["content"] = p.Report.Content
m["state"] = fmt.Sprintf("%d", p.Report.State)
m["mtime"] = p.Report.MTime.Time().Format(timeFormat)
m["index_time"] = p.Report.CTime.Time().Format(timeFormat)
if p.Report.Attr == 1 {
m["attr"] = []int{1}
} else {
m["attr"] = []int{}
}
ms = append(ms, m)
}
if b, err = json.Marshal(ms); err != nil {
log.Error("json.Marshal(%v) error(%v)", ms, err)
return
}
p.Set("val", string(b))
if err = searchHTTPClient.Post(c, urlStr, "", p, &res); err != nil {
log.Error("xhttp.Post(%s) failed error(%v)", urlStr+"?"+p.Encode(), err)
}
log.Info("updateSearch: %s post:%s ret:%v", urlStr, p.Encode(), res)
} else {
// reply
var rps = make(map[int64]*model.Reply)
for _, p := range params {
rps[p.Reply.RpID] = p.Reply
}
err = s.UpSearchReply(c, rps)
}
return
}
// UpSearchReply update search reply index.
func (s *Service) UpSearchReply(c context.Context, rps map[int64]*model.Reply) (err error) {
if len(rps) <= 0 {
return
}
stales := s.es.NewUpdate("reply_list")
for _, rp := range rps {
m := make(map[string]interface{})
m["id"] = rp.RpID
m["state"] = rp.State
m["mtime"] = rp.MTime.Time().Format("2006-01-02 15:04:05")
m["oid"] = rp.Oid
m["type"] = rp.Type
if rp.Content != nil {
m["message"] = rp.Content.Message
}
stales = stales.AddData(s.es.NewUpdate("reply_list").IndexByTime("reply_list", elastic.IndexTypeWeek, rp.CTime.Time()), m)
}
err = stales.Do(c)
if err != nil {
log.Error("upSearchReply update stales(%s) failed!err:=%v", stales.Params(), err)
return
}
log.Info("upSearchReply:stale:%s ret:%+v", stales.Params(), err)
return
}
// getBlackListRelation check if the source user blacklisted the target user
func (s *Service) getBlackListRelation(c context.Context, srcID, targetID int64) (rel bool) {
relMap, err := s.accSrv.RichRelations3(c, &accmdl.RichRelationReq{Owner: srcID, Mids: []int64{targetID}, RealIp: ""})
if err != nil {
log.Error("s.acc.RichRelations2 sourceId(%v) targetId(%v)error(%v)", srcID, targetID, err)
err = nil
return false
}
if len(relMap.RichRelations) == 0 {
return false
}
if rel, ok := relMap.RichRelations[targetID]; ok && relmdl.Attr(uint32(rel)) == relmdl.AttrBlack {
return true
}
return false
}
// getFilterBlacklist filters the user list that the mid user can notify message for
func (s *Service) getFilterBlacklist(c context.Context, mid int64, targetIds []int64) (filterIds []int64) {
filterIds = make([]int64, 0, len(targetIds))
for _, tmp := range targetIds {
if !s.getBlackListRelation(c, tmp, mid) {
filterIds = append(filterIds, tmp)
}
}
return
}
func (s *Service) addAssistLog(c context.Context, mid, uid, subjectID, typeID, action int64, objectID, content string) (err error) {
if len(content) > 50 {
content = substr2(content, 0, 50) + "..."
}
arg := &assmdl.ArgAssistLogAdd{
Mid: mid,
AssistMid: uid,
Type: 1,
Action: 1,
SubjectID: subjectID,
ObjectID: objectID,
Detail: content,
RealIP: "",
}
if err = s.assistSrv.AssistLogAdd(c, arg); err != nil {
log.Error("s.assistSrv.Assist(%d, %d, %d, %d, %d) error(%v)", mid, uid, subjectID, typeID, action, err)
}
return
}
func substr2(str string, start int, subLength int) string {
rs := []rune(str)
length := len(rs)
if start < 0 || start > length {
start = 0
}
if subLength < 0 || subLength > length {
subLength = length
}
return string(rs[start:subLength])
}

View File

@@ -0,0 +1,71 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestFilterViolationMsg(t *testing.T) {
Convey("TestFilterViolationMsg", t, func() {
res := filterViolationMsg("123456789评论过虑违规内容评论过虑违规内容")
t.Log(res)
})
}
func TestTopicReg(t *testing.T) {
s := Service{}
c := context.Background()
Convey("TestAtReg", t, func() {
topics := s.regTopic(c, "#你懂 得##222#")
So(len(topics), ShouldEqual, 2)
So(topics[0], ShouldEqual, "你懂 得")
So(topics[1], ShouldEqual, "222")
topics = s.regTopic(c, "#你懂 \n得##22@有人艾特2#")
So(len(topics), ShouldEqual, 0)
topics = s.regTopic(c, "#你懂 \n得#哈哈哈#22@有人艾特2#")
So(len(topics), ShouldEqual, 1)
So(topics[0], ShouldEqual, "哈哈哈")
topics = s.regTopic(c, "# ## ##你懂得")
So(len(topics), ShouldEqual, 0)
topics = s.regTopic(c, "热热# ##%……&**&*……&……%……¥%##同一套##协助特大号哈哈哈嘎嘎协助特大号哈哈哈嘎嘎协助特大号哈哈哈ee120##协助特大号哈哈哈嘎嘎协助特大号哈哈哈嘎嘎协助特大号哈哈哈ee12##@1r##tet##899##5677#")
So(len(topics), ShouldEqual, 5)
topics = s.regTopic(c, "#我是大佬你是谁你是大佬嘛哈哈啊#123#")
So(len(topics), ShouldEqual, 1)
topics = s.regTopic(c, "#2😁3#123#3😁3##2😁3#")
So(len(topics), ShouldEqual, 1)
So(topics[0], ShouldEqual, "123")
topics = s.regTopic(c, " http://t.bilibili.com/av111111#reply#haha #didi")
So(len(topics), ShouldEqual, 0)
topics = s.regTopic(c, " http://t.bilibili.com/av111111#reply#haha #didi# http://t.baidu.com/av111111#reply#haha")
So(len(topics), ShouldEqual, 2)
So(topics[0], ShouldEqual, "didi")
So(topics[1], ShouldEqual, "reply")
topics = s.regTopic(c, "asdasd#av1000#33333#vc11111#44444#cv1111#55555#")
So(len(topics), ShouldEqual, 3)
})
}
func TestAtReg(t *testing.T) {
Convey("TestAtReg", t, func() {
ss := _atReg.FindAllStringSubmatch("@aa:hh@bb,cc", 10)
So(len(ss), ShouldEqual, 2)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "bb")
ss = _atReg.FindAllStringSubmatch("@aa@bb", 10)
So(len(ss), ShouldEqual, 2)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "bb")
ss = _atReg.FindAllStringSubmatch("@aa @bb", 10)
So(len(ss), ShouldEqual, 2)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "bb")
ss = _atReg.FindAllStringSubmatch("@aa bb@cc;@dd:sa", 10)
So(len(ss), ShouldEqual, 3)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "cc;")
So(ss[2][1], ShouldEqual, "dd")
})
}

View File

@@ -0,0 +1,366 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"sync"
"time"
artrpc "go-common/app/interface/openplatform/article/rpc/client"
"go-common/app/job/main/reply/conf"
"go-common/app/job/main/reply/dao/message"
"go-common/app/job/main/reply/dao/notice"
"go-common/app/job/main/reply/dao/reply"
"go-common/app/job/main/reply/dao/search"
"go-common/app/job/main/reply/dao/spam"
"go-common/app/job/main/reply/dao/stat"
model "go-common/app/job/main/reply/model/reply"
accrpc "go-common/app/service/main/account/api"
arcrpc "go-common/app/service/main/archive/api/gorpc"
assrpc "go-common/app/service/main/assist/rpc/client"
eprpc "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
es "go-common/library/database/elastic"
"go-common/library/log"
xhttp "go-common/library/net/http/blademaster"
"go-common/library/net/rpc/warden"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline/fanout"
)
const (
_chLen = 2048
)
var (
_rpChs []chan *databus.Message
_likeChs []chan *databus.Message
)
// action the message struct of kafka
type consumerMsg struct {
Action string `json:"action"`
Data json.RawMessage `json:"data"`
}
type searchFlush struct {
OldState int8
Reply *model.Reply
Report *model.Report
}
func (s *searchFlush) Key() (key string) {
if s.Report != nil {
return fmt.Sprintf("%d%d", s.Report.RpID, s.Report.ID)
}
return fmt.Sprintf("%d", s.Reply.RpID)
}
// Service is reply-job service
type Service struct {
c *conf.Config
waiter *sync.WaitGroup
dataConsumer *databus.Databus
likeConsumer *databus.Databus
searchChan chan *searchFlush
// rpc client
accSrv accrpc.AccountClient
arcSrv *arcrpc.Service2
articleSrv *artrpc.Service
assistSrv *assrpc.Service
bangumiSrv eprpc.EpisodeClient
// depend
messageDao *message.Dao
// notice
noticeDao *notice.Dao
// stat
statDao *stat.Dao
// reply
dao *reply.Dao
// spam
spam *spam.Cache
// search
searchDao *search.Dao
batchNumber int
es *es.Elastic
notify *fanout.Fanout
typeMapping map[int32]string
aliasMapping map[string]int32
marker *fanout.Fanout
}
// New return new service
func New(c *conf.Config) (s *Service) {
if c.Job.BatchNumber <= 0 {
c.Job.BatchNumber = 2000
}
searchHTTPClient = xhttp.NewClient(c.HTTPClient)
wardenClient := warden.DefaultClient()
cc, err := wardenClient.Dial(context.Background(), "discovery://default/season.service")
if err != nil {
panic(err)
}
bangumiClient := eprpc.NewEpisodeClient(cc)
s = &Service{
c: c,
bangumiSrv: bangumiClient,
waiter: new(sync.WaitGroup),
searchChan: make(chan *searchFlush, 1024),
dataConsumer: databus.New(c.Databus.Consumer),
likeConsumer: databus.New(c.Databus.Like),
//rpc
arcSrv: arcrpc.New2(c.RPCClient2.Archive),
articleSrv: artrpc.New(c.RPCClient2.Article),
assistSrv: assrpc.New(c.RPCClient2.Assist),
messageDao: message.NewMessageDao(c),
searchDao: search.New(c),
noticeDao: notice.New(c),
// stat
statDao: stat.New(c),
// init reply dao
dao: reply.New(c),
// init spam cache
batchNumber: c.Job.BatchNumber,
spam: spam.NewCache(c.Redis.Config),
notify: fanout.New("cache", fanout.Worker(1), fanout.Buffer(2048)),
typeMapping: make(map[int32]string),
aliasMapping: make(map[string]int32),
es: es.NewElastic(c.Es),
marker: fanout.New("marker", fanout.Worker(1), fanout.Buffer(1024)),
}
accSvc, err := accrpc.NewClient(c.AccountClient)
if err != nil {
panic(err)
}
s.accSrv = accSvc
time.Sleep(time.Second)
_rpChs = make([]chan *databus.Message, c.Job.Proc)
_likeChs = make([]chan *databus.Message, c.Job.Proc)
for i := 0; i < c.Job.Proc; i++ {
_rpChs[i] = make(chan *databus.Message, _chLen)
_likeChs[i] = make(chan *databus.Message, _chLen)
s.waiter.Add(1)
go s.consumeproc(i)
s.waiter.Add(1)
go s.consumelikeproc(i)
}
s.waiter.Add(1)
go s.likeConsume()
s.waiter.Add(1)
go s.dataConsume()
go s.searchproc()
go s.mappingproc()
return
}
func (s *Service) addSearchUp(c context.Context, oldState int8, rp *model.Reply, rpt *model.Report) {
select {
case s.searchChan <- &searchFlush{OldState: oldState, Reply: rp, Report: rpt}:
default:
log.Error("addSearchUp chan full, type:%d oid:%d rpID:%d", rp.Type, rp.Oid, rp.RpID)
}
}
func (s *Service) searchproc() {
var (
m *searchFlush
merge = make(map[string]*searchFlush)
num = s.c.Job.SearchNum
ticker = time.NewTicker(time.Duration(s.c.Job.SearchFlush))
)
for {
select {
case m = <-s.searchChan:
merge[m.Key()] = m
if len(merge) < num {
continue
}
case <-ticker.C:
}
if len(merge) > 0 {
s.callSearchUp(context.Background(), merge)
merge = make(map[string]*searchFlush)
}
}
}
func (s *Service) likeConsume() {
defer func() {
s.waiter.Done()
for i := 0; i < s.c.Job.Proc; i++ {
close(_rpChs[i])
}
}()
msgs := s.likeConsumer.Messages()
for {
msg, ok := <-msgs
if !ok {
log.Warn("[service.dataConsume|reply] dataConsumer has been closed.")
return
}
if msg.Topic != s.c.Databus.Like.Topic {
continue
}
rpid, err := strconv.ParseInt(string(msg.Key), 10, 64)
if err != nil {
continue
}
_likeChs[rpid%int64(s.c.Job.Proc)] <- msg
}
}
func (s *Service) dataConsume() {
defer func() {
s.waiter.Done()
for i := 0; i < s.c.Job.Proc; i++ {
close(_rpChs[i])
}
}()
msgs := s.dataConsumer.Messages()
for {
msg, ok := <-msgs
if !ok {
log.Warn("[service.dataConsume|reply] dataConsumer has been closed.")
return
}
if msg.Topic != s.c.Databus.Consumer.Topic {
continue
}
oid, err := strconv.ParseInt(string(msg.Key), 10, 64)
if err != nil {
continue
}
_rpChs[oid%int64(s.c.Job.Proc)] <- msg
}
}
// StatMsg stat msg.
type StatMsg struct {
Type string `json:"type,omitempty"`
ID int64 `json:"id,omitempty"`
Count int `json:"count,omitempty"`
Oid int64 `json:"origin_id,omitempty"`
DislikeCount int `json:"dislike_count,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Mid int64 `json:"mid,omitempty"`
}
func (s *Service) consumelikeproc(i int) {
defer s.waiter.Done()
for {
msg, ok := <-_likeChs[i]
if !ok {
log.Info("consumeproc exit")
return
}
cmsg := &StatMsg{}
if err := json.Unmarshal(msg.Value, cmsg); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
continue
}
if cmsg.Type != "reply" {
continue
}
s.setLike(context.Background(), cmsg)
msg.Commit()
log.Info("consumer topic:%s, partitionId:%d, offset:%d, Key:%s, Value:%s", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value)
}
}
func (s *Service) consumeproc(i int) {
defer s.waiter.Done()
for {
msg, ok := <-_rpChs[i]
if !ok {
log.Info("consumeproc exit")
return
}
cmsg := &consumerMsg{}
if err := json.Unmarshal(msg.Value, cmsg); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
continue
}
switch cmsg.Action {
case "add":
s.actionAdd(context.Background(), cmsg)
case "add_top":
s.addTopCache(context.Background(), cmsg)
case "rpt":
s.actionRpt(context.Background(), cmsg)
case "act":
//s.actionAct(context.Background(), cmsg)
s.recAct(context.Background(), cmsg)
case "re_idx":
s.actionRecoverIndex(context.Background(), cmsg)
case "idx_floor":
s.acionRecoverFloorIdx(context.Background(), cmsg)
case "re_rt_idx":
s.actionRecoverRootIndex(context.Background(), cmsg)
case "idx_dialog":
s.actionRecoverDialog(context.Background(), cmsg)
case "fix_dialog":
s.actionRecoverFixDialog(context.Background(), cmsg)
case "re_act":
// s.actionRecoverAction(context.Background(),cmsg)
case "up":
s.actionUp(context.Background(), cmsg)
case "admin":
s.actionAdmin(context.Background(), cmsg)
case "spam":
s.addRecReply(context.Background(), cmsg)
s.addDailyReply(context.Background(), cmsg)
case "folder":
s.folderHanlder(context.Background(), cmsg)
default:
log.Error("invalid action %s, cmsg is %v", cmsg.Action, cmsg)
}
msg.Commit()
log.Info("consumer topic:%s, partitionId:%d, offset:%d, Key:%s, Value:%s", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value)
}
}
// TypeToAlias map type to alias
func (s *Service) TypeToAlias(t int32) (alias string, exists bool) {
alias, exists = s.typeMapping[t]
return
}
// AliasToType map alias to type
func (s *Service) AliasToType(alias string) (t int32, exists bool) {
t, exists = s.aliasMapping[alias]
return
}
func (s *Service) mappingproc() {
for {
if business, err := s.ListBusiness(context.Background()); err != nil {
log.Error("s.ListBusiness error(%v)", err)
} else {
for _, b := range business {
s.typeMapping[b.Type] = b.Alias
s.aliasMapping[b.Alias] = b.Type
}
}
time.Sleep(time.Duration(time.Minute * 5))
}
}
// Close close service
func (s *Service) Close() error {
return s.dataConsumer.Close()
}
// Wait wait all chan close
func (s *Service) Wait() {
s.waiter.Wait()
}
// Ping check service health
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}

View File

@@ -0,0 +1,50 @@
package service
import (
"context"
"flag"
"os"
"path/filepath"
"testing"
"go-common/app/job/main/reply/conf"
_ "github.com/go-sql-driver/mysql"
. "github.com/smartystreets/goconvey/convey"
)
var (
ser *Service
)
func CleanCache() {
}
func TestMain(m *testing.M) {
dir, _ := filepath.Abs("../cmd/reply-job-test.toml")
flag.Set("conf", dir)
err := conf.Init()
if err != nil {
panic("conf init err:" + err.Error())
}
ser = New(conf.Conf)
m.Run()
os.Exit(0)
}
func WithService(f func(ser *Service)) func() {
return func() {
Reset(func() { CleanCache() })
f(ser)
}
}
func TestClose(t *testing.T) {
err := ser.Close()
So(err, ShouldBeNil)
}
func TestPing(t *testing.T) {
err := ser.Ping(context.Background())
So(err, ShouldBeNil)
}

View File

@@ -0,0 +1,139 @@
package service
import (
"context"
"encoding/json"
accmdl "go-common/app/service/main/account/api"
"go-common/library/ecode"
"go-common/library/log"
)
type spamMessage struct {
Mid int64 `json:"mid"`
IsUp bool `json:"is_up"`
Tp int8 `json:"tp"`
}
func (s *Service) getLevel(c context.Context, mid int64) (cur int, err error) {
arg := &accmdl.MidReq{Mid: mid}
res, err := s.accSrv.Card3(c, arg)
if err != nil {
return
}
cur = int(res.Card.Level)
return
}
func (s *Service) addRecReply(c context.Context, msg *consumerMsg) {
var (
d spamMessage
exp int
code = ecode.OK
)
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
count, err := s.spam.IncrReply(c, d.Mid, d.IsUp)
if err != nil {
log.Error("spam.IncrReply(%d) error(%v)", d.Mid, err)
return
}
if d.IsUp && count >= 20 {
exp = 5 * 60 // 5min
code = ecode.ReplyDeniedAsCaptcha
} else if count >= 5 {
exp = 5 * 60 // 5min
code = ecode.ReplyDeniedAsCaptcha
}
if code == ecode.OK {
return
}
if err = s.spam.SetReplyRecSpam(c, d.Mid, code.Code(), exp); err == nil {
log.Info("spam.SetReplyRecSpam(%d, %d, %d)", d.Mid, code, exp)
} else {
log.Error("spam.SetReplyRecSpam(%d, %d, %d), err (%v)", d.Mid, code, exp, err)
}
}
func (s *Service) addDailyReply(c context.Context, msg *consumerMsg) {
var d spamMessage
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
ttl, err := s.spam.TTLDailyReply(c, d.Mid)
if err != nil {
log.Error("spam.TTLDailyReply(%d) error(%v)", d.Mid, err)
return
}
count, err := s.spam.IncrDailyReply(c, d.Mid)
if err != nil {
log.Error("spam.IncrDailyReply(%d) error(%v)", d.Mid, err)
return
}
if ttl == -2 || ttl == -1 {
ttl = 24 * 60 * 60 // one day
if err = s.spam.ExpireDailyReply(c, d.Mid, ttl); err != nil {
log.Error("spam.ExpireDailyReply(%d) error(%v)", d.Mid, err)
}
}
var code ecode.Codes
// 23 BBQ 22 火鸟
if d.Tp == 23 || d.Tp == 22 {
if count <= 1000 {
return
}
code = ecode.ReplyDeniedAsCD
} else {
lv, err := s.getLevel(c, d.Mid)
if err != nil {
log.Error("s.getLevel(%d) error(%v)", d.Mid, err)
return
}
switch {
case lv <= 1 && count < 25:
return
case lv == 2 && count < 250:
return
case lv == 3 && count < 300:
return
case lv == 4 && count < 400:
return
case lv >= 5 && count < 800:
return
}
code = ecode.ReplyDeniedAsCaptcha
if count >= 1000 {
code = ecode.ReplyDeniedAsCD
}
}
if err = s.spam.SetReplyDailySpam(c, d.Mid, code.Code(), ttl); err == nil {
log.Info("spam.SetReplyDailySpam(%d, %d, %d)", d.Mid, code, ttl)
} else {
log.Error("spam.SetReplyDailySpam(%d, %d, %d) error(%v)", d.Mid, code, ttl, err)
}
}
func (s *Service) recAct(c context.Context, cmsg *consumerMsg) {
const _exp = 60
var d spamMessage
if err := json.Unmarshal([]byte(cmsg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
count, err := s.spam.IncrAct(c, d.Mid)
if err != nil {
log.Error("spam.IncUserRecAct(%d) error(%v)", d.Mid, err)
return
}
if count < 15 {
return
}
if err = s.spam.SetActionRecSpam(c, d.Mid, ecode.ReplyForbidAction.Code(), _exp); err == nil {
log.Info("spam.SetActRecSpam(%d, %d, %d)", d.Mid, ecode.ReplyForbidAction, _exp)
} else {
log.Error("spam.SetActRecSpam(%d, %d, %d) error(%v)", d.Mid, ecode.ReplyForbidAction, _exp, err)
}
}

View File

@@ -0,0 +1,69 @@
package service
import (
"context"
"encoding/json"
model "go-common/app/job/main/reply/model/reply"
"go-common/library/log"
xtime "go-common/library/time"
)
func (s *Service) actionUp(c context.Context, msg *consumerMsg) {
var d struct {
Op string `json:"op"`
Action uint32 `json:"action"`
Oid int64 `json:"oid"`
Tp int64 `json:"tp"`
RpID int64 `json:"rpid"`
MTime xtime.Time `json:"mtime"`
}
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if d.Oid == 0 || d.RpID == 0 {
log.Error("The structure of action(%s) from rpCh was wrong", msg.Data)
return
}
rp, err := s.getReplyCache(c, d.Oid, d.RpID)
if err != nil {
log.Error("s.getReply failed , oid(%d), RpID(%d) err(%v)", d.Oid, d.RpID, err)
return
}
if rp == nil {
log.Error("reply is nil oid(%d) RpID(%d)", d.Oid, d.RpID)
return
}
var state int8
switch {
case d.Op == "show":
if rp.State != model.ReplyStateHidden {
log.Warn("reply state(%d) is not hidden", rp.State)
return
}
state = model.ReplyStateNormal
case d.Op == "hide":
if rp.State != model.ReplyStateNormal && rp.State != model.ReplyStateGarbage && rp.State != model.ReplyStateFiltered && rp.State != model.ReplyStateFolded {
log.Warn("reply state(%d) is not normal", rp.State)
return
}
state = model.ReplyStateHidden
case d.Op == "top_add":
if err := s.topAdd(c, rp, d.MTime, d.Action, model.SubAttrUpperTop); err != nil {
log.Error("s.topAdd(oid %d) err(%v)", d.Oid, err)
}
return
}
if rows, err := s.dao.Reply.UpState(c, d.Oid, d.RpID, state, d.MTime.Time()); err != nil || rows < 1 {
log.Error("dao.Reply.Update(%d, %d, %d), error(%v) and rows==0", d.Oid, d.RpID, state, err)
} else {
// if rp, err := s.dao.Mc.GetReply(nil, d.RpID); err == nil && rp != nil {
rp.State = state
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply(%d, %d, %d), error(%v) and rows==0", d.Oid, d.RpID, state, err)
}
// }
}
// callSearchUp(rp.Oid, rp.RpID, rp.Type, false)
}