Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/interface/openplatform/article/cache:all-srcs",
"//app/interface/openplatform/article/cmd:all-srcs",
"//app/interface/openplatform/article/conf:all-srcs",
"//app/interface/openplatform/article/dao:all-srcs",
"//app/interface/openplatform/article/http:all-srcs",
"//app/interface/openplatform/article/model:all-srcs",
"//app/interface/openplatform/article/rpc/client:all-srcs",
"//app/interface/openplatform/article/rpc/server:all-srcs",
"//app/interface/openplatform/article/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,683 @@
# article
##### Version 2.0.4
> 1. fix read split
##### Version 2.0.3
> 1. fix reCount article list read
##### Version 2.0.2
> 1. fix reason
> 2. add keywords of view
##### Version 2.0.1
> 1. 喜欢 => 点赞
> 2. 关键词
##### Version 2.0.0
> 1. 专栏支持重复编辑
##### Version 1.52.3
> 1. 适配蓝版app
##### Version 1.51.2
> 1. 长评接入
##### Version 1.50.1
> 1. 迁移capture
> 2. 点赞加入反作弊
> 3. xss data-vote-id
##### Version 1.50.0
> 1. 修复点赞bug
##### Version 1.49.9
> 1. 修复并发bug
##### Version 1.49.8
> 1. 修改推荐数据源
##### Version 1.49.7
> 1. 增加推荐池分区统计
> 2. 增加时间删选
##### Version 1.49.6
> 1. 详情页推荐迭代
> 2. 点赞增加up mid
##### Version 1.49.5
> 1. 添加用户阅读心跳接口记录写入redis交给job处理
##### Version 1.49.4
> 1. 添加用户阅读心跳接口记录写入redis交给job处理
##### Version 1.49.3
> 1. fix nil pointer bug
##### Version 1.49.2
> 1. 开放创建文集的限制
##### Version 1.49.1
> 1. 左右滑动推荐内容策略调整
##### Version 1.48.15
> 1. 使用metadata中的remoteIP
##### Version 1.48.14
> 1. 去除tag服务无用代码
##### Version 1.48.13
> 1. 通知增加content字段
> 2. 新加草稿箱数量接口
> 3. 增加推荐作者列表接口
> 4. viewInfo增加转发数
##### Version 1.48.12
> 1. 一周年接口
> 2. 图片上传支持uri
##### Version 1.48.11
> 1. stat表过滤预加载
##### Version 1.48.10
> 1. 修改空数据error为info
##### Version 1.48.9
> 1. 去除重复error
##### Version 1.48.8
> 1. 空buvid不调用天马
> 2. 去除重复error
> 3. -404将为WARN
##### Version 1.48.7
> 1. 修改天马数据空日志级别为warning
##### Version 1.48.6
> 1. 历史记录过滤预加载
##### Version 1.48.5
> 1. 左右滑动修改天马获取数量
##### Version 1.48.4
> 1. 添加天马入口策略
> 2. 忽略articleSlide数据上报
> 3. 天马数据获取异常降级
##### Version 1.48.1
> 1. 增加创作中心稿件管理接口
> 2. viewInfo增加上下篇文章信息
##### Version 1.48.1
> 1. 添加databus阅读数回查
##### Version 1.47.3
> 1. 添加hbase share1share0
> 2. 添加scheme bilibili
##### Version 1.47.2
> 1. 去掉debug和trace配置
##### Version 1.47.1
> 1. use new infoc
##### Version 1.47.0
> 1. 使用bm
##### Version 1.46.1
> 1. 升级tools/cache
##### Version 1.46.0
> 1. 生成分区数据重构: 生成分区数据(最新文章 排序列表)移动到article-job中
> 2. 作者权限缓存重构: 由redis移动到mc中
##### Version 1.45.4
> 1. 修复xss漏洞
##### Version 1.45.3
> 1. 修复作者列表不全的问题
##### Version 1.45.2
> 1. 优化计算分区排序列表
##### Version 1.45.0
> 1. 支持过期热点显示
> 2. 热点过滤禁止分区文章
> 3. 增加详情页banner
> 4. 支持同时开展多个活动
##### Version 1.44.0
> 1. 文章和文集接入历史记录
##### Version 1.43.2
> 1. 天马加日志
##### Version 1.43.1
> 1. 天马降级
##### Version 1.43.0
> 1. 用户文章列表接口增加文集字段
> 2. 增加上传图片API
> 3. 天马加入推荐运营位
##### Version 1.42.0
> 1. 增加web端文章文集列表接口
##### Version 1.41.3
> 1. 重构作者缓存 移除redis pie
##### Version 1.41.1
> 1. 修复创作中心列表页无数据的问题
> 2. 修复裁剪图片验证不全的问题
##### Version 1.41.0
> 1. 文集增加默认图片
> 2. 文集管理界面增加文集字数和阅读数
> 3. 商品页接口增加类型字段 支持价格待定类型
##### Version 1.40.1
> 1. 文集缓存用mc工具重构
##### Version 1.40.0
> 1. 专栏草稿写库方式重构
> 2. 专栏投稿改为强依赖tag
> 3. 分批获取搜索表数据
##### Version 1.39.0
> 1. 专栏阅读数防刷需求
##### Version 1.38.0
> 1. web端接入天马
##### Version 1.37.2
> 1. 修复粉丝数问题
##### Version 1.37.1
> 1. 使用account-service v7
##### Version 1.37.0
> 1. lv2以上的用户开放投稿权限
##### Version 1.36.0
> 1. 增加热点标签功能
##### Version 1.35.0
> 1. 文集列表支持排序
##### Version 1.34.1
> 1. 修复空指针问题
##### Version 1.34.0
> 1. 空间页增加文集入口+文集详情落地页
> 2. 文集新增发布时间 阅读数 字数 简介 文章数信息
##### Version 1.33.2
> 1. 修复更多文章过滤问题
##### Version 1.33.1
> 1. 修复更多文章过滤问题
##### Version 1.33.0
> 1. 文集用缓存框架重构
> 2. 上报收藏infoc日志
##### Version 1.32.0
> 1. 增加up主投稿地址跳转接口
##### Version 1.31.1
> 1. 修复文集过滤问题
##### Version 1.31.0
> 1. 专栏首页/推荐页面接入天马
##### Version 1.30.4
> 1. 提醒状态增加新手引导类型
> 2. 修复文集排序问题
##### Version 1.30.3
> 1. 修复文集bug 过滤删除文章
> 2. 文集update_time改为文集本身修改时间
> 3. 修复tag绑定问题
##### Version 1.30.2
> 1. 写死移动端第五个分区
##### Version 1.30.1
> 1. fixed panic bug
##### Version 1.30.0
> 1. 增加文集功能
> 2. 待审视频up没有写专栏权限
##### Version 1.29.7
> 1. 增加推荐接口pn限制
##### Version 1.29.6
> 1. 增加推荐接口pn限制
> 2. 增加收藏和点赞时验证id存在
##### Version 1.29.5
> 1. 支持公告分版本平台显示
##### Version 1.29.4
> 1. ups文章列表 接口不调用infos rpc
##### Version 1.29.3
> 1. ups文章列表 接口不调用infos rpc
##### Version 1.29.2
> 1. fix isAuthor bug
##### Version 1.29.1
> 1. 异步载入作者列表
##### Version 1.29.0
> 1. 活动开始后自动增加活动tag
> 2. 增加活动时成为创作者API
> 3. 更改查询作者权限方案 不存内存 存redis 解决延时问题
> 4. 支持草稿无标题
> 5. 增加勋章字段
##### Version 1.28.1
> 1. 增加推荐池列表接口
##### Version 1.28.0
> 1. 创作中心逻辑迁移到专栏
##### Version 1.27.1
> 1. 修复更多文章接口返回null的问题
##### Version 1.27.0
> 1. isAuthor接口增加是否封禁字段
> 2. 增加更多文章接口
> 3. home接口增加热门排行
> 4. viewinfo增加稍后再看和小窗播放选项
> 5. 增加用户引导状态功能
##### Version 1.26.7
> 1.增加isAuthor接口
##### Version 1.26.6
> 1.避免分区排序批量回源
##### Version 1.26.5
> 1.修复author bug
##### Version 1.26.4
> 1.修复banner bug
##### Version 1.26.2
> 1.修复票务接口bug
##### Version 1.26.1
> 1.增加notice接口
##### Version 1.26.0
> 1.增加recommends/plus接口
##### Version 1.25.2
> 1.增加origin_image_urls字段
##### Version 1.25.1
> 1.增加动态简介字段
##### Version 1.25.0
> 1.增加番剧/音乐/商品/票务卡片接口
##### Version 1.24.9
> 1.读流量接入点赞服务
##### Version 1.24.8
> 1.修复客户端参数bug
##### Version 1.24.7
> 1.修复banner index 从0开始的问题
> 2.修复作者更多文章没有过滤禁止分发的问题
##### Version 1.24.6
> 1.修复up主缓存不过期的问题
> 2.修复up主列表为空时空缓存不生效的问题
> 3.applyinfo去掉未登录错误
##### Version 1.24.4
> 1.接入点赞服务
##### Version 1.24.1
> 1.封禁用户不能投稿
##### Version 1.24.0
> 1.支持自定义投稿上限
> 2.viewinfo增加能否分享字段
> 3.支持关闭通过投稿视频获得的专栏投稿权限
##### Version 1.23.9
> 1.修复排序时间bug
##### Version 1.23.8
> 1.修复duration计算bug
##### Version 1.23.7
> 1.修复过期排序数据问题
##### Version 1.23.6
> 1.修复同一用户多次投诉问题
##### Version 1.23.5
> 1.修复专栏锁定再次提交的问题
##### Version 1.23.4
> 1.排序缓存过滤禁止分发的内容
##### Version 1.23.3
> 1.修复addView接口 && home banner
##### Version 1.23.2
> 1.修复空间接口bug
##### Version 1.23.1
> 1.修复banner上报字段
##### Version 1.23.0
> 1.banner增加客户端上报所需的字段
> 2.增加客户端浏览数据上报http接口
> 3.增加获取更多文章的rpc接口
> 4.up主文章列表增加排序字段
> 5.内网接口增加验证
##### Version 1.22.5
> 1.fix rand panic问题
##### Version 1.22.4
> 1.修复article josn序列化问题
##### Version 1.22.3
> 1. article model 换pb
> 2. log换log context
##### Version 1.22.2
> 1. 修复收藏失效文档导致数量不对的问题
> 2. 排行榜换新接口
##### Version 1.22.1
> 1.修复点赞消息链接
##### Version 1.22.0
> 1.增加UP主点赞消息通知
> 2.新增批量获取metas的接口供前端专栏卡片使用
> 3.recommends和home接口聚合是否点赞的状态
> 4.viewinfo增加image_urls字段
> 5.文章详情页Ta的更多文章模块展示前一篇+后三篇文章
##### Version 1.21.2
> 1.修复排行榜缓存更新问题
> 2.增加获取up主专栏列表的内网接口
##### Version 1.21.1
> 1.跟随基础库升级http client
##### Version 1.21.0
> 1.排行榜增加score和note字段
> 2.增加获取最新投稿数量的rpc接口
> 3.增加批量获取是否点赞的rpc接口
##### Version 1.20.5
> 1.返回先发后审的文章
##### Version 1.20.4
> 1.修复推荐角标问题
##### Version 1.20.3
> 1.增加人工智能部门需要的点击日志上报
##### Version 1.20.2
> 1.修复排序缓存bug
##### Version 1.20.1
> 1.修复收藏bug
##### Version 1.20.0
> 1.增加web需要的收藏接口
> 2.支持首页降级
> 3.增加阅读数排序
> 4.增加排行榜功能
> 5.banner从读库改成rpc接口
> 6.排序缓存改为最近3周数据 最新投稿全量缓存
##### Version 1.19.4
> 1.fix 推荐页面 panic bug
##### Version 1.19.3
> 1.视频up主自动获得发专栏权限
##### Version 1.19.2
> 1.回源时禁止分发不出现在作者的缓存中里
##### Version 1.19.1
> 1.更新时禁止分发不出现在作者的缓存中里
##### Version 1.19.0
> 1.新增是否是作者的RPC接口
> 2.Recommends RPC接口增加cid字段
> 3.专栏管理增加按照阅读数、收藏数、投币数排序的接口逻辑
> 4.viewinfo增加is_author字段
> 5.recommends接口增加rec_text字段
> 6.改变推荐分区为推荐池 每次随机取
##### Version 1.18.4
> 1.修复创作中心草稿分页错误
##### Version 1.18.3
> 1.修复作者表不更新的问题
##### Version 1.18.2
> 1.fix UserGet脏数据导致的panic问题
##### Version 1.18.1
> 1.fix 空指针问题
##### Version 1.18.0
> 1.新增一二级分区排序功能
> 2.新增首页banner位
> 3.viewinfo接口增加作者昵称
##### Version 1.17.4
> 1.修复viewinfo panic问题
##### Version 1.17.3
> 1.fix创作中心计数问题
> 2.完善保存文章和草稿时增加日志
##### Version 1.17.2
> 1.保存文章和草稿时增加日志,方便查问题
##### Version 1.17.1
> 1.修复更新推荐bug
##### Version 1.17
> 1.新增更新作者缓存RPC接口
##### Version 1.16.4
> 1.文章新增缓存增加日志
> 2.修复重启程序关闭顺序问题
##### Version 1.16.3
> 1."更早文章"接口不显示禁止分发文章
> 2.推荐接口增加from字段
##### Version 1.16.2
> 1.fix AddCache panic
##### Version 1.16.1
> 1.推荐接口支持按aid分页获取
##### Version 1.16.0
> 1.增加分区禁止属性
> 2.推荐位改为逆序排列
##### Version 1.15.0
> 1.添加"更早文章"接口
> 2.增加coin计数
> 3.meta中增加分类列表
> 4.喜欢功能做成双向表
##### Version 1.14.0
> 1.投诉计数
> 2.增加是否展示推荐新投稿配置项
##### Version 1.13.1
> 1.修复推荐不显示最新投稿问题
##### Version 1.13.0
> 1.新增收藏RPC接口
> 2.修复数据上报问题
> 3.修复推荐角标没有取消的问题
##### Version 1.12.0
> 1.修复草稿列表时间问题
##### Version 1.11.0
> 1.接入大仓库
##### Version 1.10.0
> 1.添加申请成为专栏作者的入口
##### Version 1.9.2
> 1.添加单个获取文章的HTTP接口
##### Version 1.9.1
> 1.修改viewinfo增加title字段
##### Version 1.9.0
> 1.添加批量获取文章的HTTP接口
##### Version 1.8.8
> 1.优化查询tag
##### Version 1.8.7
> 1.移除addView接口合并到viewinfo接口中
##### Version 1.8.6
> 1.修复创作中心获取文章内容调用表不对的问题
##### Version 1.8.5
> 1.修改文章列表不获取tag
##### Version 1.8.4
> 1.调整添加、更新、删除文章缓存
> 2.修复作者列表空缓存问题
> 3.增加推荐标志
##### Version 1.8.3
> 1.修复创作中心文章撤回后文章内容丢失
##### Version 1.8.2
> 1.http接口返回[]
##### Version 1.8.1
> 1.优化草稿列表,加读缓存逻辑
> 2.ptimes多返回值重构
> 3.收藏http接口返回[]
##### Version 1.8.0
> 1.增加投诉API
> 2.过滤落库存表
> 3.其他细微优化
##### Version 1.7.2
> 1.使用rpc token
##### Version 1.7.1
> 1.草稿分表
##### Version 1.7.0
> 1.长文本过滤从job转移过来
> 2.正文增加是否过滤的标志位
> 3.增加每日发布文章次数限制
##### Version 1.6.0
> 1.修复stat解析bug
> 2.作者信息增加认证和挂件字段
##### Version 1.5.0
> 1.stat 单个查询优化
> 2.去掉推荐读写锁改为chan
> 3.点赞缓存优化mc改为redis hash
> 4.切换成新版过滤rpc
##### Version 1.4.0
> 1.添加草稿异步写db
> 2.文章过滤放在job里做
##### Version 1.3.1
> 1.修复stats信息被置0的bug
##### Version 1.3.0
> 1.长文本过滤优化
> 2.修复稿件分区改变最新文章缓存未清理的bug
> 3.修复pn为0导致panic的bug
##### Version 1.2.1
> 1.稿件列表接口添加根据状态过滤
##### Version 1.2.0
> 1.增加批量获取稿件信息接口 使用过滤rpc
##### Version 1.1.1
> 1.修复过滤bug
##### Version 1.0.0
> 1.专栏项目初始化

View File

@@ -0,0 +1,11 @@
# Owner
changxuanran
lijiadong
qiuliang
# Author
lijiadong
changxuanran
# Reviewer
qiuliang

View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- changxuanran
- lijiadong
- qiuliang
labels:
- interface
- interface/openplatform/article
- openplatform
options:
no_parent_owners: true
reviewers:
- changxuanran
- lijiadong
- qiuliang

View File

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

View File

@@ -0,0 +1,21 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_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(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 = [
"article-test.toml",
"convey-test.toml",
],
importpath = "go-common/app/interface/openplatform/article/cmd",
tags = ["automanaged"],
deps = [
"//app/interface/openplatform/article/conf:go_default_library",
"//app/interface/openplatform/article/http:go_default_library",
"//app/interface/openplatform/article/rpc/server:go_default_library",
"//app/interface/openplatform/article/service:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,496 @@
version = "1.0.0"
user = "nobody"
pid = "/tmp/article.pid"
dir = "./"
perf = "0.0.0.0:6750"
family = "article"
[identify]
whiteAccessKey = ""
whiteMid = 0
csrfOn = true
[identify.app]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 5
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.co"
secret = "http://open.bilibili.co"
[identify.httpClient]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
dial = "30ms"
timeout = "100ms"
keepAlive = "60s"
[identify.httpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.httpClient.url]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[rpcServer2]
token= "123456"
[[rpcServer2.servers]]
proto = "tcp"
addr = "0.0.0.0:6755"
weight = 10
[rpcServer2.zookeeper]
root = "/microservice/article-service-x/"
addrs = ["172.16.33.172:2181"]
timeout = "60s"
[displayInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[clickInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[AIclickInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[ShowInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[CheatInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[log]
dir = "/data/log/article/"
#[log.syslog]
# proto = "udp"
# addr = "172.18.19.22:9999"
# project = "article"
# chanSize = 10240
[app]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
[bm]
addr = "0.0.0.0:6751"
timeout = "1s"
[HTTPClient]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
dial = "50ms"
timeout = "1s"
keepAlive = "60s"
timer = 128
[HTTPClient.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[messageHTTPClient]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
dial = "50ms"
timeout = "1s"
keepAlive = "60s"
timer = 128
[messageHTTPClient.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[tracer]
family = "platform/article"
proto = "unixgram"
addr = "/var/run/dapper-collect/dapper-collect.sock"
[accountRPC]
pullInterval = "10s"
[accountRPC.client]
timeout = "1500ms"
[accountRPC.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[accountRPC.conf]
domain = "api.bilibili.co"
key = "53e2fa226f5ad348"
secret = "3cf6bd1b0ff671021da5f424fea4b04a"
[tagRPC]
pullInterval = "10s"
[tagRPC.client]
proto = "tcp"
timeout = "1s"
timer = 1000
[tagRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[tagRPC.zookeeper]
root = "/microservice/tag-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[favRPC]
pullInterval = "10s"
[favRPC.client]
proto = "tcp"
addr = "172.16.33.56:6019"
timeout = "1s"
timer = 1000
[favRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[favRPC.zookeeper]
root = "/microservice/favorite/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[arcRPC]
pullInterval = "10s"
group = "test"
[arcRPC.client]
proto = "tcp"
token = "123456"
timeout = "1s"
timer = 1000
[arcRPC.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[arcRPC.zookeeper]
root = "/microservice/archive-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[coinRPC]
pullInterval = "10s"
[coinRPC.client]
proto = "tcp"
timeout = "1s"
timer = 1000
[coinRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[coinRPC.zookeeper]
root = "/microservice/coin-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[ResRPC]
pullInterval = "10s"
[ResRPC.client]
proto = "tcp"
timeout = "1s"
timer = 1000
[resRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[resRPC.zookeeper]
root = "/microservice/resource-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[ThumbupRPC]
pullInterval = "10s"
[ThumbupRPC.client]
proto = "tcp"
timeout = "1s"
timer = 1000
[ThumbupRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[ThumbupRPC.zookeeper]
root = "/microservice/thumbup-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[FilterRPC]
pullInterval = "10s"
[FilterRPC.client]
proto = "tcp"
timeout = "10s"
timer = 1000
[FilterRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[FilterRPC.zookeeper]
root = "/microservice/filter-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[HistoryRPC]
pullInterval = "10s"
[HistoryRPC.client]
proto = "tcp"
timeout = "10s"
timer = 1000
[HistoryRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[HistoryRPC.zookeeper]
root = "/microservice/history/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[redis]
name = "article"
proto = "tcp"
addr = "172.16.33.54:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[mysql]
[mysql.article]
addr = "172.16.33.54"
dsn = "test:test@tcp(172.16.33.54:3306)/bilibili_article?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8mb4,utf8"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[mysql.article.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[memcache]
name = "article"
proto = "tcp"
addr = "172.16.33.54:11211"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
expire = "15m"
articleExpire = "10m"
articleNoFilterExpire = "5m"
statsExpire = "15m"
likeExpire = "72h"
cardsExpire = "6h"
submitExpire = "2m"
ListArtsExpire = "24h"
ListExpire = "24h"
ArtListExpire = "24h"
UpListsExpire = "6h"
ListReadExpire = "1h"
HotspotExpire = "30m"
AuthorExpire = "24h"
[ecode]
domain = "172.16.33.248:6401"
all = "1h"
diff = "5m"
[ecode.clientconfig]
key = "test"
secret = "e6c4c252dc7e3d8a90805eecd7c73396"
dial = "2000ms"
timeout = "2s"
keepAlive = "10s"
timer = 128
[ecode.clientconfig.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[ecode.app]
key = "test"
secret = "e6c4c252dc7e3d8a90805eecd7c73396"
[statDatabus]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "ArticleStat-Article-P"
topic= "ArticleStat-T"
action="pub"
name = "article/article-pub"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[[RankCategories]]
name = "月榜"
id = 1
[[RankCategories]]
name = "周榜"
id = 2
[[RankCategories]]
name = "昨天"
id = 3
[[RankCategories]]
name = "前天"
id = 4
[message]
URL = "http://message.bilibili.com/api/notify/send.user.notify.do"
MC = "1_12_1"
[cards]
TicketURL = "http://api.bilibili.co/api/ticket/project/getcard"
MallURL = "http://mall.bilibili.co/mall-items/items/itemsListByQuery"
AudioURL = "http://api.bilibili.co/x/internal/v1/audio/songs/batch"
BangumiURL = "http://bangumi.bilibili.co/ext/internal/cardinfos"
[hbase]
master = ""
meta = ""
dialTimeout = "1s"
readTimeout = "150ms"
readsTimeout = "600ms"
writeTimeout = "200ms"
writesTimeout = "600ms"
[hbase.zookeeper]
root = ""
addrs = ["172.18.33.163:2181","172.18.33.164:2181","172.18.33.165:2181"]
timeout = "30s"
[bfs]
timeout="5s"
maxFileSize=5242880
bucket="article"
url="http://bfs.bilibili.co/bfs/article/"
method="PUT"
key="4d08035f0b341509"
secret="ed4161d96c7612b670f75fa4be4e15"
[antispam]
on=true
second=3
n=100
hour=12
m=1000
[antispam.redis]
name = "article"
proto = "tcp"
addr = "172.16.33.54:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[degradeConfig]
expire = 86400
[degradeConfig.memcache]
name = "article"
proto = "tcp"
addr = "172.16.33.54:11211"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
[article]
expireUpper = "72h"
expireArtLikes = "72h"
expireSortArts = "24h"
TTLSortArts = "72h"
ExpireRank = "10m"
TTLRank = "24m"
ExpireMaxLike = "720h"
ExpireHotspot = "6h"
creationDefaultSize = 20
creationMaxSize = 200
upperDraftLimit = 30
upperArticleLimit = 5
updateRecommendsInteval = "5m"
maxRecommendPsSize = 100
maxRecommendPnSize = 10000
maxUpperListPsSize = 100
maxArchives = 40
maxComplaintReasonLimit = 1000
maxArticleMetas = 100
maxApplyContentLimit = 5000
maxApplyCategoryLimit = 200
maxLikeMidLen = 10
RecommendAidLen = 20
SortLimitTime = "504h"
UpdateBannersInteval = "5m"
bannerIDs = [2127,2128,2129,2130,2131,2132,2133,2134]
ActBannerIDs = [2442]
RecommendRegionLen = 2
SkyHorseRecommendRegionLen = 5
RankHost = "http://172.16.33.57:10800"
MessageMids = []
MaxContentSize = 1048576
MaxContentLength = 20000
MinContentLength = 200
ActAddURI = "http://matsuri.bilibili.co/api/likes/item/add/%d"
ActDelURI = "http://matsuri.bilibili.co/api/likes/item/up"
ActURI = "http://matsuri.bilibili.co/activity/list/article"
ListLimit = 10
ListArtsLimit = 500
AppCategoryName = "更多"
AppCategoryURL = "http://i0.hdslb.com/bfs/archive/2740eba8124516ac62edf84f65a858ed68b390de.png"
SkyHorseURL = ""
SkyHorseGray = [1]
SkyHorseGrayUsers = [1]
ListDefaultImage = "http://i0.hdslb.com/bfs/archive/2740eba8124516ac62edf84f65a858ed68b390de.png"
ExpireReadPing = "2m"
ExpireReadSet = "24h"

View File

@@ -0,0 +1,497 @@
version = "1.0.0"
user = "nobody"
pid = "/tmp/article.pid"
dir = "./"
perf = "127.0.0.1:6750"
family = "article"
[identify]
whiteAccessKey = ""
whiteMid = 0
csrfOn = true
[identify.app]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 5
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.co"
secret = "http://open.bilibili.co"
[identify.httpClient]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
dial = "30ms"
timeout = "100ms"
keepAlive = "60s"
[identify.httpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.httpClient.url]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[rpcServer2]
token= "123456"
[[rpcServer2.servers]]
proto = "tcp"
addr = "0.0.0.0:6755"
weight = 10
[rpcServer2.zookeeper]
root = "/microservice/article-test/"
addrs = ["172.18.33.169:2181"]
timeout = "1s"
[displayInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[clickInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[AIclickInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[ShowInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[CheatInfoc]
taskID = "1"
proto = "tcp"
addr = "127.0.0.1:80"
chanSize = 10240
[log]
dir = "/data/log/article"
[app]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
[bm]
addr = "0.0.0.0:6751"
timeout = "1s"
[HTTPClient]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
dial = "50ms"
timeout = "1s"
keepAlive = "60s"
timer = 128
[HTTPClient.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[messageHTTPClient]
key = "9cfc54570033cd61"
secret = "9d63835fa38fe58a62d9f49ef5da296f"
dial = "50ms"
timeout = "1s"
keepAlive = "60s"
timer = 128
[messageHTTPClient.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[tracer]
family = "platform/article"
proto = "unixgram"
addr = "/var/run/dapper-collect/dapper-collect.sock"
[accountRPC]
pullInterval = "10s"
[accountRPC.client]
timeout = "1500ms"
[accountRPC.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[accountRPC.conf]
domain = "api.bilibili.co"
key = "53e2fa226f5ad348"
secret = "3cf6bd1b0ff671021da5f424fea4b04a"
[tagRPC]
pullInterval = "10s"
[tagRPC.client]
proto = "tcp"
addr = "172.16.33.56:6099"
timeout = "1s"
timer = 1000
[tagRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[tagRPC.zookeeper]
root = "/microservice/tag-service/"
addrs = [" 172.16.33.169:2181"]
timeout = "30s"
[favRPC]
pullInterval = "10s"
[favRPC.client]
proto = "tcp"
addr = "172.16.33.56:6019"
timeout = "1s"
timer = 1000
[favRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[favRPC.zookeeper]
root = "/microservice/favorite/"
addrs = [" 172.16.33.169:2181"]
timeout = "30s"
[arcRPC]
pullInterval = "10s"
group = "test"
[arcRPC.client]
proto = "tcp"
addr = "172.16.33.56:6279"
token = "123456"
timeout = "1s"
timer = 1000
[arcRPC.client.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[arcRPC.zookeeper]
root = "/microservice/archive-service/"
addrs = [" 172.16.33.169:2181"]
timeout = "30s"
[coinRPC]
pullInterval = "10s"
[coinRPC.client]
proto = "tcp"
addr = "172.16.33.56:6159"
timeout = "1s"
timer = 1000
[coinRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[coinRPC.zookeeper]
root = "/microservice/coin-service/"
addrs = [" 172.16.33.54:2181"]
timeout = "30s"
[ResRPC]
pullInterval = "10s"
[ResRPC.client]
proto = "tcp"
timeout = "1s"
timer = 1000
[resRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[resRPC.zookeeper]
root = "/microservice/resource-service/"
addrs = ["172.16.33.54:2181"]
timeout = "30s"
[ThumbupRPC]
pullInterval = "10s"
[ThumbupRPC.client]
proto = "tcp"
timeout = "1s"
timer = 1000
[ThumbupRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[ThumbupRPC.zookeeper]
root = "/microservice/thumbup-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[FilterRPC]
pullInterval = "10s"
[FilterRPC.client]
proto = "tcp"
timeout = "10s"
timer = 1000
[FilterRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[FilterRPC.zookeeper]
root = "/microservice/filter-service/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[HistoryRPC]
pullInterval = "10s"
[HistoryRPC.client]
proto = "tcp"
timeout = "10s"
timer = 1000
[HistoryRPC.client.breaker]
window ="10s"
sleep ="10ms"
bucket = 10
ratio = 0.5
request = 100
[HistoryRPC.zookeeper]
root = "/microservice/history/"
addrs = ["172.16.33.172:2181"]
timeout = "30s"
[redis]
name = "article"
proto = "tcp"
addr = "172.18.33.61:6881"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[mysql]
[mysql.article]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_article?timeout=500s&readTimeout=500s&writeTimeout=500s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "1000s"
execTimeout = "200s"
tranTimeout = "2000s"
[mysql.article.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[memcache]
name = "article"
proto = "tcp"
addr = "172.18.33.61:11233"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
expire = "15m"
articleExpire = "10m"
articleNoFilterExpire = "5m"
statsExpire = "15m"
likeExpire = "72h"
cardsExpire = "6h"
submitExpire = "2m"
ListArtsExpire = "24h"
ListExpire = "24h"
ArtListExpire = "24h"
UpListsExpire = "6h"
ListReadExpire = "1h"
HotspotExpire = "30m"
AuthorExpire = "24h"
[ecode]
domain = "172.16.33.248:6401"
# domain = "api.bilibili.co"//线上
all = "1h"
diff = "5m"
[ecode.clientconfig]
key = "test" # 测试环境,线上使用业务自己的
secret = "e6c4c252dc7e3d8a90805eecd7c73396"
dial = "2000ms"
timeout = "2s"
keepAlive = "10s"
timer = 128
[ecode.clientconfig.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[ecode.app]
key = "test" # 测试环境,线上使用业务自己的
secret = "e6c4c252dc7e3d8a90805eecd7c73396"
[statDatabus]
key = "key"
secret= "secret"
group= "ArticleStat-Article-P"
topic= "ArticleStat-T"
action="pub"
name = "article-job/article-stat-sub"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[[RankCategories]]
name = "月榜"
id = 1
[[RankCategories]]
name = "周榜"
id = 2
[[RankCategories]]
name = "昨天"
id = 3
[[RankCategories]]
name = "前天"
id = 4
[message]
URL = "http://message.bilibili.com/api/notify/send.user.notify.do"
MC = "1_12_1"
[cards]
TicketURL = "http://uat-show.bilibili.com/api/ticket-b/project/getcard"
MallURL = "http://uat-mall.bilibili.co/mall-items/items/itemsListByQuery"
AudioURL = "http://uat-api.bilibili.co/x/internal/v1/audio/songs/batch"
BangumiURL = "http://bangumi.bilibili.co/ext/internal/cardinfos"
[hbase]
master = ""
meta = ""
dialTimeout = "1s"
readTimeout = "150ms"
readsTimeout = "600ms"
writeTimeout = "200ms"
writesTimeout = "600ms"
[hbase.zookeeper]
root = ""
addrs = ["172.18.33.163:2181","172.18.33.164:2181","172.18.33.165:2181"]
timeout = "30s"
[bfs]
timeout="5s"
maxFileSize=5242880
bucket="article"
url="http://bfs.bilibili.co/bfs/article/"
method="PUT"
key="4d08035f0b341509"
secret="ed4161d96c7612b670f75fa4be4e15"
[antispam]
on=true
second=3
n=100
hour=12
m=1000
[antispam.redis]
name = "article"
proto = "tcp"
addr = "172.16.33.54:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[degradeConfig]
expire = 86400
[degradeConfig.memcache]
name = "article"
proto = "tcp"
addr = "172.16.33.54:11211"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
[article]
expireUpper = "72h"
expireArtLikes = "72h"
expireSortArts = "24h"
TTLSortArts = "72h"
ExpireRank = "10m"
TTLRank = "24m"
ExpireMaxLike = "720h"
ExpireHotspot = "6h"
creationDefaultSize = 20
creationMaxSize = 200
upperDraftLimit = 30
upperArticleLimit = 500
updateRecommendsInteval = "5m"
maxRecommendPsSize = 100
maxRecommendPnSize = 10000
maxUpperListPsSize = 100
maxArchives = 40
maxComplaintReasonLimit = 1000
maxArticleMetas = 100
maxApplyContentLimit = 5000
maxApplyCategoryLimit = 200
maxLikeMidLen = 200
RecommendAidLen = 20
SortLimitTime = "504h"
UpdateBannersInteval = "5m"
bannerIDs = [2127,2128,2129,2130,2131,2132,2133,2134]
ActBannerIDs = [2442]
RecommendRegionLen = 20
SkyHorseRecommendRegionLen = 5
RankHost = "http://172.16.33.57:10800"
MessageMids = []
MaxContentSize = 1048576
MaxContentLength = 20000
MinContentLength = 200
ActAddURI = "http://matsuri.bilibili.co/api/likes/item/add/%d"
ActDelURI = "http://matsuri.bilibili.co/api/likes/item/up"
ActURI = "http://matsuri.bilibili.co/activity/list/article"
ListLimit = 10
ListArtsLimit = 500
AppCategoryName = "更多"
AppCategoryURL = "http://i0.hdslb.com/bfs/archive/2740eba8124516ac62edf84f65a858ed68b390de.png"
SkyHorseURL = ""
SkyHorseGray = [1]
SkyHorseGrayUsers = [1]
ListDefaultImage = "http://i0.hdslb.com/bfs/archive/2740eba8124516ac62edf84f65a858ed68b390de.png"
ExpireReadPing = "2m"
ExpireReadSet = "24h"

View File

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

View File

@@ -0,0 +1,6 @@
#!/bin/bash
command -v goconvey >/dev/null 2>&1 || { echo >&2 "required goconvey but it's not installed."; echo "Aborting."; echo "Please run commond: go get github.com/smartystreets/goconvey"; exit 1; }
cd ../
goconvey -excludedDirs "vendor,node_modules,rpc" -packages 1

View File

@@ -0,0 +1,48 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/interface/openplatform/article/conf",
tags = ["automanaged"],
deps = [
"//app/interface/openplatform/article/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/auth:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc: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,267 @@
package conf
import (
"errors"
"flag"
"go-common/app/interface/openplatform/article/model"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
hbase "go-common/library/database/hbase.v2"
)
// global var
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config config set
type Config struct {
// base
// elk
Log *log.Config
// BM
BM *bm.ServerConfig
// HTTPClient .
HTTPClient *bm.ClientConfig
MessageHTTPClient *bm.ClientConfig
// tracer
Tracer *trace.Config
// auth
Auth *auth.Config
// verify
Verify *verify.Config
// redis
Redis *redis.Config
// memcache
Memcache *Memcache
// MySQL
MySQL MySQL
// rpc
AccountRPC *rpc.ClientConfig
TagRPC *rpc.ClientConfig
FavRPC *rpc.ClientConfig
ArcRPC *rpc.ClientConfig
CoinRPC *rpc.ClientConfig
ResRPC *rpc.ClientConfig
ThumbupRPC *rpc.ClientConfig
FilterRPC *rpc.ClientConfig
HistoryRPC *rpc.ClientConfig
SearchRPC *warden.ClientConfig
// databus
StatDatabus *databus.Config
// infoc log2
DisplayInfoc *infoc.Config
ClickInfoc *infoc.Config
AIClickInfoc *infoc.Config
ShowInfoc *infoc.Config
CheatInfoc *infoc.Config
// ecode
Ecode *ecode.Config
//RankCategories .
RankCategories []*model.RankCategory
//Message
Message Message
Cards Cards
//hbase
HBase *hbase.Config
// BFS
BFS *BFS
// Antispam
Antispam *antispam.Config
// DegradeConfig
DegradeConfig *DegradeConfig
// artcile
Article Article
//Berserker
Berserker Berserker
//Sentinel
Sentinel *Sentinel
}
//Sentinel .
type Sentinel struct {
EnableSentinel int `json:"enableSentinel"`
DurationSample int `json:"durationSample"`
MonitorCountSample int `json:"monitorCountSample"`
MonitorRateSample int `json:"monitorRateSample"`
DebugSample int `json:"debugSample"`
}
// Berserker .
type Berserker struct {
AppKey string
AppSecret string
URL string
}
// Memcache config
type Memcache struct {
*memcache.Config
ArticleExpire time.Duration
StatsExpire time.Duration
LikeExpire time.Duration
CardsExpire time.Duration
SubmitExpire time.Duration
ListArtsExpire time.Duration
ListExpire time.Duration
ArtListExpire time.Duration
UpListsExpire time.Duration
ListReadExpire time.Duration
HotspotExpire time.Duration
AuthorExpire time.Duration
ArticlesIDExpire time.Duration
ArticleTagExpire time.Duration
UpStatDailyExpire time.Duration
}
// MySQL config
type MySQL struct {
Article *sql.Config
}
// Cards config
type Cards struct {
TicketURL string
MallURL string
AudioURL string
BangumiURL string
}
// BFS bfs config
type BFS struct {
Timeout time.Duration
MaxFileSize int
Bucket string
URL string
Method string
Key string
Secret string
}
// DegradeConfig .
type DegradeConfig struct {
Expire int32
Memcache *memcache.Config
}
// Article article config
type Article struct {
ExpireUpper time.Duration
ExpireArtLikes time.Duration
ExpireSortArts time.Duration
TTLSortArts time.Duration
ExpireRank time.Duration
TTLRank time.Duration
ExpireMaxLike time.Duration
ExpireHotspot time.Duration
CreationDefaultSize int
CreationMaxSize int
UpperDraftLimit int
UpperArticleLimit int
UpdateRecommendsInteval time.Duration
MaxRecommendPnSize int64
MaxRecommendPsSize int64
MaxUpperListPsSize int64
MaxArchives int
MaxComplaintReasonLimit int64
MaxArticleMetas int
MaxApplyContentLimit int64
MaxApplyCategoryLimit int64
MaxLikeMidLen int
RecommendAidLen int
SortLimitTime time.Duration
UpdateBannersInteval time.Duration
BannerIDs []int
ActBannerIDs []int
RecommendRegionLen int
SkyHorseRecommendRegionLen int
RankHost string
MessageMids []int64
MaxContentSize int
MaxContentLength int
MinContentLength int
ActAddURI string
ActDelURI string
ActURI string
ListLimit int
ListArtsLimit int
AppCategoryName string
AppCategoryURL string
SkyHorseURL string
SkyHorseGray []int64
SkyHorseGrayUsers []int64
ListDefaultImage string
RecommendAuthors int
ExpireReadPing time.Duration
ExpireReadSet time.Duration
Media []int64
EditTimes int
RecommendAuthorsURL string
}
// Message .
type Message struct {
URL string
MC string
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
err = load()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,119 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"bigdata_test.go",
"cards_test.go",
"creation_mc_test.go",
"creation_test.go",
"dao_test.go",
"list_mc_test.go",
"memcached_test.go",
"mysql_article_test.go",
"mysql_author_test.go",
"mysql_list_test.go",
"mysql_recommend_test.go",
"mysql_test.go",
"mysql_upper_test.go",
"rank_test.go",
"redis_like_test.go",
"redis_sort_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/openplatform/article/conf:go_default_library",
"//app/interface/openplatform/article/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/ecode:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/go-sql-driver/mysql:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/gopkg.in/h2non/gock.v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"activity.go",
"anniversary_mc.go",
"author.go",
"berserker.go",
"bfs.go",
"bigdata.go",
"cache.go",
"cards.go",
"creation.go",
"creation_mc.go",
"dao.cache.go",
"dao.go",
"databus.go",
"dynamic.go",
"list_mc.go",
"mc.cache.go",
"media.go",
"memcached.go",
"message.go",
"mysql.go",
"mysql_article.go",
"mysql_author.go",
"mysql_complaint.go",
"mysql_draft.go",
"mysql_list.go",
"mysql_recommend.go",
"mysql_upper.go",
"rank.go",
"redis.go",
"redis_like.go",
"redis_read.go",
"redis_sort.go",
],
importpath = "go-common/app/interface/openplatform/article/dao",
tags = ["automanaged"],
deps = [
"//app/interface/main/creative/model/data:go_default_library",
"//app/interface/openplatform/article/conf:go_default_library",
"//app/interface/openplatform/article/model:go_default_library",
"//library/cache:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf/env:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/golang.org/x/sync/singleflight:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,87 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
)
// HandleActivity add or delete activity
func (d *Dao) HandleActivity(c context.Context, mid, aid, actID int64, state int, ip string) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
params.Set("oid", strconv.FormatInt(aid, 10))
params.Set("state", strconv.Itoa(state)) //-1 0-待审 1
params.Set("type", strconv.Itoa(12))
var res struct {
Code int `json:"code"`
}
log.Info("HandleActivity url(%s)", d.c.Article.ActAddURI+"?"+params.Encode())
if err = d.httpClient.RESTfulPost(c, d.c.Article.ActAddURI, ip, params, &res, actID); err != nil {
log.Error("activity: HandleActivity url(%s) response(%s) error(%+v)", d.c.Article.ActAddURI+"?"+params.Encode(), res, err)
err = ecode.CreativeActivityErr
PromError("activity:活动绑定")
return
}
if res.Code != 0 {
log.Error("activity: HandleActivity url(%s) res(%v)", d.c.Article.ActAddURI+"?"+params.Encode(), res)
err = ecode.CreativeActivityErr
PromError("activity:活动绑定")
}
return
}
// DelActivity delete activity
func (d *Dao) DelActivity(c context.Context, aid int64, ip string) (err error) {
params := url.Values{}
params.Set("oid", strconv.FormatInt(aid, 10))
params.Set("otype", strconv.Itoa(12))
params.Set("state", strconv.Itoa(-1))
var res struct {
Code int `json:"code"`
}
if err = d.httpClient.Post(c, d.c.Article.ActDelURI, ip, params, &res); err != nil {
log.Error("DelActivity url(%s) response(%s) error(%+v)", d.c.Article.ActDelURI+"?"+params.Encode(), res, err)
err = ecode.CreativeActivityErr
PromError("activity:活动取消绑定")
return
}
if res.Code != 0 {
log.Error("DelActivity url(%s) res(%v)", d.c.Article.ActDelURI+"?"+params.Encode(), res)
err = ecode.CreativeActivityErr
PromError("activity:活动取消绑定")
}
return
}
// Activity .
func (d *Dao) Activity(c context.Context) (resp map[int64]*model.Activity, err error) {
var res struct {
Code int `json:"errno"`
Msg string `json:"msg"`
Data []*model.Activity `json:"data"`
}
err = d.httpClient.Get(c, d.c.Article.ActURI, "", nil, &res)
if err != nil {
PromError("activity:在线活动")
log.Error("activity: d.client.Get(%s) error(%+v)", d.c.Article.ActURI+"?", err)
return
}
if res.Code != 0 {
PromError("activity:在线活动")
log.Error("activity: url(%s) res code(%d) msg: %s", d.c.Article.ActURI+"?", res.Code, res.Msg)
err = ecode.Int(res.Code)
return
}
for _, act := range res.Data {
if resp == nil {
resp = make(map[int64]*model.Activity)
}
resp[act.ID] = act
}
return
}

View File

@@ -0,0 +1,10 @@
package dao
import (
"fmt"
)
// AnniversaryKey format anniversary key
func AnniversaryKey(mid int64) string {
return fmt.Sprintf("art_anniversary_%d", mid)
}

View File

@@ -0,0 +1,39 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/app/interface/openplatform/article/model"
"go-common/library/log"
)
// RecommendAuthors .
func (d *Dao) RecommendAuthors(c context.Context, platform string, mobiApp string, device string, build int, clientIP string, userID int64, buvid string, recType string, serviceArea string, _rapagesizen int, mid int64) (res []*model.RecommendAuthor, err error) {
params := url.Values{}
params.Set("platform", platform)
params.Set("mobi_app", mobiApp)
params.Set("device", device)
params.Set("clientip", clientIP)
params.Set("buvid", buvid)
params.Set("rec_type", recType)
params.Set("service_area", serviceArea)
params.Set("userid", strconv.FormatInt(userID, 10))
params.Set("build", strconv.Itoa(build))
params.Set("context_id", strconv.FormatInt(mid, 10))
var r struct {
Code int `json:"code"`
Data []*model.RecommendAuthor
}
if err = d.httpClient.Get(c, d.c.Article.RecommendAuthorsURL, "", params, &r); err != nil {
log.Error("activity: RecommendAuthors url(%s) error(%+v)", d.c.Article.RecommendAuthorsURL+"?"+params.Encode(), err)
return
}
if r.Code != 0 {
log.Error("activity: RecommendAuthors url(%s) res(%d) error(%+v)", d.c.Article.RecommendAuthorsURL+"?"+params.Encode(), r.Code, err)
return
}
res = r.Data
return
}

View File

@@ -0,0 +1,101 @@
package dao
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"go-common/app/interface/openplatform/article/model"
"go-common/library/log"
"go-common/library/xstr"
)
var _queryStr = `{"select":[{"name":"tid"},{"name":"oid"},{"name":"log_date"}],"where":{"tid":{"in":[%s]}},"page":{"limit":10,"skip":0}}`
// BerserkerTagArts .
func (d *Dao) BerserkerTagArts(c context.Context, tags []int64) (aids []int64, err error) {
var (
query string
res struct {
Code int
Msg string
Result []struct {
Tid int64 `json:"tid"`
Oid string `json:"oid"`
LogDate string `json:"log_date"`
}
}
tmps = make(map[int64]bool)
aid int64
date time.Time
now = time.Now()
)
query = fmt.Sprintf(_queryStr, xstr.JoinInts(tags))
if err = d.berserkerQuery(c, query, &res); err != nil {
return
}
if res.Code != 200 {
log.Error("s.BerserkerTagArts.query code(%d) msg(%s)", res.Code, res.Msg)
return
}
for _, v := range res.Result {
if date, err = time.Parse("20060102", v.LogDate); err != nil {
log.Error("s.BerserkerTagArts.time.Parse(%s) error(%+v)", v.LogDate, err)
return
}
if now.Sub(date) > time.Hour*60 {
continue
}
ids := strings.Split(v.Oid, "")
var ts []int64
for _, id := range ids {
if aid, err = strconv.ParseInt(id, 10, 64); err != nil {
log.Error("s.BerserkerTagArts.ParseInt(%s) error(%+v)", id, err)
return
}
if !tmps[aid] {
aids = append(aids, aid)
tmps[aid] = true
}
ts = append(ts, aid)
}
d.AddCacheAidsByTag(c, v.Tid, &model.TagArts{Tid: v.Tid, Aids: ts})
}
return
}
func (d *Dao) berserkerQuery(c context.Context, query string, res interface{}) (err error) {
var (
params = url.Values{}
now = time.Now().Format("2006-01-02 15:04:05")
sign string
req *http.Request
)
sign = d.sign(now)
params.Set("appKey", d.c.Berserker.AppKey)
params.Set("signMethod", "md5")
params.Set("timestamp", now)
params.Set("version", "1.0")
params.Set("query", query)
params.Set("sign", sign)
req, err = http.NewRequest(http.MethodGet, d.c.Berserker.URL+"?"+params.Encode(), nil)
if err != nil {
log.Error("d.berserkerQuery.NewRequest error(%+v)", err)
return
}
return d.httpClient.Do(c, req, res)
}
// Sign calc appkey and appsecret sign.
func (d *Dao) sign(ts string) string {
str := d.c.Berserker.AppSecret + "appKey" + d.c.Berserker.AppKey + "timestamp" + ts + "version1.0" + d.c.Berserker.AppSecret
mh := md5.Sum([]byte(str))
return strings.ToUpper(hex.EncodeToString(mh[:]))
}

View File

@@ -0,0 +1,126 @@
package dao
import (
"context"
"errors"
"io/ioutil"
"net"
"net/http"
nurl "net/url"
"strings"
"time"
"go-common/library/conf/env"
"go-common/library/ecode"
"go-common/library/log"
)
//Capture performs a HTTP Get request for the image url and upload bfs.
func (d *Dao) Capture(c context.Context, url string) (loc string, size int, err error) {
if err = checkURL(url); err != nil {
return
}
bs, ct, err := d.download(c, url)
if err != nil {
return
}
size = len(bs)
if size == 0 {
log.Error("capture image size(%d)|url(%s)", size, url)
return
}
if ct != "image/jpeg" && ct != "image/jpg" && ct != "image/png" && ct != "image/gif" {
log.Error("capture not allow image file type(%s)", ct)
err = ecode.CreativeArticleImageTypeErr
return
}
loc, err = d.UploadImage(c, ct, bs)
return loc, size, err
}
func checkURL(url string) (err error) {
// http || https
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
log.Error("capture url invalid(%s)", url)
err = ecode.RequestErr
return
}
u, err := nurl.Parse(url)
if err != nil {
log.Error("capture url.Parse error(%v)", err)
err = ecode.RequestErr
return
}
// make sure ip is public. avoid ssrf
ips, err := net.LookupIP(u.Host) // take from 1st argument
if err != nil {
log.Error("capture url(%s) LookupIP failed", url)
err = ecode.RequestErr
return
}
if len(ips) == 0 {
log.Error("capture url(%s) LookupIP length 0", url)
err = ecode.RequestErr
return
}
for _, v := range ips {
if !isPublicIP(v) {
log.Error("capture url(%s) is not public ip(%v)", url, v)
err = ecode.RequestErr
return
}
}
return
}
func isPublicIP(IP net.IP) bool {
if env.DeployEnv == env.DeployEnvDev || env.DeployEnv == env.DeployEnvFat1 || env.DeployEnv == env.DeployEnvUat {
return true
}
if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
return false
}
if ip4 := IP.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
return false
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
return false
case ip4[0] == 192 && ip4[1] == 168:
return false
default:
return true
}
}
return false
}
func (d *Dao) download(c context.Context, url string) (bs []byte, ct string, err error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Error("capture http.NewRequest error(%v)|url (%s)", err, url)
return
}
// timeout
ctx, cancel := context.WithTimeout(c, 800*time.Millisecond)
req = req.WithContext(ctx)
defer cancel()
resp, err := d.bfsClient.Do(req)
if err != nil {
log.Error("capture d.client.Do error(%v)|url(%s)", err, url)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Error("capture http.StatusCode nq http.StatusOK(%d)|url(%s)", resp.StatusCode, url)
err = errors.New("Download out image link failed")
return
}
if bs, err = ioutil.ReadAll(resp.Body); err != nil {
log.Error("capture ioutil.ReadAll error(%v)", err)
err = errors.New("Download out image link failed")
return
}
ct = http.DetectContentType(bs)
return
}

View File

@@ -0,0 +1,238 @@
package dao
import (
"bytes"
"crypto/md5"
"encoding/hex"
"net/url"
"strconv"
"time"
"go-common/app/interface/main/creative/model/data"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
"golang.org/x/net/context"
)
var (
//HBaseArticleTable 文章作者概况
HBaseArticleTable = "read_auth_stats_daily"
)
func hbaseMd5Key(aid int64) []byte {
hasher := md5.New()
hasher.Write([]byte(strconv.Itoa(int(aid))))
return []byte(hex.EncodeToString(hasher.Sum(nil)))
}
// UpStat get the stat of article.
func (d *Dao) UpStat(c context.Context, mid int64) (stat model.UpStat, err error) {
var (
tableName = HBaseArticleTable
)
result, err := d.hbase.Get(c, []byte(tableName), hbaseMd5Key(mid))
if err != nil {
log.Error("bigdata: d.hbase.Get BackupTable(%s, %d) error(%+v)", tableName, mid, err)
PromError("bigdata:hbase")
err = ecode.CreativeDataErr
return
}
if result == nil {
return
}
for _, c := range result.Cells {
if c == nil {
continue
}
v, _ := strconv.ParseInt(string(c.Value[:]), 10, 64)
if !bytes.Equal(c.Family, []byte("r")) {
continue
}
switch {
case bytes.Equal(c.Qualifier, []byte("view1")):
stat.View = v
case bytes.Equal(c.Qualifier, []byte("reply1")):
stat.Reply = v
case bytes.Equal(c.Qualifier, []byte("coin1")):
stat.Coin = v
case bytes.Equal(c.Qualifier, []byte("like1")):
stat.Like = v
case bytes.Equal(c.Qualifier, []byte("fav1")):
stat.Fav = v
case bytes.Equal(c.Qualifier, []byte("share1")):
stat.Share = v
case bytes.Equal(c.Qualifier, []byte("view0")):
stat.PreView = v
case bytes.Equal(c.Qualifier, []byte("reply0")):
stat.PreReply = v
case bytes.Equal(c.Qualifier, []byte("coin0")):
stat.PreCoin = v
case bytes.Equal(c.Qualifier, []byte("like0")):
stat.PreLike = v
case bytes.Equal(c.Qualifier, []byte("fav0")):
stat.PreFav = v
case bytes.Equal(c.Qualifier, []byte("share0")):
stat.PreShare = v
}
}
stat.IncrView = stat.View - stat.PreView
stat.IncrReply = stat.Reply - stat.PreReply
stat.IncrCoin = stat.Coin - stat.PreCoin
stat.IncrLike = stat.Like - stat.PreLike
stat.IncrFav = stat.Fav - stat.PreFav
stat.IncrShare = stat.Share - stat.PreShare
d.AddCacheUpStatDaily(c, mid, &stat)
return
}
// ThirtyDayArticle for Read/Reply/Like/Fav/Coin for article 30 days.
func (d *Dao) ThirtyDayArticle(c context.Context, mid int64) (res []*model.ThirtyDayArticle, err error) {
var (
tableName = "read_auth_stats" //文章30天数据
)
result, err := d.hbase.Get(c, []byte(tableName), hbaseMd5Key(mid))
if err != nil {
log.Error("bigdata: d.hbase.Get tableName(%s) mid(%d) error(%+v)", tableName, mid, err)
PromError("bigdata:30天数据")
err = ecode.CreativeDataErr
return
}
if result == nil || len(result.Cells) == 0 {
log.Warn("bigdata: ThirtyDay article no data (%s, %d)", tableName, mid)
PromError("bigdata:30天数据")
return
}
res = make([]*model.ThirtyDayArticle, 0, 5)
vtds := make([]*data.ThirtyDay, 0, 30)
ptds := make([]*data.ThirtyDay, 0, 30)
ltds := make([]*data.ThirtyDay, 0, 30)
ftds := make([]*data.ThirtyDay, 0, 30)
ctds := make([]*data.ThirtyDay, 0, 30)
view := &model.ThirtyDayArticle{Category: "view"}
reply := &model.ThirtyDayArticle{Category: "reply"}
like := &model.ThirtyDayArticle{Category: "like"}
fav := &model.ThirtyDayArticle{Category: "fav"}
coin := &model.ThirtyDayArticle{Category: "coin"}
for _, c := range result.Cells {
if c == nil {
continue
}
family := string(c.Family)
qual := string(c.Qualifier[:])
val := string(c.Value[:])
switch family {
case "v": //"阅读量"
t, v, err := parseKeyValue(qual, val)
if err != nil {
break
}
td := &data.ThirtyDay{}
td.DateKey = t
td.TotalIncr = v
vtds = append(vtds, td)
view.ThirtyDay = vtds
case "p": //"评论量"
t, v, err := parseKeyValue(qual, val)
if err != nil {
break
}
td := &data.ThirtyDay{}
td.DateKey = t
td.TotalIncr = v
ptds = append(ptds, td)
reply.Category = "reply"
reply.ThirtyDay = ptds
case "l": //"点赞量"
t, v, err := parseKeyValue(qual, val)
if err != nil {
break
}
td := &data.ThirtyDay{}
td.DateKey = t
td.TotalIncr = v
ltds = append(ltds, td)
like.Category = "like"
like.ThirtyDay = ltds
case "f": //"收藏量"
t, v, err := parseKeyValue(qual, val)
if err != nil {
break
}
td := &data.ThirtyDay{}
td.DateKey = t
td.TotalIncr = v
ftds = append(ftds, td)
fav.Category = "fav"
fav.ThirtyDay = ftds
case "c": //"投币量"
t, v, err := parseKeyValue(qual, val)
if err != nil {
break
}
td := &data.ThirtyDay{}
td.DateKey = t
td.TotalIncr = v
ctds = append(ctds, td)
coin.Category = "coin"
coin.ThirtyDay = ctds
}
}
res = append(res, view)
res = append(res, reply)
res = append(res, like)
res = append(res, fav)
res = append(res, coin)
return
}
func parseKeyValue(k string, v string) (timestamp, value int64, err error) {
tm, err := time.Parse("20060102", k)
if err != nil {
log.Error("time.Parse error(%+v)", err)
return
}
timestamp = tm.Unix()
value, err = strconv.ParseInt(v, 10, 64)
if err != nil {
log.Error("strconv.ParseInt error(%+v)", err)
}
return
}
// SkyHorse sky horse
func (d *Dao) SkyHorse(c context.Context, mid int64, build int, buvid string, plat int8, ps int) (res *model.SkyHorseResp, err error) {
if buvid == "" {
err = ecode.NothingFound
return
}
params := url.Values{}
params.Set("cmd", "article")
params.Set("mid", strconv.FormatInt(mid, 10))
params.Set("buvid", buvid)
params.Set("build", strconv.Itoa(build))
params.Set("plat", strconv.FormatInt(int64(plat), 10))
params.Set("ts", strconv.FormatInt(time.Now().Unix(), 10))
params.Set("request_cnt", strconv.Itoa(ps))
params.Set("from", "8")
res = &model.SkyHorseResp{}
err = d.httpClient.Get(c, d.c.Article.SkyHorseURL, "", params, &res)
if err != nil {
PromError("bigdata:天马接口")
log.Error("bigdata: d.client.Get(%s) error(%+v)", d.c.Article.SkyHorseURL+"?"+params.Encode(), err)
return
}
// -3: 数量不足
if res.Code != 0 && res.Code != -3 {
PromError("bigdata:天马接口")
log.Error("bigdata: url(%s) res: %+v", d.c.Article.SkyHorseURL+"?"+params.Encode(), res)
err = ecode.Int(res.Code)
return
}
if len(res.Data) == 0 {
PromError("bigdata:天马返回空")
log.Warn("bigdata: url(%s) res: %+v", d.c.Article.SkyHorseURL+"?"+params.Encode(), res)
}
return
}

View File

@@ -0,0 +1,71 @@
package dao
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_SkyHorse(t *testing.T) {
Convey("normal should get data", t, func() {
data := `{
"code": 0,
"data": [
{
"tid": 1652,
"id": 1,
"goto": "av",
"source": "user_group",
"image_cnt" : 3,
"av_feature": "a"
},
{
"tid": 8227,
"id": 2,
"goto": "av",
"source": "user_group",
"av_feature": "b"
}
],
"user_feature": "c"
}`
httpMock("GET", d.c.Article.SkyHorseURL).Reply(200).JSON(data)
res, err := d.SkyHorse(ctx(), 1, 0, "", 1, 20)
So(err, ShouldBeNil)
So(res.Data, ShouldNotBeEmpty)
})
Convey("-3 should get data", t, func() {
data := `{
"code": -3,
"data": [
{
"tid": 1652,
"id": 1,
"goto": "av",
"source": "user_group",
"image_cnt" : 3,
"av_feature": "a"
},
{
"tid": 8227,
"id": 2,
"goto": "av",
"source": "user_group",
"av_feature": "b"
}
],
"user_feature": "c"
}`
httpMock("GET", d.c.Article.SkyHorseURL).Reply(200).JSON(data)
res, err := d.SkyHorse(ctx(), 1, 0, "", 1, 20)
So(err, ShouldBeNil)
So(res.Data, ShouldNotBeEmpty)
})
Convey("code !=0 or -3 should get error", t, func() {
data := `{"code":-10}`
httpMock("GET", d.c.Article.SkyHorseURL).Reply(200).JSON(data)
res, err := d.SkyHorse(ctx(), 1, 0, "", 1, 20)
So(err, ShouldNotBeNil)
So(res.Data, ShouldBeEmpty)
})
}

View File

@@ -0,0 +1,131 @@
package dao
import (
"context"
"strconv"
"go-common/app/interface/openplatform/article/model"
)
func (d *Dao) cacheSFList(id int64) string {
return strconv.FormatInt(id, 10)
}
func (d *Dao) cacheSFListArts(id int64) string {
return strconv.FormatInt(id, 10)
}
func (d *Dao) cacheSFUpLists(id int64) string {
return strconv.FormatInt(id, 10)
}
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// cache: -nullcache=&model.List{ID:-1} -check_null_code=$!=nil&&$.ID==-1 -singleflight=true
List(c context.Context, id int64) (*model.List, error)
// cache: -batch=100 -max_group=10 -nullcache=&model.List{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Lists(c context.Context, keys []int64) (map[int64]*model.List, error)
// cache: -singleflight=true -nullcache=[]*model.ListArtMeta{{ID:-1}} -check_null_code=len($)==1&&$[0].ID==-1
ListArts(c context.Context, id int64) ([]*model.ListArtMeta, error)
// cache: -nullcache=[]*model.ListArtMeta{{ID:-1}} -check_null_code=len($)==1&&$[0].ID==-1
ListsArts(c context.Context, ids []int64) (map[int64][]*model.ListArtMeta, error)
// cache: -nullcache=-1 -batch=100 -max_group=10
ArtsListID(c context.Context, keys []int64) (map[int64]int64, error)
// cache: -nullcache=[]int64{-1} -check_null_code=len($)==1&&$[0]==-1 -singleflight=true
UpLists(c context.Context, mid int64) ([]int64, error)
// cache: -nullcache=&model.AuthorLimit{Limit:-1} -check_null_code=$!=nil&&$.Limit==-1
Author(c context.Context, mid int64) (*model.AuthorLimit, error)
}
//go:generate $GOPATH/src/go-common/app/tool/cache/mc
type _mc interface {
// 获取文集文章列表缓存
//mc: -key=listArtsKey
CacheListArts(c context.Context, id int64) (res []*model.ListArtMeta, err error)
// 增加文集含有的文章列表缓存
//mc: -key=listArtsKey -expire=d.mcListArtsExpire
AddCacheListArts(c context.Context, id int64, arts []*model.ListArtMeta) (err error)
// 获取文章所属文集
//mc: -key=articleListKey -type=get
ArticleListCache(c context.Context, id int64) (res int64, err error)
// 增加文章所属文集缓存
//mc: -key=articleListKey -expire=d.mcArtListExpire
SetArticlesListCache(c context.Context, arts map[int64]int64) (err error)
//mc: -key=listKey
CacheList(c context.Context, id int64) (res *model.List, err error)
//mc: -key=listKey -expire=d.mcListExpire
AddCacheList(c context.Context, id int64, list *model.List) (err error)
//mc: -key=listKey
CacheLists(c context.Context, ids []int64) (res map[int64]*model.List, err error)
//mc: -key=listKey -expire=d.mcListExpire
AddCacheLists(c context.Context, lists map[int64]*model.List) (err error)
//mc: -key=listArtsKey
CacheListsArts(c context.Context, ids []int64) (res map[int64][]*model.ListArtMeta, err error)
//mc: -key=listArtsKey -expire=d.mcListArtsExpire
AddCacheListsArts(c context.Context, arts map[int64][]*model.ListArtMeta) (err error)
//mc: -key=articleListKey
CacheArtsListID(c context.Context, ids []int64) (res map[int64]int64, err error)
//mc: -key=articleListKey -expire=d.mcArtListExpire
AddCacheArtsListID(c context.Context, arts map[int64]int64) (err error)
//mc: -key=upListsKey -expire=d.mcUpListsExpire
AddCacheUpLists(c context.Context, mid int64, lists []int64) (err error)
//mc: -key=upListsKey
CacheUpLists(c context.Context, id int64) (res []int64, err error)
//mc: -key=listReadCountKey -expire=d.mcListReadExpire
AddCacheListReadCount(c context.Context, id int64, read int64) (err error)
//mc: -key=listReadCountKey
CacheListReadCount(c context.Context, id int64) (res int64, err error)
//mc: -key=listReadCountKey
CacheListsReadCount(c context.Context, ids []int64) (res map[int64]int64, err error)
//mc: -key=hotspotsKey -expire=d.mcHotspotExpire
AddCacheHotspots(c context.Context, hots []*model.Hotspot) (err error)
//mc: -key=hotspotsKey
DelCacheHotspots(c context.Context) (err error)
//mc: -key=hotspotsKey
cacheHotspots(c context.Context) (res []*model.Hotspot, err error)
//mc: -key=mcHotspotKey
CacheHotspot(c context.Context, id int64) (res *model.Hotspot, err error)
//mc: -key=mcHotspotKey -expire=d.mcHotspotExpire
AddCacheHotspot(c context.Context, id int64, val *model.Hotspot) (err error)
// 增加作者状态缓存
//mc: -key=mcAuthorKey -expire=d.mcAuthorExpire
AddCacheAuthor(c context.Context, mid int64, author *model.AuthorLimit) (err error)
//mc: -key=mcAuthorKey
CacheAuthor(c context.Context, mid int64) (res *model.AuthorLimit, err error)
//mc: -key=mcAuthorKey
DelCacheAuthor(c context.Context, mid int64) (err error)
//mc: -key=slideArticlesKey
CacheListArtsId(c context.Context, buvid string) (*model.ArticleViewList, error)
//mc: -key=slideArticlesKey -expire=d.mcArticlesIDExpire
AddCacheListArtsId(c context.Context, buvid string, val *model.ArticleViewList) error
//mc: -key=slideArticlesKey
DelCacheListArtsId(c context.Context, buvid string) error
//mc: -key=AnniversaryKey -expire=60*60*24*30
CacheAnniversary(c context.Context, mid int64) (*model.AnniversaryInfo, error)
//mc: -key=mcTagKey
CacheAidsByTag(c context.Context, tag int64) (*model.TagArts, error)
//mc: -key=mcTagKey -expire=d.mcArticleTagExpire
AddCacheAidsByTag(c context.Context, tag int64, val *model.TagArts) error
//mc: -key=mcUpStatKey -expire=d.mcUpStatDailyExpire
CacheUpStatDaily(c context.Context, mid int64) (*model.UpStat, error)
//mc: -key=mcUpStatKey -expire=d.mcUpStatDailyExpire
AddCacheUpStatDaily(c context.Context, mid int64, val *model.UpStat) error
}
// RebuildUpListsCache .
func (d *Dao) RebuildUpListsCache(c context.Context, mid int64) (err error) {
lists, err := d.RawUpLists(c, mid)
if err != nil {
return
}
return d.AddCacheUpLists(c, mid, lists)
}
// RebuildListReadCountCache .
func (d *Dao) RebuildListReadCountCache(c context.Context, id int64) (err error) {
res, err := d.RawListReadCount(c, id)
if err != nil {
return
}
return d.AddCacheListReadCount(c, id, res)
}

View File

@@ -0,0 +1,139 @@
package dao
import (
"context"
"net/http"
"net/url"
"strings"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
// TicketCard get ticket card from api
func (d *Dao) TicketCard(c context.Context, ids []int64) (resp map[int64]*model.TicketCard, err error) {
params := url.Values{}
params.Set("id", xstr.JoinInts(ids))
params.Set("for", "2")
params.Set("tag", "0")
params.Set("price", "1")
params.Set("imgtype", "2")
params.Set("rettype", "1")
var res struct {
Code int `json:"errno"`
Msg string `json:"msg"`
Data map[int64]*model.TicketCard `json:"data"`
}
err = d.httpClient.Get(c, d.c.Cards.TicketURL, "", params, &res)
if err != nil {
PromError("cards:ticket接口")
log.Error("cards: d.client.Get(%s) error(%+v)", d.c.Cards.TicketURL+"?"+params.Encode(), err)
return
}
if res.Code != 0 {
PromError("cards:ticket接口")
log.Error("cards: url(%s) res code(%d) msg: %s", d.c.Cards.TicketURL+"?"+params.Encode(), res.Code, res.Msg)
err = ecode.Int(res.Code)
return
}
resp = res.Data
return
}
// MallCard .
func (d *Dao) MallCard(c context.Context, ids []int64) (resp map[int64]*model.MallCard, err error) {
idsStr := `{"itemsIdList":[` + xstr.JoinInts(ids) + "]}"
req, err := http.NewRequest("POST", d.c.Cards.MallURL, strings.NewReader(idsStr))
if err != nil {
PromError("cards:mall接口")
log.Error("cards: NewRequest(%s) error(%+v)", d.c.Cards.MallURL+"?"+idsStr, err)
return
}
var res struct {
Code int `json:"code"`
Data struct {
List []*model.MallCard `json:"list"`
} `json:"data"`
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("X-BACKEND-BILI-REAL-IP", "")
err = d.httpClient.Do(c, req, &res)
if err != nil {
PromError("cards:mall接口")
log.Error("cards: d.client.Get(%s) error(%+v)", d.c.Cards.MallURL+"?"+idsStr, err)
return
}
if res.Code != 0 {
PromError("cards:mall接口")
log.Error("cards: d.client.Get(%s) error(%+v)", d.c.Cards.MallURL+"?"+idsStr, err)
err = ecode.Int(res.Code)
return
}
resp = make(map[int64]*model.MallCard)
for _, l := range res.Data.List {
resp[l.ID] = l
}
return
}
// AudioCard .
func (d *Dao) AudioCard(c context.Context, ids []int64) (resp map[int64]*model.AudioCard, err error) {
params := url.Values{}
params.Set("ids", xstr.JoinInts(ids))
params.Set("level", "1")
var res struct {
Code int `json:"code"`
Data map[int64]*model.AudioCard `json:"data"`
}
err = d.httpClient.Get(c, d.c.Cards.AudioURL, "", params, &res)
if err != nil {
PromError("cards:audio接口")
log.Error("cards: d.client.Get(%s) error(%+v)", d.c.Cards.AudioURL+"?"+params.Encode(), err)
return
}
if res.Code != 0 {
PromError("cards:audio接口")
log.Error("cards: d.client.Get(%s) error(%+v)", d.c.Cards.AudioURL+"?"+params.Encode(), err)
err = ecode.Int(res.Code)
return
}
resp = res.Data
return
}
// BangumiCard .
func (d *Dao) BangumiCard(c context.Context, seasonIDs []int64, episodeIDs []int64) (resp map[int64]*model.BangumiCard, err error) {
params := url.Values{}
params.Set("season_ids", xstr.JoinInts(seasonIDs))
params.Set("episode_ids", xstr.JoinInts(episodeIDs))
var res struct {
Code int `json:"code"`
Data struct {
SeasonMap map[int64]*model.BangumiCard `json:"season_map"`
EpisodeMap map[int64]*model.BangumiCard `json:"episode_map"`
} `json:"result"`
}
err = d.httpClient.Post(c, d.c.Cards.BangumiURL, "", params, &res)
if err != nil {
PromError("cards:bangumi接口")
log.Error("cards: d.client.Get(%s) error(%+v)", d.c.Cards.BangumiURL+"?"+params.Encode(), err)
return
}
if res.Code != 0 {
PromError("cards:bangumi接口")
log.Error("cards: url(%s) res code(%d)", d.c.Cards.BangumiURL+"?"+params.Encode(), res.Code)
err = ecode.Int(res.Code)
return
}
resp = make(map[int64]*model.BangumiCard)
for id, item := range res.Data.EpisodeMap {
resp[id] = item
}
for id, item := range res.Data.SeasonMap {
resp[id] = item
}
return
}

View File

@@ -0,0 +1,172 @@
package dao
import (
"testing"
"go-common/app/interface/openplatform/article/model"
. "github.com/smartystreets/goconvey/convey"
)
func Test_MallCard(t *testing.T) {
Convey("normal should get data", t, func() {
data := `{"code":0,"message":"success","data":{"pageNum":1,"pageSize":2,"size":2,"startRow":1,"endRow":2,"total":2,"pages":1,"list":[{"itemsId":1,"brief":"韩版帅气","isLastestVersion":1,"name":"短袖T恤男学生新款韩版衬衫12","img":["//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg","//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg","//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg","//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg"],"onSaleTime":null,"offSaleTime":null,"price":0,"maxPrice":0,"sales":0,"frozenStock":null,"stock":null,"needUserinfoCollection":[1,2,3],"presaleStartOrderTime":0,"presaleEndOrderTime":0,"depositPrice":0,"deliveryTemplateId":0,"tianmaImg":"","version":3},{"itemsId":5,"brief":"韩版帅气","isLastestVersion":1,"name":"短袖T恤男学生新款韩版衬衫","img":["//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg","//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg","//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg","//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg"],"onSaleTime":null,"offSaleTime":null,"price":0,"maxPrice":0,"sales":0,"frozenStock":null,"stock":null,"needUserinfoCollection":[1,2,3],"presaleStartOrderTime":0,"presaleEndOrderTime":0,"depositPrice":0,"deliveryTemplateId":13566,"tianmaImg":"","version":2}],"prePage":0,"nextPage":0,"isFirstPage":true,"isLastPage":true,"hasPreviousPage":false,"hasNextPage":false,"navigatePages":8,"navigatepageNums":[1],"navigateFirstPage":1,"navigateLastPage":1,"firstPage":1,"lastPage":1}}`
httpMock("POST", d.c.Cards.MallURL).Reply(200).JSON(data)
res, err := d.MallCard(ctx(), []int64{1, 5})
So(err, ShouldBeNil)
So(res, ShouldResemble, map[int64]*model.MallCard{
1: &model.MallCard{
ID: 1,
Name: "短袖T恤男学生新款韩版衬衫12",
Brief: "韩版帅气",
Images: []string{
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
},
Price: 0,
},
5: &model.MallCard{
ID: 5,
Name: "短袖T恤男学生新款韩版衬衫",
Brief: "韩版帅气",
Images: []string{
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
"//img10.360buyimg.com/n0/jfs/t5392/234/1745592889/186437/4e9da0f7/5913b8dfNcc393bff.jpg",
},
Price: 0,
},
})
})
Convey("code !=0 should get error", t, func() {
data := `{"code":-3,"message":"faild","data":{}}`
httpMock("POST", d.c.Cards.MallURL).Reply(200).JSON(data)
_, err := d.MallCard(ctx(), []int64{1, 5})
So(err, ShouldNotBeNil)
})
}
func Test_TicketCard(t *testing.T) {
Convey("normal get data", t, func() {
data := `{"errno":0,"msg":"","data":{"75":{"id":75,"name":"赵丽颖见面会","status":1,"start_time":1500268460,"end_time":1538284460,"performance_image":"//uat-i1.hdslb.com/bfs/openplatform/201707/imrGbwzlkCYUs.jpeg","is_sale":1,"promo_tags":"1-2","stime":"7/17","etime":"9/30","province_name":"上海市","city_name":"上海市","district_name":"浦东新区","venue_name":"梅赛德斯奔驰文化中心","url":"https://show.bilibili.com/m/platform/detail.html?id=75&from=","price_low":0.01,"price_high":500},"80":{"id":80,"name":"演唱会测试C","status":0,"start_time":1501050922,"end_time":1501137326,"performance_image":"//uat-i0.hdslb.com/bfs/openplatform/201707/imXtcy7Kgllz2.jpeg","is_sale":1,"promo_tags":"1-1","stime":"7/26","etime":"7/27","province_name":"上海市","city_name":"上海市","district_name":"浦东新区","venue_name":"文化中心","url":"https://show.bilibili.com/m/platform/detail.html?id=80&from=","price_low":200,"price_high":500}}}`
httpMock("get", d.c.Cards.TicketURL).Reply(200).JSON(data)
res, err := d.TicketCard(ctx(), []int64{75, 80})
So(err, ShouldBeNil)
So(res, ShouldResemble, map[int64]*model.TicketCard{
75: &model.TicketCard{
ID: 75,
Name: "赵丽颖见面会",
Image: "//uat-i1.hdslb.com/bfs/openplatform/201707/imrGbwzlkCYUs.jpeg",
StartTime: 1500268460,
EndTime: 1538284460,
Province: "上海市",
City: "上海市",
District: "浦东新区",
Venue: "梅赛德斯奔驰文化中心",
PriceLow: 0.01,
URL: "https://show.bilibili.com/m/platform/detail.html?id=75&from=",
},
80: &model.TicketCard{
ID: 80,
Name: "演唱会测试C",
Image: "//uat-i0.hdslb.com/bfs/openplatform/201707/imXtcy7Kgllz2.jpeg",
StartTime: 1501050922,
EndTime: 1501137326,
Province: "上海市",
City: "上海市",
District: "浦东新区",
Venue: "文化中心",
PriceLow: 200,
URL: "https://show.bilibili.com/m/platform/detail.html?id=80&from=",
},
})
})
Convey("code != 0 should return error", t, func() {
data := `{"errno":-1,"msg":"","data":{}}`
httpMock("get", d.c.Cards.TicketURL).Reply(200).JSON(data)
res, err := d.TicketCard(ctx(), []int64{75, 80})
So(err, ShouldNotBeNil)
So(res, ShouldBeNil)
})
}
func Test_AudioCard(t *testing.T) {
Convey("normal get data", t, func() {
data := `{"code":0,"msg":"success","data":{"75":{"song_id":75,"title":"【Hanser】星电感应","up_mid":26609612,"up_name":"siroccox","play_num":17,"reply_num":0,"cover_url":"http://i0.hdslb.com/bfs/test/80740468b108a4f1b98316caa02dc8dcf5976caf.jpg"}}}`
httpMock("get", d.c.Cards.AudioURL).Reply(200).JSON(data)
res, err := d.AudioCard(ctx(), []int64{75})
So(err, ShouldBeNil)
So(res, ShouldResemble, map[int64]*model.AudioCard{
75: &model.AudioCard{
ID: 75,
Title: "【Hanser】星电感应",
UpMid: 26609612,
UpName: "siroccox",
Play: 17,
Reply: 0,
CoverURL: "http://i0.hdslb.com/bfs/test/80740468b108a4f1b98316caa02dc8dcf5976caf.jpg",
},
})
})
Convey("code != 0 should return error", t, func() {
data := `{"code":-1,"msg":"fail","data":{}}}`
httpMock("get", d.c.Cards.AudioURL).Reply(200).JSON(data)
_, err := d.AudioCard(ctx(), []int64{75})
So(err, ShouldNotBeNil)
})
}
func Test_BangumiCard(t *testing.T) {
exp := map[int64]*model.BangumiCard{
20031: &model.BangumiCard{
ID: 20031,
Image: "http://i0.hdslb.com/bfs/bangumi/77605418c0921578c469201d6384d6a32ed218e9.jpg",
Title: "地狱少女 宵伽",
Rating: struct {
Score float64 `json:"score"`
Count int64 `json:"count"`
}{
Score: 0,
Count: 0,
},
Playable: true,
FollowCount: 0,
PlayCount: 0,
},
}
Convey("seasons", t, func() {
Convey("normal get data", func() {
data := `{"code":0,"message":"success","result":{"season_map":{"20031":{"allow_review":1,"cover":"http://i0.hdslb.com/bfs/bangumi/77605418c0921578c469201d6384d6a32ed218e9.jpg","is_finish":1,"is_started":1,"media_id":11,"playable":true,"season_id":20031,"season_type":1,"season_type_name":"番剧","title":"地狱少女 宵伽","total_count":13}}}}`
httpMock("post", d.c.Cards.BangumiURL).Reply(200).JSON(data)
res, err := d.BangumiCard(ctx(), []int64{20031}, nil)
So(err, ShouldBeNil)
So(res, ShouldResemble, exp)
})
Convey("code != 0 should return error", func() {
data := `{"code":-1,"message":"fail","result":{}}`
httpMock("post", d.c.Cards.BangumiURL).Reply(200).JSON(data)
_, err := d.BangumiCard(ctx(), []int64{20031}, nil)
So(err, ShouldNotBeNil)
})
})
Convey("eps", t, func() {
Convey("normal get data", func() {
data := `{"code":0,"message":"success","result":{"episode_map":{"20031":{"allow_review":1,"cover":"http://i0.hdslb.com/bfs/bangumi/77605418c0921578c469201d6384d6a32ed218e9.jpg","is_finish":1,"is_started":1,"media_id":11,"playable":true,"season_id":20031,"season_type":1,"season_type_name":"番剧","title":"地狱少女 宵伽","total_count":13}}}}`
httpMock("post", d.c.Cards.BangumiURL).Reply(200).JSON(data)
res, err := d.BangumiCard(ctx(), nil, []int64{20031})
So(err, ShouldBeNil)
So(res, ShouldResemble, exp)
})
Convey("code != 0 should return error", func() {
data := `{"code":-1,"message":"fail","result":{}}`
httpMock("post", d.c.Cards.BangumiURL).Reply(200).JSON(data)
_, err := d.BangumiCard(ctx(), nil, []int64{20031})
So(err, ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,509 @@
package dao
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"hash"
"net/http"
"strconv"
"strings"
"time"
"database/sql"
artmdl "go-common/app/interface/openplatform/article/model"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
"go-common/library/xstr"
)
const (
// article
_addArticleMetaSQL = "INSERT INTO articles (category_id,title,summary,banner_url,template_id,state,mid,reprint,image_urls,attributes,words,dynamic_intro,origin_image_urls,act_id,media_id,spoiler,apply_time) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_addArticleContentSQL = "INSERT INTO article_contents_%s (article_id,content,tags) values (?,?,?)"
_addArticleVersionSQL = "INSERT INTO article_versions (article_id,category_id,title,state,content,summary,banner_url,template_id,mid,reprint,image_urls,attributes,words,dynamic_intro,origin_image_urls,act_id,media_id,spoiler,apply_time,ext_msg)values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_updateArticleVersionSQL = "UPDATE article_versions SET category_id=?,title=?,state=?,content=?,summary=?,banner_url=?,template_id=?,mid=?,reprint=?,image_urls=?,attributes=?,words=?,dynamic_intro=?,origin_image_urls=?,spoiler=?,apply_time=?,ext_msg=? where article_id=? and deleted_time=0"
_updateArticleMetaSQL = "UPDATE articles SET category_id=?,title=?,summary=?,banner_url=?,template_id=?,state=?,mid=?,reprint=?,image_urls=?,attributes=?,words=?,dynamic_intro=?,origin_image_urls =?,spoiler=?,apply_time=? WHERE id=?"
_updateArticleContentSQL = "UPDATE article_contents_%s SET content=?, tags=? WHERE article_id=?"
_deleteArticleMetaSQL = "UPDATE articles SET deleted_time=? WHERE id=?"
_deleteArticleContentSQL = "UPDATE article_contents_%s SET deleted_time=? WHERE article_id=?"
_deleteArticleVerionSQL = "UPDATE article_versions SET deleted_time=? WHERE article_id=?"
_updateArticleStateSQL = "UPDATE articles SET state=? WHERE id=?"
_updateArticleStateApplyTimeSQL = "UPDATE articles SET state=?,apply_time=? WHERE id=?"
_upperArticlesMetaCreationSQL = `SELECT id,category_id,title,summary,banner_url,template_id,state,mid,reprint,image_urls,publish_time,ctime,reason, attributes, dynamic_intro, origin_image_urls FROM articles WHERE mid=? and deleted_time=0
and state in (%s)`
_upperArticleCountCreationSQL = "SELECT state FROM articles WHERE mid=? and deleted_time=0"
_articleMetaCreationSQL = "SELECT id,category_id,title,summary,banner_url, template_id, state, mid, reprint, image_urls, publish_time,ctime, attributes, dynamic_intro, origin_image_urls, media_id, spoiler FROM articles WHERE id = ? and deleted_time = 0"
_articleContentCreationSQL = "SELECT content FROM article_contents_%s WHERE article_id=? AND deleted_time=0"
_countEditTimesSQL = "SELECT count(*) FROM article_histories_%s WHERE article_id=? AND deleted_time=0 AND state in (5,6,7)"
_articleVersionSQL = "SELECT article_id,category_id,title,state,content,summary,banner_url,template_id,reprint,image_urls,attributes,words,dynamic_intro,origin_image_urls,media_id,spoiler,apply_time,ext_msg FROM article_versions WHERE article_id=? AND deleted_time=0"
_reasonOfVersion = "SELECT reason FROM article_versions WHERE article_id=? AND state=? AND deleted_time=0 ORDER BY id DESC LIMIT 1"
)
// TxAddArticleMeta adds article's meta via transaction.
func (d *Dao) TxAddArticleMeta(c context.Context, tx *xsql.Tx, a *artmdl.Meta, actID int64) (id int64, err error) {
a.ImageURLs = artmdl.CleanURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CleanURLs(a.OriginImageURLs)
a.BannerURL = artmdl.CleanURL(a.BannerURL)
var (
res sql.Result
imageUrls = strings.Join(a.ImageURLs, ",")
originImageUrls = strings.Join(a.OriginImageURLs, ",")
applyTime = time.Now().Format("2006-01-02 15:04:05")
)
if res, err = tx.Exec(_addArticleMetaSQL, a.Category.ID, a.Title, a.Summary, a.BannerURL, a.TemplateID, a.State, a.Author.Mid, a.Reprint, imageUrls, a.Attributes, a.Words, a.Dynamic, originImageUrls, actID, a.Media.MediaID, a.Media.Spoiler, applyTime); err != nil {
PromError("db:新增文章meta")
log.Error("tx.Exec() error(%+v)", err)
return
}
if id, err = res.LastInsertId(); err != nil {
log.Error("res.LastInsertId() error(%+v)", err)
}
return
}
// TxAddArticleContent adds article's body via transaction.
func (d *Dao) TxAddArticleContent(c context.Context, tx *xsql.Tx, aid int64, content string, tags []string) (err error) {
var sqlStr = fmt.Sprintf(_addArticleContentSQL, d.hit(aid))
if _, err = tx.Exec(sqlStr, aid, content, strings.Join(tags, "\001")); err != nil {
PromError("db:新增文章content")
log.Error("tx.Exec(%s,%d) error(%+v)", sqlStr, aid, err)
}
return
}
// TxAddArticleVersion adds article version.
func (d *Dao) TxAddArticleVersion(c context.Context, tx *xsql.Tx, id int64, a *artmdl.Article, actID int64) (err error) {
a.ImageURLs = artmdl.CleanURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CleanURLs(a.OriginImageURLs)
a.BannerURL = artmdl.CleanURL(a.BannerURL)
var (
applyTime = time.Now().Format("2006-01-02 15:04:05")
imageUrls = strings.Join(a.ImageURLs, ",")
originImageUrls = strings.Join(a.OriginImageURLs, ",")
extMsg = &artmdl.ExtMsg{
Tags: a.Tags,
}
extStr []byte
)
if extStr, err = json.Marshal(extMsg); err != nil {
log.Error("json.Marshal error(%+v)", err)
return
}
if _, err = tx.Exec(_addArticleVersionSQL, id, a.Category.ID, a.Title, a.State, a.Content, a.Summary, a.BannerURL, a.TemplateID, a.Author.Mid, a.Reprint, imageUrls, a.Attributes, a.Words, a.Dynamic, originImageUrls, actID, a.Media.MediaID, a.Media.Spoiler, applyTime, string(extStr)); err != nil {
PromError("db:新增版本")
log.Error("tx.Exec() error(%+v)", err)
}
return
}
// TxUpdateArticleVersion updates article version.
func (d *Dao) TxUpdateArticleVersion(c context.Context, tx *xsql.Tx, id int64, a *artmdl.Article, actID int64) (err error) {
a.ImageURLs = artmdl.CleanURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CleanURLs(a.OriginImageURLs)
a.BannerURL = artmdl.CleanURL(a.BannerURL)
var (
applyTime = time.Now().Format("2006-01-02 15:04:05")
imageUrls = strings.Join(a.ImageURLs, ",")
originImageUrls = strings.Join(a.OriginImageURLs, ",")
extMsg = &artmdl.ExtMsg{
Tags: a.Tags,
}
extStr []byte
)
if extStr, err = json.Marshal(extMsg); err != nil {
log.Error("json.Marshal error(%+v)", err)
return
}
if _, err = tx.Exec(_updateArticleVersionSQL, a.Category.ID, a.Title, a.State, a.Content, a.Summary, a.BannerURL, a.TemplateID, a.Author.Mid, a.Reprint, imageUrls, a.Attributes, a.Words, a.Dynamic, originImageUrls, a.Media.Spoiler, applyTime, string(extStr), id); err != nil {
PromError("db:更新版本")
log.Error("tx.Exec() error(%+v)", err)
}
return
}
// TxUpdateArticleMeta updates article's meta via transaction.
func (d *Dao) TxUpdateArticleMeta(c context.Context, tx *xsql.Tx, a *artmdl.Meta) (err error) {
a.ImageURLs = artmdl.CleanURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CleanURLs(a.OriginImageURLs)
a.BannerURL = artmdl.CleanURL(a.BannerURL)
var (
imageURLs = strings.Join(a.ImageURLs, ",")
originImageURLs = strings.Join(a.OriginImageURLs, ",")
applyTime = time.Now().Format("2006-01-02 15:04:05")
)
if _, err = tx.Exec(_updateArticleMetaSQL, a.Category.ID, a.Title, a.Summary, a.BannerURL, a.TemplateID, a.State, a.Author.Mid, a.Reprint, imageURLs, a.Attributes, a.Words, a.Dynamic, originImageURLs, a.Media.Spoiler, applyTime, a.ID); err != nil {
PromError("db:更新文章meta")
log.Error("tx.Exec() error(%+v)", err)
}
return
}
// TxUpdateArticleContent updates article's body via transaction.
func (d *Dao) TxUpdateArticleContent(c context.Context, tx *xsql.Tx, aid int64, content string, tags []string) (err error) {
var sqlStr = fmt.Sprintf(_updateArticleContentSQL, d.hit(aid))
if _, err = tx.Exec(sqlStr, content, strings.Join(tags, "\001"), aid); err != nil {
PromError("db:更新文章content")
log.Error("tx.Exec(%s,%d) error(%+v)", sqlStr, aid, err)
}
return
}
// TxDeleteArticleMeta deletes article's meta via transaction.
func (d *Dao) TxDeleteArticleMeta(c context.Context, tx *xsql.Tx, aid int64) (err error) {
var now = time.Now().Unix()
if _, err = tx.Exec(_deleteArticleMetaSQL, now, aid); err != nil {
PromError("db:删除文章meta")
log.Error("tx.Exec() error(%+v)", err)
}
return
}
// TxDeleteArticleContent deletes article's meta via transaction.
func (d *Dao) TxDeleteArticleContent(c context.Context, tx *xsql.Tx, aid int64) (err error) {
var (
now = time.Now().Unix()
sqlStr = fmt.Sprintf(_deleteArticleContentSQL, d.hit(aid))
)
if _, err = tx.Exec(sqlStr, now, aid); err != nil {
PromError("db:删除文章content")
log.Error("tx.Exec(%s,%d,%d) error(%+v)", sqlStr, now, aid, err)
}
return
}
// TxDelArticleVersion deletes article version.
func (d *Dao) TxDelArticleVersion(c context.Context, tx *xsql.Tx, aid int64) (err error) {
var now = time.Now().Unix()
if _, err = tx.Exec(_deleteArticleVerionSQL, now, aid); err != nil {
PromError("db:删除文章版本content")
log.Error("tx.Exec(%s,%d,%d) error(%+v)", _deleteArticleVerionSQL, now, aid, err)
}
return
}
// TxDelFilteredArtMeta delete filetered article meta
func (d *Dao) TxDelFilteredArtMeta(c context.Context, tx *xsql.Tx, aid int64) (err error) {
if _, err = tx.Exec(_delFilteredArtMetaSQL, aid); err != nil {
PromError("db:删除过滤文章")
log.Error("dao.DelFilteredArtMeta exec(%v) error(%+v)", aid, err)
}
return
}
//TxDelFilteredArtContent delete filtered article content
func (d *Dao) TxDelFilteredArtContent(c context.Context, tx *xsql.Tx, aid int64) (err error) {
contentSQL := fmt.Sprintf(_delFilteredArtContentSQL, d.hit(aid))
if _, err = tx.Exec(contentSQL, aid); err != nil {
PromError("db:删除过滤文章正文")
log.Error("dao.DelFilteredArtContent exec(%v) error(%+v)", aid, err)
}
return
}
// UpdateArticleState updates article's state.
func (d *Dao) UpdateArticleState(c context.Context, aid int64, state int) (err error) {
var res sql.Result
if res, err = d.updateArticleStateStmt.Exec(c, state, aid); err != nil {
PromError("db:更新文章状态")
log.Error("s.dao.UpdateArticleState.Exec(aid: %v, state: %v) error(%+v)", aid, state, err)
return
}
if count, _ := res.RowsAffected(); count == 0 {
err = ecode.NothingFound
}
return
}
// TxUpdateArticleState updates article's state.
func (d *Dao) TxUpdateArticleState(c context.Context, tx *xsql.Tx, aid int64, state int32) (err error) {
var res sql.Result
if res, err = tx.Exec(_updateArticleStateSQL, state, aid); err != nil {
PromError("db:更新文章状态")
log.Error("s.dao.TxUpdateArticleState.Exec(aid: %v, state: %v) error(%+v)", aid, state, err)
return
}
if count, _ := res.RowsAffected(); count == 0 {
err = ecode.NothingFound
}
return
}
// TxUpdateArticleStateApplyTime updates article's state and apply time.
func (d *Dao) TxUpdateArticleStateApplyTime(c context.Context, tx *xsql.Tx, aid int64, state int32) (err error) {
var (
res sql.Result
applyTime = time.Now().Format("2006-01-02 15:03:04")
)
if res, err = tx.Exec(_updateArticleStateApplyTimeSQL, state, applyTime, aid); err != nil {
PromError("db:更新文章状态和申请时间")
log.Error("s.dao.TxUpdateArticleStateApplyTime.Exec(aid: %v, state: %v) error(%+v)", aid, state, err)
return
}
if count, _ := res.RowsAffected(); count == 0 {
err = ecode.NothingFound
}
return
}
// UpperArticlesMeta gets article list by mid.
func (d *Dao) UpperArticlesMeta(c context.Context, mid int64, group, category int) (as []*artmdl.Meta, err error) {
var (
rows *xsql.Rows
sqlStr string
)
sqlStr = fmt.Sprintf(_upperArticlesMetaCreationSQL, xstr.JoinInts(artmdl.Group2State(group)))
if category > 0 {
sqlStr += " and category_id=" + strconv.Itoa(category)
}
if rows, err = d.articleDB.Query(c, sqlStr, mid); err != nil {
PromError("db:获取文章meta")
log.Error("d.articleDB.Query(%s,%s) error(%+v)", sqlStr, err)
return
}
defer rows.Close()
for rows.Next() {
a := &artmdl.Meta{Category: &artmdl.Category{}, Author: &artmdl.Author{}}
var (
ptime int64
ctime time.Time
imageURLs, originImageURLs string
)
if err = rows.Scan(&a.ID, &a.Category.ID, &a.Title, &a.Summary, &a.BannerURL, &a.TemplateID, &a.State, &a.Author.Mid, &a.Reprint, &imageURLs, &ptime, &ctime, &a.Reason, &a.Attributes, &a.Dynamic, &originImageURLs); err != nil {
promErrorCheck(err)
log.Error("rows.Scan error(%+v)", err)
return
}
if imageURLs == "" {
a.ImageURLs = []string{}
} else {
a.ImageURLs = strings.Split(imageURLs, ",")
}
if originImageURLs == "" {
a.OriginImageURLs = []string{}
} else {
a.OriginImageURLs = strings.Split(originImageURLs, ",")
}
a.PublishTime = xtime.Time(ptime)
a.Ctime = xtime.Time(ctime.Unix())
a.BannerURL = artmdl.CompleteURL(a.BannerURL)
a.ImageURLs = artmdl.CompleteURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CompleteURLs(a.OriginImageURLs)
as = append(as, a)
}
err = rows.Err()
promErrorCheck(err)
return
}
// UpperArticlesTypeCount gets article count by type.
func (d *Dao) UpperArticlesTypeCount(c context.Context, mid int64) (res *artmdl.CreationArtsType, err error) {
var rows *xsql.Rows
res = &artmdl.CreationArtsType{}
if rows, err = d.upperArtCntCreationStmt.Query(c, mid); err != nil {
PromError("db:获取各种文章状态总数")
log.Error("d.articleDB.Query(%d) error(%+v)", mid, err)
return
}
defer rows.Close()
for rows.Next() {
var state int
if err = rows.Scan(&state); err != nil {
promErrorCheck(err)
return
}
switch state {
case artmdl.StateAutoLock, artmdl.StateLock, artmdl.StateReject, artmdl.StateOpenReject:
res.NotPassed++
case artmdl.StateAutoPass, artmdl.StateOpen, artmdl.StateRePass, artmdl.StateReReject:
res.Passed++
case artmdl.StatePending, artmdl.StateOpenPending, artmdl.StateRePending:
res.Audit++
}
}
err = rows.Err()
promErrorCheck(err)
res.All = res.NotPassed + res.Passed + res.Audit
return
}
// CreationArticleMeta querys article's meta info for creation center by aid.
func (d *Dao) CreationArticleMeta(c context.Context, id int64) (am *artmdl.Meta, err error) {
var (
imageURLs, originImageURLs string
category = &artmdl.Category{}
author = &artmdl.Author{}
ptime int64
ct time.Time
)
am = &artmdl.Meta{Media: &artmdl.Media{}}
if err = d.articleMetaCreationStmt.QueryRow(c, id).Scan(&am.ID, &category.ID, &am.Title, &am.Summary,
&am.BannerURL, &am.TemplateID, &am.State, &author.Mid, &am.Reprint, &imageURLs, &ptime, &ct, &am.Attributes, &am.Dynamic, &originImageURLs, &am.Media.MediaID, &am.Media.Spoiler); err != nil {
if err == sql.ErrNoRows {
err = nil
am = nil
return
}
PromError("db:文章内容表")
log.Error("row.ArticleContent.QueryRow error(%+v)", err)
return
}
am.Category = category
am.Author = author
am.Ctime = xtime.Time(ct.Unix())
if imageURLs == "" {
am.ImageURLs = []string{}
} else {
am.ImageURLs = strings.Split(imageURLs, ",")
}
if originImageURLs == "" {
am.OriginImageURLs = []string{}
} else {
am.OriginImageURLs = strings.Split(originImageURLs, ",")
}
am.PublishTime = xtime.Time(ptime)
am.BannerURL = artmdl.CompleteURL(am.BannerURL)
am.ImageURLs = artmdl.CompleteURLs(am.ImageURLs)
am.OriginImageURLs = artmdl.CompleteURLs(am.OriginImageURLs)
return
}
// CreationArticleContent gets article's content.
func (d *Dao) CreationArticleContent(c context.Context, aid int64) (res string, err error) {
contentSQL := fmt.Sprintf(_articleContentCreationSQL, d.hit(aid))
if err = d.articleDB.QueryRow(c, contentSQL, aid).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:CreationArticleContent")
log.Error("dao.CreationArticleContent(%s) error(%+v)", contentSQL, err)
}
return
}
// UploadImage upload bfs.
func (d *Dao) UploadImage(c context.Context, fileType string, bs []byte) (location string, err error) {
req, err := http.NewRequest(d.c.BFS.Method, d.c.BFS.URL, bytes.NewBuffer(bs))
if err != nil {
PromError("creation:UploadImage")
log.Error("creation: http.NewRequest error (%v) | fileType(%s)", err, fileType)
return
}
expire := time.Now().Unix()
authorization := authorize(d.c.BFS.Key, d.c.BFS.Secret, d.c.BFS.Method, d.c.BFS.Bucket, expire)
req.Header.Set("Host", d.c.BFS.URL)
req.Header.Add("Date", fmt.Sprint(expire))
req.Header.Add("Authorization", authorization)
req.Header.Add("Content-Type", fileType)
// timeout
ctx, cancel := context.WithTimeout(c, time.Duration(d.c.BFS.Timeout))
req = req.WithContext(ctx)
defer cancel()
resp, err := d.bfsClient.Do(req)
if err != nil {
PromError("creation:UploadImage")
log.Error("creation: d.Client.Do error(%v) | url(%s)", err, d.c.BFS.URL)
err = ecode.BfsUploadServiceUnavailable
return
}
if resp.StatusCode != http.StatusOK {
log.Error("creation: Upload http.StatusCode nq http.StatusOK (%d) | url(%s)", resp.StatusCode, d.c.BFS.URL)
PromError("creation:UploadImage")
err = errors.New("Upload failed")
return
}
header := resp.Header
code := header.Get("Code")
if code != strconv.Itoa(http.StatusOK) {
log.Error("creation: strconv.Itoa err, code(%s) | url(%s)", code, d.c.BFS.URL)
PromError("creation:UploadImage")
err = errors.New("Upload failed")
return
}
location = header.Get("Location")
return
}
// authorize returns authorization for upload file to bfs
func authorize(key, secret, method, bucket string, expire int64) (authorization string) {
var (
content string
mac hash.Hash
signature string
)
content = fmt.Sprintf("%s\n%s\n\n%d\n", method, bucket, expire)
mac = hmac.New(sha1.New, []byte(secret))
mac.Write([]byte(content))
signature = base64.StdEncoding.EncodeToString(mac.Sum(nil))
authorization = fmt.Sprintf("%s:%s:%d", key, signature, expire)
return
}
// EditTimes count times of article edited.
func (d *Dao) EditTimes(c context.Context, id int64) (count int, err error) {
var sqlStr = fmt.Sprintf(_countEditTimesSQL, d.hit(id))
row := d.articleDB.QueryRow(c, sqlStr, id)
if err = row.Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:EditTimes")
log.Error("dao.EditTimes error(%+v)", err)
}
return
}
// ArticleVersion .
func (d *Dao) ArticleVersion(c context.Context, aid int64) (a *artmdl.Article, err error) {
var (
extStr string
extMsg artmdl.ExtMsg
imageURLs, originURLs string
)
row := d.articleDB.QueryRow(c, _articleVersionSQL, aid)
a = &artmdl.Article{
Meta: &artmdl.Meta{
Media: &artmdl.Media{},
Category: &artmdl.Category{},
List: &artmdl.List{},
},
}
if err = row.Scan(&a.ID, &a.Category.ID, &a.Title, &a.State, &a.Content, &a.Summary, &a.BannerURL, &a.TemplateID, &a.Reprint, &imageURLs, &a.Attributes, &a.Words, &a.Dynamic, &originURLs, &a.Media.MediaID, &a.Media.Spoiler, &a.ApplyTime, &extStr); err != nil {
log.Error("dao.ArticleHistory.Scan error(%+v)", err)
return
}
if err = json.Unmarshal([]byte(extStr), &extMsg); err != nil {
log.Error("dao.ArticleHistory.Unmarshal error(%+v)", err)
return
}
a.ImageURLs = strings.Split(imageURLs, ",")
a.OriginImageURLs = strings.Split(originURLs, ",")
a.BannerURL = artmdl.CompleteURL(a.BannerURL)
a.ImageURLs = artmdl.CompleteURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CompleteURLs(a.OriginImageURLs)
a.Tags = extMsg.Tags
return
}
// LastReason return last reason from article_versions by aid and state.
func (d *Dao) LastReason(c context.Context, id int64, state int32) (res string, err error) {
if err = d.articleDB.QueryRow(c, _reasonOfVersion, id, state).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:LastReason")
log.Error("dao.LastReason(%s) error(%+v)", _reasonOfVersion, err)
}
return
}

View File

@@ -0,0 +1,68 @@
package dao
import (
"context"
"crypto/md5"
"encoding/binary"
"encoding/hex"
"strconv"
"go-common/library/cache/memcache"
"go-common/library/log"
)
const (
_subPrefix = "artsl_"
)
func midSub(mid int64, title string) string {
ms := md5.Sum([]byte(title))
return _subPrefix + strconv.FormatInt(mid, 10) + "_" + hex.EncodeToString(ms[:])
}
// SubmitCache get user submit cache.
func (d *Dao) SubmitCache(c context.Context, mid int64, title string) (exist bool, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := midSub(mid, title)
_, err = conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Get error(%+v) | key(%s) mid(%d) title(%s)", err, key, mid, title)
PromError("creation:获取标题缓存")
}
return
}
exist = true
return
}
// AddSubmitCache add submit cache into mc.
func (d *Dao) AddSubmitCache(c context.Context, mid int64, title string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := midSub(mid, title)
bs := make([]byte, 8)
binary.BigEndian.PutUint64(bs, 1)
if err = conn.Set(&memcache.Item{Key: key, Object: bs, Flags: memcache.FlagJSON, Expiration: d.mcSubExp}); err != nil {
log.Error("memcache.set error(%+v) | key(%s) mid(%d) title(%s)", err, key, mid, title)
PromError("creation:设定标题缓存")
}
return
}
// DelSubmitCache del submit cache into mc.
func (d *Dao) DelSubmitCache(c context.Context, mid int64, title string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(midSub(mid, title)); err == memcache.ErrNotFound {
err = nil
}
if err != nil {
PromError("creation:删除标题缓存")
log.Error("creation: dao.DelSubmitCache(mid: %v, title: %v) err: %+v", mid, title, err)
}
return
}

View File

@@ -0,0 +1,30 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_SubmitCache(t *testing.T) {
c := context.TODO()
Convey("add cache", t, func() {
err := d.AddSubmitCache(c, 100, "title")
So(err, ShouldBeNil)
Convey("get cache should work", func() {
res, err1 := d.SubmitCache(c, 100, "title")
So(err1, ShouldBeNil)
So(res, ShouldBeTrue)
res, err1 = d.SubmitCache(c, 200, "title")
So(err1, ShouldBeNil)
So(res, ShouldBeFalse)
})
Convey("delete cache should not present", func() {
err = d.DelSubmitCache(c, 100, "title")
So(err, ShouldBeNil)
err = d.DelSubmitCache(c, 200, "title")
So(err, ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,128 @@
package dao
import (
"testing"
"go-common/app/interface/openplatform/article/model"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Articles(t *testing.T) {
var (
c = ctx()
aid int64
art = model.Article{
Meta: &model.Meta{
ID: 0,
Title: "1",
Summary: "2",
BannerURL: "https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg",
TemplateID: 1,
State: 0,
Category: &model.Category{ID: 1},
Author: &model.Author{Mid: 123},
Reprint: 0,
ImageURLs: []string{"https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg"},
OriginImageURLs: []string{"https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "https://i0.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg"},
},
Content: "content",
}
)
Convey("creation article operations", t, func() {
Convey("add article", func() {
tx, err := d.BeginTran(c)
So(err, ShouldBeNil)
var meta = &model.Meta{}
*meta = *art.Meta
aid, err = d.TxAddArticleMeta(c, tx, meta, 0)
So(err, ShouldBeNil)
err = d.TxAddArticleContent(c, tx, aid, art.Content, []string{})
So(err, ShouldBeNil)
err = tx.Commit()
So(err, ShouldBeNil)
Convey("get article", func() {
res, err1 := d.CreationArticleMeta(c, aid)
So(err1, ShouldBeNil)
art.ID = aid
res.Ctime = 0
So(res, ShouldResemble, art.Meta)
content, err2 := d.CreationArticleContent(c, aid)
So(err2, ShouldBeNil)
So(content, ShouldEqual, art.Content)
})
Convey("list should not be empty", func() {
res, err1 := d.UpperArticlesMeta(c, art.Author.Mid, 0, 1)
So(err1, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
Convey("count should > 0", func() {
var cnt = &model.CreationArtsType{}
cnt, err = d.UpperArticlesTypeCount(c, 8167601)
So(err, ShouldBeNil)
So(cnt.All, ShouldBeGreaterThan, 0)
})
Convey("update state", func() {
err = d.UpdateArticleState(c, aid, model.StateLock)
So(err, ShouldBeNil)
res3, err := d.CreationArticleMeta(c, aid)
So(err, ShouldBeNil)
So(res3.State, ShouldEqual, model.StateLock)
})
Convey("delete article", func() {
tx, err := d.BeginTran(c)
err = d.TxDeleteArticleContent(c, tx, aid)
So(err, ShouldBeNil)
err = d.TxDeleteArticleMeta(c, tx, aid)
So(err, ShouldBeNil)
err = tx.Commit()
Convey("article not be present", func() {
res, err := d.CreationArticleMeta(c, aid)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
content, err := d.CreationArticleContent(c, aid)
So(err, ShouldBeNil)
So(content, ShouldBeEmpty)
})
})
Convey("update article", func() {
art := model.Article{
Meta: &model.Meta{
ID: aid,
Title: "new",
Summary: "new",
BannerURL: "https://i0.hdslb.com/bfs/archive/1.jpg",
TemplateID: 4,
State: 2,
Category: &model.Category{ID: 2},
Author: &model.Author{Mid: 123},
Reprint: 0,
ImageURLs: []string{"https://i0.hdslb.com/bfs/archive/2.jpg"},
OriginImageURLs: []string{"https://i0.hdslb.com/bfs/archive/3.jpg"},
},
Content: "new",
}
tx, err := d.BeginTran(c)
var meta = &model.Meta{}
*meta = *art.Meta
err = d.TxUpdateArticleMeta(c, tx, meta)
So(err, ShouldBeNil)
err = d.TxUpdateArticleContent(c, tx, aid, art.Content, []string{})
So(err, ShouldBeNil)
err = tx.Commit()
So(err, ShouldBeNil)
Convey("article should be updated", func() {
res, err := d.CreationArticleMeta(c, aid)
So(err, ShouldBeNil)
art.Ctime = res.Ctime // ignore ctime
So(res, ShouldResemble, art.Meta)
content, err := d.CreationArticleContent(c, aid)
So(err, ShouldBeNil)
So(content, ShouldEqual, art.Content)
})
})
})
})
}

View File

@@ -0,0 +1,443 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
/*
Package dao is a generated cache proxy package.
It is generated from:
type _cache interface {
// cache: -nullcache=&model.List{ID:-1} -check_null_code=$!=nil&&$.ID==-1 -singleflight=true
List(c context.Context, id int64) (*model.List, error)
// cache: -batch=100 -max_group=10 -nullcache=&model.List{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Lists(c context.Context, keys []int64) (map[int64]*model.List, error)
// cache: -singleflight=true -nullcache=[]*model.ListArtMeta{{ID:-1}} -check_null_code=len($)==1&&$[0].ID==-1
ListArts(c context.Context, id int64) ([]*model.ListArtMeta, error)
// cache: -nullcache=[]*model.ListArtMeta{{ID:-1}} -check_null_code=len($)==1&&$[0].ID==-1
ListsArts(c context.Context, ids []int64) (map[int64][]*model.ListArtMeta, error)
// cache: -nullcache=-1 -batch=100 -max_group=10
ArtsListID(c context.Context, keys []int64) (map[int64]int64, error)
// cache: -nullcache=[]int64{-1} -check_null_code=len($)==1&&$[0]==-1 -singleflight=true
UpLists(c context.Context, mid int64) ([]int64, error)
// cache: -nullcache=&model.AuthorLimit{Limit:-1} -check_null_code=$!=nil&&$.Limit==-1
Author(c context.Context, mid int64) (*model.AuthorLimit, error)
}
*/
package dao
import (
"context"
"sync"
"go-common/app/interface/openplatform/article/model"
"go-common/library/net/metadata"
"go-common/library/stat/prom"
"go-common/library/sync/errgroup"
"golang.org/x/sync/singleflight"
)
var _ _cache
var cacheSingleFlights = [3]*singleflight.Group{{}, {}, {}}
// List get data from cache if miss will call source method, then add to cache.
func (d *Dao) List(c context.Context, id int64) (res *model.List, err error) {
addCache := true
res, err = d.CacheList(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.ID == -1 {
res = nil
}
}()
if res != nil {
prom.CacheHit.Incr("List")
return
}
var rr interface{}
sf := d.cacheSFList(id)
rr, err, _ = cacheSingleFlights[0].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("List")
r, e = d.RawList(c, id)
return
})
res = rr.(*model.List)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.List{ID: -1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheList(metadata.WithContext(c), id, miss)
})
return
}
// Lists get data from cache if miss will call source method, then add to cache.
func (d *Dao) Lists(c context.Context, keys []int64) (res map[int64]*model.List, err error) {
if len(keys) == 0 {
return
}
addCache := true
res, err = d.CacheLists(c, keys)
if err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("Lists", int64(len(keys)-len(miss)))
defer func() {
for k, v := range res {
if v != nil && v.ID == -1 {
delete(res, k)
}
}
}()
if len(miss) == 0 {
return
}
var missData map[int64]*model.List
missLen := len(miss)
prom.CacheMiss.Add("Lists", int64(missLen))
mutex := sync.Mutex{}
for i := 0; i < missLen; i += 100 * 10 {
var subKeys []int64
group, ctx := errgroup.WithContext(c)
if (i + 100*10) > missLen {
subKeys = miss[i:]
} else {
subKeys = miss[i : i+100*10]
}
missSubLen := len(subKeys)
for j := 0; j < missSubLen; j += 100 {
var ks []int64
if (j + 100) > missSubLen {
ks = subKeys[j:]
} else {
ks = subKeys[j : j+100]
}
group.Go(func() (err error) {
data, err := d.RawLists(ctx, ks)
mutex.Lock()
for k, v := range data {
if missData == nil {
missData = make(map[int64]*model.List, len(keys))
}
missData[k] = v
}
mutex.Unlock()
return
})
}
err1 := group.Wait()
if err1 != nil {
err = err1
break
}
}
if res == nil {
res = make(map[int64]*model.List)
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range keys {
if res[key] == nil {
if missData == nil {
missData = make(map[int64]*model.List, len(keys))
}
missData[key] = &model.List{ID: -1}
}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheLists(metadata.WithContext(c), missData)
})
return
}
// ListArts get data from cache if miss will call source method, then add to cache.
func (d *Dao) ListArts(c context.Context, id int64) (res []*model.ListArtMeta, err error) {
addCache := true
res, err = d.CacheListArts(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if len(res) == 1 && res[0].ID == -1 {
res = nil
}
}()
if len(res) != 0 {
prom.CacheHit.Incr("ListArts")
return
}
var rr interface{}
sf := d.cacheSFListArts(id)
rr, err, _ = cacheSingleFlights[1].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("ListArts")
r, e = d.RawListArts(c, id)
return
})
res = rr.([]*model.ListArtMeta)
if err != nil {
return
}
miss := res
if len(miss) == 0 {
miss = []*model.ListArtMeta{{ID: -1}}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheListArts(metadata.WithContext(c), id, miss)
})
return
}
// ListsArts get data from cache if miss will call source method, then add to cache.
func (d *Dao) ListsArts(c context.Context, keys []int64) (res map[int64][]*model.ListArtMeta, err error) {
if len(keys) == 0 {
return
}
addCache := true
res, err = d.CacheListsArts(c, keys)
if err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (len(res[key]) == 0) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("ListsArts", int64(len(keys)-len(miss)))
defer func() {
for k, v := range res {
if len(v) == 1 && v[0].ID == -1 {
delete(res, k)
}
}
}()
if len(miss) == 0 {
return
}
var missData map[int64][]*model.ListArtMeta
prom.CacheMiss.Add("ListsArts", int64(len(miss)))
missData, err = d.RawListsArts(c, miss)
if res == nil {
res = make(map[int64][]*model.ListArtMeta)
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range keys {
if len(res[key]) == 0 {
if missData == nil {
missData = make(map[int64][]*model.ListArtMeta, len(keys))
}
missData[key] = []*model.ListArtMeta{{ID: -1}}
}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheListsArts(metadata.WithContext(c), missData)
})
return
}
// ArtsListID get data from cache if miss will call source method, then add to cache.
func (d *Dao) ArtsListID(c context.Context, keys []int64) (res map[int64]int64, err error) {
if len(keys) == 0 {
return
}
addCache := true
res, err = d.CacheArtsListID(c, keys)
if err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if _, ok := res[key]; !ok {
miss = append(miss, key)
}
}
prom.CacheHit.Add("ArtsListID", int64(len(keys)-len(miss)))
defer func() {
for k, v := range res {
if v == -1 {
delete(res, k)
}
}
}()
if len(miss) == 0 {
return
}
var missData map[int64]int64
missLen := len(miss)
prom.CacheMiss.Add("ArtsListID", int64(missLen))
mutex := sync.Mutex{}
for i := 0; i < missLen; i += 100 * 10 {
var subKeys []int64
group, ctx := errgroup.WithContext(c)
if (i + 100*10) > missLen {
subKeys = miss[i:]
} else {
subKeys = miss[i : i+100*10]
}
missSubLen := len(subKeys)
for j := 0; j < missSubLen; j += 100 {
var ks []int64
if (j + 100) > missSubLen {
ks = subKeys[j:]
} else {
ks = subKeys[j : j+100]
}
group.Go(func() (err error) {
data, err := d.RawArtsListID(ctx, ks)
mutex.Lock()
for k, v := range data {
if missData == nil {
missData = make(map[int64]int64, len(keys))
}
missData[k] = v
}
mutex.Unlock()
return
})
}
err1 := group.Wait()
if err1 != nil {
err = err1
break
}
}
if res == nil {
res = make(map[int64]int64)
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range keys {
if res[key] == 0 {
if missData == nil {
missData = make(map[int64]int64, len(keys))
}
missData[key] = -1
}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheArtsListID(metadata.WithContext(c), missData)
})
return
}
// UpLists get data from cache if miss will call source method, then add to cache.
func (d *Dao) UpLists(c context.Context, id int64) (res []int64, err error) {
addCache := true
res, err = d.CacheUpLists(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if len(res) == 1 && res[0] == -1 {
res = nil
}
}()
if len(res) != 0 {
prom.CacheHit.Incr("UpLists")
return
}
var rr interface{}
sf := d.cacheSFUpLists(id)
rr, err, _ = cacheSingleFlights[2].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("UpLists")
r, e = d.RawUpLists(c, id)
return
})
res = rr.([]int64)
if err != nil {
return
}
miss := res
if len(miss) == 0 {
miss = []int64{-1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheUpLists(metadata.WithContext(c), id, miss)
})
return
}
// Author get data from cache if miss will call source method, then add to cache.
func (d *Dao) Author(c context.Context, id int64) (res *model.AuthorLimit, err error) {
addCache := true
res, err = d.CacheAuthor(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.Limit == -1 {
res = nil
}
}()
if res != nil {
prom.CacheHit.Incr("Author")
return
}
prom.CacheMiss.Incr("Author")
res, err = d.RawAuthor(c, id)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.AuthorLimit{Limit: -1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheAuthor(metadata.WithContext(c), id, miss)
})
return
}

View File

@@ -0,0 +1,266 @@
package dao
import (
"context"
xhttp "net/http"
"runtime"
"time"
"go-common/app/interface/openplatform/article/conf"
"go-common/library/cache"
"go-common/library/cache/memcache"
xredis "go-common/library/cache/redis"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
"go-common/library/stat/prom"
hbase "go-common/library/database/hbase.v2"
)
var (
errorsCount = prom.BusinessErrCount
infosCount = prom.BusinessInfoCount
cachedCount = prom.CacheHit
missedCount = prom.CacheMiss
)
// PromError prom error
func PromError(name string) {
errorsCount.Incr(name)
}
// promErrorCheck check prom error
func promErrorCheck(err error) {
if err != nil {
pc, _, _, _ := runtime.Caller(1)
name := "d." + runtime.FuncForPC(pc).Name()
PromError(name)
log.Error("%s err: %+v", name, err)
}
}
// PromInfo add prom info
func PromInfo(name string) {
infosCount.Incr(name)
}
// Dao dao
type Dao struct {
// config
c *conf.Config
// db
articleDB *sql.DB
// http client
httpClient *bm.Client
messageHTTPClient *bm.Client
bfsClient *xhttp.Client
// memcache
mc *memcache.Pool
//redis
redis *xredis.Pool
mcArticleExpire int32
mcStatsExpire int32
mcLikeExpire int32
mcCardsExpire int32
mcListArtsExpire int32
mcListExpire int32
mcArtListExpire int32
mcUpListsExpire int32
mcSubExp int32
mcListReadExpire int32
mcHotspotExpire int32
mcAuthorExpire int32
mcArticlesIDExpire int32
mcArticleTagExpire int32
mcUpStatDailyExpire int32
redisUpperExpire int32
redisSortExpire int64
redisSortTTL int64
redisArtLikesExpire int32
redisRankExpire int64
redisRankTTL int64
redisMaxLikeExpire int64
redisHotspotExpire int64
redisReadPingExpire int64
redisReadSetExpire int64
// stmt
categoriesStmt *sql.Stmt
authorsStmt *sql.Stmt
applyStmt *sql.Stmt
addAuthorStmt *sql.Stmt
applyCountStmt *sql.Stmt
articleMetaStmt *sql.Stmt
allArticleMetaStmt *sql.Stmt
articleContentStmt *sql.Stmt
articleUpperCountStmt *sql.Stmt
updateArticleStateStmt *sql.Stmt
upPassedStmt *sql.Stmt
recommendCategoryStmt *sql.Stmt
delRecommendStmt *sql.Stmt
allRecommendStmt *sql.Stmt
allRecommendCountStmt *sql.Stmt
newestArtsMetaStmt *sql.Stmt
upperArtCntCreationStmt *sql.Stmt
articleMetaCreationStmt *sql.Stmt
articleUpCntTodayStmt *sql.Stmt
addComplaintStmt *sql.Stmt
complaintExistStmt *sql.Stmt
complaintProtectStmt *sql.Stmt
addComplaintCountStmt *sql.Stmt
settingsStmt *sql.Stmt
authorStmt *sql.Stmt
noticeStmt *sql.Stmt
userNoticeStmt *sql.Stmt
updateUserNoticeStmt *sql.Stmt
creativeListsStmt *sql.Stmt
creativeListAddStmt *sql.Stmt
creativeListDelStmt *sql.Stmt
creativeListUpdateStmt *sql.Stmt
creativeListUpdateTimeStmt *sql.Stmt
creativeListArticlesStmt *sql.Stmt
listStmt *sql.Stmt
creativeListDelAllArticleStmt *sql.Stmt
creativeListAddArticleStmt *sql.Stmt
creativeListDelArticleStmt *sql.Stmt
allListStmt *sql.Stmt
hotspotsStmt *sql.Stmt
searchArtsStmt *sql.Stmt
addCheatStmt *sql.Stmt
delCheatStmt *sql.Stmt
// databus
statDbus *databus.Databus
// inteval
UpdateRecommendsInterval int64
UpdateBannersInterval int64
// hbase
hbase *hbase.Client
//cache
cache *cache.Cache
}
// New dao new
func New(c *conf.Config) (d *Dao) {
d = &Dao{
// config
c: c,
// http client
httpClient: bm.NewClient(c.HTTPClient),
messageHTTPClient: bm.NewClient(c.MessageHTTPClient),
bfsClient: &xhttp.Client{Timeout: time.Duration(c.BFS.Timeout)},
// mc
mc: memcache.NewPool(c.Memcache.Config),
mcArticleExpire: int32(time.Duration(c.Memcache.ArticleExpire) / time.Second),
mcStatsExpire: int32(time.Duration(c.Memcache.StatsExpire) / time.Second),
mcLikeExpire: int32(time.Duration(c.Memcache.LikeExpire) / time.Second),
mcCardsExpire: int32(time.Duration(c.Memcache.CardsExpire) / time.Second),
mcSubExp: int32(time.Duration(c.Memcache.SubmitExpire) / time.Second),
mcListArtsExpire: int32(time.Duration(c.Memcache.ListArtsExpire) / time.Second),
mcListExpire: int32(time.Duration(c.Memcache.ListExpire) / time.Second),
mcArtListExpire: int32(time.Duration(c.Memcache.ArtListExpire) / time.Second),
mcUpListsExpire: int32(time.Duration(c.Memcache.UpListsExpire) / time.Second),
mcListReadExpire: int32(time.Duration(c.Memcache.ListReadExpire) / time.Second),
mcHotspotExpire: int32(time.Duration(c.Memcache.HotspotExpire) / time.Second),
mcAuthorExpire: int32(time.Duration(c.Memcache.AuthorExpire) / time.Second),
mcArticlesIDExpire: int32(time.Duration(c.Memcache.ArticlesIDExpire) / time.Second),
mcArticleTagExpire: int32(time.Duration(c.Memcache.ArticleTagExpire) / time.Second),
mcUpStatDailyExpire: int32(time.Duration(c.Memcache.UpStatDailyExpire) / time.Second),
//redis
redis: xredis.NewPool(c.Redis),
redisUpperExpire: int32(time.Duration(c.Article.ExpireUpper) / time.Second),
redisSortExpire: int64(time.Duration(c.Article.ExpireSortArts) / time.Second),
redisSortTTL: int64(time.Duration(c.Article.TTLSortArts) / time.Second),
redisArtLikesExpire: int32(time.Duration(c.Article.ExpireArtLikes) / time.Second),
redisRankExpire: int64(time.Duration(c.Article.ExpireRank) / time.Second),
redisRankTTL: int64(time.Duration(c.Article.TTLRank) / time.Second),
redisMaxLikeExpire: int64(time.Duration(c.Article.ExpireMaxLike) / time.Second),
redisHotspotExpire: int64(time.Duration(c.Article.ExpireHotspot) / time.Second),
redisReadPingExpire: int64(time.Duration(c.Article.ExpireReadPing) / time.Second),
redisReadSetExpire: int64(time.Duration(c.Article.ExpireReadSet) / time.Second),
// db
articleDB: sql.NewMySQL(c.MySQL.Article),
// prom
statDbus: databus.New(c.StatDatabus),
UpdateRecommendsInterval: int64(time.Duration(c.Article.UpdateRecommendsInteval) / time.Second),
UpdateBannersInterval: int64(time.Duration(c.Article.UpdateBannersInteval) / time.Second),
// hbase
hbase: hbase.NewClient(c.HBase),
cache: cache.New(1, 1024),
}
d.categoriesStmt = d.articleDB.Prepared(_categoriesSQL)
d.authorsStmt = d.articleDB.Prepared(_authorsSQL)
d.applyStmt = d.articleDB.Prepared(_applySQL)
d.addAuthorStmt = d.articleDB.Prepared(_addAuthorSQL)
d.applyCountStmt = d.articleDB.Prepared(_applyCountSQL)
d.articleMetaStmt = d.articleDB.Prepared(_articleMetaSQL)
d.allArticleMetaStmt = d.articleDB.Prepared(_allArticleMetaSQL)
d.articleContentStmt = d.articleDB.Prepared(_articleContentSQL)
d.updateArticleStateStmt = d.articleDB.Prepared(_updateArticleStateSQL)
d.upPassedStmt = d.articleDB.Prepared(_upperPassedSQL)
d.recommendCategoryStmt = d.articleDB.Prepared(_recommendCategorySQL)
d.allRecommendStmt = d.articleDB.Prepared(_allRecommendSQL)
d.allRecommendCountStmt = d.articleDB.Prepared(_allRecommendCountSQL)
d.delRecommendStmt = d.articleDB.Prepared(_deleteRecommendSQL)
d.newestArtsMetaStmt = d.articleDB.Prepared(_newestArtsMetaSQL)
d.upperArtCntCreationStmt = d.articleDB.Prepared(_upperArticleCountCreationSQL)
d.articleUpperCountStmt = d.articleDB.Prepared(_articleUpperCountSQL)
d.articleMetaCreationStmt = d.articleDB.Prepared(_articleMetaCreationSQL)
d.articleUpCntTodayStmt = d.articleDB.Prepared(_articleUpCntTodaySQL)
d.addComplaintStmt = d.articleDB.Prepared(_addComplaintsSQL)
d.complaintExistStmt = d.articleDB.Prepared(_complaintExistSQL)
d.complaintProtectStmt = d.articleDB.Prepared(_complaintProtectSQL)
d.addComplaintCountStmt = d.articleDB.Prepared(_addComplaintCountSQL)
d.settingsStmt = d.articleDB.Prepared(_settingsSQL)
d.authorStmt = d.articleDB.Prepared(_authorSQL)
d.noticeStmt = d.articleDB.Prepared(_noticeSQL)
d.userNoticeStmt = d.articleDB.Prepared(_userNoticeSQL)
d.updateUserNoticeStmt = d.articleDB.Prepared(_updateUserNoticeSQL)
d.creativeListsStmt = d.articleDB.Prepared(_creativeListsSQL)
d.creativeListAddStmt = d.articleDB.Prepared(_creativeListAddSQL)
d.creativeListDelStmt = d.articleDB.Prepared(_creativeListDelSQL)
d.creativeListUpdateStmt = d.articleDB.Prepared(_creativeListUpdateSQL)
d.creativeListUpdateTimeStmt = d.articleDB.Prepared(_creativeListUpdateTimeSQL)
d.creativeListArticlesStmt = d.articleDB.Prepared(_creativeListArticlesSQL)
d.creativeListDelAllArticleStmt = d.articleDB.Prepared(_creativeListDelAllArticleSQL)
d.creativeListAddArticleStmt = d.articleDB.Prepared(_creativeListAddArticleSQL)
d.listStmt = d.articleDB.Prepared(_listSQL)
d.creativeListDelArticleStmt = d.articleDB.Prepared(_creativeListDelArticleSQL)
d.allListStmt = d.articleDB.Prepared(_allListsSQL)
d.hotspotsStmt = d.articleDB.Prepared(_hotspotsSQL)
d.searchArtsStmt = d.articleDB.Prepared(_searchArticles)
d.addCheatStmt = d.articleDB.Prepared(_addCheatSQL)
d.delCheatStmt = d.articleDB.Prepared(_delCheatSQL)
return d
}
// BeginTran begin transaction.
func (d *Dao) BeginTran(c context.Context) (*sql.Tx, error) {
return d.articleDB.Begin(c)
}
// Ping check connection success.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingMC(c); err != nil {
PromError("mc:Ping")
log.Error("d.pingMC error(%+v)", err)
return
}
if err = d.pingRedis(c); err != nil {
PromError("redis:Ping")
log.Error("d.pingRedis error(%+v)", err)
return
}
if err = d.articleDB.Ping(c); err != nil {
PromError("db:Ping")
log.Error("d.articleDB.Ping error(%+v)", err)
}
return
}
// Close close resource.
func (d *Dao) Close() {
d.articleDB.Close()
d.mc.Close()
d.redis.Close()
}

View File

@@ -0,0 +1,91 @@
package dao
import (
"context"
"flag"
"path/filepath"
"strings"
"go-common/app/interface/openplatform/article/conf"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/cache/redis"
_ "github.com/go-sql-driver/mysql"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/h2non/gock.v1"
)
var (
dataMID = int64(1)
noDataMID = int64(10000)
_noData = int64(1000000)
d *Dao
categories = []*artmdl.Category{
&artmdl.Category{Name: "游戏", ID: 1},
&artmdl.Category{Name: "动漫", ID: 2},
}
art = artmdl.Article{
Meta: &artmdl.Meta{
ID: 100,
Category: categories[0],
Title: "隐藏于时区记忆中的,是希望还是绝望!",
Summary: "说起日本校服,第一个浮现在我们脑海中的必然是那象征着青春阳光 蓝白色相称的水手服啦. 拉色短裙配上洁白的直袜",
BannerURL: "http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg",
TemplateID: 1,
State: 0,
Author: &artmdl.Author{Mid: 123, Name: "爱蜜莉雅", Face: "http://i1.hdslb.com/bfs/face/5c6109964e78a84021299cdf71739e21cd7bc208.jpg"},
Reprint: 0,
ImageURLs: []string{"http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg"},
OriginImageURLs: []string{"http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg", "http://i2.hdslb.com/bfs/archive/b5727f244d5c7a34c1c0e78f49765d09ff30c129.jpg"},
PublishTime: 1495784507,
Tags: []*artmdl.Tag{},
Stats: &artmdl.Stats{Favorite: 100, Like: 10, View: 500, Dislike: 1, Share: 99},
},
Content: "content",
}
)
func CleanCache() {
c := context.TODO()
pool := redis.NewPool(conf.Conf.Redis)
pool.Get(c).Do("FLUSHDB")
}
func init() {
dir, _ := filepath.Abs("../cmd/convey-test.toml")
flag.Set("conf", dir)
conf.Init()
d = New(conf.Conf)
d.httpClient.SetTransport(gock.DefaultTransport)
}
func WithDao(f func(d *Dao)) func() {
return func() {
Reset(func() { CleanCache() })
f(d)
}
}
func WithMysql(f func(d *Dao)) func() {
return func() {
Reset(func() { CleanCache() })
f(d)
}
}
func WithCleanCache(f func()) func() {
return func() {
Reset(func() { CleanCache() })
f()
}
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
return r
}
func ctx() context.Context {
return context.Background()
}

View File

@@ -0,0 +1,48 @@
package dao
import (
"context"
"strconv"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/log"
)
var _defaultAdd = int64(1)
// PubView adds a view count.
func (d *Dao) PubView(c context.Context, mid int64, aid int64, ip string, cheat *artmdl.CheatInfo) (err error) {
msg := &artmdl.StatMsg{
Aid: aid,
Mid: mid,
IP: ip,
View: &_defaultAdd,
CheatInfo: cheat,
}
if err = d.statDbus.Send(c, strconv.FormatInt(aid, 10), msg); err != nil {
PromError("databus:发送浏览")
log.Error("d.databus.SendView(%+v) error(%+v)", msg, err)
return
}
PromInfo("databus:发送浏览")
log.Info("s.PubView(mid: %v, aid: %v, ip: %v, cheat: %+v)", msg.Mid, msg.Aid, msg.IP, cheat)
return
}
// PubShare add share count
func (d *Dao) PubShare(c context.Context, mid int64, aid int64, ip string) (err error) {
msg := &artmdl.StatMsg{
Aid: aid,
Mid: mid,
IP: ip,
Share: &_defaultAdd,
}
if err = d.statDbus.Send(c, strconv.FormatInt(aid, 10), msg); err != nil {
PromError("databus:发送分享")
log.Error("d.databus.SendShare(%+v) error(%+v)", msg, err)
return
}
PromInfo("databus:发送分享")
log.Info("s.PubShare(mid: %v, aid: %v, ip: %v)", msg.Mid, msg.Aid, msg.IP)
return
}

View File

@@ -0,0 +1,41 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/library/ecode"
"go-common/library/log"
)
const _Dynamic = "http://api.vc.bilibili.co/dynamic_repost/v0/dynamic_repost/view_repost"
// DynamicCount get dynamic count from api
func (d *Dao) DynamicCount(c context.Context, aid int64) (count int64, err error) {
params := url.Values{}
params.Set("rid", strconv.FormatInt(aid, 10))
params.Set("type", "64")
params.Set("offset", "0")
var res struct {
Code int `json:"errno"`
Msg string `json:"msg"`
Data struct {
TotalCount int64 `json:"total_count"`
} `json:"data"`
}
err = d.httpClient.Get(c, _Dynamic, "", params, &res)
if err != nil {
PromError("count:dynamic")
log.Error("dynamic: d.client.Get(%s) error(%+v)", _Dynamic+"?"+params.Encode(), err)
return
}
if res.Code != 0 {
PromError("count:dynamic接口")
log.Error("dynamic: url(%s) res code(%d) msg: %s", _Dynamic+"?"+params.Encode(), res.Code, res.Msg)
err = ecode.Int(res.Code)
return
}
count = res.Data.TotalCount
return
}

View File

@@ -0,0 +1,60 @@
package dao
import (
"context"
"fmt"
"go-common/app/interface/openplatform/article/model"
)
func listArtsKey(id int64) string {
return fmt.Sprintf("art_rl1_arts_%d", id)
}
func listKey(id int64) string {
return fmt.Sprintf("art_rll_%d", id)
}
func articleListKey(aid int64) string {
return fmt.Sprintf("art_rlal_%d", aid)
}
func upListsKey(mid int64) string {
return fmt.Sprintf("art_uplists_%d", mid)
}
func listReadCountKey(id int64) string {
return fmt.Sprintf("art_lrc_%d", id)
}
func slideArticlesKey(buvid string) string {
return fmt.Sprintf("art_slidelists_%s", buvid)
}
// ListArtsCacheMap get read list articles cache
func (d *Dao) ListArtsCacheMap(c context.Context, id int64) (res map[int64]*model.ListArtMeta, err error) {
var arts []*model.ListArtMeta
if arts, err = d.CacheListArts(c, id); err != nil {
return
}
for _, art := range arts {
if res == nil {
res = make(map[int64]*model.ListArtMeta)
}
res[art.ID] = art
}
return
}
// SetArticleListCache set article list cache
func (d *Dao) SetArticleListCache(c context.Context, listID int64, arts []*model.ListArtMeta) (err error) {
if len(arts) == 0 {
return
}
m := make(map[int64]int64)
for _, art := range arts {
m[art.ID] = listID
}
err = d.SetArticlesListCache(c, m)
return
}

View File

@@ -0,0 +1,164 @@
package dao
import (
"context"
"testing"
"go-common/app/interface/openplatform/article/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_ListCache(t *testing.T) {
c := context.TODO()
list := &model.List{ID: 1, Name: "name", UpdateTime: xtime.Time(100)}
Convey("set cache", t, func() {
err := d.AddCacheList(c, list.ID, list)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.CacheList(c, 1)
So(err, ShouldBeNil)
So(res, ShouldResemble, list)
})
Convey("get cache not exist", func() {
res, err := d.CacheList(c, 2000000)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
})
}
func Test_ListArtsCache(t *testing.T) {
c := context.TODO()
arts := []*model.ListArtMeta{&model.ListArtMeta{ID: 1, Title: "title", State: 1, PublishTime: xtime.Time(100)}}
Convey("set cache", t, func() {
err := d.AddCacheListArts(c, 1, arts)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.CacheListArts(c, 1)
So(err, ShouldBeNil)
So(res, ShouldResemble, arts)
})
Convey("get cache not exist", func() {
res, err := d.CacheListArts(c, 20000000)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
})
}
func Test_ListsCache(t *testing.T) {
c := context.TODO()
list := &model.List{ID: 1, Name: "name", UpdateTime: xtime.Time(100)}
list2 := &model.List{ID: 2, Name: "name", UpdateTime: xtime.Time(100)}
m := map[int64]*model.List{1: list, 2: list2}
Convey("set cache", t, func() {
err := d.AddCacheLists(c, m)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.CacheLists(c, []int64{1, 2})
So(err, ShouldBeNil)
So(res, ShouldResemble, m)
})
Convey("get cache not exist", func() {
res, err := d.CacheLists(c, []int64{300000})
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
Convey("get blank cache", func() {
res, err := d.CacheLists(c, []int64{})
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
})
}
func Test_ListsArtsCache(t *testing.T) {
c := context.TODO()
arts := []*model.ListArtMeta{&model.ListArtMeta{ID: 1, Title: "title", State: 1, PublishTime: xtime.Time(100)}}
arts2 := []*model.ListArtMeta{&model.ListArtMeta{ID: 2, Title: "title", State: 1, PublishTime: xtime.Time(100)}}
Convey("set cache", t, func() {
err := d.AddCacheListsArts(c, map[int64][]*model.ListArtMeta{1: arts, 2: arts2})
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.CacheListsArts(c, []int64{1, 2})
So(err, ShouldBeNil)
So(res, ShouldResemble, map[int64][]*model.ListArtMeta{1: arts, 2: arts2})
})
Convey("get cache not exist", func() {
res, err := d.CacheListsArts(c, []int64{200000})
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
})
}
func Test_SetArticleListCache(t *testing.T) {
c := context.TODO()
arts := []*model.ListArtMeta{&model.ListArtMeta{ID: 1, Title: "title", State: 1, PublishTime: xtime.Time(100)}}
Convey("set cache", t, func() {
err := d.SetArticleListCache(c, 100, arts)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.ArticleListCache(c, 1)
So(err, ShouldBeNil)
So(res, ShouldEqual, 100)
})
Convey("get cache not exist", func() {
res, err := d.ArticleListCache(c, 1000000)
So(err, ShouldBeNil)
So(res, ShouldEqual, 0)
})
Convey("multi get", func() {
res, err := d.CacheArtsListID(c, []int64{1, 100000})
So(err, ShouldBeNil)
So(res, ShouldResemble, map[int64]int64{1: 100})
})
})
}
func Test_UpListsCache(t *testing.T) {
c := context.TODO()
lists := []int64{1}
mid := int64(1)
Convey("set cache", t, func() {
err := d.AddCacheUpLists(c, mid, lists)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.CacheUpLists(c, mid)
So(err, ShouldBeNil)
So(res, ShouldResemble, lists)
})
Convey("get cache not exist", func() {
res, err := d.CacheUpLists(c, 2000000)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
})
}
func Test_ListReadCountCache(t *testing.T) {
c := context.TODO()
id := int64(1)
count := int64(100)
Convey("set cache", t, func() {
err := d.AddCacheListReadCount(c, id, count)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.CacheListReadCount(c, id)
So(err, ShouldBeNil)
So(res, ShouldEqual, count)
})
Convey("gets cache", func() {
res, err := d.CacheListsReadCount(c, []int64{id})
So(err, ShouldBeNil)
So(res, ShouldResemble, map[int64]int64{id: count})
})
Convey("get cache not exist", func() {
res, err := d.CacheListReadCount(c, 2000000)
So(err, ShouldBeNil)
So(res, ShouldEqual, 0)
})
})
}

View File

@@ -0,0 +1,867 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/mc. DO NOT EDIT.
/*
Package dao is a generated mc cache package.
It is generated from:
type _mc interface {
// 获取文集文章列表缓存
//mc: -key=listArtsKey
CacheListArts(c context.Context, id int64) (res []*model.ListArtMeta, err error)
// 增加文集含有的文章列表缓存
//mc: -key=listArtsKey -expire=d.mcListArtsExpire
AddCacheListArts(c context.Context, id int64, arts []*model.ListArtMeta) (err error)
// 获取文章所属文集
//mc: -key=articleListKey -type=get
ArticleListCache(c context.Context, id int64) (res int64, err error)
// 增加文章所属文集缓存
//mc: -key=articleListKey -expire=d.mcArtListExpire
SetArticlesListCache(c context.Context, arts map[int64]int64) (err error)
//mc: -key=listKey
CacheList(c context.Context, id int64) (res *model.List, err error)
//mc: -key=listKey -expire=d.mcListExpire
AddCacheList(c context.Context, id int64, list *model.List) (err error)
//mc: -key=listKey
CacheLists(c context.Context, ids []int64) (res map[int64]*model.List, err error)
//mc: -key=listKey -expire=d.mcListExpire
AddCacheLists(c context.Context, lists map[int64]*model.List) (err error)
//mc: -key=listArtsKey
CacheListsArts(c context.Context, ids []int64) (res map[int64][]*model.ListArtMeta, err error)
//mc: -key=listArtsKey -expire=d.mcListArtsExpire
AddCacheListsArts(c context.Context, arts map[int64][]*model.ListArtMeta) (err error)
//mc: -key=articleListKey
CacheArtsListID(c context.Context, ids []int64) (res map[int64]int64, err error)
//mc: -key=articleListKey -expire=d.mcArtListExpire
AddCacheArtsListID(c context.Context, arts map[int64]int64) (err error)
//mc: -key=upListsKey -expire=d.mcUpListsExpire
AddCacheUpLists(c context.Context, mid int64, lists []int64) (err error)
//mc: -key=upListsKey
CacheUpLists(c context.Context, id int64) (res []int64, err error)
//mc: -key=listReadCountKey -expire=d.mcListReadExpire
AddCacheListReadCount(c context.Context, id int64, read int64) (err error)
//mc: -key=listReadCountKey
CacheListReadCount(c context.Context, id int64) (res int64, err error)
//mc: -key=listReadCountKey
CacheListsReadCount(c context.Context, ids []int64) (res map[int64]int64, err error)
//mc: -key=hotspotsKey -expire=d.mcHotspotExpire
AddCacheHotspots(c context.Context, hots []*model.Hotspot) (err error)
//mc: -key=hotspotsKey
DelCacheHotspots(c context.Context) (err error)
//mc: -key=hotspotsKey
cacheHotspots(c context.Context) (res []*model.Hotspot, err error)
//mc: -key=mcHotspotKey
CacheHotspot(c context.Context, id int64) (res *model.Hotspot, err error)
//mc: -key=mcHotspotKey -expire=d.mcHotspotExpire
AddCacheHotspot(c context.Context, id int64, val *model.Hotspot) (err error)
// 增加作者状态缓存
//mc: -key=mcAuthorKey -expire=d.mcAuthorExpire
AddCacheAuthor(c context.Context, mid int64, author *model.AuthorLimit) (err error)
//mc: -key=mcAuthorKey
CacheAuthor(c context.Context, mid int64) (res *model.AuthorLimit, err error)
//mc: -key=mcAuthorKey
DelCacheAuthor(c context.Context, mid int64) (err error)
//mc: -key=slideArticlesKey
CacheListArtsId(c context.Context, buvid string) (*model.ArticleViewList, error)
//mc: -key=slideArticlesKey -expire=d.mcArticlesIDExpire
AddCacheListArtsId(c context.Context, buvid string, val *model.ArticleViewList) error
//mc: -key=slideArticlesKey
DelCacheListArtsId(c context.Context, buvid string) error
//mc: -key=AnniversaryKey -expire=60*60*24*30
CacheAnniversary(c context.Context, mid int64) (*model.AnniversaryInfo, error)
//mc: -key=mcTagKey
CacheAidsByTag(c context.Context, tag int64) (*model.TagArts, error)
//mc: -key=mcTagKey -expire=d.mcArticleTagExpire
AddCacheAidsByTag(c context.Context, tag int64, val *model.TagArts) error
//mc: -key=mcUpStatKey -expire=d.mcUpStatDailyExpire
CacheUpStatDaily(c context.Context, mid int64) (*model.UpStat, error)
//mc: -key=mcUpStatKey -expire=d.mcUpStatDailyExpire
AddCacheUpStatDaily(c context.Context, mid int64, val *model.UpStat) error
}
*/
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/interface/openplatform/article/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/stat/prom"
)
var _ _mc
// CacheListArts 获取文集文章列表缓存
func (d *Dao) CacheListArts(c context.Context, id int64) (res []*model.ListArtMeta, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := listArtsKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheListArts")
log.Errorv(c, log.KV("CacheListArts", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = []*model.ListArtMeta{}
err = conn.Scan(reply, &res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListArts")
log.Errorv(c, log.KV("CacheListArts", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheListArts 增加文集含有的文章列表缓存
func (d *Dao) AddCacheListArts(c context.Context, id int64, val []*model.ListArtMeta) (err error) {
if len(val) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := listArtsKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcListArtsExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheListArts")
log.Errorv(c, log.KV("AddCacheListArts", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// ArticleListCache 获取文章所属文集
func (d *Dao) ArticleListCache(c context.Context, id int64) (res int64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := articleListKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:ArticleListCache")
log.Errorv(c, log.KV("ArticleListCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:ArticleListCache")
log.Errorv(c, log.KV("ArticleListCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:ArticleListCache")
log.Errorv(c, log.KV("ArticleListCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = int64(r)
return
}
// SetArticlesListCache 增加文章所属文集缓存
func (d *Dao) SetArticlesListCache(c context.Context, values map[int64]int64) (err error) {
if len(values) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
for id, val := range values {
key := articleListKey(id)
bs := []byte(strconv.FormatInt(int64(val), 10))
item := &memcache.Item{Key: key, Value: bs, Expiration: d.mcArtListExpire, Flags: memcache.FlagRAW}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:SetArticlesListCache")
log.Errorv(c, log.KV("SetArticlesListCache", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
}
return
}
// CacheList get data from mc
func (d *Dao) CacheList(c context.Context, id int64) (res *model.List, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := listKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheList")
log.Errorv(c, log.KV("CacheList", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.List{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheList")
log.Errorv(c, log.KV("CacheList", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheList Set data to mc
func (d *Dao) AddCacheList(c context.Context, id int64, val *model.List) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := listKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcListExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheList")
log.Errorv(c, log.KV("AddCacheList", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheLists get data from mc
func (d *Dao) CacheLists(c context.Context, ids []int64) (res map[int64]*model.List, err error) {
l := len(ids)
if l == 0 {
return
}
keysMap := make(map[string]int64, l)
keys := make([]string, 0, l)
for _, id := range ids {
key := listKey(id)
keysMap[key] = id
keys = append(keys, key)
}
conn := d.mc.Get(c)
defer conn.Close()
replies, err := conn.GetMulti(keys)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheLists")
log.Errorv(c, log.KV("CacheLists", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
return
}
for key, reply := range replies {
var v *model.List
v = &model.List{}
err = conn.Scan(reply, v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheLists")
log.Errorv(c, log.KV("CacheLists", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
if res == nil {
res = make(map[int64]*model.List, len(keys))
}
res[keysMap[key]] = v
}
return
}
// AddCacheLists Set data to mc
func (d *Dao) AddCacheLists(c context.Context, values map[int64]*model.List) (err error) {
if len(values) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
for id, val := range values {
key := listKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcListExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheLists")
log.Errorv(c, log.KV("AddCacheLists", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
}
return
}
// CacheListsArts get data from mc
func (d *Dao) CacheListsArts(c context.Context, ids []int64) (res map[int64][]*model.ListArtMeta, err error) {
l := len(ids)
if l == 0 {
return
}
keysMap := make(map[string]int64, l)
keys := make([]string, 0, l)
for _, id := range ids {
key := listArtsKey(id)
keysMap[key] = id
keys = append(keys, key)
}
conn := d.mc.Get(c)
defer conn.Close()
replies, err := conn.GetMulti(keys)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListsArts")
log.Errorv(c, log.KV("CacheListsArts", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
return
}
for key, reply := range replies {
var v []*model.ListArtMeta
v = []*model.ListArtMeta{}
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListsArts")
log.Errorv(c, log.KV("CacheListsArts", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
if res == nil {
res = make(map[int64][]*model.ListArtMeta, len(keys))
}
res[keysMap[key]] = v
}
return
}
// AddCacheListsArts Set data to mc
func (d *Dao) AddCacheListsArts(c context.Context, values map[int64][]*model.ListArtMeta) (err error) {
if len(values) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
for id, val := range values {
key := listArtsKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcListArtsExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheListsArts")
log.Errorv(c, log.KV("AddCacheListsArts", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
}
return
}
// CacheArtsListID get data from mc
func (d *Dao) CacheArtsListID(c context.Context, ids []int64) (res map[int64]int64, err error) {
l := len(ids)
if l == 0 {
return
}
keysMap := make(map[string]int64, l)
keys := make([]string, 0, l)
for _, id := range ids {
key := articleListKey(id)
keysMap[key] = id
keys = append(keys, key)
}
conn := d.mc.Get(c)
defer conn.Close()
replies, err := conn.GetMulti(keys)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheArtsListID")
log.Errorv(c, log.KV("CacheArtsListID", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
return
}
for key, reply := range replies {
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheArtsListID")
log.Errorv(c, log.KV("CacheArtsListID", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheArtsListID")
log.Errorv(c, log.KV("CacheArtsListID", fmt.Sprintf("%+v", err)), log.KV("key", key))
return res, err
}
if res == nil {
res = make(map[int64]int64, len(keys))
}
res[keysMap[key]] = int64(r)
}
return
}
// AddCacheArtsListID Set data to mc
func (d *Dao) AddCacheArtsListID(c context.Context, values map[int64]int64) (err error) {
if len(values) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
for id, val := range values {
key := articleListKey(id)
bs := []byte(strconv.FormatInt(int64(val), 10))
item := &memcache.Item{Key: key, Value: bs, Expiration: d.mcArtListExpire, Flags: memcache.FlagRAW}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheArtsListID")
log.Errorv(c, log.KV("AddCacheArtsListID", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
}
return
}
// AddCacheUpLists Set data to mc
func (d *Dao) AddCacheUpLists(c context.Context, id int64, val []int64) (err error) {
if len(val) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := upListsKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcUpListsExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheUpLists")
log.Errorv(c, log.KV("AddCacheUpLists", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheUpLists get data from mc
func (d *Dao) CacheUpLists(c context.Context, id int64) (res []int64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := upListsKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheUpLists")
log.Errorv(c, log.KV("CacheUpLists", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = []int64{}
err = conn.Scan(reply, &res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheUpLists")
log.Errorv(c, log.KV("CacheUpLists", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheListReadCount Set data to mc
func (d *Dao) AddCacheListReadCount(c context.Context, id int64, val int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := listReadCountKey(id)
bs := []byte(strconv.FormatInt(int64(val), 10))
item := &memcache.Item{Key: key, Value: bs, Expiration: d.mcListReadExpire, Flags: memcache.FlagRAW}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheListReadCount")
log.Errorv(c, log.KV("AddCacheListReadCount", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheListReadCount get data from mc
func (d *Dao) CacheListReadCount(c context.Context, id int64) (res int64, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := listReadCountKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheListReadCount")
log.Errorv(c, log.KV("CacheListReadCount", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListReadCount")
log.Errorv(c, log.KV("CacheListReadCount", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListReadCount")
log.Errorv(c, log.KV("CacheListReadCount", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = int64(r)
return
}
// CacheListsReadCount get data from mc
func (d *Dao) CacheListsReadCount(c context.Context, ids []int64) (res map[int64]int64, err error) {
l := len(ids)
if l == 0 {
return
}
keysMap := make(map[string]int64, l)
keys := make([]string, 0, l)
for _, id := range ids {
key := listReadCountKey(id)
keysMap[key] = id
keys = append(keys, key)
}
conn := d.mc.Get(c)
defer conn.Close()
replies, err := conn.GetMulti(keys)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListsReadCount")
log.Errorv(c, log.KV("CacheListsReadCount", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
return
}
for key, reply := range replies {
var v string
err = conn.Scan(reply, &v)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListsReadCount")
log.Errorv(c, log.KV("CacheListsReadCount", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListsReadCount")
log.Errorv(c, log.KV("CacheListsReadCount", fmt.Sprintf("%+v", err)), log.KV("key", key))
return res, err
}
if res == nil {
res = make(map[int64]int64, len(keys))
}
res[keysMap[key]] = int64(r)
}
return
}
// AddCacheHotspots Set data to mc
func (d *Dao) AddCacheHotspots(c context.Context, val []*model.Hotspot) (err error) {
if len(val) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := hotspotsKey()
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcHotspotExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheHotspots")
log.Errorv(c, log.KV("AddCacheHotspots", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheHotspots delete data from mc
func (d *Dao) DelCacheHotspots(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := hotspotsKey()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheHotspots")
log.Errorv(c, log.KV("DelCacheHotspots", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// cacheHotspots get data from mc
func (d *Dao) cacheHotspots(c context.Context) (res []*model.Hotspot, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := hotspotsKey()
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:cacheHotspots")
log.Errorv(c, log.KV("cacheHotspots", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = []*model.Hotspot{}
err = conn.Scan(reply, &res)
if err != nil {
prom.BusinessErrCount.Incr("mc:cacheHotspots")
log.Errorv(c, log.KV("cacheHotspots", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheHotspot get data from mc
func (d *Dao) CacheHotspot(c context.Context, id int64) (res *model.Hotspot, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := mcHotspotKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheHotspot")
log.Errorv(c, log.KV("CacheHotspot", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.Hotspot{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheHotspot")
log.Errorv(c, log.KV("CacheHotspot", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheHotspot Set data to mc
func (d *Dao) AddCacheHotspot(c context.Context, id int64, val *model.Hotspot) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := mcHotspotKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcHotspotExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheHotspot")
log.Errorv(c, log.KV("AddCacheHotspot", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheAuthor 增加作者状态缓存
func (d *Dao) AddCacheAuthor(c context.Context, id int64, val *model.AuthorLimit) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := mcAuthorKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcAuthorExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheAuthor")
log.Errorv(c, log.KV("AddCacheAuthor", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheAuthor get data from mc
func (d *Dao) CacheAuthor(c context.Context, id int64) (res *model.AuthorLimit, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := mcAuthorKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheAuthor")
log.Errorv(c, log.KV("CacheAuthor", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.AuthorLimit{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheAuthor")
log.Errorv(c, log.KV("CacheAuthor", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheAuthor delete data from mc
func (d *Dao) DelCacheAuthor(c context.Context, id int64) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := mcAuthorKey(id)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheAuthor")
log.Errorv(c, log.KV("DelCacheAuthor", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheListArtsId get data from mc
func (d *Dao) CacheListArtsId(c context.Context, id string) (res *model.ArticleViewList, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := slideArticlesKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheListArtsId")
log.Errorv(c, log.KV("CacheListArtsId", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.ArticleViewList{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheListArtsId")
log.Errorv(c, log.KV("CacheListArtsId", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheListArtsId Set data to mc
func (d *Dao) AddCacheListArtsId(c context.Context, id string, val *model.ArticleViewList) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := slideArticlesKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcArticlesIDExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheListArtsId")
log.Errorv(c, log.KV("AddCacheListArtsId", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// DelCacheListArtsId delete data from mc
func (d *Dao) DelCacheListArtsId(c context.Context, id string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := slideArticlesKey(id)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:DelCacheListArtsId")
log.Errorv(c, log.KV("DelCacheListArtsId", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheAnniversary get data from mc
func (d *Dao) CacheAnniversary(c context.Context, id int64) (res *model.AnniversaryInfo, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := AnniversaryKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheAnniversary")
log.Errorv(c, log.KV("CacheAnniversary", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.AnniversaryInfo{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheAnniversary")
log.Errorv(c, log.KV("CacheAnniversary", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheAidsByTag get data from mc
func (d *Dao) CacheAidsByTag(c context.Context, id int64) (res *model.TagArts, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := mcTagKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheAidsByTag")
log.Errorv(c, log.KV("CacheAidsByTag", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.TagArts{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheAidsByTag")
log.Errorv(c, log.KV("CacheAidsByTag", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheAidsByTag Set data to mc
func (d *Dao) AddCacheAidsByTag(c context.Context, id int64, val *model.TagArts) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := mcTagKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcArticleTagExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheAidsByTag")
log.Errorv(c, log.KV("AddCacheAidsByTag", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// CacheUpStatDaily get data from mc
func (d *Dao) CacheUpStatDaily(c context.Context, id int64) (res *model.UpStat, err error) {
conn := d.mc.Get(c)
defer conn.Close()
key := mcUpStatKey(id)
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
prom.BusinessErrCount.Incr("mc:CacheUpStatDaily")
log.Errorv(c, log.KV("CacheUpStatDaily", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
res = &model.UpStat{}
err = conn.Scan(reply, res)
if err != nil {
prom.BusinessErrCount.Incr("mc:CacheUpStatDaily")
log.Errorv(c, log.KV("CacheUpStatDaily", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}
// AddCacheUpStatDaily Set data to mc
func (d *Dao) AddCacheUpStatDaily(c context.Context, id int64, val *model.UpStat) (err error) {
if val == nil {
return
}
conn := d.mc.Get(c)
defer conn.Close()
key := mcUpStatKey(id)
item := &memcache.Item{Key: key, Object: val, Expiration: d.mcUpStatDailyExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
prom.BusinessErrCount.Incr("mc:AddCacheUpStatDaily")
log.Errorv(c, log.KV("AddCacheUpStatDaily", fmt.Sprintf("%+v", err)), log.KV("key", key))
return
}
return
}

View File

@@ -0,0 +1,104 @@
package dao
import (
"context"
"errors"
"net/url"
"strconv"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_mediaURL = "http://api.bilibili.co/pgc/internal/review/score/card"
_setScoreURL = "http://api.bilibili.co/pgc/internal/review/score/rating"
_delScoreURL = "http://api.bilibili.co/pgc/internal/review/score/delete"
)
// Media get media score.
func (d *Dao) Media(c context.Context, mediaID, mid int64) (res *model.MediaResult, err error) {
if mediaID == 0 {
return
}
params := url.Values{}
params.Set("media_id", strconv.FormatInt(mediaID, 10))
params.Set("mid", strconv.FormatInt(mid, 10))
resp := &model.MediaResp{}
err = d.httpClient.Get(c, _mediaURL, "", params, &resp)
if err != nil {
PromError("media:番剧信息接口")
log.Error("media: d.client.Get(%s) error(%+v)", _mediaURL+"?"+params.Encode(), err)
return
}
if resp.Code != 0 {
PromError("media:番剧信息接口")
log.Error("media: url(%s) res: %+v", _mediaURL+"?"+params.Encode(), resp)
err = ecode.Int(resp.Code)
return
}
res = resp.Result
return
}
// SetScore set media score.
func (d *Dao) SetScore(c context.Context, score, aid, mediaID, mid int64) (err error) {
if score < 1 || score > 10 || score%2 != 0 {
err = errors.New("评分分值错误")
return
}
params := url.Values{}
params.Set("media_id", strconv.FormatInt(mediaID, 10))
params.Set("mid", strconv.FormatInt(mid, 10))
params.Set("oid", strconv.FormatInt(aid, 10))
params.Set("score", strconv.FormatInt(score, 10))
params.Set("from", "1")
var resp struct {
Code int `json:"code"`
Message string `json:"message"`
}
err = d.httpClient.Post(c, _setScoreURL, "", params, &resp)
if err != nil {
PromError("media:番剧评分接口")
log.Error("media: d.client.Post(%s, data:%s) error(%+v)", _setScoreURL, params.Encode(), err)
return
}
if resp.Code != 0 {
PromError("media:番剧评分接口")
log.Error("media: d.client.Post(%s, data:%s) res: %+v", _setScoreURL, params.Encode(), resp)
err = ecode.Int(resp.Code)
return
}
log.Info("media: set score success(media_id: %d, mid: %d, oid: %d, score: %d)", mediaID, mid, aid, score)
return
}
// DelScore get media score.
func (d *Dao) DelScore(c context.Context, aid, mediaID, mid int64) (err error) {
if mediaID == 0 || mid == 0 || aid == 0 {
return
}
params := url.Values{}
params.Set("media_id", strconv.FormatInt(mediaID, 10))
params.Set("mid", strconv.FormatInt(mid, 10))
params.Set("oid", strconv.FormatInt(aid, 10))
params.Set("from", "1")
var resp struct {
Code int `json:"code"`
Message string `json:"message"`
}
err = d.httpClient.Post(c, _delScoreURL, "", params, &resp)
if err != nil {
PromError("media:番剧删除评分接口")
log.Error("media: d.client.Post(%s, data:%s) error(%+v)", _delScoreURL, params.Encode(), err)
return
}
if resp.Code != 0 {
PromError("media:番剧删除评分接口")
log.Error("media: d.client.Post(%s, data:%s) res: %+v", _delScoreURL, params.Encode(), resp)
err = ecode.Int(resp.Code)
}
log.Info("media: del score success(media_id: %d, mid: %d, oid: %d)", mediaID, mid, aid)
return
}

View File

@@ -0,0 +1,663 @@
package dao
import (
"context"
"fmt"
"strconv"
"strings"
"sync"
"time"
"go-common/app/interface/openplatform/article/model"
"go-common/library/cache/memcache"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
"go-common/library/sync/errgroup"
)
const (
_prefixArtMeta = "art_mp_%d"
_prefixArtContent = "art_c_%d"
_prefixArtKeywords = "art_kw_%d"
_prefixArtStat = "art_s_%d"
_prefixCard = "art_cards_"
_bulkSize = 50
)
func artMetaKey(id int64) string {
return fmt.Sprintf(_prefixArtMeta, id)
}
func artContentKey(id int64) string {
return fmt.Sprintf(_prefixArtContent, id)
}
func artKeywordsKey(id int64) string {
return fmt.Sprintf(_prefixArtKeywords, id)
}
func artStatsKey(id int64) string {
return fmt.Sprintf(_prefixArtStat, id)
}
func cardKey(id string) string {
return _prefixCard + id
}
func hotspotsKey() string {
return fmt.Sprintf("art_hotspots")
}
func mcHotspotKey(id int64) string {
return fmt.Sprintf("art_hotspot_%d", id)
}
func mcAuthorKey(mid int64) string {
return fmt.Sprintf("art_author_%d", mid)
}
func mcTagKey(tag int64) string {
return fmt.Sprintf("tag_aids_%d", tag)
}
func mcUpStatKey(mid int64) string {
var (
hour int
day int
)
now := time.Now()
hour = now.Hour()
if hour < 7 {
day = now.Add(time.Hour * -24).Day()
} else {
day = now.Day()
}
return fmt.Sprintf("up_stat_daily_%d_%d", mid, day)
}
// statsValue convert stats to string, format: "view,favorite,like,unlike,reply..."
func statsValue(s *model.Stats) string {
if s == nil {
return ",,,,,,"
}
ids := []int64{s.View, s.Favorite, s.Like, s.Dislike, s.Reply, s.Share, s.Coin}
return xstr.JoinInts(ids)
}
func revoverStatsValue(c context.Context, s string) (res *model.Stats) {
var (
vs []int64
err error
)
res = new(model.Stats)
if s == "" {
return
}
if vs, err = xstr.SplitInts(s); err != nil || len(vs) < 7 {
PromError("mc:stats解析")
log.Error("dao.revoverStatsValue(%s) err: %+v", s, err)
return
}
res = &model.Stats{
View: vs[0],
Favorite: vs[1],
Like: vs[2],
Dislike: vs[3],
Reply: vs[4],
Share: vs[5],
Coin: vs[6],
}
return
}
// pingMc ping memcache
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcArticleExpire}
err = conn.Set(&item)
return
}
//AddArticlesMetaCache add articles meta cache
func (d *Dao) AddArticlesMetaCache(c context.Context, vs ...*model.Meta) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
for _, v := range vs {
if v == nil {
continue
}
item := &memcache.Item{Key: artMetaKey(v.ID), Object: v, Flags: memcache.FlagProtobuf, Expiration: d.mcArticleExpire}
if err = conn.Set(item); err != nil {
PromError("mc:增加文章meta缓存")
log.Error("conn.Store(%s) error(%+v)", artMetaKey(v.ID), err)
return
}
}
return
}
// ArticleMetaCache gets article's meta cache.
func (d *Dao) ArticleMetaCache(c context.Context, aid int64) (res *model.Meta, err error) {
var (
conn = d.mc.Get(c)
key = artMetaKey(aid)
)
defer conn.Close()
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
missedCount.Incr("article-meta")
err = nil
return
}
PromError("mc:获取文章meta缓存")
log.Error("conn.Get(%v) error(%+v)", key, err)
return
}
res = &model.Meta{}
if err = conn.Scan(reply, res); err != nil {
PromError("mc:文章meta缓存json解析")
log.Error("reply.Scan(%s) error(%+v)", reply.Value, err)
return
}
res.Strong()
cachedCount.Incr("article-meta")
return
}
//ArticlesMetaCache articles meta cache
func (d *Dao) ArticlesMetaCache(c context.Context, ids []int64) (cached map[int64]*model.Meta, missed []int64, err error) {
if len(ids) == 0 {
return
}
cached = make(map[int64]*model.Meta, len(ids))
allKeys := make([]string, 0, len(ids))
idmap := make(map[string]int64, len(ids))
for _, id := range ids {
k := artMetaKey(id)
allKeys = append(allKeys, k)
idmap[k] = id
}
group, errCtx := errgroup.WithContext(c)
mutex := sync.Mutex{}
keysLen := len(allKeys)
for i := 0; i < keysLen; i += _bulkSize {
var keys []string
if (i + _bulkSize) > keysLen {
keys = allKeys[i:]
} else {
keys = allKeys[i : i+_bulkSize]
}
group.Go(func() (err error) {
conn := d.mc.Get(errCtx)
defer conn.Close()
replys, err := conn.GetMulti(keys)
if err != nil {
PromError("mc:获取文章meta缓存")
log.Error("conn.Gets(%v) error(%+v)", keys, err)
err = nil
return
}
for key, item := range replys {
art := &model.Meta{}
if err = conn.Scan(item, art); err != nil {
PromError("mc:文章meta缓存json解析")
log.Error("item.Scan(%s) error(%+v)", item.Value, err)
err = nil
continue
}
mutex.Lock()
cached[idmap[key]] = art.Strong()
delete(idmap, key)
mutex.Unlock()
}
return
})
}
group.Wait()
missed = make([]int64, 0, len(idmap))
for _, id := range idmap {
missed = append(missed, id)
}
missedCount.Add("article-meta", int64(len(missed)))
cachedCount.Add("article-meta", int64(len(cached)))
return
}
// AddArticleStatsCache batch set article cache.
func (d *Dao) AddArticleStatsCache(c context.Context, id int64, v *model.Stats) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
bs := []byte(statsValue(v))
item := &memcache.Item{Key: artStatsKey(id), Value: bs, Expiration: d.mcStatsExpire}
if err = conn.Set(item); err != nil {
PromError("mc:增加文章统计缓存")
log.Error("conn.Store(%s) error(%+v)", artStatsKey(id), err)
}
return
}
//AddArticleContentCache add article content cache
func (d *Dao) AddArticleContentCache(c context.Context, id int64, content string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
var bs = []byte(content)
item := &memcache.Item{Key: artContentKey(id), Value: bs, Expiration: d.mcArticleExpire, Flags: memcache.FlagGzip}
if err = conn.Set(item); err != nil {
PromError("mc:增加文章内容缓存")
log.Error("conn.Store(%s) error(%+v)", artContentKey(id), err)
}
return
}
// AddArticleKeywordsCache add article keywords cache.
func (d *Dao) AddArticleKeywordsCache(c context.Context, id int64, keywords string) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
var bs = []byte(keywords)
item := &memcache.Item{Key: artKeywordsKey(id), Value: bs, Expiration: d.mcArticleExpire, Flags: memcache.FlagGzip}
if err = conn.Set(item); err != nil {
PromError("mc:增加文章关键字缓存")
log.Error("conn.Store(%s) error(%+v)", artKeywordsKey(id), err)
}
return
}
// ArticleContentCache article content cache
func (d *Dao) ArticleContentCache(c context.Context, id int64) (res string, err error) {
conn := d.mc.Get(c)
defer conn.Close()
reply, err := conn.Get(artContentKey(id))
if err != nil {
if err == memcache.ErrNotFound {
missedCount.Incr("article-content")
err = nil
return
}
PromError("mc:获取文章内容缓存")
log.Error("conn.Get(%v) error(%+v)", artContentKey(id), err)
return
}
err = conn.Scan(reply, &res)
return
}
// ArticleKeywordsCache article Keywords cache
func (d *Dao) ArticleKeywordsCache(c context.Context, id int64) (res string, err error) {
conn := d.mc.Get(c)
defer conn.Close()
reply, err := conn.Get(artKeywordsKey(id))
if err != nil {
if err == memcache.ErrNotFound {
missedCount.Incr("article-keywords")
err = nil
return
}
PromError("mc:获取文章关键字缓存")
log.Error("conn.Get(%v) error(%+v)", artKeywordsKey(id), err)
return
}
err = conn.Scan(reply, &res)
return
}
//DelArticleMetaCache delete article meta cache
func (d *Dao) DelArticleMetaCache(c context.Context, id int64) (err error) {
var (
key = artMetaKey(id)
conn = d.mc.Get(c)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
PromError("mc:删除文章meta缓存")
log.Error("key(%v) error(%+v)", key, err)
}
}
return
}
// DelArticleStatsCache delete article stats cache
func (d *Dao) DelArticleStatsCache(c context.Context, id int64) (err error) {
var (
key = artStatsKey(id)
conn = d.mc.Get(c)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
PromError("mc:删除文章stats缓存")
log.Error("key(%v) error(%+v)", key, err)
}
}
return
}
//DelArticleContentCache delete article content cache
func (d *Dao) DelArticleContentCache(c context.Context, id int64) (err error) {
var (
key = artContentKey(id)
conn = d.mc.Get(c)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
PromError("mc:删除文章content缓存")
log.Error("key(%v) error(%+v)", key, err)
}
}
return
}
// ArticleStatsCache article stats cache
func (d *Dao) ArticleStatsCache(c context.Context, id int64) (res *model.Stats, err error) {
if id == 0 {
err = ecode.NothingFound
return
}
var (
conn = d.mc.Get(c)
key = artStatsKey(id)
statsStr string
)
defer conn.Close()
reply, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
res = nil
err = nil
return
}
PromError("mc:获取文章计数缓存")
log.Error("conn.Get(%v) error(%+v)", key, err)
return
}
if err = conn.Scan(reply, &statsStr); err == nil {
res = revoverStatsValue(c, statsStr)
} else {
PromError("mc:获取文章计数缓存")
log.Error("dao.ArticleStatsCache.reply.Scan(%v, %v) error(%+v)", key, statsStr, err)
}
return
}
// ArticlesStatsCache articles stats cache
func (d *Dao) ArticlesStatsCache(c context.Context, ids []int64) (cached map[int64]*model.Stats, missed []int64, err error) {
if len(ids) == 0 {
return
}
cached = make(map[int64]*model.Stats, len(ids))
allKeys := make([]string, 0, len(ids))
idmap := make(map[string]int64, len(ids))
for _, id := range ids {
k := artStatsKey(id)
allKeys = append(allKeys, k)
idmap[k] = id
}
group, errCtx := errgroup.WithContext(c)
mutex := sync.Mutex{}
keysLen := len(allKeys)
for i := 0; i < keysLen; i += _bulkSize {
var keys []string
if (i + _bulkSize) > keysLen {
keys = allKeys[i:]
} else {
keys = allKeys[i : i+_bulkSize]
}
group.Go(func() (err error) {
conn := d.mc.Get(errCtx)
defer conn.Close()
replys, err := conn.GetMulti(keys)
if err != nil {
PromError("mc:获取文章计数缓存")
log.Error("conn.Gets(%v) error(%+v)", keys, err)
err = nil
return
}
for _, reply := range replys {
var info string
if e := conn.Scan(reply, &info); e != nil {
PromError("mc:获取文章计数缓存scan")
continue
}
art := revoverStatsValue(c, info)
mutex.Lock()
cached[idmap[reply.Key]] = art
delete(idmap, reply.Key)
mutex.Unlock()
}
return
})
}
group.Wait()
missed = make([]int64, 0, len(idmap))
for _, id := range idmap {
missed = append(missed, id)
}
missedCount.Add("article-stats", int64(len(missed)))
cachedCount.Add("article-stats", int64(len(cached)))
return
}
// AddCardsCache .
func (d *Dao) addCardsCache(c context.Context, vs ...*model.Cards) (err error) {
if len(vs) == 0 {
return
}
conn := d.mc.Get(c)
defer conn.Close()
for _, v := range vs {
if v == nil {
continue
}
key := cardKey(v.Key())
item := memcache.Item{Key: key, Object: v, Expiration: d.mcCardsExpire, Flags: memcache.FlagJSON}
if err = conn.Set(&item); err != nil {
PromError("mc:增加卡片缓存")
log.Error("conn.Set(%s) error(%+v)", key, err)
return
}
}
return
}
// CardsCache ids like cv123 av123 au123
func (d *Dao) cardsCache(c context.Context, ids []string) (res map[string]*model.Cards, err error) {
if len(ids) == 0 {
return
}
res = make(map[string]*model.Cards, len(ids))
var keys []string
for _, id := range ids {
keys = append(keys, cardKey(id))
}
conn := d.mc.Get(c)
replys, err := conn.GetMulti(keys)
defer conn.Close()
if err != nil {
PromError("mc:获取cards缓存")
log.Error("conn.Gets(%v) error(%+v)", keys, err)
err = nil
return
}
for _, reply := range replys {
s := model.Cards{}
if err = conn.Scan(reply, &s); err != nil {
PromError("获取cards缓存json解析")
log.Error("json.Unmarshal(%v) error(%+v)", reply.Value, err)
err = nil
continue
}
res[strings.TrimPrefix(reply.Key, _prefixCard)] = &s
}
return
}
// AddBangumiCardsCache .
func (d *Dao) AddBangumiCardsCache(c context.Context, vs map[int64]*model.BangumiCard) (err error) {
var cards []*model.Cards
for _, v := range vs {
cards = append(cards, &model.Cards{Type: model.CardPrefixBangumi, BangumiCard: v})
}
err = d.addCardsCache(c, cards...)
return
}
// BangumiCardsCache .
func (d *Dao) BangumiCardsCache(c context.Context, ids []int64) (vs map[int64]*model.BangumiCard, err error) {
var cards map[string]*model.Cards
var idsStr []string
for _, id := range ids {
idsStr = append(idsStr, model.CardPrefixBangumi+strconv.FormatInt(id, 10))
}
if cards, err = d.cardsCache(c, idsStr); err != nil {
return
}
vs = make(map[int64]*model.BangumiCard)
for _, card := range cards {
if (card != nil) && (card.BangumiCard != nil) {
vs[card.BangumiCard.ID] = card.BangumiCard
}
}
return
}
// AddBangumiEpCardsCache .
func (d *Dao) AddBangumiEpCardsCache(c context.Context, vs map[int64]*model.BangumiCard) (err error) {
var cards []*model.Cards
for _, v := range vs {
cards = append(cards, &model.Cards{Type: model.CardPrefixBangumiEp, BangumiCard: v})
}
err = d.addCardsCache(c, cards...)
return
}
// BangumiEpCardsCache .
func (d *Dao) BangumiEpCardsCache(c context.Context, ids []int64) (vs map[int64]*model.BangumiCard, err error) {
var cards map[string]*model.Cards
var idsStr []string
for _, id := range ids {
idsStr = append(idsStr, model.CardPrefixBangumiEp+strconv.FormatInt(id, 10))
}
if cards, err = d.cardsCache(c, idsStr); err != nil {
return
}
vs = make(map[int64]*model.BangumiCard)
for _, card := range cards {
if (card != nil) && (card.BangumiCard != nil) {
vs[card.BangumiCard.ID] = card.BangumiCard
}
}
return
}
// AddAudioCardsCache .
func (d *Dao) AddAudioCardsCache(c context.Context, vs map[int64]*model.AudioCard) (err error) {
var cards []*model.Cards
for _, v := range vs {
cards = append(cards, &model.Cards{Type: model.CardPrefixAudio, AudioCard: v})
}
err = d.addCardsCache(c, cards...)
return
}
// AudioCardsCache .
func (d *Dao) AudioCardsCache(c context.Context, ids []int64) (vs map[int64]*model.AudioCard, err error) {
var cards map[string]*model.Cards
var idsStr []string
for _, id := range ids {
idsStr = append(idsStr, model.CardPrefixAudio+strconv.FormatInt(id, 10))
}
if cards, err = d.cardsCache(c, idsStr); err != nil {
return
}
vs = make(map[int64]*model.AudioCard)
for _, card := range cards {
if (card != nil) && (card.AudioCard != nil) {
vs[card.AudioCard.ID] = card.AudioCard
}
}
return
}
// AddMallCardsCache .
func (d *Dao) AddMallCardsCache(c context.Context, vs map[int64]*model.MallCard) (err error) {
var cards []*model.Cards
for _, v := range vs {
cards = append(cards, &model.Cards{Type: model.CardPrefixMall, MallCard: v})
}
err = d.addCardsCache(c, cards...)
return
}
// MallCardsCache .
func (d *Dao) MallCardsCache(c context.Context, ids []int64) (vs map[int64]*model.MallCard, err error) {
var cards map[string]*model.Cards
var idsStr []string
for _, id := range ids {
idsStr = append(idsStr, model.CardPrefixMall+strconv.FormatInt(id, 10))
}
if cards, err = d.cardsCache(c, idsStr); err != nil {
return
}
vs = make(map[int64]*model.MallCard)
for _, card := range cards {
if (card != nil) && (card.MallCard != nil) {
vs[card.MallCard.ID] = card.MallCard
}
}
return
}
// AddTicketCardsCache .
func (d *Dao) AddTicketCardsCache(c context.Context, vs map[int64]*model.TicketCard) (err error) {
var cards []*model.Cards
for _, v := range vs {
cards = append(cards, &model.Cards{Type: model.CardPrefixTicket, TicketCard: v})
}
err = d.addCardsCache(c, cards...)
return
}
// TicketCardsCache .
func (d *Dao) TicketCardsCache(c context.Context, ids []int64) (vs map[int64]*model.TicketCard, err error) {
var cards map[string]*model.Cards
var idsStr []string
for _, id := range ids {
idsStr = append(idsStr, model.CardPrefixTicket+strconv.FormatInt(id, 10))
}
if cards, err = d.cardsCache(c, idsStr); err != nil {
return
}
vs = make(map[int64]*model.TicketCard)
for _, card := range cards {
if (card != nil) && (card.TicketCard != nil) {
vs[card.TicketCard.ID] = card.TicketCard
}
}
return
}
// CacheHotspots .
func (d *Dao) CacheHotspots(c context.Context) (res []*model.Hotspot, err error) {
res, err = d.cacheHotspots(c)
for _, r := range res {
if r.TopArticles == nil {
r.TopArticles = []int64{}
}
}
return
}

View File

@@ -0,0 +1,90 @@
package dao
import (
"context"
"go-common/app/interface/openplatform/article/model"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_ArticlesCache(t *testing.T) {
c := context.TODO()
Convey("add cache", t, WithDao(func(d *Dao) {
err := d.AddArticlesMetaCache(c, art.Meta)
So(err, ShouldBeNil)
err = d.AddArticleContentCache(c, art.ID, art.Content)
So(err, ShouldBeNil)
err = d.AddArticleStatsCache(c, art.ID, art.Stats)
So(err, ShouldBeNil)
Convey("get meta cache", func() {
_, err = d.ArticleMetaCache(c, art.ID)
So(err, ShouldBeNil)
cached, missed, err1 := d.ArticlesMetaCache(c, []int64{art.ID})
So(err1, ShouldBeNil)
So(missed, ShouldBeEmpty)
So(cached, ShouldResemble, map[int64]*model.Meta{art.ID: art.Meta})
})
Convey("get content cache", func() {
content, err1 := d.ArticleContentCache(c, art.ID)
So(err1, ShouldBeNil)
So(content, ShouldEqual, art.Content)
})
Convey("get no filter content cache", func() {
err = d.AddArticleContentCache(c, art.ID, art.Content)
So(err, ShouldBeNil)
content, err := d.ArticleContentCache(c, art.ID)
So(err, ShouldBeNil)
So(content, ShouldEqual, art.Content)
})
Convey("get stats cache", func() {
cached, missed, err := d.ArticlesStatsCache(c, []int64{art.ID})
So(err, ShouldBeNil)
So(missed, ShouldBeEmpty)
So(cached, ShouldResemble, map[int64]*model.Stats{art.ID: art.Stats})
})
Convey("get stat cache", func() {
res, err := d.ArticleStatsCache(c, art.ID)
So(err, ShouldBeNil)
So(res, ShouldResemble, art.Stats)
})
}))
}
func Test_AudioCache(t *testing.T) {
c := context.TODO()
card := model.AudioCard{ID: 1, Title: "audio"}
Convey("add cache", t, WithDao(func(d *Dao) {
err := d.AddAudioCardsCache(c, map[int64]*model.AudioCard{1: &card})
So(err, ShouldBeNil)
x, err := d.AudioCardsCache(c, []int64{card.ID})
So(err, ShouldBeNil)
So(x[card.ID], ShouldResemble, &card)
}))
}
func Test_Hotspots(t *testing.T) {
c := context.TODO()
hots := []*model.Hotspot{&model.Hotspot{ID: 1, Tag: "tag"}}
Convey("add cache", t, func() {
err := d.AddCacheHotspots(c, hots)
So(err, ShouldBeNil)
res, err := d.CacheHotspots(c)
So(err, ShouldBeNil)
So(res, ShouldResemble, []*model.Hotspot{&model.Hotspot{ID: 1, Tag: "tag", TopArticles: []int64{}}})
err = d.DelCacheHotspots(c)
So(err, ShouldBeNil)
// delete twice
err = d.DelCacheHotspots(c)
So(err, ShouldBeNil)
res, err = d.CacheHotspots(c)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
}

View File

@@ -0,0 +1,41 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/library/ecode"
"go-common/library/log"
)
var _notify = "4"
// SendMessage .
func (d *Dao) SendMessage(c context.Context, tid, mid, aid int64, title, msg string) (err error) {
params := url.Values{}
params.Set("mid_list", strconv.FormatInt(mid, 10))
params.Set("title", title)
params.Set("mc", d.c.Message.MC)
params.Set("data_type", _notify)
params.Set("context", msg)
params.Set("notify_type", strconv.FormatInt(tid, 10))
params.Set("res_id", strconv.FormatInt(aid, 10))
var res struct {
Code int `json:"code"`
}
err = d.messageHTTPClient.Post(c, d.c.Message.URL, "", params, &res)
if err != nil {
PromError("message:send接口")
log.Error("d.client.Post(%s) error(%+v)", d.c.Message.URL+"?"+params.Encode(), err)
return
}
if res.Code != 0 {
PromError("message:send接口")
log.Error("url(%s) res code(%d)", d.c.Message.URL+"?"+params.Encode(), res.Code)
err = ecode.Int(res.Code)
return
}
log.Info("发送点赞消息通知 (%s) error(%+v)", d.c.Message.URL+"?"+params.Encode(), err)
return
}

View File

@@ -0,0 +1,322 @@
package dao
import (
"context"
"fmt"
"strings"
"sync"
"time"
"go-common/app/interface/openplatform/article/model"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
"go-common/library/sync/errgroup"
)
const (
_sharding = 100
_mysqlBulkSize = 50
// article
_articleMetaSQL = "SELECT article_id,category_id,title,summary,banner_url, template_id, state, mid, reprint, image_urls, publish_time, ctime, attributes,words,dynamic_intro, origin_image_urls, media_id, spoiler FROM filtered_articles WHERE article_id = ?"
_allArticleMetaSQL = "SELECT id,category_id,title,summary,banner_url, template_id, state, mid, reprint, image_urls, publish_time, ctime, attributes,words,dynamic_intro, origin_image_urls, media_id, spoiler FROM articles WHERE id = ?"
_articlesMetaSQL = "SELECT article_id,category_id,title,summary,banner_url, template_id, state, mid, reprint, image_urls, publish_time, ctime, attributes,words,dynamic_intro, origin_image_urls, media_id, spoiler FROM filtered_articles WHERE article_id in (%s)"
_upperPassedSQL = "SELECT article_id, publish_time, attributes FROM filtered_articles WHERE mid = ? ORDER BY publish_time desc"
_uppersPassedSQL = "SELECT article_id, mid, publish_time, attributes FROM filtered_articles WHERE mid in (%s) ORDER BY publish_time desc"
_articleContentSQL = "SELECT content FROM filtered_article_contents_%s WHERE article_id = ?"
_articleKeywordsSQL = "SELECT tags FROM article_contents_%s WHERE article_id = ?"
_articleUpperCountSQL = "SELECT count(*) FROM filtered_articles WHERE mid = ?"
_articleUpCntTodaySQL = "SELECT count(*) FROM articles WHERE mid = ? and ctime >= ?"
_delFilteredArtMetaSQL = "DELETE FROM filtered_articles where article_id = ?"
_delFilteredArtContentSQL = "DELETE FROM filtered_article_contents_%s where article_id = ?"
// stat
_statSQL = "SELECT view,favorite,likes,dislike,reply,share,coin,dynamic FROM article_stats_%s WHERE article_id = ? and deleted_time = 0"
_statsSQL = "SELECT article_id, view,favorite,likes,dislike,reply,share,coin,dynamic FROM article_stats_%s WHERE article_id in (%s) and deleted_time = 0"
// category
_categoriesSQL = "SELECT id,parent_id,name,position,banner_url FROM article_categories WHERE state = 1 and deleted_time = 0"
// authors
_authorsSQL = "SELECT mid, daily_limit, state FROM article_authors WHERE deleted_time=0"
_authorSQL = "SELECT state,rtime, daily_limit FROM article_authors WHERE mid=? AND deleted_time=0"
_applyCountSQL = "SELECT count(*) FROM article_authors WHERE atime >= ?"
_applySQL = "INSERT INTO article_authors (mid,atime,count,content,category) VALUES (?,?,1,?,?) ON DUPLICATE KEY UPDATE atime=?,rtime=0,state=0,count=count+1,content=?,category=?,deleted_time=0"
_addAuthorSQL = "INSERT INTO article_authors (mid,state,type) VALUES (?,1,5) ON DUPLICATE KEY UPDATE state=1,deleted_time=0"
// recommends
_recommendCategorySQL = "SELECT article_id, big_banner_url, show_recommend, position, end_time, big_banner_start_time, big_banner_end_time FROM article_recommends WHERE start_time <= ? and (end_time >= ? or end_time = 0) and category_id = ? and deleted_time = 0 ORDER BY position ASC"
_allRecommendSQL = "SELECT article_id FROM article_recommends WHERE start_time <= ? and (end_time >= ? or end_time = 0) and deleted_time = 0 and category_id = 0 ORDER BY mtime DESC LIMIT ?,?"
_allRecommendCountSQL = "SELECT COUNT(*) FROM article_recommends WHERE start_time <= ? and (end_time >= ? or end_time = 0) and deleted_time = 0 and category_id = 0"
_deleteRecommendSQL = "UPDATE article_recommends SET deleted_time=? WHERE article_id=? and deleted_time = 0"
// setting
_settingsSQL = "SELECT name,value FROM article_settings WHERE deleted_time=0"
// sort
_newestArtsMetaSQL = "SELECT article_id, publish_time, attributes FROM filtered_articles ORDER BY publish_time DESC LIMIT ?"
//notice
_noticeSQL = "SELECT id, title, url, plat, condi, build from article_notices where state = 1 and stime <= ? and etime > ?"
// users
_userNoticeSQL = "SELECT notice_state from users where mid = ?"
_updateUserNoticeSQL = "INSERT INTO users (mid,notice_state) VALUES (?,?) ON DUPLICATE KEY UPDATE notice_state=?"
// hotspots
_hotspotsSQL = "select id, title, tag, icon, top_articles from hotspots where deleted_time = 0 and `order` != 0 order by `order` asc"
// search articles
_searchArticles = "select article_id, publish_time, tags, stats_view, stats_reply from search_articles where publish_time >= ? and publish_time < ?"
_addCheatSQL = "INSERT INTO stats_filters(article_id, lv) VALUES(?,?) ON DUPLICATE KEY UPDATE lv=?, deleted_time = 0"
_delCheatSQL = "UPDATE stats_filters SET deleted_time = ? WHERE article_id = ? and deleted_time = 0"
_tagArticlesSQL = "select tid, oid, log_date FROM article_tags where tid in (%s) and is_deleted = 0"
_mediaArticleSQL = "select id from articles where mid = ? and media_id = ? and deleted_time = 0 and state > -10"
_mediaByIDSQL = "select media_id from articles where id = ?"
)
var _searchInterval = int64(3 * 24 * 3600)
func (d *Dao) hit(id int64) string {
return fmt.Sprintf("%02d", id%_sharding)
}
// Categories get Categories
func (d *Dao) Categories(c context.Context) (res map[int64]*model.Category, err error) {
var rows *sql.Rows
if rows, err = d.categoriesStmt.Query(c); err != nil {
PromError("db:分区查询")
log.Error("mysql: db.Categories.Query error(%+v)", err)
return
}
defer rows.Close()
res = make(map[int64]*model.Category)
for rows.Next() {
ca := &model.Category{}
if err = rows.Scan(&ca.ID, &ca.ParentID, &ca.Name, &ca.Position, &ca.BannerURL); err != nil {
PromError("分区Scan")
log.Error("mysql: rows.Categories.Scan error(%+v)", err)
return
}
res[ca.ID] = ca
}
err = rows.Err()
promErrorCheck(err)
return
}
// ArticleStats get article stats
func (d *Dao) ArticleStats(c context.Context, id int64) (res *model.Stats, err error) {
res = new(model.Stats)
row := d.articleDB.QueryRow(c, fmt.Sprintf(_statSQL, d.hit(id)), id)
if err = row.Scan(&res.View, &res.Favorite, &res.Like, &res.Dislike, &res.Reply, &res.Share, &res.Coin, &res.Dynamic); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
} else {
PromError("Stat scan")
log.Error("mysql: ArticleStats row.Scan(%d) error(%+v)", id, err)
}
}
return
}
// ArticlesStats get articles stats
func (d *Dao) ArticlesStats(c context.Context, ids []int64) (res map[int64]*model.Stats, err error) {
var (
shardings = make(map[int64][]int64)
group = &errgroup.Group{}
mutex = &sync.Mutex{}
)
res = make(map[int64]*model.Stats)
for _, id := range ids {
shardings[id%_sharding] = append(shardings[id%_sharding], id)
}
for sharding, subIDs := range shardings {
keysLen := len(subIDs)
sharding := sharding
subIDs := subIDs
for i := 0; i < keysLen; i += _mysqlBulkSize {
var keys []int64
if (i + _mysqlBulkSize) > keysLen {
keys = subIDs[i:]
} else {
keys = subIDs[i : i+_mysqlBulkSize]
}
group.Go(func() error {
statsSQL := fmt.Sprintf(_statsSQL, d.hit(sharding), xstr.JoinInts(keys))
rows, e := d.articleDB.Query(c, statsSQL)
if e != nil {
return e
}
defer rows.Close()
for rows.Next() {
s := &model.Stats{}
var aid int64
e = rows.Scan(&aid, &s.View, &s.Favorite, &s.Like, &s.Dislike, &s.Reply, &s.Share, &s.Coin, &s.Dynamic)
if e != nil {
return e
}
mutex.Lock()
res[aid] = s
mutex.Unlock()
}
return rows.Err()
})
}
}
err = group.Wait()
if err != nil {
PromError("stats Scan")
log.Error("mysql: rows.ArticleStats.Scan error(%+v)", err)
}
if len(res) == 0 {
res = nil
}
return
}
// Settings gets article settings.
func (d *Dao) Settings(c context.Context) (res map[string]string, err error) {
var rows *sql.Rows
if rows, err = d.settingsStmt.Query(c); err != nil {
PromError("db:文章配置查询")
log.Error("mysql: db.settingsStmt.Query error(%+v)", err)
return
}
defer rows.Close()
res = make(map[string]string)
for rows.Next() {
var name, value string
if err = rows.Scan(&name, &value); err != nil {
PromError("文章配置scan")
log.Error("mysql: rows.Scan error(%+v)", err)
return
}
res[name] = value
}
err = rows.Err()
promErrorCheck(err)
return
}
// Notices notice .
func (d *Dao) Notices(c context.Context, t time.Time) (res []*model.Notice, err error) {
var rows *sql.Rows
if rows, err = d.noticeStmt.Query(c, t, t); err != nil {
PromError("db:notice")
log.Error("mysql: notice Query() error(%+v)", err)
return
}
defer rows.Close()
for rows.Next() {
ba := &model.Notice{}
if err = rows.Scan(&ba.ID, &ba.Title, &ba.URL, &ba.Plat, &ba.Condition, &ba.Build); err != nil {
PromError("db:notice")
log.Error("mysql: notice Scan() error(%+v)", err)
return
}
res = append(res, ba)
}
err = rows.Err()
promErrorCheck(err)
return
}
// NoticeState .
func (d *Dao) NoticeState(c context.Context, mid int64) (res int64, err error) {
if err = d.userNoticeStmt.QueryRow(c, mid).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
PromError("db:notice_state")
log.Error("mysql: notice state row.Scan error(%+v)", err)
}
}
return
}
// UpdateNoticeState update notice state
func (d *Dao) UpdateNoticeState(c context.Context, mid int64, state int64) (err error) {
if _, err = d.updateUserNoticeStmt.Exec(c, mid, state, state); err != nil {
PromError("db:修改用户引导状态")
log.Error("mysql: update_notice state(mid: %v, state: %v) error(%+v)", mid, state, err)
}
return
}
// Hotspots .
func (d *Dao) Hotspots(c context.Context) (res []*model.Hotspot, err error) {
var rows *sql.Rows
if rows, err = d.hotspotsStmt.Query(c); err != nil {
PromError("db:hotspots")
log.Error("mysql: hotspot Query() error(%+v)", err)
return
}
defer rows.Close()
for rows.Next() {
ba := &model.Hotspot{}
var ic int
var arts string
if err = rows.Scan(&ba.ID, &ba.Title, &ba.Tag, &ic, &arts); err != nil {
PromError("db:hostspot")
log.Error("mysql: hotspot Scan() error(%+v)", err)
return
}
if ic != 0 {
ba.Icon = true
}
ba.TopArticles, _ = xstr.SplitInts(arts)
res = append(res, ba)
}
err = rows.Err()
promErrorCheck(err)
return
}
// SearchArts get articles publish time after ptime
func (d *Dao) SearchArts(c context.Context, ptime int64) (res []*model.SearchArt, err error) {
var rows *sql.Rows
now := time.Now().Unix()
for ; ptime < now; ptime += _searchInterval {
if rows, err = d.searchArtsStmt.Query(c, ptime, ptime+_searchInterval); err != nil {
PromError("db:searchArts")
log.Error("mysql: search arts Query() error(%+v)", err)
return
}
defer rows.Close()
for rows.Next() {
ba := &model.SearchArt{}
var t string
if err = rows.Scan(&ba.ID, &ba.PublishTime, &t, &ba.StatsView, &ba.StatsReply); err != nil {
PromError("db:searchArts")
log.Error("mysql: search arts Scan() error(%+v)", err)
return
}
if t != "" {
ba.Tags = strings.Split(t, ",")
}
res = append(res, ba)
}
if err = rows.Err(); err != nil {
PromError("db:searchArts")
log.Error("mysql: search arts Query() error(%+v)", err)
return
}
}
return
}
// AddCheatFilter .
func (d *Dao) AddCheatFilter(c context.Context, aid int64, lv int) (err error) {
if _, err = d.addCheatStmt.Exec(c, aid, lv, lv); err != nil {
PromError("db:新增防刷过滤")
log.Error("mysql: addCheatFilter state(aid: %v, lv: %v) error(%+v)", aid, lv, err)
return
}
log.Info("mysql: addCheatFilter state(aid: %v, lv: %v)", aid, lv)
return
}
// DelCheatFilter .
func (d *Dao) DelCheatFilter(c context.Context, aid int64) (err error) {
if _, err = d.delCheatStmt.Exec(c, time.Now().Unix(), aid); err != nil {
PromError("db:删除防刷过滤")
log.Error("mysql: delCheatFilter state(aid: %v) error(%+v)", aid, err)
return
}
log.Info("mysql: delCheatFilter state(aid: %v)", aid)
return
}

View File

@@ -0,0 +1,304 @@
package dao
import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
"sync"
"time"
artmdl "go-common/app/interface/openplatform/article/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
"go-common/library/xstr"
"go-common/library/sync/errgroup"
)
// Article gets article's meta and content.
func (d *Dao) Article(c context.Context, aid int64) (res *artmdl.Article, err error) {
res = &artmdl.Article{}
if res.Meta, err = d.ArticleMeta(c, aid); err != nil {
PromError("article:获取文章meta")
return
}
if res.Meta == nil {
res = nil
return
}
if res.Content, err = d.ArticleContent(c, aid); err != nil {
PromError("article:获取文章content")
}
if res.Keywords, err = d.ArticleKeywords(c, aid); err != nil {
PromError("article:获取文章keywords")
}
res.Strong()
return
}
// ArticleContent get article content
func (d *Dao) ArticleContent(c context.Context, id int64) (res string, err error) {
contentSQL := fmt.Sprintf(_articleContentSQL, d.hit(id))
if err = d.articleDB.QueryRow(c, contentSQL, id).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:ArticleContent")
log.Error("dao.ArticleContent(%s) error(%+v)", contentSQL, err)
}
return
}
// ArticleKeywords get article keywords
func (d *Dao) ArticleKeywords(c context.Context, id int64) (res string, err error) {
keywordsSQL := fmt.Sprintf(_articleKeywordsSQL, d.hit(id))
if err = d.articleDB.QueryRow(c, keywordsSQL, id).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:ArticleKeywords")
log.Error("dao.ArticleKeywords(%s) error(%+v)", keywordsSQL, err)
}
res = strings.Replace(res, "\001", ",", -1)
return
}
// ArticleMeta get article meta
func (d *Dao) ArticleMeta(c context.Context, id int64) (res *artmdl.Meta, err error) {
var (
row *xsql.Row
imageURLs, originImageURLs string
category = &artmdl.Category{}
author = &artmdl.Author{}
t int64
ct time.Time
)
res = &artmdl.Meta{Media: &artmdl.Media{}}
row = d.articleMetaStmt.QueryRow(c, id)
if err = row.Scan(&res.ID, &category.ID, &res.Title, &res.Summary, &res.BannerURL, &res.TemplateID, &res.State, &author.Mid, &res.Reprint, &imageURLs, &t, &ct, &res.Attributes, &res.Words, &res.Dynamic, &originImageURLs, &res.Media.MediaID, &res.Media.Spoiler); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
return
}
PromError("db:ArticleMeta")
log.Error("dao.ArticleMeta.Scan error(%+v)", err)
return
}
res.PublishTime = xtime.Time(t)
res.Category = category
res.Author = author
res.Ctime = xtime.Time(ct.Unix())
res.ImageURLs = strings.Split(imageURLs, ",")
res.OriginImageURLs = strings.Split(originImageURLs, ",")
res.BannerURL = artmdl.CompleteURL(res.BannerURL)
res.ImageURLs = artmdl.CompleteURLs(res.ImageURLs)
res.OriginImageURLs = artmdl.CompleteURLs(res.OriginImageURLs)
res.Strong()
return
}
// ArticleMetas get article metats
func (d *Dao) ArticleMetas(c context.Context, aids []int64) (res map[int64]*artmdl.Meta, err error) {
var (
group, errCtx = errgroup.WithContext(c)
mutex = &sync.Mutex{}
)
if len(aids) == 0 {
return
}
res = make(map[int64]*artmdl.Meta)
keysLen := len(aids)
for i := 0; i < keysLen; i += _mysqlBulkSize {
var keys []int64
if (i + _mysqlBulkSize) > keysLen {
keys = aids[i:]
} else {
keys = aids[i : i+_mysqlBulkSize]
}
group.Go(func() (err error) {
var rows *xsql.Rows
metasSQL := fmt.Sprintf(_articlesMetaSQL, xstr.JoinInts(keys))
if rows, err = d.articleDB.Query(errCtx, metasSQL); err != nil {
PromError("db:ArticleMetas")
return
}
defer rows.Close()
for rows.Next() {
var (
imageURLs, originImageURLs string
t int64
ct time.Time
a = &artmdl.Meta{Category: &artmdl.Category{}, Author: &artmdl.Author{}, Media: &artmdl.Media{}}
)
err = rows.Scan(&a.ID, &a.Category.ID, &a.Title, &a.Summary, &a.BannerURL, &a.TemplateID, &a.State, &a.Author.Mid, &a.Reprint, &imageURLs, &t, &ct, &a.Attributes, &a.Words, &a.Dynamic, &originImageURLs, &a.Media.MediaID, &a.Media.Spoiler)
if err != nil {
return
}
a.ImageURLs = strings.Split(imageURLs, ",")
a.OriginImageURLs = strings.Split(originImageURLs, ",")
a.PublishTime = xtime.Time(t)
a.Ctime = xtime.Time(ct.Unix())
a.BannerURL = artmdl.CompleteURL(a.BannerURL)
a.ImageURLs = artmdl.CompleteURLs(a.ImageURLs)
a.OriginImageURLs = artmdl.CompleteURLs(a.OriginImageURLs)
a.Strong()
mutex.Lock()
res[a.ID] = a
mutex.Unlock()
}
err = rows.Err()
return err
})
}
if err = group.Wait(); err != nil {
PromError("db:ArticleMetas")
log.Error("dao.ArticleMetas error(%+v)", err)
return
}
if len(res) == 0 {
res = nil
}
return
}
// AllArticleMeta 所有状态/删除 的文章
func (d *Dao) AllArticleMeta(c context.Context, id int64) (res *artmdl.Meta, err error) {
var (
row *xsql.Row
imageURLs, originImageURLs string
category = &artmdl.Category{}
author = &artmdl.Author{}
t int64
ct time.Time
)
res = &artmdl.Meta{Media: &artmdl.Media{}}
row = d.allArticleMetaStmt.QueryRow(c, id)
if err = row.Scan(&res.ID, &category.ID, &res.Title, &res.Summary, &res.BannerURL, &res.TemplateID, &res.State, &author.Mid, &res.Reprint, &imageURLs, &t, &ct, &res.Attributes, &res.Words, &res.Dynamic, &originImageURLs, &res.Media.MediaID, &res.Media.Spoiler); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
return
}
PromError("db:AllArticleMeta")
log.Error("row.AllArticleMeta.Scan error(%+v)", err)
return
}
res.PublishTime = xtime.Time(t)
res.Category = category
res.Author = author
res.Ctime = xtime.Time(ct.Unix())
res.ImageURLs = strings.Split(imageURLs, ",")
res.OriginImageURLs = strings.Split(originImageURLs, ",")
res.BannerURL = artmdl.CompleteURL(res.BannerURL)
res.ImageURLs = artmdl.CompleteURLs(res.ImageURLs)
res.OriginImageURLs = artmdl.CompleteURLs(res.OriginImageURLs)
res.Strong()
return
}
// UpperArticleCount get upper article count
func (d *Dao) UpperArticleCount(c context.Context, mid int64) (res int, err error) {
row := d.articleUpperCountStmt.QueryRow(c, mid)
if err = row.Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:UpperArticleCount")
log.Error("dao.UpperArticleCount error(%+v)", err)
}
return
}
// ArticleRemainCount returns the number that user could be use to posting new articles.
func (d *Dao) ArticleRemainCount(c context.Context, mid int64) (count int, err error) {
beginTime := time.Now().Format("2006-01-02") + " 00:00:00"
if err = d.articleUpCntTodayStmt.QueryRow(c, mid, beginTime).Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:ArticleRemainCount")
log.Error("dao.ArticleRemainCount(%d,%s) error(%+v)", mid, beginTime, err)
}
return
}
// TagArticles .
func (d *Dao) TagArticles(c context.Context, tags []int64) (aids []int64, err error) {
var (
rows *xsql.Rows
query string
tmps = make(map[int64]bool)
)
query = fmt.Sprintf(_tagArticlesSQL, xstr.JoinInts(tags))
if rows, err = d.articleDB.Query(c, query); err != nil {
PromError("dao:TagArticles")
log.Error("dao.TagArticles(%s) error(%+v)", query, err)
return
}
defer rows.Close()
for rows.Next() {
var (
tid int64
oid string
logDate xtime.Time
ts []int64
aid int64
now = time.Now()
)
rows.Scan(&tid, &oid, &logDate)
if now.Sub(logDate.Time()) > time.Hour*60 {
continue
}
ids := strings.Split(oid, "")
for _, id := range ids {
if aid, err = strconv.ParseInt(id, 10, 64); err != nil {
log.Error("dao.TagArticles.ParseInt(%s) error(%+v)", id, err)
return
}
if !tmps[aid] {
aids = append(aids, aid)
tmps[aid] = true
}
ts = append(ts, aid)
}
d.AddCacheAidsByTag(c, tid, &artmdl.TagArts{Tid: tid, Aids: ts})
}
return
}
// MediaArticle .
func (d *Dao) MediaArticle(c context.Context, mediaID int64, mid int64) (id int64, err error) {
var rows *xsql.Rows
if rows, err = d.articleDB.Query(c, _mediaArticleSQL, mid, mediaID); err != nil {
log.Error("dao.MediaArticle.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
if err = rows.Scan(&id); err != nil {
log.Error("dao.MediaArticle.Scan error(%v)", err)
return
}
if id > 0 {
return
}
}
return
}
// MediaIDByID .
func (d *Dao) MediaIDByID(c context.Context, aid int64) (id int64, err error) {
row := d.articleDB.QueryRow(c, _mediaByIDSQL, aid)
if err = row.Scan(&id); err != nil {
log.Error("dao.MediaIDByID.Scan error(%v)", err)
}
return
}

View File

@@ -0,0 +1,66 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
var (
dataID = int64(175)
noDataID = int64(100000000)
)
func Test_ArticleContent(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
res, err := d.ArticleContent(context.TODO(), dataID)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
}))
Convey("no data", t, WithDao(func(d *Dao) {
res, err := d.ArticleContent(context.TODO(), noDataID)
So(err, ShouldBeNil)
So(res, ShouldBeEmpty)
}))
}
func Test_ArticleMeta(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
res, err := d.ArticleMeta(context.TODO(), dataID)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res.PublishTime, ShouldNotEqual, 0)
}))
Convey("no data", t, WithDao(func(d *Dao) {
res, err := d.ArticleMeta(context.TODO(), noDataID)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
}))
}
func Test_ArticleMetas(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
res, err := d.ArticleMetas(context.TODO(), []int64{dataID})
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
}))
Convey("no data", t, WithDao(func(d *Dao) {
res, err := d.ArticleMetas(context.TODO(), []int64{noDataID})
So(err, ShouldBeNil)
So(res, ShouldBeEmpty)
}))
}
func Test_UpperArticleCount(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
res, err := d.UpperArticleCount(context.TODO(), dataID)
So(err, ShouldBeNil)
So(res, ShouldBeGreaterThan, 0)
}))
Convey("no data", t, WithDao(func(d *Dao) {
res, err := d.UpperArticleCount(context.TODO(), _noData)
So(err, ShouldBeNil)
So(res, ShouldEqual, 0)
}))
}

View File

@@ -0,0 +1,115 @@
package dao
import (
"context"
"net/url"
"strconv"
"time"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_verifyAPI = "http://account.bilibili.co/api/internal/identify/info"
)
// Authors loads author list who are permitted to post articles.
func (d *Dao) Authors(c context.Context) (res map[int64]*artmdl.AuthorLimit, err error) {
var rows *sql.Rows
if rows, err = d.authorsStmt.Query(c); err != nil {
PromError("db:作者列表查询")
log.Error("db.authorsStmt.Query error(%+v)", err)
return
}
defer rows.Close()
res = make(map[int64]*artmdl.AuthorLimit)
for rows.Next() {
var (
mid int64
author = &artmdl.AuthorLimit{}
)
if err = rows.Scan(&mid, &author.Limit, &author.State); err != nil {
PromError("作者列表scan")
log.Error("rows.Authors.Scan error(%+v)", err)
return
}
res[mid] = author
}
err = rows.Err()
promErrorCheck(err)
return
}
// ApplyCount get today apply count
func (d *Dao) ApplyCount(c context.Context) (count int64, err error) {
var t = time.Now().Truncate(24 * time.Hour)
if err = d.applyCountStmt.QueryRow(c, t).Scan(&count); err != nil {
PromError("db:查询申请总数")
log.Error("db.ApplyCountStmt.Query(%v) error(%+v)", t, err)
}
return
}
// AddApply add new apply
func (d *Dao) AddApply(c context.Context, mid int64, content, category string) (err error) {
var t = time.Now()
if _, err = d.applyStmt.Exec(c, mid, t, content, category, t, content, category); err != nil {
PromError("db:申请作者权限")
log.Error("db.applyStmt.Query(mid: %v, t: %v, category: %v) error(%+v)", mid, t, category, err)
}
return
}
// AddAuthor add author
func (d *Dao) AddAuthor(c context.Context, mid int64) (err error) {
if _, err = d.addAuthorStmt.Exec(c, mid); err != nil {
PromError("db:增加作者权限")
log.Error("mysql: db.addAuthorStmt.Query(mid: %v) error(%+v)", mid, err)
}
return
}
// RawAuthor get author's info.
func (d *Dao) RawAuthor(c context.Context, mid int64) (res *artmdl.AuthorLimit, err error) {
res = new(artmdl.AuthorLimit)
if err = d.authorStmt.QueryRow(c, mid).Scan(&res.State, &res.Rtime, &res.Limit); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
return
}
PromError("db:RawAuthor scan")
log.Error("RawAuthor row.Scan(%d) error(%+v)", mid, err)
}
if int64(res.Rtime) < 0 {
res.Rtime = xtime.Time(0)
}
return
}
// Identify gets user verify info.
func (d *Dao) Identify(c context.Context, mid int64) (res *artmdl.Identify, err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
var resp struct {
Code int `json:"code"`
Data *artmdl.Identify `json:"data"`
}
if err = d.httpClient.Get(c, _verifyAPI, "", params, &resp); err != nil {
log.Error("d.httpClient.Get(%s) error(%+v)", _verifyAPI+"?"+params.Encode(), err)
PromError("http:获取用户实名认证信息")
return
}
if resp.Code != ecode.OK.Code() {
log.Error("d.httpClient.Get(%s) code(%d)", _verifyAPI+"?"+params.Encode(), resp.Code)
PromError("http:获取用户实名认证信息状态码异常")
err = ecode.Int(resp.Code)
return
}
res = resp.Data
return
}

View File

@@ -0,0 +1,58 @@
package dao
import (
"testing"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
. "github.com/smartystreets/goconvey/convey"
)
func Test_ApplyCount(t *testing.T) {
mid := int64(1)
pending := 0
Convey("add apply", t, func() {
err := d.AddApply(ctx(), mid, "content", "category")
So(err, ShouldBeNil)
Convey("get apply", func() {
author, err := d.RawAuthor(ctx(), mid)
So(err, ShouldBeNil)
So(author, ShouldResemble, &model.AuthorLimit{State: pending})
})
Convey("apply count", func() {
res, err := d.ApplyCount(ctx())
So(err, ShouldBeNil)
So(res, ShouldBeGreaterThan, 0)
})
Convey("add twice should be ok", func() {
err := d.AddApply(ctx(), mid, "content", "category")
So(err, ShouldBeNil)
author, err := d.RawAuthor(ctx(), mid)
So(err, ShouldBeNil)
So(author, ShouldResemble, &model.AuthorLimit{State: pending})
})
})
}
func Test_Authors(t *testing.T) {
Convey("should get authors", t, func() {
res, err := d.Authors(ctx())
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_Identity(t *testing.T) {
Convey("should return identity when no error", t, func() {
httpMock("get", _verifyAPI).Reply(200).JSON(`{"ts":1514341945,"code":0,"data":{"identify":1,"phone":0}}`)
res, err := d.Identify(ctx(), 1)
So(err, ShouldBeNil)
So(res, ShouldResemble, &model.Identify{Identify: 1, Phone: 0})
})
Convey("should return error when code != 0", t, func() {
httpMock("get", _verifyAPI).Reply(200).JSON(`{"ts":1514341945,"code":-3,"data":null}`)
_, err := d.Identify(ctx(), 1)
So(err, ShouldEqual, ecode.SignCheckErr)
})
}

View File

@@ -0,0 +1,69 @@
package dao
import (
"context"
"database/sql"
"go-common/library/log"
)
const (
_addComplaintsSQL = "INSERT INTO article_complaints(article_id,mid,type,reason,image_urls) VALUES (?,?,?,?,?)"
_complaintExistSQL = "SELECT id FROM article_complaints WHERE article_id=? AND mid=? AND state=0"
_complaintProtectSQL = "SELECT protect FROM article_complain_articles WHERE article_id=? AND deleted_time=0"
_addComplaintCountSQL = "INSERT INTO article_complain_articles(article_id,count) VALUES (?,1) ON DUPLICATE KEY UPDATE count=count+1,state=0"
_articleProtected = 1 // 0: no pretected 1: protected
)
// AddComplaint add complaint.
func (d *Dao) AddComplaint(c context.Context, aid, mid, ctype int64, reason, imageUrls string) (err error) {
if _, err = d.addComplaintStmt.Exec(c, aid, mid, ctype, reason, imageUrls); err != nil {
PromError("db:新增投诉")
log.Error("dao.addComplaintStmt.exec(%s, %v, %v, %v, %v) error(%+v)", aid, mid, ctype, reason, imageUrls, err)
}
return
}
// ComplaintExist .
func (d *Dao) ComplaintExist(c context.Context, aid, mid int64) (exist bool, err error) {
var id int
if err = d.complaintExistStmt.QueryRow(c, aid, mid).Scan(&id); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("d.complaintExistStmt.QueryRow(%d,%d) error(%+v)", aid, mid, err)
PromError("db:判断之前是否投诉过")
}
return
}
exist = true
return
}
// ComplaintProtected .
func (d *Dao) ComplaintProtected(c context.Context, aid int64) (protected bool, err error) {
var p int
if err = d.complaintProtectStmt.QueryRow(c, aid).Scan(&p); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("d.complaintProtectStmt.QueryRow(%d) error(%+v)", aid, err)
PromError("db:判断文章是否被保护")
}
return
}
if p == _articleProtected {
protected = true
}
return
}
// AddComplaintCount .
func (d *Dao) AddComplaintCount(c context.Context, aid int64) (err error) {
if _, err = d.addComplaintCountStmt.Exec(c, aid); err != nil {
log.Error("d.addComplaintCountStmt.Exec(%d) error(%+v)", aid, err)
PromError("db:增加投诉计数")
}
return
}

View File

@@ -0,0 +1,216 @@
package dao
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
artmdl "go-common/app/interface/openplatform/article/model"
xsql "go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_addArticleDraftSQL = "REPLACE INTO article_draft_%s (id,category_id,title,summary,banner_url,template_id,mid,reprint,image_urls,tags,content, dynamic_intro, origin_image_urls, list_id, media_id, spoiler) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_articleDraftSQL = "SELECT id,category_id,title,summary,banner_url,template_id,mid,reprint,image_urls,tags,content,mtime,dynamic_intro,origin_image_urls, list_id, media_id, spoiler FROM article_draft_%s WHERE id=? AND deleted_time=0"
_checkDraftSQL = "SELECT deleted_time FROM article_draft_%s WHERE id=?"
_deleteArticleDraftSQL = "UPDATE article_draft_%s SET deleted_time=? WHERE id=?"
_upperDraftsSQL = "SELECT id,category_id,title,summary,template_id,reprint,image_urls,tags,mtime,dynamic_intro,origin_image_urls, list_id FROM article_draft_%s WHERE mid=? AND deleted_time=0 " +
"ORDER BY mtime DESC LIMIT ?,?"
_countUpperDraftSQL = "SELECT COUNT(*) FROM article_draft_%s WHERE mid=? AND deleted_time=0"
)
// ArtDraft get draft by article_id
func (d *Dao) ArtDraft(c context.Context, mid, aid int64) (res *artmdl.Draft, err error) {
var (
row *xsql.Row
tags string
imageURLs, originImageURLs string
category = &artmdl.Category{}
author = &artmdl.Author{}
meta = &artmdl.Meta{Media: &artmdl.Media{}}
mtime time.Time
sqlStr = fmt.Sprintf(_articleDraftSQL, d.hit(mid))
)
res = &artmdl.Draft{Article: &artmdl.Article{}}
row = d.articleDB.QueryRow(c, sqlStr, aid)
if err = row.Scan(&meta.ID, &category.ID, &meta.Title, &meta.Summary, &meta.BannerURL, &meta.TemplateID, &author.Mid, &meta.Reprint, &imageURLs, &tags, &res.Content, &mtime, &meta.Dynamic, &originImageURLs, &res.ListID, &meta.Media.MediaID, &meta.Media.Spoiler); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
return
}
PromError("db:读取草稿")
log.Error("ArtDraft.row.Scan() error(%d,%d,%v)", mid, aid, err)
return
}
meta.Category = category
meta.Author = author
meta.Mtime = xtime.Time(mtime.Unix())
if imageURLs == "" {
meta.ImageURLs = []string{}
} else {
meta.ImageURLs = strings.Split(imageURLs, ",")
}
if originImageURLs == "" {
meta.OriginImageURLs = []string{}
} else {
meta.OriginImageURLs = strings.Split(originImageURLs, ",")
}
if tags == "" {
res.Tags = []string{}
} else {
res.Tags = strings.Split(tags, ",")
}
res.Meta = meta
return
}
// UpperDrafts batch get draft by mid.
func (d *Dao) UpperDrafts(c context.Context, mid int64, start, ps int) (res []*artmdl.Draft, err error) {
var (
rows *xsql.Rows
sqlStr = fmt.Sprintf(_upperDraftsSQL, d.hit(mid))
)
if rows, err = d.articleDB.Query(c, sqlStr, mid, start, ps); err != nil {
PromError("db:读取草稿")
log.Error("d.articleDB.Query(%d,%d,%d) error(%+v)", mid, start, ps, err)
return
}
defer rows.Close()
for rows.Next() {
var (
tags string
imageURLs, originImageURLs string
category = &artmdl.Category{}
author = &artmdl.Author{}
art = &artmdl.Draft{Article: &artmdl.Article{}}
meta = &artmdl.Meta{}
mtime time.Time
listID int64
)
if err = rows.Scan(&meta.ID, &category.ID, &meta.Title, &meta.Summary, &meta.TemplateID, &meta.Reprint, &imageURLs, &tags, &mtime, &meta.Dynamic, &originImageURLs, &listID); err != nil {
log.Error("UpperDrafts.row.Scan() error(%d,%d,%d,%v)", mid, start, ps, err)
return
}
meta.Category = category
meta.Author = author
meta.Mtime = xtime.Time(mtime.Unix())
if imageURLs == "" {
meta.ImageURLs = []string{}
} else {
meta.ImageURLs = strings.Split(imageURLs, ",")
}
if originImageURLs == "" {
meta.OriginImageURLs = []string{}
} else {
meta.OriginImageURLs = strings.Split(originImageURLs, ",")
}
if tags == "" {
art.Tags = []string{}
} else {
art.Tags = strings.Split(tags, ",")
}
art.Meta = meta
art.ListID = listID
res = append(res, art)
}
err = rows.Err()
promErrorCheck(err)
return
}
// AddArtDraft add article draft .
func (d *Dao) AddArtDraft(c context.Context, a *artmdl.Draft) (id int64, err error) {
var (
deleted bool
res sql.Result
tags = strings.Join(a.Tags, ",")
imageUrls = strings.Join(a.ImageURLs, ",")
originImageUrls = strings.Join(a.OriginImageURLs, ",")
sqlStr = fmt.Sprintf(_addArticleDraftSQL, d.hit(a.Author.Mid))
)
if a.ID > 0 {
if deleted, err = d.IsDraftDeleted(c, a.Author.Mid, a.ID); err != nil {
return
} else if deleted {
err = ecode.ArtCreationDraftDeleted
return
}
}
if res, err = d.articleDB.Exec(c, sqlStr, a.ID, a.Category.ID, a.Title, a.Summary, a.BannerURL, a.TemplateID, a.Author.Mid, a.Reprint, imageUrls, tags, a.Content, a.Dynamic, originImageUrls, a.ListID, a.Media.MediaID, a.Media.Spoiler); err != nil {
PromError("db:新增或更新草稿")
log.Error("d.articleDB.Exec(%+v) error(%+v)", a, err)
return
}
if id, err = res.LastInsertId(); err != nil {
PromError("db:获取新增草稿ID")
log.Error("res.LastInsertId() error(%+v)", err)
}
return
}
// IsDraftDeleted judges is draft has been deleted.
func (d *Dao) IsDraftDeleted(c context.Context, mid, aid int64) (deleted bool, err error) {
var (
dt int
sqlStr = fmt.Sprintf(_checkDraftSQL, d.hit(mid))
)
if err = d.articleDB.QueryRow(c, sqlStr, aid).Scan(&dt); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:判断草稿是否被删除")
log.Error("d.articleDB.QueryRow(%d,%d) error(%+v)", mid, aid, err)
return
}
if dt > 0 {
deleted = true
}
return
}
// TxDeleteArticleDraft deletes article draft via transaction.
func (d *Dao) TxDeleteArticleDraft(c context.Context, tx *xsql.Tx, mid, aid int64) (err error) {
var (
now = time.Now().Unix()
sqlStr = fmt.Sprintf(_deleteArticleDraftSQL, d.hit(mid))
)
if _, err = tx.Exec(sqlStr, now, aid); err != nil {
PromError("db:删除草稿")
log.Error("TxDeleteArticleDraft.Exec(%d,%d) error(%+v)", mid, aid, err)
}
return
}
// DelArtDraft deletes article draft.
func (d *Dao) DelArtDraft(c context.Context, mid, aid int64) (err error) {
var (
now = time.Now().Unix()
sqlStr = fmt.Sprintf(_deleteArticleDraftSQL, d.hit(mid))
)
if _, err = d.articleDB.Exec(c, sqlStr, now, aid); err != nil {
PromError("db:删除草稿")
log.Error("d.articleDB.Exec(%d,%d) error(%+v)", mid, aid, err)
}
return
}
// CountUpperDraft count upper's draft
func (d *Dao) CountUpperDraft(c context.Context, mid int64) (count int, err error) {
var sqlStr = fmt.Sprintf(_countUpperDraftSQL, d.hit(mid))
if err = d.articleDB.QueryRow(c, sqlStr, mid).Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:读取草稿计数")
log.Error("CountUpperDraft.row.Scan() error(%d,%v)", mid, err)
}
return
}

View File

@@ -0,0 +1,610 @@
package dao
import (
"context"
"database/sql"
"fmt"
"sort"
"sync"
"time"
"go-common/app/interface/openplatform/article/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/sync/errgroup"
xtime "go-common/library/time"
"go-common/library/xstr"
)
const (
_creativeCountArticlesSQL = "SELECT count(*) FROM articles WHERE mid = ? and deleted_time = 0 AND category_id in (%s)"
_creativeListsSQL = "SELECT id, image_url, name, update_time, ctime, summary, publish_time, words FROM lists WHERE deleted_time = 0 AND mid = ?"
_creativeListAddSQL = "INSERT INTO lists(name, image_url, summary, publish_time, words, mid) VALUES(?,?,?,?,?,?)"
_creativeListDelSQL = "update lists SET deleted_time = ? WHERE id = ? AND deleted_time = 0"
_creativeListUpdateSQL = "update lists SET name = ?, image_url = ?, summary = ?, words = ?, publish_time = ? where id = ? and deleted_time = 0"
_creativeListUpdateTimeSQL = "update lists SET update_time = ? where id = ? and update_time < ? and deleted_time = 0"
_creativeCategoryArticlesSQL = "SELECT id, title, publish_time, state FROM articles WHERE mid = ? AND deleted_time = 0"
_creativeListArticlesSQL = "SELECT article_id, position FROM article_lists WHERE list_id = ? AND deleted_time = 0 ORDER BY position ASC"
_creativeListsArticlesSQL = "SELECT article_id, position, list_id FROM article_lists WHERE list_id in (%s) AND deleted_time = 0"
_creativeListAddArticleSQL = "INSERT INTO article_lists(article_id, list_id, position) values(?,?,?) ON DUPLICATE KEY UPDATE deleted_time =0, position=?"
_creativeListDelArticleSQL = "UPDATE article_lists SET deleted_time = ? WHERE article_id = ? and list_id = ? and deleted_time = 0"
_creativeDelArticleListSQL = "UPDATE article_lists SET deleted_time = ? WHERE article_id = ? and deleted_time = 0"
_creativeListDelAllArticleSQL = "UPDATE article_lists SET deleted_time = ? WHERE list_id = ? and deleted_time = 0"
_creativeArticlesSQL = "SELECT id, title, state, publish_time FROM articles WHERE id in (%s) and deleted_time = 0"
_listSQL = "SELECT id, mid, image_url, name, update_time, ctime, summary, words, publish_time FROM lists WHERE deleted_time = 0 AND id = ?"
_listsSQL = "SELECT id, mid, image_url, name, update_time, ctime, summary, words, publish_time FROM lists WHERE deleted_time = 0 AND id in (%s)"
_allListsSQL = "SELECT id, mid, image_url, name, update_time, ctime, summary, words, publish_time FROM lists WHERE deleted_time = 0"
_artslistSQL = "SELECT article_id, list_id FROM article_lists WHERE article_id IN (%s) AND deleted_time = 0"
_allListsExSQL = "SELECT id, mid, image_url, name, update_time, ctime, summary, words, publish_time FROM lists WHERE deleted_time = 0 ORDER BY id DESC LIMIT ?, ?"
)
// CreativeUpLists get article lists
func (d *Dao) CreativeUpLists(c context.Context, mid int64) (res []*model.List, err error) {
rows, err := d.creativeListsStmt.Query(c, mid)
if err != nil {
PromError("db:up主文集")
log.Errorv(c, log.KV("log", "CreativeUplists"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
t, ctime time.Time
r = &model.List{Mid: mid}
pt int64
)
if err = rows.Scan(&r.ID, &r.ImageURL, &r.Name, &t, &ctime, &r.Summary, &pt, &r.Words); err != nil {
PromError("db:up主文集scan")
log.Error("dao.CreativeUpLists.rows.Scan error(%+v)", err)
return
}
if t.Unix() > 0 {
r.UpdateTime = xtime.Time(t.Unix())
}
r.Ctime = xtime.Time(ctime.Unix())
r.PublishTime = xtime.Time(pt)
res = append(res, r)
}
err = rows.Err()
promErrorCheck(err)
return
}
// RawUpLists .
func (d *Dao) RawUpLists(c context.Context, mid int64) (res []int64, err error) {
lists, err := d.CreativeUpLists(c, mid)
for _, list := range lists {
res = append(res, list.ID)
}
return
}
// CreativeListUpdate update list
func (d *Dao) CreativeListUpdate(c context.Context, id int64, name, imageURL, summary string, publishTime xtime.Time, words int64) (err error) {
if _, err := d.creativeListUpdateStmt.Exec(c, name, imageURL, summary, words, int64(publishTime), id); err != nil {
PromError("db:修改文集")
log.Errorv(c, log.KV("dao.CreativeListUpdate.Exec", id), log.KV("name", name), log.KV("image_url", imageURL), log.KV("error", err), log.KV("summary", summary))
}
return
}
// CreativeListDelAllArticles del list
func (d *Dao) CreativeListDelAllArticles(c context.Context, id int64) (err error) {
if _, err := d.creativeListDelAllArticleStmt.Exec(c, time.Now(), id); err != nil {
PromError("db:删除文集下的文章")
log.Errorv(c, log.KV("dao.CreativeListDelAllArticles.Exec", id), log.KV("error", err))
}
return
}
// CreativeListDel del list
func (d *Dao) CreativeListDel(c context.Context, id int64) (err error) {
if _, err := d.creativeListDelStmt.Exec(c, time.Now().Unix(), id); err != nil {
PromError("db:删除文集")
log.Errorv(c, log.KV("dao.CreativeListDel.Exec", id), log.KV("error", err))
}
return
}
// CreativeListUpdateTime update list time
func (d *Dao) CreativeListUpdateTime(c context.Context, id int64, t time.Time) (err error) {
if _, err := d.creativeListUpdateTimeStmt.Exec(c, t, id, t); err != nil {
PromError("db:修改文集更新时间")
log.Errorv(c, log.KV("dao.CreativeListUpdateTime.Exec", id), log.KV("time", t), log.KV("error", err))
}
return
}
// CreativeListAdd add list
func (d *Dao) CreativeListAdd(c context.Context, mid int64, name, imageURL, summary string, publishTime xtime.Time, words int64) (res int64, err error) {
r, err := d.creativeListAddStmt.Exec(c, name, imageURL, summary, int64(publishTime), words, mid)
if err != nil {
PromError("db:增加文集")
log.Errorv(c, log.KV("dao.CreativeListAdd.Exec", mid), log.KV("name", name), log.KV("image_url", imageURL), log.KV("error", err), log.KV(summary, summary))
return
}
if res, err = r.LastInsertId(); err != nil {
PromError("db:增加文集ID")
log.Errorv(c, log.KV("log", "res.LastInsertId"), log.KV("error", err))
}
return
}
// CreativeCountArticles novel count
func (d *Dao) CreativeCountArticles(c context.Context, mid int64, cids []int64) (res int64, err error) {
s := fmt.Sprintf(_creativeCountArticlesSQL, xstr.JoinInts(cids))
if err = d.articleDB.QueryRow(c, s, mid).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:小说计数")
log.Errorv(c, log.KV("log", "dao.CreativeCountArticles"), log.KV("error", err))
}
return
}
// CreativeCategoryArticles can add articles
func (d *Dao) CreativeCategoryArticles(c context.Context, mid int64) (res []*model.ListArtMeta, err error) {
rows, err := d.articleDB.Query(c, _creativeCategoryArticlesSQL, mid)
if err != nil {
PromError("db:up主可被加入文集的文章列表")
log.Errorv(c, log.KV("log", "CreativeCategoryArticles"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
t int64
m = &model.ListArtMeta{}
)
if err = rows.Scan(&m.ID, &m.Title, &t, &m.State); err != nil {
PromError("db:up主可被加入文集的文章列表scan")
log.Errorv(c, log.KV("log", "CreativeCategoryArticles"), log.KV("error", err))
return
}
m.PublishTime = xtime.Time(t)
res = append(res, m)
}
err = rows.Err()
promErrorCheck(err)
return
}
// CreativeListArticles .
func (d *Dao) CreativeListArticles(c context.Context, listID int64) (res []*model.ListArtMeta, err error) {
rows, err := d.creativeListArticlesStmt.Query(c, listID)
if err != nil {
PromError("db:文集的文章列表")
log.Errorv(c, log.KV("log", "CreativeListArticles"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
r = &model.ListArtMeta{}
)
if err = rows.Scan(&r.ID, &r.Position); err != nil {
PromError("db:文集的文章列表scan")
log.Errorv(c, log.KV("log", "CreativeListArticles"), log.KV("error", err))
return
}
res = append(res, r)
}
err = rows.Err()
promErrorCheck(err)
return
}
// CreativeListsArticles .
func (d *Dao) CreativeListsArticles(c context.Context, listIDs []int64) (res map[int64][]*model.ListArtMeta, err error) {
if len(listIDs) == 0 {
return
}
s := fmt.Sprintf(_creativeListsArticlesSQL, xstr.JoinInts(listIDs))
rows, err := d.articleDB.Query(c, s)
if err != nil {
PromError("db:多个文集的文章列表")
log.Errorv(c, log.KV("log", "CreativeListsArticles"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
r = &model.ListArtMeta{}
lid int64
)
if err = rows.Scan(&r.ID, &r.Position, &lid); err != nil {
PromError("db:多个文集的文章列表scan")
log.Errorv(c, log.KV("log", "CreativeListsArticles"), log.KV("error", err))
return
}
if res == nil {
res = make(map[int64][]*model.ListArtMeta)
}
res[lid] = append(res[lid], r)
}
for lid, arts := range res {
sort.Slice(arts, func(i, j int) bool { return arts[i].Position < arts[j].Position })
res[lid] = arts
}
err = rows.Err()
promErrorCheck(err)
return
}
// CreativeArticles get up all state articles
func (d *Dao) CreativeArticles(c context.Context, aids []int64) (res map[int64]*model.ListArtMeta, err error) {
var (
group, errCtx = errgroup.WithContext(c)
mutex = &sync.Mutex{}
)
if len(aids) == 0 {
return
}
res = make(map[int64]*model.ListArtMeta)
keysLen := len(aids)
for i := 0; i < keysLen; i += _mysqlBulkSize {
var keys []int64
if (i + _mysqlBulkSize) > keysLen {
keys = aids[i:]
} else {
keys = aids[i : i+_mysqlBulkSize]
}
group.Go(func() (err error) {
var rows *xsql.Rows
metasSQL := fmt.Sprintf(_creativeArticlesSQL, xstr.JoinInts(keys))
if rows, err = d.articleDB.Query(errCtx, metasSQL); err != nil {
PromError("db:CreativeArticles")
log.Errorv(c, log.KV("log", "CreativeArticles"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
t int64
a = &model.ListArtMeta{}
)
err = rows.Scan(&a.ID, &a.Title, &a.State, &t)
if err != nil {
return
}
a.PublishTime = xtime.Time(t)
mutex.Lock()
res[a.ID] = a
mutex.Unlock()
}
err = rows.Err()
return err
})
}
if err = group.Wait(); err != nil {
PromError("db:CreativeArticles")
log.Errorv(c, log.KV("error", err))
return
}
if len(res) == 0 {
res = nil
}
return
}
// RawList get list from db
func (d *Dao) RawList(c context.Context, id int64) (res *model.List, err error) {
var (
t, ctime time.Time
pt int64
)
res = &model.List{ID: id}
if err = d.listStmt.QueryRow(c, id).Scan(&res.ID, &res.Mid, &res.ImageURL, &res.Name, &t, &ctime, &res.Summary, &res.Words, &pt); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
return
}
PromError("db文集")
log.Errorv(c, log.KV("log", err))
}
if t.Unix() > 0 {
res.UpdateTime = xtime.Time(t.Unix())
}
res.Ctime = xtime.Time(ctime.Unix())
res.PublishTime = xtime.Time(pt)
return
}
// TxAddListArticle tx add list article
func (d *Dao) TxAddListArticle(c context.Context, tx *xsql.Tx, listID int64, aid int64, position int) (err error) {
if _, err = tx.Exec(_creativeListAddArticleSQL, aid, listID, position, position); err != nil {
PromError("db:新增文集文章tx")
log.Error("tx.Exec() error(%+v)", err)
return
}
return
}
// TxDelListArticle tx del list article
func (d *Dao) TxDelListArticle(c context.Context, tx *xsql.Tx, listID int64, aid int64) (err error) {
t := time.Now().Unix()
if _, err = tx.Exec(_creativeListDelArticleSQL, t, aid, listID); err != nil {
PromError("db:删除文集文章tx")
log.Error("tx.Exec() error(%+v)", err)
return
}
return
}
// TxDelArticleList .
func (d *Dao) TxDelArticleList(tx *xsql.Tx, aid int64) (err error) {
t := time.Now().Unix()
if _, err = tx.Exec(_creativeDelArticleListSQL, t, aid); err != nil {
PromError("db:tx删除文集文章")
log.Error("tx.Exec() error(%+v)", err)
return
}
return
}
// RawLists get lists from db
func (d *Dao) RawLists(c context.Context, ids []int64) (res map[int64]*model.List, err error) {
s := fmt.Sprintf(_listsSQL, xstr.JoinInts(ids))
rows, err := d.articleDB.Query(c, s)
if err != nil {
PromError("db:文集列表")
log.Errorv(c, log.KV("log", "Lists"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
t, ctime time.Time
r = &model.List{}
pt int64
)
if err = rows.Scan(&r.ID, &r.Mid, &r.ImageURL, &r.Name, &t, &ctime, &r.Summary, &r.Words, &pt); err != nil {
PromError("db:文集列表scan")
log.Error("dao.Lists.rows.Scan error(%+v)", err)
return
}
if t.Unix() > 0 {
r.UpdateTime = xtime.Time(t.Unix())
}
r.Ctime = xtime.Time(ctime.Unix())
r.PublishTime = xtime.Time(pt)
if res == nil {
res = make(map[int64]*model.List)
}
res[r.ID] = r
}
err = rows.Err()
promErrorCheck(err)
return
}
// RawAllLists get lists from db
func (d *Dao) RawAllLists(c context.Context) (res []*model.List, err error) {
rows, err := d.allListStmt.Query(c)
if err != nil {
PromError("db:全部文集列表")
log.Errorv(c, log.KV("log", "AllLists"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
t, ctime time.Time
r = &model.List{}
pt int64
)
if err = rows.Scan(&r.ID, &r.Mid, &r.ImageURL, &r.Name, &t, &ctime, &r.Summary, &r.Words, &pt); err != nil {
PromError("db:全部文集列表scan")
log.Error("dao.AllLists.rows.Scan error(%+v)", err)
return
}
if t.Unix() > 0 {
r.UpdateTime = xtime.Time(t.Unix())
}
r.Ctime = xtime.Time(ctime.Unix())
r.PublishTime = xtime.Time(pt)
res = append(res, r)
}
err = rows.Err()
promErrorCheck(err)
return
}
// RawAllListsEx get lists from db
func (d *Dao) RawAllListsEx(c context.Context, start int, size int) (res []*model.List, err error) {
rows, err := d.articleDB.Query(c, _allListsExSQL, start, size)
if err != nil {
PromError("db:全部文集列表")
log.Errorv(c, log.KV("log", "AllLists"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
t, ctime time.Time
r = &model.List{}
pt int64
)
if err = rows.Scan(&r.ID, &r.Mid, &r.ImageURL, &r.Name, &t, &ctime, &r.Summary, &r.Words, &pt); err != nil {
PromError("db:全部文集列表scan")
log.Error("dao.AllLists.rows.Scan error(%+v)", err)
return
}
if t.Unix() > 0 {
r.UpdateTime = xtime.Time(t.Unix())
}
r.Ctime = xtime.Time(ctime.Unix())
r.PublishTime = xtime.Time(pt)
res = append(res, r)
}
err = rows.Err()
promErrorCheck(err)
return
}
// RawArtsListID get articles list from db
func (d *Dao) RawArtsListID(c context.Context, aids []int64) (res map[int64]int64, err error) {
s := fmt.Sprintf(_artslistSQL, xstr.JoinInts(aids))
rows, err := d.articleDB.Query(c, s)
if err != nil {
PromError("db:文章所属文集")
log.Errorv(c, log.KV("log", "ArtsList"), log.KV("error", err))
return
}
defer rows.Close()
for rows.Next() {
var (
aid, listID int64
)
if err = rows.Scan(&aid, &listID); err != nil {
PromError("db:文章所属文集scan")
log.Error("dao.ArtsList.rows.Scan error(%+v)", err)
return
}
if res == nil {
res = make(map[int64]int64)
}
res[aid] = listID
}
err = rows.Err()
promErrorCheck(err)
return
}
// AddListArticle add list article
func (d *Dao) AddListArticle(c context.Context, listID int64, aid int64, position int) (err error) {
if _, err = d.creativeListAddArticleStmt.Exec(c, aid, listID, position, position); err != nil {
PromError("db:新增文集文章")
log.Error("d.creativeListAddArticleStmt(list: %v, aid: %v, position: %v) error(%+v)", listID, aid, position, err)
}
return
}
// DelListArticle delete list article
func (d *Dao) DelListArticle(c context.Context, listID int64, aid int64) (err error) {
if _, err = d.creativeListDelArticleStmt.Exec(c, time.Now().Unix(), aid, listID); err != nil {
PromError("db:删除文集文章")
log.Error("d.DelListArticle(list: %v, aid: %v) error(%+v)", listID, aid, err)
}
return
}
// RawListArts .
func (d *Dao) RawListArts(c context.Context, listID int64) (res []*model.ListArtMeta, err error) {
if listID <= 0 {
return
}
arts, err := d.CreativeListArticles(c, listID)
if err != nil {
return
}
var ids []int64
for _, art := range arts {
ids = append(ids, art.ID)
}
metas, err := d.ArticleMetas(c, ids)
if err != nil {
return
}
for _, id := range ids {
if metas[id] != nil {
res = append(res, &model.ListArtMeta{
ID: id,
Title: metas[id].Title,
PublishTime: metas[id].PublishTime,
Words: metas[id].Words,
ImageURLs: metas[id].ImageURLs,
Category: metas[id].Category,
Categories: metas[id].Categories,
Summary: metas[id].Summary,
})
}
}
return
}
// RawListsArts .
func (d *Dao) RawListsArts(c context.Context, ids []int64) (res map[int64][]*model.ListArtMeta, err error) {
if len(ids) == 0 {
return
}
for _, id := range ids {
lists, err := d.RawListArts(c, id)
if err != nil {
return nil, err
}
if res == nil {
res = make(map[int64][]*model.ListArtMeta)
}
res[id] = lists
}
return
}
// ArtsList get article's read list
func (d *Dao) ArtsList(c context.Context, aids []int64) (res map[int64]*model.List, err error) {
if len(aids) == 0 {
return
}
arts, err := d.ArtsListID(c, aids)
if err != nil {
return
}
listsMap := make(map[int64]bool)
for _, list := range arts {
listsMap[list] = true
}
var lids []int64
for l := range listsMap {
lids = append(lids, l)
}
lists, err := d.Lists(c, lids)
if err != nil {
return
}
res = make(map[int64]*model.List)
for aid, lid := range arts {
if lists[lid] != nil {
res[aid] = lists[lid]
}
}
return
}
// ArtList article list
func (d *Dao) ArtList(c context.Context, aid int64) (res *model.List, err error) {
if aid <= 0 {
return
}
lists, err := d.ArtsList(c, []int64{aid})
res = lists[aid]
return
}
// RawListReadCount .
func (d *Dao) RawListReadCount(c context.Context, id int64) (res int64, err error) {
metas, err := d.RawListArts(c, id)
if err != nil {
return
}
var ids []int64
for _, meta := range metas {
if meta.IsNormal() {
ids = append(ids, meta.ID)
}
}
// get stats
stats, err := d.ArticlesStats(c, ids)
if err != nil {
return
}
for _, aid := range ids {
if stats[aid] != nil {
res += stats[aid].View
}
}
return
}

View File

@@ -0,0 +1,148 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/interface/openplatform/article/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_List(t *testing.T) {
c := context.TODO()
list := &model.List{Name: "name", Mid: 100}
Convey("add list", t, func() {
id, err := d.CreativeListAdd(c, list.Mid, list.Name, "", "summary", xtime.Time(200), 200)
So(err, ShouldBeNil)
So(id, ShouldBeGreaterThan, 0)
Convey("get list", func() {
res, err := d.RawList(c, id)
So(err, ShouldBeNil)
res.Ctime = 0
So(res, ShouldResemble, &model.List{Name: "name", Mid: 100, ID: id, Summary: "summary", PublishTime: xtime.Time(200), Words: 200})
})
Convey("update time", func() {
t := time.Now()
err := d.CreativeListUpdateTime(c, id, t)
So(err, ShouldBeNil)
Convey("get list", func() {
res, err := d.RawList(c, id)
So(err, ShouldBeNil)
res.Ctime = 0
So(res, ShouldResemble, &model.List{Name: "name", Mid: 100, ID: id, UpdateTime: xtime.Time(t.Unix()), PublishTime: xtime.Time(200), Words: 200, Summary: "summary"})
})
})
Convey("update name", func() {
err := d.CreativeListUpdate(c, id, "new name", "", "summary", xtime.Time(300), 300)
So(err, ShouldBeNil)
Convey("get list", func() {
res, err := d.RawList(c, id)
So(err, ShouldBeNil)
res.Ctime = 0
So(res, ShouldResemble, &model.List{Name: "new name", Mid: 100, ID: id, Summary: "summary", Words: 300, PublishTime: xtime.Time(300)})
})
})
Convey("up list", func() {
res, err := d.CreativeUpLists(c, list.Mid)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
Convey("del", func() {
err := d.CreativeListDel(c, id)
So(err, ShouldBeNil)
Convey("get list", func() {
res, err := d.RawList(c, id)
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
})
Convey("del all articles", func() {
err := d.CreativeListDelAllArticles(c, id)
So(err, ShouldBeNil)
})
})
}
func Test_CreativeListArticlesCount(t *testing.T) {
c := context.TODO()
Convey("get count", t, func() {
res, err := d.CreativeCountArticles(c, 88888929, []int64{25, 38})
So(err, ShouldBeNil)
So(res, ShouldBeGreaterThan, 0)
})
}
func Test_CreativeListArticles(t *testing.T) {
c := context.TODO()
Convey("get data", t, func() {
res, err := d.CreativeListArticles(c, 8)
So(err, ShouldBeNil)
So(len(res), ShouldBeGreaterThan, 0)
})
}
func Test_CreativeListsArticles(t *testing.T) {
c := context.TODO()
Convey("get data", t, func() {
res, err := d.CreativeListsArticles(c, []int64{8})
So(err, ShouldBeNil)
So(len(res[8]), ShouldBeGreaterThan, 0)
})
}
func Test_CreativeCategoryArticles(t *testing.T) {
c := context.TODO()
Convey("get count", t, func() {
res, err := d.CreativeCategoryArticles(c, 88888929)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_passedListArts(t *testing.T) {
Convey("get data", t, func() {
res, err := d.RawListArts(context.TODO(), 1)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_CpListArts(t *testing.T) {
Convey("get data", t, func() {
res, err := d.ListArts(context.TODO(), 8)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
Convey("null data", t, func() {
res, err := d.ListArts(context.TODO(), 999999999)
So(err, ShouldBeNil)
So(res, ShouldBeEmpty)
})
}
func Test_ArtsList(t *testing.T) {
Convey("get data", t, func() {
res, err := d.ArtsList(context.TODO(), []int64{821})
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
Convey("list not exist", t, func() {
res, err := d.ArtsList(context.TODO(), []int64{99999})
So(err, ShouldBeNil)
So(res, ShouldBeEmpty)
})
Convey("list blank", t, func() {
res, err := d.ArtsList(context.TODO(), []int64{})
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
}
func Test_AllArtsList(t *testing.T) {
Convey("get data", t, func() {
res, err := d.RawAllLists(context.TODO())
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,87 @@
package dao
import (
"context"
"time"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/database/sql"
"go-common/library/log"
)
// RecommendByCategory find recommend by category
func (d *Dao) RecommendByCategory(c context.Context, categoryID int64) (res []*artmdl.Recommend, err error) {
ts := time.Now().Unix()
rows, err := d.recommendCategoryStmt.Query(c, ts, ts, categoryID)
if err != nil {
PromError("db:推荐列表")
log.Error("dao.recommendCategoryStmt.Query error(%+v)", err)
return
}
defer rows.Close()
for rows.Next() {
var (
r = &artmdl.Recommend{Rec: true}
)
if err = rows.Scan(&r.ArticleID, &r.RecImageURL, &r.RecFlag, &r.Position, &r.EndTime, &r.RecImageStartTime, &r.RecImageEndTime); err != nil {
PromError("db:推荐列表scan")
log.Error("dao.RecommendByCategory.rows.Scan error(%+v)", err)
return
}
r.RecImageURL = artmdl.CompleteURL(r.RecImageURL)
res = append(res, r)
}
err = rows.Err()
promErrorCheck(err)
return
}
// DelRecommend delete recommend
func (d *Dao) DelRecommend(c context.Context, aid int64) (err error) {
if _, err := d.delRecommendStmt.Exec(c, time.Now().Unix(), aid); err != nil {
PromError("db:删除推荐")
log.Error("dao.delRecommendStmt.Exec(%v) error(%+v)", aid, err)
}
return
}
// AllRecommends .
func (d *Dao) AllRecommends(c context.Context, t time.Time, pn, ps int) (res []int64, err error) {
ts := t.Unix()
offset := (pn - 1) * ps
rows, err := d.allRecommendStmt.Query(c, ts, ts, offset, ps)
if err != nil {
PromError("db:全部推荐列表")
log.Error("dao.AllRecommends(pn: %v ps: %v)error(%+v)", pn, ps, err)
return
}
defer rows.Close()
for rows.Next() {
var (
aid int64
)
if err = rows.Scan(&aid); err != nil {
PromError("db:全部推荐列表")
log.Error("dao.AllRecommends.rows.Scan error(%+v)", err)
return
}
res = append(res, aid)
}
err = rows.Err()
promErrorCheck(err)
return
}
// AllRecommendCount .
func (d *Dao) AllRecommendCount(c context.Context, t time.Time) (res int64, err error) {
ts := t.Unix()
if err = d.allRecommendCountStmt.QueryRow(c, ts, ts).Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
return
}
PromError("db:推荐列表计数")
log.Error("dao.AllRecommendCount() error(%+v)", err)
}
return
}

View File

@@ -0,0 +1,38 @@
package dao
import (
"context"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_RecommendByCategory(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
res, err := d.RecommendByCategory(context.TODO(), 0)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
}))
Convey("no data", t, WithMysql(func(d *Dao) {
res, err := d.RecommendByCategory(context.TODO(), 1000)
So(err, ShouldBeNil)
So(res, ShouldBeEmpty)
}))
}
func Test_AllRecommendCount(t *testing.T) {
Convey("get data", t, func() {
res, err := d.AllRecommendCount(context.TODO(), time.Now())
So(err, ShouldBeNil)
So(res, ShouldBeGreaterThan, 0)
})
}
func Test_AllRecommends(t *testing.T) {
Convey("get data", t, func() {
res, err := d.AllRecommends(context.TODO(), time.Now(), 1, 5)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,86 @@
package dao
import (
"context"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Categories(t *testing.T) {
Convey("should get data", t, func() {
res, err := d.Categories(context.TODO())
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_ArticlesStats(t *testing.T) {
Convey("get data", t, func() {
res, err := d.ArticlesStats(context.TODO(), []int64{1})
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
Convey("no data", t, func() {
res, err := d.ArticlesStats(context.TODO(), []int64{100000})
So(err, ShouldBeNil)
So(res, ShouldBeNil)
})
}
func Test_AddComplaint(t *testing.T) {
Convey("add data", t, func() {
err := d.AddComplaint(context.TODO(), 1, 2, 3, "reason", "http://1.ipg")
So(err, ShouldBeNil)
})
}
func Test_Notices(t *testing.T) {
Convey("get data", t, func() {
t := time.Unix(1513322993, 0)
res, err := d.Notices(context.TODO(), t)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_NoticeState(t *testing.T) {
Convey("add data", t, func() {
mid := int64(100)
state := int64(1)
err := d.UpdateNoticeState(context.TODO(), mid, state)
So(err, ShouldBeNil)
Convey("get data", func() {
res, err := d.NoticeState(context.TODO(), mid)
So(err, ShouldBeNil)
So(res, ShouldEqual, state)
})
})
}
func Test_Hotspot(t *testing.T) {
Convey("should get data", t, func() {
res, err := d.Hotspots(context.TODO())
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_SearchArts(t *testing.T) {
Convey("should get data", t, func() {
_searchInterval = 24 * 3600 * 365
res, err := d.SearchArts(context.TODO(), 0)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func Test_CheatFilter(t *testing.T) {
Convey("add data", t, func() {
err := d.AddCheatFilter(context.TODO(), 100, 2)
So(err, ShouldBeNil)
err = d.DelCheatFilter(context.TODO(), 100)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,70 @@
package dao
import (
"context"
"fmt"
"go-common/app/interface/openplatform/article/model"
"go-common/library/log"
"go-common/library/xstr"
)
// UpperPassed upper passed articles
func (d *Dao) UpperPassed(c context.Context, mid int64) (aids [][2]int64, err error) {
rows, err := d.upPassedStmt.Query(c, mid)
if err != nil {
PromError("db:up文章列表")
log.Error("getUpPasStmt.Query(%d) error(%+v)", mid, err)
return
}
defer rows.Close()
for rows.Next() {
var (
aid, ptime int64
attributes int32
)
if err = rows.Scan(&aid, &ptime, &attributes); err != nil {
log.Error("rows.Scan error(%+v)", err)
return
}
if !model.NoDistributeAttr(attributes) {
aids = append(aids, [2]int64{aid, ptime})
}
}
err = rows.Err()
promErrorCheck(err)
return
}
// UppersPassed uppers passed articles
func (d *Dao) UppersPassed(c context.Context, mids []int64) (aidm map[int64][][2]int64, err error) {
rows, err := d.articleDB.Query(c, fmt.Sprintf(_uppersPassedSQL, xstr.JoinInts(mids)))
if err != nil {
PromError("db:批量查询up文章列表")
log.Error("UpsPassed error(%+v)", err)
return
}
defer rows.Close()
aidm = make(map[int64][][2]int64, len(mids))
for rows.Next() {
var (
aid, mid, ptime int64
attributes int32
)
if err = rows.Scan(&aid, &mid, &ptime, &attributes); err != nil {
log.Error("rows.Scan error(%+v)", err)
return
}
if !model.NoDistributeAttr(attributes) {
aidm[mid] = append(aidm[mid], [2]int64{aid, ptime})
}
}
for _, mid := range mids {
if aidm[mid] == nil {
aidm[mid] = [][2]int64{}
}
}
err = rows.Err()
promErrorCheck(err)
return
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_UpperPassed(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
aids, err := d.UpperPassed(context.TODO(), dataMID)
So(err, ShouldBeNil)
So(aids, ShouldNotBeEmpty)
}))
Convey("no data", t, WithMysql(func(d *Dao) {
aids, err := d.UpperPassed(context.TODO(), noDataMID)
So(err, ShouldBeNil)
So(aids, ShouldBeEmpty)
}))
}
func Test_UppersPassed(t *testing.T) {
Convey("get data", t, WithMysql(func(d *Dao) {
arts, err := d.UppersPassed(context.TODO(), []int64{dataMID})
So(err, ShouldBeNil)
So(arts, ShouldNotBeEmpty)
}))
Convey("no data", t, WithMysql(func(d *Dao) {
arts, err := d.UppersPassed(context.TODO(), []int64{noDataMID})
So(err, ShouldBeNil)
So(arts[noDataMID], ShouldBeEmpty)
}))
}

View File

@@ -0,0 +1,53 @@
package dao
import (
"context"
"net/url"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
)
var (
_monthURL = "/data/rank/article/all-30.json"
_weekURL = "/data/rank/article/all-7.json"
_yesterDayURL = "/data/rank/article/all-1.json"
_beforeYesterDayURL = "/data/rank/article/all-2.json"
)
// Rank get rank from bigdata
func (d *Dao) Rank(c context.Context, cid int64, ip string) (res model.RankResp, err error) {
var addr string
switch cid {
case model.RankMonth:
addr = _monthURL
case model.RankWeek:
addr = _weekURL
case model.RankYesterday:
addr = _yesterDayURL
case model.RankBeforeYesterday:
addr = _beforeYesterDayURL
default:
err = ecode.RequestErr
return
}
params := url.Values{}
var resp struct {
Code int `json:"code"`
model.RankResp
}
if err = d.httpClient.Get(c, d.c.Article.RankHost+addr, ip, params, &resp); err != nil {
PromError("rank:rank接口")
log.Error("d.client.Get(%s) error(%+v)", addr+"?"+params.Encode(), err)
return
}
if resp.Code != ecode.OK.Code() {
PromError("rank:rank接口")
log.Error("url(%s) res code(%d) or res.result(%+v)", addr+"?"+params.Encode(), resp.Code, resp)
err = ecode.Int(resp.Code)
return
}
res = resp.RankResp
return
}

View File

@@ -0,0 +1,20 @@
package dao
import (
"context"
"testing"
artmdl "go-common/app/interface/openplatform/article/model"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Rank(t *testing.T) {
data := `{"code":0,"source_date":"2018-01-02","list":[{"aid":1,"mid":137952,"score":565918,"view":176708,"reply":2108,"favorites":1517,"coin":6816,"likes":10454},{"aid":2,"mid":144900177,"score":300536,"view":652823,"reply":2661,"favorites":10268,"coin":470,"likes":4130},{"aid":3,"mid":32708462,"score":241845,"view":485737,"reply":969,"favorites":7347,"coin":1290,"likes":5542},{"aid":4,"mid":124799,"score":188953,"view":46594,"reply":595,"favorites":797,"coin":1771,"likes":6268}],"num":4,"note":"统计7日内新投稿的数据综合得分"}`
Convey("get data", t, func() {
httpMock("GET", d.c.Article.RankHost+"/data/rank/article/all-7.json").Reply(200).JSON(data)
ranks, err := d.Rank(context.TODO(), artmdl.RankWeek, "")
So(err, ShouldBeNil)
So(ranks, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,484 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"strconv"
"go-common/app/interface/openplatform/article/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_prefixUpper = "art_u_%d" // upper's article list
_prefixSorted = "art_sort_%d_%d" // sorted aids sort_category_field
_prefixRank = "art_ranks_%d" // ranks by cid
_prefixMaxLike = "art_mlt_%d" // like message number
_readPingSet = "art:readping" // reading start set
_prefixReadPing = "art:readping:%s:%d" // reading during on some device for some article
_blank = int64(-1)
)
func upperKey(mid int64) string {
return fmt.Sprintf(_prefixUpper, mid)
}
func sortedKey(categoryID int64, field int) string {
return fmt.Sprintf(_prefixSorted, categoryID, field)
}
func rankKey(cid int64) string {
return fmt.Sprintf(_prefixRank, cid)
}
func hotspotKey(typ int8, id int64) string {
return fmt.Sprintf("art_hotspot%d_%d", typ, id)
}
func authorCategoriesKey(mid int64) string {
return fmt.Sprintf("author:categories:%d", mid)
}
func recommendsAuthorsKey(category int64) string {
return fmt.Sprintf("recommends:authors:%d", category)
}
func readPingSetKey() string {
return _readPingSet
}
func readPingKey(buvid string, aid int64) string {
return fmt.Sprintf(_prefixReadPing, buvid, aid)
}
// pingRedis ping redis.
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
if _, err = conn.Do("SET", "PING", "PONG"); err != nil {
PromError("redis: ping remote")
log.Error("remote redis: conn.Do(SET,PING,PONG) error(%+v)", err)
}
conn.Close()
return
}
// ExpireUpperCache expire the upper key.
func (d *Dao) ExpireUpperCache(c context.Context, mid int64) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", upperKey(mid), d.redisUpperExpire)); err != nil {
PromError("redis:up主设定过期")
log.Error("conn.Send(EXPIRE, %s) error(%+v)", upperKey(mid), err)
}
return
}
// ExpireUppersCache expire the upper key.
func (d *Dao) ExpireUppersCache(c context.Context, mids []int64) (res map[int64]bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
res = make(map[int64]bool, len(mids))
for _, mid := range mids {
if err = conn.Send("EXPIRE", upperKey(mid), d.redisUpperExpire); err != nil {
PromError("redis:up主设定过期")
log.Error("conn.Send(EXPIRE, %s) error(%+v)", upperKey(mid), err)
return
}
}
if err = conn.Flush(); err != nil {
PromError("redis:up主flush")
log.Error("conn.Flush error(%+v)", err)
return
}
var ok bool
for _, mid := range mids {
if ok, err = redis.Bool(conn.Receive()); err != nil {
PromError("redis:up主receive")
log.Error("conn.Receive() error(%+v)", err)
return
}
res[mid] = ok
}
return
}
// UppersCaches batch get new articles of uppers by cache.
func (d *Dao) UppersCaches(c context.Context, mids []int64, start, end int) (res map[int64][]int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
res = make(map[int64][]int64, len(mids))
for _, mid := range mids {
if err = conn.Send("ZREVRANGE", upperKey(mid), start, end); err != nil {
PromError("redis:获取up主")
log.Error("conn.Send(%s) error(%+v)", upperKey(mid), err)
return
}
}
if err = conn.Flush(); err != nil {
PromError("redis:获取up主flush")
log.Error("conn.Flush error(%+v)", err)
return
}
for _, mid := range mids {
aids, err := redis.Int64s(conn.Receive())
if err != nil {
PromError("redis:获取up主receive")
log.Error("conn.Send(ZREVRANGE, %d) error(%+v)", mid, err)
}
l := len(aids)
if l == 0 {
continue
}
if aids[l-1] == _blank {
aids = aids[:l-1]
}
res[mid] = aids
}
cachedCount.Add("up", int64(len(res)))
return
}
// AddUpperCache adds passed article of upper.
func (d *Dao) AddUpperCache(c context.Context, mid, aid int64, ptime int64) (err error) {
art := map[int64][][2]int64{mid: [][2]int64{[2]int64{aid, ptime}}}
err = d.AddUpperCaches(c, art)
return
}
// AddUpperCaches batch add passed article of upper.
func (d *Dao) AddUpperCaches(c context.Context, idsm map[int64][][2]int64) (err error) {
var (
mid, aid, ptime int64
arts [][2]int64
conn = d.redis.Get(c)
count int
)
defer conn.Close()
for mid, arts = range idsm {
key := upperKey(mid)
if len(arts) == 0 {
arts = [][2]int64{[2]int64{_blank, _blank}}
}
for _, art := range arts {
aid = art[0]
ptime = art[1]
if err = conn.Send("ZADD", key, "CH", ptime, aid); err != nil {
PromError("redis:增加up主缓存")
log.Error("conn.Send(ZADD, %s, %d, %d) error(%+v)", key, aid, err)
return
}
count++
}
if err = conn.Send("EXPIRE", key, d.redisUpperExpire); err != nil {
PromError("redis:增加up主expire")
log.Error("conn.Expire error(%+v)", err)
return
}
count++
}
if err = conn.Flush(); err != nil {
PromError("redis:增加up主flush")
log.Error("conn.Flush error(%+v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
PromError("redis:增加up主receive")
log.Error("conn.Receive error(%+v)", err)
return
}
}
return
}
// DelUpperCache delete article of upper cache.
func (d *Dao) DelUpperCache(c context.Context, mid int64, aid int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("ZREM", upperKey(mid), aid); err != nil {
PromError("redis:删除up主")
log.Error("conn.Do(ZERM, %s, %d) error(%+v)", upperKey(mid), aid, err)
}
return
}
// UpperArtsCountCache get upper articles count
func (d *Dao) UpperArtsCountCache(c context.Context, mid int64) (res int, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if res, err = redis.Int(conn.Do("ZCOUNT", upperKey(mid), 0, "+inf")); err != nil {
PromError("redis:up主文章计数")
log.Error("conn.Do(ZCARD, %s) error(%+v)", upperKey(mid), err)
}
return
}
// MoreArtsCaches batch get early articles of upper by publish time.
func (d *Dao) MoreArtsCaches(c context.Context, mid, ptime int64, num int) (before []int64, after []int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZREVRANGEBYSCORE", upperKey(mid), fmt.Sprintf("(%d", ptime), "-inf", "LIMIT", 0, num); err != nil {
PromError("redis:获取up主更早文章")
log.Error("conn.Send(%s) error(%+v)", upperKey(mid), err)
return
}
if err = conn.Send("ZRANGEBYSCORE", upperKey(mid), fmt.Sprintf("(%d", ptime), "+inf", "LIMIT", 0, num); err != nil {
PromError("redis:获取up主更晚文章")
log.Error("conn.Send(%s) error(%+v)", upperKey(mid), err)
return
}
if err = conn.Flush(); err != nil {
PromError("redis:获取up主更晚文章")
log.Error("conn.Flush error(%+v)", err)
return
}
if before, err = redis.Int64s(conn.Receive()); err != nil {
PromError("redis:获取up主更早文章")
log.Error("conn.Receive error(%+v)", err)
return
}
if after, err = redis.Int64s(conn.Receive()); err != nil {
PromError("redis:获取up主更晚文章")
log.Error("conn.Receive error(%+v)", err)
return
}
l := len(before)
if l == 0 {
return
}
if before[l-1] == _blank {
before = before[:l-1]
}
return
}
// ExpireRankCache expire rank cache
func (d *Dao) ExpireRankCache(c context.Context, cid int64) (res bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
var ttl int64
if ttl, err = redis.Int64(conn.Do("TTL", rankKey(cid))); err != nil {
PromError("redis:排行榜expire")
log.Error("ExpireRankCache(ttl %s) error(%+v)", rankKey(cid), err)
return
}
if ttl > (d.redisRankTTL - d.redisRankExpire) {
res = true
return
}
return
}
// RankCache get rank cache
func (d *Dao) RankCache(c context.Context, cid int64) (res model.RankResp, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := rankKey(cid)
var s string
if s, err = redis.String(conn.Do("GET", key)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
PromError("redis:获取排行榜")
log.Error("dao.RankCache zrevrange(%s) err: %+v", key, err)
return
}
err = json.Unmarshal([]byte(s), &res)
return
}
// AddRankCache add rank cache
func (d *Dao) AddRankCache(c context.Context, cid int64, arts model.RankResp) (err error) {
var (
key = rankKey(cid)
conn = d.redis.Get(c)
count int
)
defer conn.Close()
if len(arts.List) == 0 {
return
}
if err = conn.Send("DEL", key); err != nil {
PromError("redis:删除排行榜缓存")
log.Error("conn.Send(DEL, %s) error(%+v)", key, err)
return
}
count++
value, _ := json.Marshal(arts)
if err = conn.Send("SET", key, value); err != nil {
PromError("redis:增加排行榜缓存")
log.Error("conn.Send(SET, %s, %s) error(%+v)", key, value, err)
return
}
count++
if err = conn.Send("EXPIRE", key, d.redisRankTTL); err != nil {
PromError("redis:expire排行榜")
log.Error("conn.Send(EXPIRE, %s, %v) error(%+v)", key, d.redisRankTTL, err)
return
}
count++
if err = conn.Flush(); err != nil {
PromError("redis:增加排行榜flush")
log.Error("conn.Flush error(%+v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
PromError("redis:增加排行榜主receive")
log.Error("conn.Receive error(%+v)", err)
return
}
}
return
}
// AddCacheHotspotArts .
func (d *Dao) AddCacheHotspotArts(c context.Context, typ int8, id int64, arts [][2]int64, replace bool) (err error) {
var (
key = hotspotKey(typ, id)
conn = d.redis.Get(c)
count int
)
defer conn.Close()
if len(arts) == 0 {
return
}
if replace {
if err = conn.Send("DEL", key); err != nil {
PromError("redis:删除热点标签缓存")
log.Error("conn.Send(DEL, %s) error(%+v)", key, err)
return
}
count++
}
for _, art := range arts {
id := art[0]
score := art[1]
if err = conn.Send("ZADD", key, "CH", score, id); err != nil {
PromError("redis:增加热点标签缓存")
log.Error("conn.Send(ZADD, %s, %d, %v) error(%+v)", key, score, id, err)
return
}
count++
}
if err = conn.Send("EXPIRE", key, d.redisHotspotExpire); err != nil {
PromError("redis:热点标签设定过期")
log.Error("conn.Send(EXPIRE, %s, %d) error(%+v)", key, d.redisHotspotExpire, err)
return
}
count++
if err = conn.Flush(); err != nil {
PromError("redis:增加热点标签缓存flush")
log.Error("conn.Flush error(%+v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
PromError("redis:增加热点标签缓存receive")
log.Error("conn.Receive error(%+v)", err)
return
}
}
return
}
// HotspotArtsCache .
func (d *Dao) HotspotArtsCache(c context.Context, typ int8, id int64, start, end int) (res []int64, err error) {
key := hotspotKey(typ, id)
conn := d.redis.Get(c)
defer conn.Close()
res, err = redis.Int64s(conn.Do("ZREVRANGE", key, start, end))
if err != nil {
PromError("redis:获取热点标签列表receive")
log.Error("conn.Send(ZREVRANGE, %s) error(%+v)", key, err)
}
return
}
// HotspotArtsCacheCount .
func (d *Dao) HotspotArtsCacheCount(c context.Context, typ int8, id int64) (res int64, err error) {
key := hotspotKey(typ, id)
conn := d.redis.Get(c)
defer conn.Close()
res, err = redis.Int64(conn.Do("ZCARD", key))
if err != nil {
PromError("redis:获取热点标签计数")
log.Error("conn.Send(ZCARD, %s) error(%+v)", key, err)
}
return
}
// ExpireHotspotArtsCache .
func (d *Dao) ExpireHotspotArtsCache(c context.Context, typ int8, id int64) (ok bool, err error) {
key := hotspotKey(typ, id)
conn := d.redis.Get(c)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisHotspotExpire)); err != nil {
PromError("redis:热点运营设定过期")
log.Error("conn.Send(EXPIRE, %s) error(%+v)", key, err)
}
return
}
// DelHotspotArtsCache .
func (d *Dao) DelHotspotArtsCache(c context.Context, typ int8, hid int64, aid int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := hotspotKey(typ, hid)
if _, err = conn.Do("ZREM", key, aid); err != nil {
PromError("redis:删除热点运营文章")
log.Error("conn.Do(ZERM, %s, %d) error(%+v)", key, aid, err)
}
return
}
// AuthorMostCategories .
func (d *Dao) AuthorMostCategories(c context.Context, mid int64) (categories []int64, err error) {
var (
categoriesInts []string
category int64
)
conn := d.redis.Get(c)
defer conn.Close()
key := authorCategoriesKey(mid)
if categoriesInts, err = redis.Strings(conn.Do("SMEMBERS", key)); err != nil {
PromError("redis:获取作者分区")
log.Error("conn.Do(GET, %s) error(%+v)", key, err)
}
for _, categoryInt := range categoriesInts {
if category, err = strconv.ParseInt(categoryInt, 10, 64); err != nil {
PromError("redis:获取作者分区")
log.Error("strconv.Atoi(%s) error(%+v)", categoryInt, err)
return
}
categories = append(categories, category)
}
return
}
// CategoryAuthors .
func (d *Dao) CategoryAuthors(c context.Context, category int64, count int) (authors []int64, err error) {
var (
authorsInts []string
author int64
)
conn := d.redis.Get(c)
defer conn.Close()
key := recommendsAuthorsKey(category)
if authorsInts, err = redis.Strings(conn.Do("SRANDMEMBER", key, count)); err != nil {
PromError("redis:获取分区作者")
log.Error("conn.Do(GET, %s) error(%+v)", key, err)
}
for _, authorInt := range authorsInts {
if author, err = strconv.ParseInt(authorInt, 10, 64); err != nil {
PromError("redis:获取作者分区")
log.Error("strconv.Atoi(%s) error(%+v)", authorInt, err)
return
}
authors = append(authors, author)
}
return
}

View File

@@ -0,0 +1,81 @@
package dao
import (
"context"
"fmt"
"go-common/library/cache/redis"
"go-common/library/log"
)
func maxLikeKey(aid int64) string {
return fmt.Sprintf(_prefixMaxLike, aid)
}
// MaxLikeCache max like cache
func (d *Dao) MaxLikeCache(c context.Context, aid int64) (res int64, err error) {
var (
conn = d.redis.Get(c)
key = maxLikeKey(aid)
)
defer conn.Close()
if res, err = redis.Int64(conn.Do("GET", key)); err != nil {
if err == redis.ErrNil {
err = nil
} else {
PromError("redis:获取点赞最大数")
log.Error("MaxLikeMCache GET(%s) error(%+v)", key, err)
}
return
}
return
}
// ExpireMaxLikeCache expire max like cache
func (d *Dao) ExpireMaxLikeCache(c context.Context, aid int64) (res bool, err error) {
var (
conn = d.redis.Get(c)
key = maxLikeKey(aid)
)
defer conn.Close()
if res, err = redis.Bool(conn.Do("EXPIRE", key, d.redisMaxLikeExpire)); err != nil {
PromError("redis:Expire点赞最大数")
log.Error("MaxLikeCache EXPIRE(%s) error(%+v)", key, err)
}
return
}
// SetMaxLikeCache set max like cache
func (d *Dao) SetMaxLikeCache(c context.Context, aid int64, value int64) (err error) {
var (
conn = d.redis.Get(c)
key = maxLikeKey(aid)
count int
)
defer conn.Close()
if err = conn.Send("SET", key, value); err != nil {
PromError("redis:设定点赞最大数")
log.Error("conn.Send(SET, %s, %s) error(%+v)", key, value, err)
return
}
count++
if err = conn.Send("EXPIRE", key, d.redisMaxLikeExpire); err != nil {
PromError("redis:Expire点赞最大数")
log.Error("MaxLikeCache EXPIRE(%s) error(%+v)", key, err)
return
}
count++
if err = conn.Flush(); err != nil {
PromError("redis:设定点赞最大数flush")
log.Error("conn.Flush error(%+v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
PromError("redis:设定点赞最大数receive")
log.Error("conn.Receive error(%+v)", err)
return
}
}
return
}

View File

@@ -0,0 +1,30 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_MaxLikeCache(t *testing.T) {
var (
aid = int64(100)
value = int64(200)
err error
)
Convey("add cache", t, WithCleanCache(func() {
err = d.SetMaxLikeCache(context.TODO(), aid, value)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.MaxLikeCache(context.TODO(), aid)
So(err, ShouldBeNil)
So(res, ShouldEqual, value)
})
Convey("expire cache", func() {
res, err := d.ExpireMaxLikeCache(context.TODO(), aid)
So(err, ShouldBeNil)
So(res, ShouldEqual, true)
})
}))
}

View File

@@ -0,0 +1,46 @@
package dao
import (
"context"
"fmt"
"go-common/library/cache/redis"
"go-common/library/log"
)
// GetsetReadPing 设置并获取上次阅读心跳时间不存在则返回0
func (d *Dao) GetsetReadPing(c context.Context, buvid string, aid int64, cur int64) (last int64, err error) {
var (
key = readPingKey(buvid, aid)
conn = d.redis.Get(c)
)
defer conn.Close()
if last, err = redis.Int64(conn.Do("GETSET", key, cur)); err != nil && err != redis.ErrNil {
log.Error("conn.Do(GETSET, %s, %d) error(%+v)", key, cur, err)
return
}
if _, err = conn.Do("EXPIRE", key, d.redisReadPingExpire); err != nil {
log.Error("conn.Do(EXPIRE, %s, %d) error(%+v)", key, cur, err)
return
}
return
}
// AddReadPingSet 添加新的阅读记录
func (d *Dao) AddReadPingSet(c context.Context, buvid string, aid int64, mid int64, ip string, cur int64, source string) (err error) {
var (
key = readPingSetKey()
value = fmt.Sprintf("%s|%d|%d|%s|%d|%s", buvid, aid, mid, ip, cur, source)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("SADD", key, value); err != nil {
log.Error("conn.Do(SADD, %s, %s) error(%+v)", key, value, err)
return
}
if _, err = conn.Do("EXPIRE", key, d.redisReadSetExpire); err != nil {
log.Error("conn.Do(EXPIRE, %s, %d) error(%+v)", key, cur, err)
return
}
return
}

View File

@@ -0,0 +1,126 @@
package dao
import (
"context"
"math/rand"
"strconv"
"go-common/app/interface/openplatform/article/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
var _mainCategory = int64(0)
// AddSortCache add sort articles cache
func (d *Dao) AddSortCache(c context.Context, categoryID int64, field int, aid, score int64) (err error) {
var (
key = sortedKey(categoryID, field)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("ZADD", key, "CH", score, aid); err != nil {
PromError("redis:增加排序缓存")
log.Error("conn.Do(ZADD, %s, %d, %v) error(%+v)", key, score, aid, err)
}
return
}
// 避免同时回源
func (d *Dao) randomSortTTL() int64 {
random := rand.Int63() % (d.redisSortTTL / 20)
if rand.Int()%2 == 0 {
return d.redisSortTTL - random
}
return d.redisSortTTL + random
}
// SortCache get sort cache
func (d *Dao) SortCache(c context.Context, categoryID int64, field int, start, end int) (res []int64, err error) {
key := sortedKey(categoryID, field)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZREVRANGE", key, start, end); err != nil {
PromError("redis:获取排序列表")
log.Error("conn.Send(%s) error(%+v)", key, err)
return
}
if err = conn.Flush(); err != nil {
PromError("redis:获取排序列表flush")
log.Error("conn.Flush error(%+v)", err)
return
}
res, err = redis.Int64s(conn.Receive())
if err != nil {
PromError("redis:获取排序列表receive")
log.Error("conn.Send(ZREVRANGE, %s) error(%+v)", key, err)
}
return
}
// SortCacheByValue get new articles cache by aid
func (d *Dao) SortCacheByValue(c context.Context, categoryID int64, field int, value, score int64, ps int) (res []int64, err error) {
var (
index int
tmpRes []int64
conn = d.redis.Get(c)
key = sortedKey(categoryID, field)
)
defer conn.Close()
if tmpRes, err = redis.Int64s(conn.Do("ZREVRANGEBYSCORE", key, score, "-inf", "LIMIT", 0, ps+1)); err != nil {
PromError("redis:获取最新投稿列表")
log.Error("redis(ZREVRANGEBYSCORE %s,%d,%d) error(%+v)", key, score, ps, err)
return
}
for i, v := range tmpRes {
if v == value {
index = i + 1
break
}
}
res = tmpRes[index:]
return
}
// ExpireSortCache expire sort cache
func (d *Dao) ExpireSortCache(c context.Context, categoryID int64, field int) (ok bool, err error) {
key := sortedKey(categoryID, field)
conn := d.redis.Get(c)
defer conn.Close()
var ttl int64
if ttl, err = redis.Int64(conn.Do("TTL", key)); err != nil {
PromError("redis:排序缓存ttl")
log.Error("conn.Do(TTL, %s) error(%+v)", key, err)
}
if ttl > (d.redisSortTTL - d.redisSortExpire) {
ok = true
}
return
}
// DelSortCache delete sort cache
func (d *Dao) DelSortCache(c context.Context, categoryID int64, field int, aid int64) (err error) {
key := sortedKey(categoryID, field)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("ZREM", key, aid); err != nil {
PromError("redis:删除排序")
log.Error("conn.Do(ZERM, %s, %d) error(%+v)", key, aid, err)
}
return
}
// NewArticleCount get new article count
func (d *Dao) NewArticleCount(c context.Context, ptime int64) (res int64, err error) {
var (
key = sortedKey(_mainCategory, model.FieldNew)
conn = d.redis.Get(c)
)
defer conn.Close()
begin := "(" + strconv.FormatInt(ptime, 10)
if res, err = redis.Int64(conn.Do("ZCOUNT", key, begin, "+inf")); err != nil {
PromError("redis:排序缓存计数")
log.Error("conn.Do(ZCOUNT, %s, %s, +inf) error(%+v)", key, begin, err)
}
return
}

View File

@@ -0,0 +1,64 @@
package dao
import (
"context"
"fmt"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_NewArtsCache(t *testing.T) {
var (
err error
arts = [][2]int64{
[2]int64{1, 4},
[2]int64{2, 5},
[2]int64{3, 6},
}
revids = []int64{4, 3, 2, 1}
cid = int64(0)
field = 1
)
// 1:4,2:5,3:6,4:8
Convey("add cache", t, func() {
for _, a := range arts {
err = d.AddSortCache(context.TODO(), cid, field, a[0], a[1])
So(err, ShouldBeNil)
}
err = d.AddSortCache(context.TODO(), cid, field, 4, 8)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.SortCache(context.TODO(), cid, field, 0, -1)
So(err, ShouldBeNil)
So(res, ShouldResemble, revids)
})
Convey("expire cache", func() {
res, err := d.ExpireSortCache(context.TODO(), cid, field)
So(err, ShouldBeNil)
So(res, ShouldBeTrue)
})
Convey("count cache", func() {
res, err := d.NewArticleCount(context.TODO(), 5)
So(err, ShouldBeNil)
So(res, ShouldEqual, 2)
})
Convey("delete cache", func() {
err := d.DelSortCache(context.TODO(), cid, field, 1)
So(err, ShouldBeNil)
res, err := d.SortCache(context.TODO(), cid, field, 0, -1)
So(err, ShouldBeNil)
So(res, ShouldResemble, []int64{4, 3, 2})
})
})
}
func Test_randomSortTTL(t *testing.T) {
d.redisSortTTL = 100
Convey("random ttl should >= 95 && <= 105", t, func() {
for i := 0; i < 20; i++ {
ttl := d.randomSortTTL()
So(ttl, ShouldBeBetween, 95, 105)
fmt.Printf("%d ", ttl)
}
})
}

View File

@@ -0,0 +1,121 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/interface/openplatform/article/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_pingRedis(t *testing.T) {
Convey("ping redis", t, WithDao(func(d *Dao) {
So(d.pingRedis(context.TODO()), ShouldBeNil)
}))
}
func Test_UppersCache(t *testing.T) {
var (
mid = int64(1)
mid2 = int64(2)
now = time.Now().Unix()
err error
a1 = model.Meta{ID: 1, PublishTime: xtime.Time(now), Author: &model.Author{Mid: mid}}
a2 = model.Meta{ID: 2, PublishTime: xtime.Time(now - 1), Author: &model.Author{Mid: mid}}
a3 = model.Meta{ID: 3, PublishTime: xtime.Time(now - 2), Author: &model.Author{Mid: mid2}}
idsm = map[int64][][2]int64{
mid: [][2]int64{[2]int64{a1.ID, int64(a1.PublishTime)}, [2]int64{a2.ID, int64(a2.PublishTime)}},
mid2: [][2]int64{[2]int64{a3.ID, int64(a3.PublishTime)}},
}
)
Convey("add cache", t, WithDao(func(d *Dao) {
err = d.AddUpperCaches(context.TODO(), idsm)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.UppersCaches(context.TODO(), []int64{mid, mid2}, 0, 2)
So(res, ShouldResemble, map[int64][]int64{mid: []int64{1, 2}, mid2: []int64{3}})
So(err, ShouldBeNil)
})
Convey("purge cache", func() {
err := d.DelUpperCache(context.TODO(), a1.Author.Mid, a1.ID)
So(err, ShouldBeNil)
})
Convey("count cache", func() {
res, err := d.UpperArtsCountCache(context.TODO(), a1.Author.Mid)
So(err, ShouldBeNil)
So(res, ShouldEqual, 2)
})
}))
}
func Test_RankCache(t *testing.T) {
var (
cid = int64(2)
list = []*model.Rank{
&model.Rank{
Aid: 3,
Score: 3,
},
&model.Rank{
Aid: 2,
Score: 2,
},
&model.Rank{
Aid: 1,
Score: 1,
},
}
rank = model.RankResp{Note: "note", List: list}
err error
)
Convey("add cache", t, WithDao(func(d *Dao) {
err = d.AddRankCache(context.TODO(), cid, rank)
So(err, ShouldBeNil)
Convey("get cache", func() {
res, err := d.RankCache(context.TODO(), cid)
So(err, ShouldBeNil)
So(res, ShouldResemble, rank)
res, err = d.RankCache(context.TODO(), 1000)
So(err, ShouldBeNil)
So(res.List, ShouldBeEmpty)
})
Convey("expire cache", func() {
res, err := d.ExpireRankCache(context.TODO(), cid)
So(err, ShouldBeNil)
So(res, ShouldEqual, true)
})
}))
}
func Test_HotspotCache(t *testing.T) {
var (
id = int64(1)
c = context.TODO()
arts = [][2]int64{[2]int64{0, -1}, [2]int64{1, 1}, [2]int64{2, 2}, [2]int64{3, 3}, [2]int64{4, 4}, [2]int64{5, 5}}
)
Convey("work", t, WithCleanCache(func() {
ok, err := d.ExpireHotspotArtsCache(c, model.HotspotTypePtime, id)
So(err, ShouldBeNil)
So(ok, ShouldBeFalse)
err = d.AddCacheHotspotArts(context.TODO(), model.HotspotTypePtime, id, arts, true)
So(err, ShouldBeNil)
ok, err = d.ExpireHotspotArtsCache(c, model.HotspotTypePtime, id)
So(err, ShouldBeNil)
So(ok, ShouldBeTrue)
var num int64
num, err = d.HotspotArtsCacheCount(c, model.HotspotTypePtime, id)
So(err, ShouldBeNil)
So(num, ShouldEqual, len(arts))
Convey("get cache", func() {
res, err := d.HotspotArtsCache(context.TODO(), model.HotspotTypePtime, id, 0, -1)
So(err, ShouldBeNil)
So(res, ShouldResemble, []int64{5, 4, 3, 2, 1, 0})
})
}))
}

View File

@@ -0,0 +1,69 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"anniversary.go",
"apply.go",
"archive.go",
"article.go",
"author.go",
"cards.go",
"categories.go",
"complaint.go",
"creative.go",
"favorite.go",
"hotspots.go",
"http.go",
"like.go",
"list.go",
"notice.go",
"rank.go",
"read.go",
"recommends.go",
"search.go",
"sentinel.go",
"share.go",
"users.go",
"view.go",
],
importpath = "go-common/app/interface/openplatform/article/http",
tags = ["automanaged"],
deps = [
"//app/interface/openplatform/article/conf:go_default_library",
"//app/interface/openplatform/article/dao:go_default_library",
"//app/interface/openplatform/article/model:go_default_library",
"//app/interface/openplatform/article/service:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/auth:go_default_library",
"//library/net/http/blademaster/middleware/cache:go_default_library",
"//library/net/http/blademaster/middleware/cache/store:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/metadata: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,14 @@
package http
import (
bm "go-common/library/net/http/blademaster"
)
func anniversaryInfo(c *bm.Context) {
var (
mid int64
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
c.JSON(artSrv.AnniversaryInfo(c, mid))
}

View File

@@ -0,0 +1,91 @@
package http
import (
"go-common/app/interface/openplatform/article/conf"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"strconv"
)
func applyInfo(c *bm.Context) {
var (
mid int64
)
// get mid
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
c.JSON(artSrv.ApplyInfo(c, mid))
}
func apply(c *bm.Context) {
var (
mid int64
request = c.Request
params = request.Form
content, category string
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
content = params.Get("content")
if int64(len([]rune(content))) > conf.Conf.Article.MaxApplyContentLimit {
c.JSON(nil, ecode.RequestErr)
return
}
category = params.Get("category")
if int64(len([]rune(category))) > conf.Conf.Article.MaxApplyCategoryLimit {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.Apply(c, mid, content, category))
}
func isAuthor(c *bm.Context) {
var (
mid, mediaID int64
err error
author, forbid bool
id int64
level = true
canEdit = true
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
mediaIDStr := c.Request.Form.Get("media_id")
if mediaIDStr != "" {
mediaID, err = strconv.ParseInt(mediaIDStr, 10, 64)
if err != nil || mediaID < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if mediaID > 0 {
if level, err = artSrv.LevelRequired(c, mid); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if id, err = artSrv.MediaArticle(c, mediaID, mid); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if id > 0 && artSrv.EditTimes(c, id) <= 0 {
canEdit = false
}
}
if author, forbid, err = artSrv.IsAuthor(c, mid); err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{"is_author": author, "forbid": forbid, "level": level, "id": id, "can_edit": canEdit}, nil)
}
func addAuthor(c *bm.Context) {
var (
mid int64
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
c.JSON(nil, artSrv.AddAuthor(c, mid))
}

View File

@@ -0,0 +1,34 @@
package http
import (
"go-common/app/interface/openplatform/article/conf"
"go-common/app/service/main/archive/api"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"go-common/library/xstr"
)
func archives(c *bm.Context) {
var (
err error
aids []int64
arcs map[int64]*api.Arc
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
)
idsStr := params.Get("ids")
if aids, err = xstr.SplitInts(idsStr); err != nil || len(aids) < 1 || len(aids) > conf.Conf.Article.MaxArchives {
c.JSON(nil, ecode.RequestErr)
return
}
if arcs, err = artSrv.Archives(c, aids, ip); err != nil {
c.JSON(nil, err)
return
}
if len(arcs) == 0 {
c.JSON(nil, ecode.NothingFound)
return
}
c.JSON(arcs, err)
}

View File

@@ -0,0 +1,106 @@
package http
import (
"strconv"
"go-common/app/interface/openplatform/article/conf"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/xstr"
)
func meta(c *bm.Context) {
var (
err error
aid int64
am *artmdl.Meta
params = c.Request.Form
)
idStr := params.Get("id")
if aid, err = strconv.ParseInt(idStr, 10, 64); err != nil || aid < 1 {
c.JSON(nil, ecode.RequestErr)
return
}
if am, err = artSrv.ArticleMeta(c, aid); err != nil {
c.JSON(nil, err)
return
} else if am == nil {
c.JSON(nil, ecode.NothingFound)
return
}
c.JSON(am, nil)
}
func metas(c *bm.Context) {
var (
err error
aids []int64
ams map[int64]*artmdl.Meta
params = c.Request.Form
mid int64
resIDs []int64
)
idsStr := params.Get("ids")
midStr := params.Get("mid")
mid, _ = strconv.ParseInt(midStr, 10, 64)
if aids, err = xstr.SplitInts(idsStr); err != nil || len(aids) < 1 || len(aids) > conf.Conf.Article.MaxArticleMetas {
c.JSON(nil, ecode.RequestErr)
return
}
if ams, err = artSrv.ArticleMetas(c, aids); err != nil {
c.JSON(nil, err)
return
}
if len(ams) == 0 {
c.JSON(nil, ecode.NothingFound)
return
}
if mid > 0 {
for _, artm := range ams {
resIDs = append(resIDs, artm.ID)
}
likeRes, _ := artSrv.HadLikesByMid(c, mid, resIDs)
for _, art := range ams {
isLike := likeRes[art.ID]
if isLike > 0 {
art.IsLike = true
}
}
}
c.JSON(ams, nil)
}
func addCheatFilter(c *bm.Context) {
var (
err error
aid int64
lv int
params = c.Request.Form
)
idStr := params.Get("id")
if aid, err = strconv.ParseInt(idStr, 10, 64); err != nil || aid < 1 {
c.JSON(nil, ecode.RequestErr)
return
}
lv, err = strconv.Atoi(params.Get("lv"))
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.AddCheatFilter(c, aid, lv))
}
func delCheatFilter(c *bm.Context) {
var (
err error
aid int64
params = c.Request.Form
)
idStr := params.Get("id")
if aid, err = strconv.ParseInt(idStr, 10, 64); err != nil || aid < 1 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.DelCheatFilter(c, aid))
}

View File

@@ -0,0 +1,56 @@
package http
import (
"strconv"
bm "go-common/library/net/http/blademaster"
)
func authors(c *bm.Context) {
var (
mid int64
author int64
params = c.Request.Form
)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
author, _ = strconv.ParseInt(params.Get("author"), 10, 64)
authors := artSrv.Authors(c, mid, author)
c.JSONMap(map[string]interface{}{"authors": authors}, nil)
}
// func authors(c *bm.Context) {
// var (
// mid int64
// userID int64
// request = c.Request
// params = request.Form
// clientIP = metadata.String(c, metadata.RemoteIP)
// err error
// authors *artmdl.RecommendAuthors
// )
// author := params.Get("author")
// if author == "" {
// err = ecode.RequestErr
// c.JSON(nil, err)
// return
// }
// if mid, err = strconv.ParseInt(author, 10, 64); err != nil {
// c.JSON(nil, err)
// return
// }
// mobiApp := params.Get("mobi_app")
// device := params.Get("device")
// plat := artmdl.Plat(mobiApp, device)
// platform := artmdl.Client(plat)
// buildStr := params.Get("build")
// build, _ := strconv.Atoi(buildStr)
// buvid := buvid(c)
// if midInter, ok := c.Get("mid"); ok {
// userID = midInter.(int64)
// }
// authors, err = artSrv.RecommendAuthors(c, platform, mobiApp, device, build, clientIP, userID, buvid, mid)
// c.JSON(authors, err)
// }

View File

@@ -0,0 +1,23 @@
package http
import (
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"strings"
)
func card(c *bm.Context) {
c.JSON(artSrv.FindCard(c, c.Request.Form.Get("id")))
}
func cards(c *bm.Context) {
var (
params = c.Request.Form
)
ids := strings.Split(params.Get("ids"), ",")
if len(ids) > 100 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.FindCards(c, ids))
}

View File

@@ -0,0 +1,19 @@
package http
import (
artmdl "go-common/app/interface/openplatform/article/model"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func categories(c *bm.Context) {
data, err := artSrv.ListCategories(c, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
if data == nil {
data = artmdl.Categories{}
}
c.JSON(data, nil)
}

View File

@@ -0,0 +1,45 @@
package http
import (
"strconv"
"go-common/app/interface/openplatform/article/conf"
"go-common/app/interface/openplatform/article/dao"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func addComplaint(c *bm.Context) {
var (
err error
mid, aid, cid int64
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
reason, imageUrls string
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
aidStr := params.Get("aid")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
cidStr := params.Get("cid")
if cid, err = strconv.ParseInt(cidStr, 10, 64); err != nil || cid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
reason = params.Get("reason")
if int64(len([]rune(reason))) > conf.Conf.Article.MaxComplaintReasonLimit {
c.JSON(nil, ecode.RequestErr)
return
}
imageUrls = params.Get("images")
if err = artSrv.AddComplaint(c, aid, mid, cid, reason, imageUrls, ip); err != nil {
dao.PromError("新增投诉")
log.Error("artSrv.AddComplaint(%d,%d,%d, %s, %s) error(%+v)", mid, aid, cid, reason, imageUrls, err)
}
c.JSON(nil, err)
}

View File

@@ -0,0 +1,762 @@
package http
import (
"encoding/base64"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"go-common/app/interface/openplatform/article/conf"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func lists(c *bm.Context) {
var (
mid int64
err error
novel bool
list []*model.CreativeList
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
if novel, list, err = artSrv.CreativeUpLists(c, mid); err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{
"novel": novel,
"lists": list,
"total": len(list),
"limit": conf.Conf.Article.ListLimit,
}, nil)
}
func addList(c *bm.Context) {
var (
mid int64
err error
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
req := new(struct {
Name string `form:"name" validate:"required"`
Summary string `form:"summary" validate:"min=0,max=233"`
ImageURL string `form:"image_url"`
})
if err = c.Bind(req); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if req.ImageURL != "" && !model.CheckBFSImage(req.ImageURL) {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.CreativeAddList(c, mid, req.Name, req.Summary, req.ImageURL))
}
func delList(c *bm.Context) {
var (
mid, id int64
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
id, _ = strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.CreativeDelList(c, mid, id))
}
func updateArticleList(c *bm.Context) {
var (
mid int64
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
listID, _ := strconv.ParseInt(c.Request.Form.Get("list_id"), 10, 64)
if listID < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
articleID, _ := strconv.ParseInt(c.Request.Form.Get("article_id"), 10, 64)
if articleID <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.CreativeUpdateArticleList(c, mid, articleID, listID, true))
}
func listAllArticles(c *bm.Context) {
var (
mid, id int64
err error
list *model.List
arts []*model.ListArtMeta
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
id, _ = strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if list, arts, err = artSrv.CreativeListAllArticles(c, mid, id); err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{
"list": list,
"articles": arts,
"total": len(arts),
"limit": conf.Conf.Article.ListArtsLimit,
}, nil)
}
func updateListArticles(c *bm.Context) {
var (
mid int64
list *model.List
err error
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
req := new(struct {
ListID int64 `form:"list_id" validate:"min=0"`
Name string `form:"name" validate:"required"`
Summary string `form:"summary" validate:"min=0,max=233"`
ImageURL string `form:"image_url"`
OnlyList bool `form:"only_list"`
IDs []int64 `form:"ids,split"`
})
if err = c.Bind(req); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if req.ImageURL != "" && !model.CheckBFSImage(req.ImageURL) {
c.JSON(nil, ecode.RequestErr)
return
}
if list, err = artSrv.CreativeUpdateListArticles(c, req.ListID, req.Name, req.ImageURL, req.Summary, req.OnlyList, mid, req.IDs); err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{"list": list}, nil)
}
func canAddArts(c *bm.Context) {
var (
mid int64
err error
arts []*model.ListArtMeta
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
if arts, err = artSrv.CreativeCanAddArticles(c, mid); err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{"articles": arts}, nil)
}
func webSubArticle(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
title := params.Get("title")
content := params.Get("content")
summary := params.Get("summary")
bannerURL := params.Get("banner_url")
tidStr := params.Get("tid")
categoryStr := params.Get("category")
reprintStr := params.Get("reprint")
tags := params.Get("tags")
imageURLs := params.Get("image_urls")
wordsStr := params.Get("words")
actIDStr := params.Get("act_id")
scoreStr := params.Get("score")
mediaIDStr := params.Get("media_id")
spoilerStr := params.Get("spoiler")
dynamicIntrosStr := params.Get("dynamic_intro")
originImageURLs := params.Get("origin_image_urls")
ip := metadata.String(c, metadata.RemoteIP)
ck := c.Request.Header.Get("cookie")
// check params
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
var (
spoiler int64
aid int64
actID int64
mediaID int64
score int64
err1, err2, err3 error
)
if aidStr != "" {
id, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
aid = id
}
if actIDStr != "" {
actid, err := strconv.ParseInt(actIDStr, 10, 64)
if err != nil || actid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
actID = actid
}
listIDStr := params.Get("list_id")
var listID int64
if listIDStr != "" {
lid, err := strconv.ParseInt(listIDStr, 10, 64)
if err != nil || lid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
listID = lid
}
if mediaIDStr != "" {
mediaID, err1 = strconv.ParseInt(mediaIDStr, 10, 64)
if err1 != nil || mediaID < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if mediaID > 0 {
score, err2 = strconv.ParseInt(scoreStr, 10, 64)
spoiler, err3 = strconv.ParseInt(spoilerStr, 10, 32)
if err2 != nil || err3 != nil || spoiler < 0 || (score < 1 || score > 10 || score%2 != 0) {
c.JSON(nil, ecode.RequestErr)
return
}
if ok, err := artSrv.LevelRequired(c, mid); err != nil || !ok {
c.JSON(nil, ecode.ArtLevelFailedErr)
return
}
if id, err := artSrv.MediaArticle(c, mediaID, mid); err != nil || id > 0 {
c.JSON(nil, ecode.ArtMediaExistedErr)
return
}
}
words, _ := strconv.ParseInt(wordsStr, 10, 64)
artParam, err := artSrv.ParseParam(c, categoryStr, reprintStr, tidStr, imageURLs, originImageURLs)
if err != nil {
c.JSON(nil, err)
return
}
// params
art := &model.ArtParam{
AID: aid,
MID: mid,
Title: title,
Content: content,
Summary: summary,
BannerURL: bannerURL,
Tags: tags,
ImageURLs: artParam.ImageURLs,
OriginImageURLs: artParam.OriginImageURLs,
RealIP: ip,
Category: artParam.Category,
TemplateID: artParam.TemplateID,
Reprint: artParam.Reprint,
Words: words,
DynamicIntro: dynamicIntrosStr,
ActivityID: actID,
ListID: listID,
MediaID: mediaID,
Spoiler: int32(spoiler),
}
// submit
id, err := artSrv.CreativeSubArticle(c, mid, art, "", ck, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
// 番剧评分
if mediaID > 0 {
artSrv.SetMediaScore(c, score, id, mediaID, mid)
}
c.JSON(map[string]int64{"aid": id}, nil)
}
func webUpdateArticle(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
title := params.Get("title")
content := params.Get("content")
summary := params.Get("summary")
bannerURL := params.Get("banner_url")
tidStr := params.Get("tid")
categoryStr := params.Get("category")
reprintStr := params.Get("reprint")
tags := params.Get("tags")
imageURLs := params.Get("image_urls")
wordsStr := params.Get("words")
spoilerStr := params.Get("spoiler")
dynamicIntrosStr := params.Get("dynamic_intro")
originImageURLs := params.Get("origin_image_urls")
scoreStr := params.Get("score")
mediaIDStr := params.Get("media_id")
ip := metadata.String(c, metadata.RemoteIP)
ck := c.Request.Header.Get("cookie")
// check params
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
words, _ := strconv.ParseInt(wordsStr, 10, 64)
artParam, err := artSrv.ParseParam(c, categoryStr, reprintStr, tidStr, imageURLs, originImageURLs)
if err != nil {
c.JSON(nil, err)
return
}
listIDStr := params.Get("list_id")
var listID int64
if listIDStr != "" {
lid, err := strconv.ParseInt(listIDStr, 10, 64)
if err != nil || lid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
listID = lid
}
var (
spoiler int64
mediaID int64
score int64
err1, err2, err3 error
)
if mediaIDStr != "" {
mediaID, err1 = strconv.ParseInt(mediaIDStr, 10, 64)
if err1 != nil || mediaID < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if mediaID > 0 {
score, err2 = strconv.ParseInt(scoreStr, 10, 64)
spoiler, err3 = strconv.ParseInt(spoilerStr, 10, 32)
if err2 != nil || err3 != nil || spoiler < 0 || (score < 1 || score > 10 || score%2 != 0) {
c.JSON(nil, ecode.RequestErr)
return
}
if mediaid, err := artSrv.MediaIDByID(c, aid); err != nil || mediaid != mediaID {
c.JSON(nil, ecode.RequestErr)
return
}
}
// params
art := &model.ArtParam{
AID: aid,
MID: mid,
Title: title,
Content: content,
Summary: summary,
BannerURL: bannerURL,
Tags: tags,
ImageURLs: artParam.ImageURLs,
OriginImageURLs: artParam.OriginImageURLs,
RealIP: ip,
Category: artParam.Category,
TemplateID: artParam.TemplateID,
Reprint: artParam.Reprint,
Words: words,
DynamicIntro: dynamicIntrosStr,
ListID: listID,
Spoiler: int32(spoiler),
MediaID: mediaID,
}
if err = artSrv.CreativeUpdateArticle(c, mid, art, "", ck, ip); err != nil {
c.JSON(nil, err)
return
}
// 番剧评分
if mediaID > 0 {
artSrv.SetMediaScore(c, score, aid, mediaID, mid)
}
c.JSON(nil, nil)
}
func webSubmitDraft(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
title := params.Get("title")
content := params.Get("content")
summary := params.Get("summary")
bannerURL := params.Get("banner_url")
tidStr := params.Get("tid")
categoryStr := params.Get("category")
reprintStr := params.Get("reprint")
tags := params.Get("tags")
imageURLs := params.Get("image_urls")
wordsStr := params.Get("words")
mediaIDStr := params.Get("media_id")
spoilerStr := params.Get("spoiler")
dynamicIntrosStr := params.Get("dynamic_intro")
originImageURLs := params.Get("origin_image_urls")
ip := metadata.String(c, metadata.RemoteIP)
// check params
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
var (
did int64
mediaID int64
spoiler int64
err1, err2 error
)
if aidStr != "" {
var err error
did, err = strconv.ParseInt(aidStr, 10, 64)
if err != nil || did <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
words, _ := strconv.ParseInt(wordsStr, 10, 64)
artParam, err := artSrv.ParseDraftParam(c, categoryStr, reprintStr, tidStr, imageURLs, originImageURLs)
if err != nil {
c.JSON(nil, err)
return
}
listIDStr := params.Get("list_id")
var listID int64
if listIDStr != "" {
var lid int64
lid, err = strconv.ParseInt(listIDStr, 10, 64)
if err != nil || lid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
listID = lid
}
if mediaIDStr != "" {
mediaID, err1 = strconv.ParseInt(mediaIDStr, 10, 64)
spoiler, err2 = strconv.ParseInt(spoilerStr, 10, 32)
if err1 != nil || err2 != nil || mediaID < 0 || spoiler < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
// params
art := &model.ArtParam{
AID: did,
MID: mid,
Title: title,
Content: content,
Summary: summary,
BannerURL: bannerURL,
Tags: tags,
ImageURLs: artParam.ImageURLs,
OriginImageURLs: artParam.OriginImageURLs,
RealIP: ip,
Category: artParam.Category,
TemplateID: artParam.TemplateID,
Reprint: artParam.Reprint,
Words: words,
DynamicIntro: dynamicIntrosStr,
ListID: listID,
MediaID: mediaID,
Spoiler: int32(spoiler),
}
// add draft
id, err := artSrv.CreativeAddDraft(c, mid, art)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]int64{"aid": id}, nil)
}
func webArticleList(c *bm.Context) {
params := c.Request.Form
pnStr := params.Get("pn")
psStr := params.Get("ps")
sortStr := params.Get("sort")
groupStr := params.Get("group")
categoryStr := params.Get("category")
ip := metadata.String(c, metadata.RemoteIP)
// check
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
pn, err := strconv.Atoi(pnStr)
if err != nil || pn < 1 {
pn = 1
}
ps, err := strconv.Atoi(psStr)
if err != nil || ps <= 10 {
ps = 20
}
sort, err := strconv.Atoi(sortStr)
if err != nil || sort < 0 {
sort = 0
}
group, err := strconv.Atoi(groupStr)
if err != nil || group < 0 {
group = 0
}
category, err := strconv.Atoi(categoryStr)
if err != nil || category < 0 {
category = 0
}
arts, err := artSrv.CreativeArticles(c, mid, int(pn), int(ps), sort, group, category, ip)
if err != nil {
c.JSON(nil, err)
return
}
c.JSONMap(map[string]interface{}{"artlist": arts}, nil)
}
func webDraftList(c *bm.Context) {
params := c.Request.Form
pnStr := params.Get("pn")
psStr := params.Get("ps")
ip := metadata.String(c, metadata.RemoteIP)
// check
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
pn, err := strconv.Atoi(pnStr)
if err != nil || pn < 1 {
pn = 1
}
ps, err := strconv.Atoi(psStr)
if err != nil || ps <= 10 {
ps = 20
}
arts, err := artSrv.CreativeDrafts(c, mid, pn, ps, ip)
if err != nil {
c.JSON(nil, err)
return
}
c.JSONMap(map[string]interface{}{"artlist": arts}, nil)
}
func webDraft(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
ip := metadata.String(c, metadata.RemoteIP)
// check params
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.CreativeDraft(c, aid, mid, ip))
}
func webArticle(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
ip := metadata.String(c, metadata.RemoteIP)
// check params
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.CreativeView(c, aid, mid, ip))
}
func creatorArticlePre(c *bm.Context) {
var (
isAuthor int
url string
mid int64
)
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
ia, _, err := artSrv.IsAuthor(c, mid)
if err != nil {
c.JSON(nil, err)
return
}
if ia {
isAuthor = 1
url = "https://member.bilibili.com/article-text/mobile"
} else {
isAuthor = 0
url = "https://www.bilibili.com/read/apply/"
}
c.JSON(map[string]interface{}{
"is_author": isAuthor,
"reason": "", // 保持接口不变
"submit_url": url,
}, nil)
}
func uploadImage(c *bm.Context) {
var (
bs []byte
mid int64
)
midI, _ := c.Get("mid")
mid = midI.(int64)
log.Infov(c, log.KV("log", "creative: upload image"), log.KV("mid", mid))
dataURI := c.Request.FormValue("file")
if dataURI != "" {
dataURI = strings.Split(dataURI, ",")[1]
bs, _ = base64.StdEncoding.DecodeString(dataURI)
} else {
file, _, err := c.Request.FormFile("file")
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
defer file.Close()
bs, err = ioutil.ReadAll(file)
if err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
ftype := http.DetectContentType(bs)
if ftype != "image/jpeg" && ftype != "image/jpg" && ftype != "image/png" && ftype != "image/gif" {
log.Error("creative: file type not allow file type(%s, mid: %v)", ftype, mid)
c.JSON(nil, ecode.CreativeArticleImageTypeErr)
return
}
url, err := artSrv.ArticleUpCover(c, ftype, bs)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{
"url": url,
"size": len(bs),
}, nil)
}
func deleteDraft(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
// check params
midI, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
mid, ok := midI.(int64)
if !ok || mid <= 0 {
c.JSON(nil, ecode.NoLogin)
return
}
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.DelArtDraft(c, aid, mid))
}
func delArticle(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
// check params
midI, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
mid, ok := midI.(int64)
if !ok || mid <= 0 {
c.JSON(nil, ecode.NoLogin)
return
}
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.DelArticle(c, aid, mid))
}
func withdrawArticle(c *bm.Context) {
params := c.Request.Form
aidStr := params.Get("aid")
// check params
midI, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
mid, ok := midI.(int64)
if !ok || mid <= 0 {
c.JSON(nil, ecode.NoLogin)
return
}
aid, err := strconv.ParseInt(aidStr, 10, 64)
if err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.CreationWithdrawArticle(c, mid, aid))
}
func draftCount(c *bm.Context) {
midI, _ := c.Get("mid")
mid, _ := midI.(int64)
count := artSrv.CreativeDraftCount(c, mid)
c.JSONMap(map[string]interface{}{"count": count}, nil)
}
func articleCapture(c *bm.Context) {
params := c.Request.Form
originURL := params.Get("url")
// check params
midI, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
mid, ok := midI.(int64)
if !ok || mid <= 0 {
c.JSON(nil, ecode.NoLogin)
return
}
if originURL == "" {
c.JSON(nil, ecode.RequestErr)
return
}
log.Info("capture mid(%d) origin imageURL (%s)", mid, originURL)
_, err := url.ParseRequestURI(originURL)
if err != nil {
log.Error("capture check url(%s) format error(%v)", originURL, err)
c.JSON(nil, ecode.RequestErr)
return
}
imgURL, size, err := artSrv.ArticleCapture(c, originURL)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(map[string]interface{}{
"url": imgURL,
"size": size,
}, nil)
}

View File

@@ -0,0 +1,163 @@
package http
import (
"strconv"
"go-common/app/interface/openplatform/article/conf"
"go-common/app/interface/openplatform/article/dao"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func addFavorite(c *bm.Context) {
var (
err error
mid, aid, fid int64
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
aidStr := params.Get("id")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
fidStr := params.Get("fid")
if fidStr != "" {
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil || fid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
meta, err := artSrv.ArticleMeta(c, aid)
if err != nil {
c.JSON(nil, err)
return
}
if meta == nil {
c.JSON(nil, ecode.NothingFound)
return
}
err = artSrv.AddFavorite(c, mid, aid, fid, ip)
artSrv.CheatInfoc.InfoAntiCheat2(c, strconv.FormatInt(meta.Author.Mid, 10), "", strconv.FormatInt(mid, 10), aidStr, "article", infoc.ActionFav, fidStr)
c.JSON(nil, err)
}
func delFavorite(c *bm.Context) {
var (
err error
mid, aid, fid int64
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
aidStr := params.Get("id")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil || aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
fidStr := params.Get("fid")
if fidStr != "" {
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil || fid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
if err = artSrv.DelFavorite(c, mid, aid, fid, ip); err != nil {
dao.PromError("删除收藏")
log.Error("artSrv.DelFavorite(%d,%d,%d) error(%+v)", mid, aid, fid, err)
}
c.JSON(nil, err)
}
func favorites(c *bm.Context) {
var (
err error
mid, fid int64
pn, ps int
favs []*artmdl.Favorite
page *artmdl.Page
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
fidStr := params.Get("fid")
if fidStr != "" {
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil || fid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
pnStr := params.Get("pn")
if pn, err = strconv.Atoi(pnStr); err != nil || pn < 1 {
pn = 1
}
psStr := params.Get("ps")
if ps, err = strconv.Atoi(psStr); err != nil || ps < 1 {
ps = conf.Conf.Article.CreationDefaultSize
} else if ps > conf.Conf.Article.CreationMaxSize {
ps = conf.Conf.Article.CreationMaxSize
}
if favs, page, err = artSrv.ValidFavs(c, mid, fid, pn, ps, ip); err != nil {
c.JSON(nil, err)
return
}
type data struct {
Favorites []*artmdl.Favorite `json:"favorites"`
Page *artmdl.Page `json:"page"`
}
c.JSON(&data{
Favorites: favs,
Page: page,
}, nil)
}
func allFavorites(c *bm.Context) {
var (
err error
mid, fid int64
pn, ps int
favs []*artmdl.Favorite
page *artmdl.Page
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
)
midInter, _ := c.Get("mid")
mid = midInter.(int64)
fidStr := params.Get("fid")
if fidStr != "" {
if fid, err = strconv.ParseInt(fidStr, 10, 64); err != nil || fid < 0 {
c.JSON(nil, ecode.RequestErr)
return
}
}
pnStr := params.Get("pn")
if pn, err = strconv.Atoi(pnStr); err != nil || pn < 1 {
pn = 1
}
psStr := params.Get("ps")
if ps, err = strconv.Atoi(psStr); err != nil || ps < 1 {
ps = conf.Conf.Article.CreationDefaultSize
} else if ps > conf.Conf.Article.CreationMaxSize {
ps = conf.Conf.Article.CreationMaxSize
}
if favs, page, err = artSrv.Favs(c, mid, fid, pn, ps, ip); err != nil {
c.JSON(nil, err)
return
}
type data struct {
Favorites []*artmdl.Favorite `json:"favorites"`
Page *artmdl.Page `json:"page"`
}
c.JSON(&data{
Favorites: favs,
Page: page,
}, nil)
}

View File

@@ -0,0 +1,63 @@
package http
import (
"strconv"
"go-common/app/interface/openplatform/article/conf"
"go-common/app/interface/openplatform/article/dao"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func hotspotArts(c *bm.Context) {
var (
mid int64
rs []*model.MetaWithLike
hot *model.Hotspot
err error
params = c.Request.Form
aids []int64
)
cid, _ := strconv.ParseInt(params.Get("id"), 10, 64)
sort, _ := strconv.Atoi(params.Get("sort"))
pn, _ := strconv.ParseInt(params.Get("pn"), 10, 64)
ps, _ := strconv.ParseInt(params.Get("ps"), 10, 64)
if cid == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if pn <= 0 {
pn = 1
}
if pn > conf.Conf.Article.MaxRecommendPnSize {
c.JSON(nil, ecode.RequestErr)
return
}
if ps <= 0 {
ps = 20
} else if ps > conf.Conf.Article.MaxRecommendPsSize {
c.JSON(nil, ecode.RequestErr)
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
if hot, rs, err = artSrv.HotspotArts(c, cid, int(pn), int(ps), aids, int8(sort), mid); err != nil {
c.JSON(nil, err)
dao.PromError("热点运营接口")
log.Error("service.HotspotArts(%d) error(%+v)", mid, err)
return
}
res := make(map[string]interface{})
res["aids_len"] = conf.Conf.Article.RecommendAidLen
if rs == nil {
rs = []*model.MetaWithLike{}
}
res["data"] = &model.HotspotResp{
Hotspot: hot,
Articles: rs,
}
c.JSONMap(res, nil)
}

View File

@@ -0,0 +1,166 @@
package http
import (
"net/http"
"go-common/app/interface/openplatform/article/conf"
"go-common/app/interface/openplatform/article/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/http/blademaster/middleware/cache"
"go-common/library/net/http/blademaster/middleware/cache/store"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
artSrv *service.Service
authSvr *auth.Auth
vfySvr *verify.Verify
antispamM *antispam.Antispam
// cache components
cacheSvr *cache.Cache
deg *cache.Degrader
)
// Init init
func Init(c *conf.Config, s *service.Service) {
authSvr = auth.New(c.Auth)
vfySvr = verify.New(c.Verify)
artSrv = s
antispamM = antispam.New(c.Antispam)
cacheSvr = cache.New(store.NewMemcache(c.DegradeConfig.Memcache))
deg = cache.NewDegrader(c.DegradeConfig.Expire)
// init outer router
engine := bm.DefaultServer(c.BM)
outerRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
// outerRouter init outer router
func outerRouter(r *bm.Engine) {
r.Ping(ping)
r.Register(register)
cr := r.Group("/x/article")
{
cr.GET("/recommends", authSvr.Guest, recommends)
cr.GET("/recommends/plus", authSvr.Guest, recommendsPlus)
cr.GET("/home", authSvr.Guest, cacheSvr.Cache(deg.Args("pn", "ps", "device", "mobi_app", "build"), nil), home)
cr.GET("/view", authSvr.Guest, view)
cr.GET("/metas", metas)
cr.GET("/card", card)
cr.GET("/cards", cards)
cr.GET("/notice", notice)
cr.GET("/addview", authSvr.Guest, addView)
cr.POST("/addshare", authSvr.Guest, addShare)
cr.GET("/viewinfo", authSvr.Guest, viewInfo)
cr.GET("/actinfo", actInfo)
cr.POST("/like", authSvr.User, like)
cr.GET("/applyinfo", authSvr.Guest, applyInfo)
cr.GET("/is_author", authSvr.User, isAuthor)
cr.POST("/author/add", authSvr.User, addAuthor)
cr.POST("/apply", authSvr.User, apply)
cr.POST("/complaints", authSvr.User, addComplaint)
cr.GET("/list", list)
cr.GET("/categories", categories)
cr.GET("/anniversary", authSvr.User, anniversaryInfo)
cr.GET("/sentinel/config", authSvr.Guest, sentinel)
ccr := cr.Group("/favorites", authSvr.User)
{
ccr.POST("/add", addFavorite)
ccr.POST("/del", delFavorite)
ccr.GET("/list", favorites)
ccr.GET("/list/all", allFavorites)
}
cr.GET("/archives", archives)
cr.GET("/early", earlyArticles)
cr.GET("/more", authSvr.Guest, moreArts)
ccr = cr.Group("/rank")
{
ccr.GET("/categories", rankCategories)
ccr.GET("/list", authSvr.Guest, ranks)
}
ccr = cr.Group("/user", authSvr.User)
{
ccr.GET("/notice", userNotice)
ccr.POST("/notice/update", updateUserNotice)
}
// read list
cr.GET("/list/articles", authSvr.Guest, listArticles)
cr.GET("/list/web/articles", authSvr.Guest, webListArticles)
cr.GET("/listinfo", listInfo)
cr.GET("/up/lists", upLists)
cr.GET("/hotspots", authSvr.Guest, hotspotArts)
cr.GET("/authors", authSvr.User, authors)
ccr = cr.Group("/creative", authSvr.User)
{
cr1 := ccr.Group("/list")
{
cr1.GET("/all", lists)
cr1.POST("/add", addList)
cr1.POST("/del", delList)
cr1.POST("/update", updateListArticles)
cr1.GET("/articles/all", listAllArticles)
cr1.GET("/articles/can_add", canAddArts)
cr1.POST("/articles/update", updateArticleList)
}
// creative article
ccr.POST("/article/submit", webSubArticle)
ccr.POST("/article/update", webUpdateArticle)
ccr.POST("/draft/addupdate", webSubmitDraft)
ccr.GET("/draft/view", webDraft)
ccr.GET("/draft/list", webDraftList)
ccr.GET("/draft/count", draftCount)
ccr.GET("/article/view", webArticle)
ccr.GET("/article/list", webArticleList)
ccr.GET("/app/pre", creatorArticlePre)
ccr.POST("/upload/image", antispamM.ServeHTTP, uploadImage)
ccr.POST("/draft/delete", deleteDraft)
ccr.POST("/article/delete", delArticle)
ccr.POST("/article/withdraw", withdrawArticle)
ccr.POST("/article/capture", antispamM.ServeHTTP, articleCapture)
ccr.POST("/segment", segment)
}
// article read ping for timing
cr.GET("/read/ping", authSvr.Guest, readPing)
}
cr = r.Group("/x/internal/article", vfySvr.Verify)
{
cr.GET("/meta", meta)
cr.GET("/metas", metas)
cr.GET("/list", list)
cr.GET("/view", view)
cr.GET("/recommends/all", allRecommends)
cr.POST("/refresh_list", refreshLists)
cr.POST("/rebuild_allrc", rebuildAllListReadCount)
cr.POST("/lock", addCheatFilter)
cr.POST("/unlock", delCheatFilter)
}
}
func ping(c *bm.Context) {
if err := artSrv.Ping(c); err != nil {
log.Error("ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}
func buvid(c *bm.Context) string {
buvid := c.Request.Header.Get(_headerBuvid)
if buvid == "" {
cookie, _ := c.Request.Cookie(_buvid)
if cookie != nil {
buvid = cookie.Value
}
}
return buvid
}

View File

@@ -0,0 +1,47 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func like(c *bm.Context) {
var (
id, mid, likeType int64
params = c.Request.Form
idStr, likeTypeStr string
)
idStr = params.Get("id")
likeTypeStr = params.Get("type")
id, _ = strconv.ParseInt(idStr, 10, 64)
likeType, _ = strconv.ParseInt(likeTypeStr, 10, 64)
if (id <= 0) || (likeType == 0) {
c.JSON(nil, ecode.RequestErr)
return
}
// get mid
midInter, _ := c.Get("mid")
mid = midInter.(int64)
meta, err := artSrv.ArticleMeta(c, id)
if err != nil {
c.JSON(nil, err)
return
}
if meta == nil {
c.JSON(nil, ecode.NothingFound)
return
}
err = artSrv.Like(c, mid, id, int(likeType))
if err != nil {
c.JSON(nil, err)
return
}
likeStr := "like"
if likeType == 2 {
likeStr = "like_cancel"
}
artSrv.CheatInfoc.InfoAntiCheat2(c, strconv.FormatInt(meta.Author.Mid, 10), "", strconv.FormatInt(mid, 10), idStr, "article", likeStr, "")
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,91 @@
package http
import (
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"strconv"
)
func listArticles(c *bm.Context) {
var (
id, mid int64
)
id, _ = strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
// get mid
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
c.JSON(artSrv.ListArticles(c, id, mid))
}
func webListArticles(c *bm.Context) {
var (
id, mid int64
)
id, _ = strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
// get mid
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
c.JSON(artSrv.WebListArticles(c, id, mid))
}
func listInfo(c *bm.Context) {
var (
aid int64
)
aid, _ = strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if aid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.ListInfo(c, aid))
}
func upLists(c *bm.Context) {
var (
upMid, sort int64
err error
lists model.UpLists
)
upMid, _ = strconv.ParseInt(c.Request.Form.Get("mid"), 10, 64)
if upMid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
sort, _ = strconv.ParseInt(c.Request.Form.Get("sort"), 10, 64)
if lists, err = artSrv.UpLists(c, upMid, int8(sort)); err != nil {
c.JSON(nil, err)
return
}
if lists.Lists == nil {
lists.Lists = []*model.List{}
}
c.JSON(lists, err)
}
func refreshLists(c *bm.Context) {
var (
id int64
)
id, _ = strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, artSrv.RefreshList(c, id))
}
func rebuildAllListReadCount(c *bm.Context) {
artSrv.RebuildAllListReadCount(c)
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,13 @@
package http
import (
"strconv"
bm "go-common/library/net/http/blademaster"
)
func notice(c *bm.Context) {
params := c.Request.Form
build, _ := strconv.Atoi(params.Get("build"))
c.JSON(artSrv.Notice(params.Get("platform"), build), nil)
}

View File

@@ -0,0 +1,38 @@
package http
import (
"strconv"
artmdl "go-common/app/interface/openplatform/article/model"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func rankCategories(c *bm.Context) {
c.JSON(artSrv.RankCategories(c), nil)
}
func ranks(c *bm.Context) {
var (
request = c.Request
params = request.Form
cid, mid int64
)
cid, _ = strconv.ParseInt(params.Get("cid"), 10, 64)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
data, note, err := artSrv.Ranks(c, cid, mid, metadata.String(c, metadata.RemoteIP))
if err != nil {
c.JSON(nil, err)
return
}
if data == nil {
data = []*artmdl.RankMeta{}
}
res := make(map[string]interface{})
res["data"] = data
res["note"] = note
res["message"] = "ok"
c.JSONMap(res, nil)
}

View File

@@ -0,0 +1,32 @@
package http
import (
"strconv"
"time"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func readPing(c *bm.Context) {
var (
buvid = buvid(c)
aid int64
mid int64
ip = metadata.String(c, metadata.RemoteIP)
cur = time.Now().Unix()
source = c.Request.Form.Get("source")
err error
)
if aid, err = strconv.ParseInt(c.Request.Form.Get("aid"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if i, ok := c.Get("mid"); ok {
mid = i.(int64)
} else {
mid = 0
}
c.JSON(nil, artSrv.ReadPing(c, buvid, aid, mid, ip, cur, source))
}

View File

@@ -0,0 +1,232 @@
package http
import (
"strconv"
"time"
"go-common/app/interface/openplatform/article/conf"
"go-common/app/interface/openplatform/article/dao"
"go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"go-common/library/xstr"
)
const (
_headerBuvid = "Buvid"
_buvid = "buvid3"
_recommendRegion = 0
_rankPage = 9
)
func recommends(c *bm.Context) {
var (
mid int64
rs []*model.RecommendArtWithLike
err error
params = c.Request.Form
aids []int64
sky *model.SkyHorseResp
)
if aids, _ = xstr.SplitInts(params.Get("aids")); len(aids) == 0 {
aids, _ = xstr.SplitInts(params.Get("adis")) //兼容ios客户端bug
}
cid, _ := strconv.ParseInt(params.Get("cid"), 10, 64)
sort, _ := strconv.Atoi(params.Get("sort"))
pn, _ := strconv.ParseInt(params.Get("pn"), 10, 64)
ps, _ := strconv.ParseInt(params.Get("ps"), 10, 64)
if pn <= 0 {
pn = 1
}
if pn > conf.Conf.Article.MaxRecommendPnSize {
c.JSON(nil, ecode.RequestErr)
return
}
if ps <= 0 {
ps = 20
} else if ps > conf.Conf.Article.MaxRecommendPsSize {
ps = conf.Conf.Article.MaxRecommendPsSize
}
device := params.Get("device")
mobiApp := params.Get("mobi_app")
plat := model.Plat(mobiApp, device)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
pageType, _ := strconv.Atoi(params.Get("from"))
if pageType == 0 {
// 分区页
pageType = 2
}
buildStr := params.Get("build")
build, _ := strconv.Atoi(buildStr)
buvid := buvid(c)
if rs, sky, err = artSrv.SkyHorse(c, cid, int(pn), int(ps), aids, sort, mid, build, buvid, plat); err != nil {
dao.PromError("推荐接口")
log.Error("service.Recommends(%d) error(%+v)", mid, err)
c.JSON(nil, err)
return
}
var as []*model.Meta
for _, r := range rs {
as = append(as, &r.Meta)
}
artSrv.RecommendInfoc(mid, plat, pageType, int(cid), build, buvid, metadata.String(c, metadata.RemoteIP), as, false, time.Now(), pn, sky)
if rs == nil {
rs = []*model.RecommendArtWithLike{}
}
res := make(map[string]interface{})
res["aids_len"] = conf.Conf.Article.RecommendAidLen
res["data"] = rs
c.JSONMap(res, nil)
}
func home(c *bm.Context) {
var (
mid int64
rs *model.RecommendHome
err error
params = c.Request.Form
ip = metadata.String(c, metadata.RemoteIP)
aids []int64
sky *model.SkyHorseResp
)
aids, _ = xstr.SplitInts(params.Get("aids"))
pn, _ := strconv.ParseInt(params.Get("pn"), 10, 64)
ps, _ := strconv.ParseInt(params.Get("ps"), 10, 64)
if pn <= 0 {
pn = 1
}
if pn > conf.Conf.Article.MaxRecommendPnSize {
c.JSON(nil, ecode.RequestErr)
return
}
if ps <= 0 {
ps = 20
} else if ps > conf.Conf.Article.MaxRecommendPsSize {
ps = conf.Conf.Article.MaxRecommendPsSize
}
device := params.Get("device")
mobiApp := params.Get("mobi_app")
plat := model.Plat(mobiApp, device)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
pageType, _ := strconv.Atoi(params.Get("from"))
if pageType == 0 {
// 首页tab
pageType = 8
}
buildStr := params.Get("build")
build, _ := strconv.Atoi(buildStr)
buvid := buvid(c)
if rs, sky, err = artSrv.RecommendHome(c, int8(plat), build, int(pn), int(ps), aids, mid, ip, time.Now(), buvid); err != nil {
dao.PromError("推荐接口")
log.Error("service.Recommends(%d) error(%+v)", mid, err)
c.JSON(nil, ecode.Degrade)
return
}
var as []*model.Meta
for _, r := range rs.Articles {
as = append(as, &r.Meta)
}
artSrv.RecommendInfoc(mid, plat, pageType, _recommendRegion, build, buvid, metadata.String(c, metadata.RemoteIP), as, false, time.Now(), pn, sky)
if len(rs.Ranks) > 0 && pn == 1 {
var as []*model.Meta
for _, r := range rs.Ranks {
as = append(as, r.Meta)
}
artSrv.RecommendInfoc(mid, plat, _rankPage, 0, build, buvid, metadata.String(c, metadata.RemoteIP), as, false, time.Now(), pn, nil)
}
res := make(map[string]interface{})
res["aids_len"] = conf.Conf.Article.RecommendAidLen
res["data"] = rs
c.JSONMap(res, nil)
}
func recommendsPlus(c *bm.Context) {
var (
mid int64
rs *model.RecommendPlus
err error
params = c.Request.Form
aids []int64
sky *model.SkyHorseResp
)
if aids, _ = xstr.SplitInts(params.Get("aids")); len(aids) == 0 {
aids, _ = xstr.SplitInts(params.Get("adis")) //兼容ios客户端bug
}
cid, _ := strconv.ParseInt(params.Get("cid"), 10, 64)
sort, _ := strconv.Atoi(params.Get("sort"))
pn, _ := strconv.ParseInt(params.Get("pn"), 10, 64)
ps, _ := strconv.ParseInt(params.Get("ps"), 10, 64)
if pn <= 0 {
pn = 1
}
if pn > conf.Conf.Article.MaxRecommendPnSize {
c.JSON(nil, ecode.RequestErr)
return
}
if ps <= 0 {
ps = 20
} else if ps > conf.Conf.Article.MaxRecommendPsSize {
ps = conf.Conf.Article.MaxRecommendPsSize
}
device := params.Get("device")
mobiApp := params.Get("mobi_app")
plat := model.Plat(mobiApp, device)
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
pageType, _ := strconv.Atoi(params.Get("from"))
if pageType == 0 {
// 分区页
pageType = 2
}
buildStr := params.Get("build")
build, _ := strconv.Atoi(buildStr)
buvid := buvid(c)
if rs, sky, err = artSrv.RecommendPlus(c, cid, int8(plat), build, int(pn), int(ps), aids, mid, time.Now(), sort, buvid); err != nil {
c.JSON(nil, err)
dao.PromError("推荐接口")
log.Error("service.Recommends(%d) error(%+v)", mid, err)
return
}
var as []*model.Meta
for _, r := range rs.Articles {
as = append(as, &r.Meta)
}
artSrv.RecommendInfoc(mid, plat, pageType, int(cid), build, buvid, metadata.String(c, metadata.RemoteIP), as, false, time.Now(), pn, sky)
if len(rs.Ranks) > 0 && pn == 1 {
var as []*model.Meta
for _, r := range rs.Ranks {
as = append(as, r.Meta)
}
artSrv.RecommendInfoc(mid, plat, _rankPage, 0, build, buvid, metadata.String(c, metadata.RemoteIP), as, false, time.Now(), pn, nil)
}
if rs.Articles == nil {
rs.Articles = []*model.RecommendArtWithLike{}
}
res := make(map[string]interface{})
res["aids_len"] = conf.Conf.Article.RecommendAidLen
res["data"] = rs
c.JSONMap(res, nil)
}
func allRecommends(c *bm.Context) {
v := new(struct {
Pn int `form:"pn" validate:"min=1"`
Ps int `form:"ps" validate:"min=1"`
})
if err := c.Bind(v); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
count, recommends, err := artSrv.AllRecommends(c, v.Pn, v.Ps)
c.JSON(map[string]interface{}{
"total": count,
"list": recommends,
}, err)
}

View File

@@ -0,0 +1,23 @@
package http
import (
"strconv"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func segment(c *bm.Context) {
var params = c.Request.Form
idStr := params.Get("id")
content := params.Get("content")
withTagStr := params.Get("with_tag")
id, _ := strconv.Atoi(idStr)
withTag, _ := strconv.Atoi(withTagStr)
if id == 0 || content == "" {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.Segment(c, int32(id), content, withTag, "draft"))
}

View File

@@ -0,0 +1,7 @@
package http
import bm "go-common/library/net/http/blademaster"
func sentinel(c *bm.Context) {
c.JSON(map[string]interface{}{"sentinel": artSrv.Sentinel(c)}, nil)
}

View File

@@ -0,0 +1,26 @@
package http
import (
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"strconv"
)
func addShare(c *bm.Context) {
var (
id int64
mid int64
params = c.Request.Form
)
idStr := params.Get("id")
id, _ = strconv.ParseInt(idStr, 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
c.JSON(nil, artSrv.AddShare(c, id, mid, metadata.String(c, metadata.RemoteIP)))
}

View File

@@ -0,0 +1,18 @@
package http
import (
bm "go-common/library/net/http/blademaster"
)
func userNotice(c *bm.Context) {
midInter, _ := c.Get("mid")
mid := midInter.(int64)
c.JSON(artSrv.UserNoticeState(c, mid))
}
func updateUserNotice(c *bm.Context) {
midInter, _ := c.Get("mid")
mid := midInter.(int64)
typ := c.Request.Form.Get("type")
c.JSON(nil, artSrv.UpdateUserNoticeState(c, mid, typ))
}

View File

@@ -0,0 +1,222 @@
package http
import (
"strconv"
"time"
"go-common/app/interface/openplatform/article/conf"
artmdl "go-common/app/interface/openplatform/article/model"
"go-common/library/ecode"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
)
func view(c *bm.Context) {
var (
id int64
err error
art *artmdl.Article
params = c.Request.Form
)
idStr := params.Get("id")
id, _ = strconv.ParseInt(idStr, 10, 64)
if id <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if art, err = artSrv.Article(c, id); err != nil {
c.JSON(nil, err)
return
}
if art == nil {
c.JSON(nil, ecode.NothingFound)
return
}
c.JSON(art, err)
}
func addView(c *bm.Context) {
var (
mid int64
params = c.Request.Form
page = params.Get("page")
from = params.Get("from")
ip = metadata.String(c, metadata.RemoteIP)
)
if page == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if from == "" {
from = "unknow"
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
device := params.Get("device")
mobiApp := params.Get("mobi_app")
plat := artmdl.Plat(mobiApp, device)
build := params.Get("build")
buvid := buvid(c)
// for tianma mainCard -> 7
if from == "mainCard" {
from = "7"
}
ua := c.Request.Header.Get("User-Agent")
referer := c.Request.Header.Get("Referer")
artSrv.ShowInfoc(ip, time.Now(), buvid, mid, plat, page, from, build, ua, referer)
c.JSON(nil, nil)
}
func viewInfo(c *bm.Context) {
var (
id int64
mid int64
data *artmdl.ViewInfo
request = c.Request
params = request.Form
ip = metadata.String(c, metadata.RemoteIP)
err error
)
idStr := params.Get("id")
id, _ = strconv.ParseInt(idStr, 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
cheat := cheatInfo(c, mid, id)
device := params.Get("device")
mobiApp := params.Get("mobi_app")
plat := artmdl.Plat(mobiApp, device)
from := params.Get("from")
if data, err = artSrv.ViewInfo(c, mid, id, ip, cheat, plat, from); err != nil {
c.JSON(nil, err)
return
}
buildStr := params.Get("build")
build, _ := strconv.Atoi(buildStr)
buvid := buvid(c)
if from == "articleSlideShow" {
data.Pre, data.Next = artSrv.ViewList(c, id, buvid, "articleSlide", ip, build, plat, mid)
} else {
data.Pre, data.Next = artSrv.ViewList(c, id, buvid, from, ip, build, plat, mid)
}
// for tianma mainCard -> 7
if from == "mainCard" {
from = "7"
}
if from != "articleSlide" {
ua := c.Request.Header.Get("User-Agent")
artSrv.ViewInfoc(mid, plat, build, "doc", from, buvid, id, time.Now(), ua)
artSrv.AIViewInfoc(mid, plat, build, "doc", from, buvid, id, time.Now(), ua)
}
c.JSON(data, nil)
}
func list(c *bm.Context) {
var (
mid int64
request = c.Request
params = request.Form
pn, ps int64
)
midStr := params.Get("mid")
mid, _ = strconv.ParseInt(midStr, 10, 64)
if mid <= 0 {
c.JSON(nil, ecode.RequestErr)
return
}
pnStr := params.Get("pn")
pn, _ = strconv.ParseInt(pnStr, 10, 64)
if pn <= 0 {
pn = 1
}
psStr := params.Get("ps")
ps, _ = strconv.ParseInt(psStr, 10, 64)
if ps <= 0 {
ps = 20
} else if ps > conf.Conf.Article.MaxUpperListPsSize {
ps = conf.Conf.Article.MaxUpperListPsSize
}
c.JSON(artSrv.UpArtMetasAndLists(c, mid, int(pn), int(ps), artmdl.FieldDefault))
}
func earlyArticles(c *bm.Context) {
var (
err error
aid int64
params = c.Request.Form
)
aidStr := params.Get("aid")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(artSrv.MoreArts(c, aid))
}
func moreArts(c *bm.Context) {
var (
err error
aid, mid int64
params = c.Request.Form
)
aidStr := params.Get("aid")
if aid, err = strconv.ParseInt(aidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if midInter, ok := c.Get("mid"); ok {
mid = midInter.(int64)
}
c.JSON(artSrv.Mores(c, aid, mid))
}
func cheatInfo(c *bm.Context, mid, id int64) (res *artmdl.CheatInfo) {
req := c.Request
params := req.Form
res = &artmdl.CheatInfo{
Mid: strconv.FormatInt(mid, 10),
Cvid: strconv.FormatInt(id, 10),
Refer: req.Header.Get("Referer"),
UA: req.Header.Get("User-Agent"),
Ts: strconv.FormatInt(time.Now().Unix(), 10),
IP: metadata.String(c, metadata.RemoteIP),
}
if csid, err := req.Cookie("sid"); err == nil {
res.Sid = csid.Value
}
if params.Get("access_key") == "" {
res.Client = infoc.ClientWeb
if ck, err := req.Cookie("buvid3"); err == nil {
res.Buvid = ck.Value
}
} else {
if params.Get("platform") == "ios" {
if params.Get("device") == "pad" {
res.Client = infoc.ClientIpad
} else {
res.Client = infoc.ClientIphone
}
} else if params.Get("platform") == "android" {
res.Client = infoc.ClientAndroid
}
res.Buvid = req.Header.Get("buvid")
res.Build = params.Get("build")
}
return
}
func actInfo(c *bm.Context) {
var (
request = c.Request
params = request.Form
)
mobiApp := params.Get("mobi_app")
plat := artmdl.Plat(mobiApp, "")
c.JSON(artSrv.ActInfo(c, plat))
}

View File

@@ -0,0 +1,87 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
go_test(
name = "go_default_test",
srcs = ["article_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//vendor/github.com/smartystreets/goconvey/convey:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"activity.go",
"apply.go",
"article.go",
"author.go",
"cards.go",
"creation.go",
"hotspots.go",
"infoc.go",
"list.go",
"rank.go",
"rpc.go",
"setting.go",
"sort.go",
],
embed = [":model_go_proto"],
importpath = "go-common/app/interface/openplatform/article/model",
tags = ["automanaged"],
deps = [
"//app/interface/main/creative/model/data:go_default_library",
"//app/service/main/account/model:go_default_library",
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
],
)
proto_library(
name = "model_proto",
srcs = ["model.proto"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "model_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
importpath = "go-common/app/interface/openplatform/article/model",
proto = ":model_proto",
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/interface/openplatform/article/model/search:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,44 @@
package model
// ActInfo .
type ActInfo struct {
Activities []*Activity `json:"activities"`
Banners []*Banner `json:"banners"`
}
// AnniversaryInfo .
type AnniversaryInfo struct {
Mid int64 `json:"mid"`
Uname string `json:"uname"`
Face string `json:"face"`
ReaderInfo *AnniversaryReader `json:"reader_info"`
AuthorInfo *AnniversaryAuthor `json:"author_info"`
}
// AnniversaryAuthor .
type AnniversaryAuthor struct {
Articles int32 `json:"articles"`
Words int64 `json:"words"`
Views int64 `json:"views"`
Coins int64 `json:"coins"`
Title string `json:"title"`
Publish string `json:"publish"`
Rank string `json:"rank"`
ReaderMid int64 `json:"reader"`
ReaderUname string `json:"reader_name"`
ReaderFace string `json:"reader_face"`
}
// AnniversaryReader .
type AnniversaryReader struct {
Words int64 `json:"words"`
Views int64 `json:"views"`
Coins int64 `json:"coins"`
Comments int64 `json:"comments"`
Title string `json:"title"`
AuthorMid int64 `json:"author"`
AuthorUname string `json:"author_name"`
Rank string `json:"rank"`
FirstComment string `json:"first_comment"`
CommentDate string `json:"comment_date"`
}

View File

@@ -0,0 +1,14 @@
package model
//Apply apply model
type Apply struct {
Verify bool `json:"verify"`
Forbid bool `json:"forbid"`
Phone int `json:"phone"`
}
// Identify user verify info.
type Identify struct {
Identify int `json:"identify"`
Phone int `json:"phone"`
}

View File

@@ -0,0 +1,750 @@
package model
import (
"fmt"
"regexp"
"strconv"
"strings"
account "go-common/app/service/main/account/model"
xtime "go-common/library/time"
)
// Const .
const (
// State
StateAutoLock = -11
StateLock = -10
StateReject = -3
StatePending = -2
StateOpen = 0
StateOpenPending = 2
StateOpenReject = 3
StateAutoPass = 4
StateRePending = 5 // 重复编辑待审
StateReReject = 6 // 重复编辑未通过
StateRePass = 7 // 重复编辑通过
// groups for creation center.
GroupAll = 0 // except draft and deleted. 0 2 -2 3 -3 -10
GroupPending = 1 // -2 2
GroupPassed = 2 // 0
GroupUnpassed = 3 // -10 -3 3
NoLikeState = 0
LikeState = 1
DislikeState = 2
// Templates
TemplateText = 1
TemplateSingleImg = 2
TemplateMultiImg = 3
TemplateSingleBigImg = 4
// Attributes
//AttrBitNoDistribute 禁止分发(空间/分区/动态)
AttrBitNoDistribute = uint(1)
//AttrBitNoRegion 禁止在分区页显示
AttrBitNoRegion = uint(2)
//AttrBitNoRank 禁止排行
AttrBitNoRank = uint(3)
// Author
// AuthorStatePass 过审
AuthorStateReject = -1
AuthorStatePending = 0
AuthorStatePass = 1
AuthorStateClose = 2
// AuthorStateIgnore = 3
)
var cleanURLRegexp = regexp.MustCompile(`^.+hdslb.com`)
var bfsRegexp = regexp.MustCompile(`^https?://.{1,6}\.hdslb+\.com/.+(?:jpg|gif|png|webp|jpeg)$`)
// Categories for sorting category.
type Categories []*Category
func (as Categories) Len() int { return len(as) }
func (as Categories) Less(i, j int) bool {
return as[i].Position < as[j].Position
}
func (as Categories) Swap(i, j int) { as[i], as[j] = as[j], as[i] }
// StatMsg means article's stat message in databus.
type StatMsg struct {
View *int64 `json:"view"`
Like *int64 `json:"like"`
Dislike *int64 `json:"dislike"`
Favorite *int64 `json:"fav"`
Reply *int64 `json:"reply"`
Share *int64 `json:"share"`
Coin *int64 `json:"coin"`
Aid int64 `json:"aid"`
Mid int64 `json:"mid"`
IP string `json:"ip"`
CheatInfo *CheatInfo `json:"cheat_info"`
}
func (sm *StatMsg) String() (res string) {
if sm == nil {
res = "<nil>"
return
}
res = fmt.Sprintf("aid: %v, mid: %v, ip: %v, view(%s) likes(%s) dislike(%s) favorite(%s) reply(%s) share(%s) coin(%s)", sm.Aid, sm.Mid, sm.IP, formatPInt(sm.View), formatPInt(sm.Like), formatPInt(sm.Dislike), formatPInt(sm.Favorite), formatPInt(sm.Reply), formatPInt(sm.Share), formatPInt(sm.Coin))
return
}
// CheatInfo .
type CheatInfo struct {
Valid string `json:"valid"`
Client string `json:"client"`
Cvid string `json:"cvid"`
Mid string `json:"mid"`
Lv string `json:"lv"`
Ts string `json:"ts"`
IP string `json:"ip"`
UA string `json:"ua"`
Refer string `json:"refer"`
Sid string `json:"sid"`
Buvid string `json:"buvid"`
DeviceID string `json:"device_id"`
Build string `json:"build"`
Reason string `json:"reason"`
}
func formatPInt(s *int64) (res string) {
if s == nil {
return "<nil>"
}
return fmt.Sprintf("%d", *s)
}
// DraftMsg means article's draft message in databus.
type DraftMsg struct {
Aid int64 `json:"aid"`
Mid int64 `json:"mid"`
}
// Draft draft struct.
type Draft struct {
*Article
Tags []string `json:"tags"`
ListID int64 `json:"list_id"`
List *List `json:"list"`
}
// Metas Metas
type Metas []*Meta
func (as Metas) Len() int { return len(as) }
func (as Metas) Less(i, j int) bool {
var it, jt xtime.Time
if as[i] != nil {
it = as[i].PublishTime
}
if as[j] != nil {
jt = as[j].PublishTime
}
return it > jt
}
func (as Metas) Swap(i, j int) { as[i], as[j] = as[j], as[i] }
// CreationArtsType creation article-list type's count.
type CreationArtsType struct {
All int `json:"all"`
Audit int `json:"audit"`
Passed int `json:"passed"`
NotPassed int `json:"not_passed"`
}
// ArtPage article page.
type ArtPage struct {
Pn int `json:"pn"`
Ps int `json:"ps"`
Total int `json:"total"`
}
// CreationArts creation article list.
type CreationArts struct {
Articles []*Meta `json:"articles"`
Type *CreationArtsType `json:"type"`
Page *ArtPage `json:"page"`
}
// Drafts draft list.
type Drafts struct {
Drafts []*Draft `json:"drafts"`
Page *ArtPage `json:"page"`
}
// UpArtMetas article list.
type UpArtMetas struct {
Articles []*Meta `json:"articles"`
Pn int `json:"pn"`
Ps int `json:"ps"`
Count int `json:"count"`
}
// UpArtMetasLists .
type UpArtMetasLists struct {
*UpArtMetas
UpLists UpLists `json:"up_lists"`
}
// IsNormal judge whether article's state is normal.
func (a *Meta) IsNormal() bool {
return (a != nil) && (a.State >= StateOpen)
}
// IsNormal judge article state.
func (a *Article) IsNormal() bool {
if (a == nil) || (a.Meta == nil) {
return false
}
return a.Meta.IsNormal()
}
// AttrVal gets attr val by bit.
func (a *Meta) AttrVal(bit uint) bool {
return ((a.Attributes>>bit)&int32(1) == 1)
}
// AttrSet sets attr value by bit.
func (a *Meta) AttrSet(v int32, bit uint) {
a.Attributes = a.Attributes&(^(1 << bit)) | (v << bit)
}
// Strong fill blank images and tags
func (a *Meta) Strong() *Meta {
if a.ImageURLs == nil {
a.ImageURLs = []string{}
}
if a.OriginImageURLs == nil {
a.OriginImageURLs = []string{}
}
if a.Tags == nil {
a.Tags = []*Tag{}
}
return a
}
// AuthorPermission recode of article_authors table.
type AuthorPermission struct {
State int `json:"state"`
Rtime xtime.Time `json:"rtime"`
}
// Favorite user favorite list.
type Favorite struct {
*Meta
FavoriteTime int64 `json:"favorite_time"`
Valid bool `json:"valid"`
}
// Page model
type Page struct {
Pn int `json:"pn"`
Ps int `json:"ps"`
Total int `json:"total"`
}
// RecommendArt model
type RecommendArt struct {
Meta
Recommend
}
// RecommendArtWithLike model
type RecommendArtWithLike struct {
RecommendArt
LikeState int `json:"like_state"`
}
// MetaWithLike meta with like
type MetaWithLike struct {
Meta
LikeState int `json:"like_state"`
}
// Recommend model
type Recommend struct {
ArticleID int64 `json:"article_id,omitempty"`
Position int `json:"-"`
EndTime int64 `json:"-"`
Rec bool `json:"rec"`
RecFlag bool `json:"rec_flag"`
RecText string `json:"rec_text"`
RecImageURL string `json:"rec_image_url"`
RecImageStartTime int64 `json:"-"`
RecImageEndTime int64 `json:"-"`
}
// ViewInfo model
type ViewInfo struct {
Like int8 `json:"like"`
Attention bool `json:"attention"`
Favorite bool `json:"favorite"`
Coin int64 `json:"coin"`
Stats Stats `json:"stats"`
Title string `json:"title"`
BannerURL string `json:"banner_url"`
Mid int64 `json:"mid"`
AuthorName string `json:"author_name"`
IsAuthor bool `json:"is_author"`
ImageURLs []string `json:"image_urls"`
OriginImageURLs []string `json:"origin_image_urls"`
Shareable bool `json:"shareable"`
ShowLaterWatch bool `json:"show_later_watch"`
ShowSmallWindow bool `json:"show_small_window"`
InList bool `json:"in_list"`
Pre int64 `json:"pre"`
Next int64 `json:"next"`
}
// Group2State mapping creation group to
func Group2State(group int) (states []int64) {
switch group {
case GroupPassed:
states = []int64{StateOpen, StateAutoPass, StateRePass, StateReReject}
case GroupPending:
states = []int64{StatePending, StateOpenPending, StateRePending}
case GroupUnpassed:
states = []int64{StateReject, StateOpenReject, StateLock, StateAutoLock}
case GroupAll:
fallthrough
default:
states = []int64{StateOpen, StatePending, StateOpenPending, StateReject, StateOpenReject, StateLock, StateAutoPass, StateAutoLock, StateRePending, StateRePass, StateReReject}
}
return
}
// CompleteURL adds host on path.
func CompleteURL(path string) (url string) {
if path == "" {
// url = "http://static.hdslb.com/images/transparent.gif"
return
}
url = path
if strings.Index(path, "//") == 0 || strings.Index(path, "http://") == 0 || strings.Index(path, "https://") == 0 {
return
}
url = "https://i0.hdslb.com" + url
return
}
// CleanURL cuts host.
func CleanURL(url string) (path string) {
path = string(cleanURLRegexp.ReplaceAll([]byte(url), nil))
return
}
// CompleteURLs .
func CompleteURLs(paths []string) (urls []string) {
for _, v := range paths {
urls = append(urls, CompleteURL(v))
}
return
}
// CleanURLs .
func CleanURLs(urls []string) (paths []string) {
for _, v := range urls {
paths = append(paths, CleanURL(v))
}
return
}
// Recommends model
type Recommends [][]*Recommend
func (as Recommends) Len() int { return len(as) }
func (as Recommends) Less(i, j int) bool {
return as[i][0].Position > as[j][0].Position
}
func (as Recommends) Swap(i, j int) { as[i], as[j] = as[j], as[i] }
// RecommendHome .
type RecommendHome struct {
RecommendPlus
Categories []*Category `json:"categories"`
IP string `json:"ip"`
}
// RecommendPlus .
type RecommendPlus struct {
Banners []*Banner `json:"banners"`
Articles []*RecommendArtWithLike `json:"articles"`
Ranks []*RankMeta `json:"ranks"`
Hotspots []*Hotspot `json:"hotspots"`
}
// Banner struct
type Banner struct {
ID int `json:"id"`
Plat int8 `json:"-"`
Position int `json:"index"`
Title string `json:"title"`
Image string `json:"image"`
URL string `json:"url"`
Build int `json:"-"`
Condition string `json:"-"`
Rule string `json:"-"`
ResID int `json:"resource_id"`
ServerType int `json:"server_type"`
CmMark int `json:"cm_mark"`
IsAd bool `json:"is_ad"`
RequestID string `json:"request_id"`
}
// ConvertPlat convert plat from resource
func ConvertPlat(p int8) (plat int8) {
switch p {
case 0: // resource iphone
plat = PlatPC
case 1: // resource iphone
plat = PlatIPhone
case 2: // resource android
plat = PlatAndroid
case 3: // resource pad
plat = PlatIPad
case 4: // resource iphoneg
plat = PlatIPhoneI
case 5: // resource androidg
plat = PlatAndroidG
case 6: // resource padg
plat = PlatIPadI
case 7: // resource h5
plat = PlatH5
case 8: // resource androidi
plat = PlatAndroidI
}
return
}
// BannerRule .
type BannerRule struct {
Area string `json:"area"`
Hash string `json:"hash"`
Build int `json:"build"`
Condition string `json:"conditions"`
Channel string `json:"channel"`
}
// NoDistributeAttr check if no distribute
func NoDistributeAttr(attr int32) bool {
meta := Meta{Attributes: attr}
return meta.AttrVal(AttrBitNoDistribute)
}
// NoRegionAttr check if no region
func NoRegionAttr(attr int32) bool {
meta := Meta{Attributes: attr}
return meta.AttrVal(AttrBitNoRegion)
}
// AuthorLimit .
type AuthorLimit struct {
Limit int `json:"limit"`
State int `json:"state"`
Rtime xtime.Time `json:"rtime"`
}
// Forbid .
func (a *AuthorLimit) Forbid() bool {
// state -1 未通过 0待审 1通过 2关闭 3忽略
if a == nil {
return false
}
if a.State == AuthorStatePass {
return false
}
return true
}
// Pass .
func (a *AuthorLimit) Pass() bool {
return (a != nil) && (a.State == AuthorStatePass)
}
// Notice notice
type Notice struct {
ID int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
URL string `json:"url"`
Plat int `json:"-"`
Condition int `json:"-"`
Build int `json:"-"`
}
// MoreArts .
type MoreArts struct {
Articles []*Meta `json:"articles"`
Total int `json:"total"`
ReadCount int64 `json:"read_count"`
Author *AccountCard `json:"author"`
Attention bool `json:"attention"`
}
// AccountCard .
type AccountCard struct {
Mid string `json:"mid"`
Name string `json:"name"`
Approve bool `json:"approve"`
Sex string `json:"sex"`
Rank string `json:"rank"`
Face string `json:"face"`
DisplayRank string `json:"DisplayRank"`
Regtime int64 `json:"regtime"`
Spacesta int `json:"spacesta"`
Birthday string `json:"birthday"`
Place string `json:"place"`
Description string `json:"description"`
Article int `json:"article"`
Attentions []int64 `json:"attentions"`
Fans int `json:"fans"`
Friend int `json:"friend"`
Attention int `json:"attention"`
Sign string `json:"sign"`
LevelInfo struct {
Cur int `json:"current_level"`
Min int `json:"current_min"`
NowExp int `json:"current_exp"`
NextExp interface{} `json:"next_exp"`
} `json:"level_info"`
Pendant struct {
Pid int `json:"pid"`
Name string `json:"name"`
Image string `json:"image"`
Expire int `json:"expire"`
} `json:"pendant"`
Nameplate struct {
Nid int `json:"nid"`
Name string `json:"name"`
Image string `json:"image"`
ImageSmall string `json:"image_small"`
Level string `json:"level"`
Condition string `json:"condition"`
} `json:"nameplate"`
OfficialVerify struct {
Type int `json:"type"`
Desc string `json:"desc"`
} `json:"official_verify"`
Vip struct {
Type int `json:"vipType"`
DueDate int64 `json:"vipDueDate"`
DueRemark string `json:"dueRemark"`
AccessStatus int `json:"accessStatus"`
VipStatus int `json:"vipStatus"`
VipStatusWarn string `json:"vipStatusWarn"`
} `json:"vip"`
}
// FromCard from account card.
func (ac *AccountCard) FromCard(card *account.Card) {
ac.Mid = strconv.FormatInt(card.Mid, 10)
ac.Name = card.Name
// ac.Approve =
ac.Sex = card.Sex
ac.Rank = strconv.FormatInt(int64(card.Rank), 10)
ac.Face = card.Face
ac.DisplayRank = strconv.FormatInt(int64(card.Rank), 10)
// ac.Regtime =
// ac.Spacesta =
// ac.Birthday =
// ac.Place =
// ac.Description =
// ac.Article =
ac.Attentions = []int64{}
// ac.Fans =
// ac.Friend =
// ac.Attention =
ac.Sign = card.Sign
ac.LevelInfo.Cur = int(card.Level)
ac.Pendant.Pid = card.Pendant.Pid
ac.Pendant.Name = card.Pendant.Name
ac.Pendant.Image = card.Pendant.Image
ac.Pendant.Expire = card.Pendant.Expire
ac.Nameplate.Nid = card.Nameplate.Nid
ac.Nameplate.Name = card.Nameplate.Name
ac.Nameplate.Image = card.Nameplate.Image
ac.Nameplate.ImageSmall = card.Nameplate.ImageSmall
ac.Nameplate.Level = card.Nameplate.Level
ac.Nameplate.Condition = card.Nameplate.Condition
if card.Official.Role == 0 {
ac.OfficialVerify.Type = -1
} else {
if card.Official.Role <= 2 {
ac.OfficialVerify.Type = 0
} else {
ac.OfficialVerify.Type = 1
}
ac.OfficialVerify.Desc = card.Official.Title
}
ac.Vip.Type = int(card.Vip.Type)
ac.Vip.VipStatus = int(card.Vip.Status)
ac.Vip.DueDate = card.Vip.DueDate
}
// FromProfileStat .
func (ac *AccountCard) FromProfileStat(card *account.ProfileStat) {
ac.Mid = strconv.FormatInt(card.Mid, 10)
ac.Name = card.Name
// ac.Approve =
ac.Sex = card.Sex
ac.Rank = strconv.FormatInt(int64(card.Rank), 10)
ac.Face = card.Face
ac.DisplayRank = strconv.FormatInt(int64(card.Rank), 10)
// ac.Regtime =
// ac.Spacesta =
// ac.Birthday =
// ac.Place =
// ac.Description =
// ac.Article =
ac.Attentions = []int64{}
ac.Fans = int(card.Follower)
// ac.Friend =
// ac.Attention =
ac.Sign = card.Sign
ac.LevelInfo.Cur = int(card.Level)
ac.Pendant.Pid = card.Pendant.Pid
ac.Pendant.Name = card.Pendant.Name
ac.Pendant.Image = card.Pendant.Image
ac.Pendant.Expire = card.Pendant.Expire
ac.Nameplate.Nid = card.Nameplate.Nid
ac.Nameplate.Name = card.Nameplate.Name
ac.Nameplate.Image = card.Nameplate.Image
ac.Nameplate.ImageSmall = card.Nameplate.ImageSmall
ac.Nameplate.Level = card.Nameplate.Level
ac.Nameplate.Condition = card.Nameplate.Condition
if card.Official.Role == 0 {
ac.OfficialVerify.Type = -1
} else {
if card.Official.Role <= 2 {
ac.OfficialVerify.Type = 0
} else {
ac.OfficialVerify.Type = 1
}
ac.OfficialVerify.Desc = card.Official.Title
}
ac.Vip.Type = int(card.Vip.Type)
ac.Vip.VipStatus = int(card.Vip.Status)
ac.Vip.DueDate = card.Vip.DueDate
}
// NoticeState .
type NoticeState map[string]bool
// 数据库为tinyint 长度必须小于7 字段只能追加
var _noticeStates = []string{"lead", "new"}
// NewNoticeState .
func NewNoticeState(value int64) (res NoticeState) {
res = make(map[string]bool)
for i, name := range _noticeStates {
res[name] = ((value>>uint(i))&int64(1) == 1)
}
return
}
// ToInt64 .
func (n NoticeState) ToInt64() (res int64) {
for i, name := range _noticeStates {
if n[name] {
res = res | (1 << uint(i))
}
}
return
}
// Activity .
type Activity struct {
ActURL string `json:"act_url"`
Author string `json:"author"`
Cover string `json:"cover"`
Ctime string `json:"ctime"`
Dic string `json:"dic"`
Etime string `json:"etime"`
Flag string `json:"flag"`
H5Cover string `json:"h5_cover"`
ID int64 `json:"id"`
Letime string `json:"letime"`
Level string `json:"level"`
Lstime string `json:"lstime"`
Mtime string `json:"mtime"`
Name string `json:"name"`
Oid int64 `json:"oid"`
State int64 `json:"state"`
Stime string `json:"stime"`
Tags string `json:"tags"`
Type int64 `json:"type"`
Uetime string `json:"uetime"`
Ustime string `json:"ustime"`
}
// SkyHorseResp response
type SkyHorseResp struct {
Code int `json:"code"`
Data []struct {
ID int64 `json:"id"`
AvFeature string `json:"av_feature"`
} `json:"data"`
UserFeature string `json:"user_feature"`
}
// CheckBFSImage check bfs file
func CheckBFSImage(src string) bool {
return bfsRegexp.MatchString(src)
}
// FillDefaultImage .
func (l *List) FillDefaultImage(image string) {
if l != nil && l.ImageURL == "" {
l.ImageURL = image
}
}
// Articles .
type Articles struct {
*Article
Pre int64 `json:"pre"`
Next int64 `json:"next"`
}
// ArticleViewList .
type ArticleViewList struct {
Position int `json:"position"`
Aids []int64 `json:"articles_id"`
From string `json:"from"`
Mid int64 `json:"mid"`
Build int `json:"build"`
Buvid string `json:"buvid"`
Plat int8 `json:"plat"`
}
// TagArts .
type TagArts struct {
Tid int64 `json:"tid"`
Aids []int64 `json:"aids"`
}
// MediaResp .
type MediaResp struct {
Code int `json:"code"`
Message string `json:"message"`
Result *MediaResult `json:"result"`
}
// MediaResult .
type MediaResult struct {
Score int32 `json:"score"`
Media struct {
MediaID int64 `json:"media_id"`
Title string `json:"title"`
Cover string `json:"cover"`
Area string `json:"area"`
TypeID int32 `json:"type_id"`
TypeName string `json:"type_name"`
} `json:"media"`
}

View File

@@ -0,0 +1,58 @@
package model
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestCleanURL(t *testing.T) {
Convey("different urls", t, func() {
url := "/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"
So(CleanURL("http://i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"), ShouldEqual, url)
So(CleanURL("https://i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"), ShouldEqual, url)
So(CleanURL("//i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"), ShouldEqual, url)
So(CleanURL("http://uat-i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"), ShouldEqual, url)
So(CleanURL("https://uat-i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"), ShouldEqual, url)
So(CleanURL("//uat-i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"), ShouldEqual, url)
})
}
func TestCompleteURL(t *testing.T) {
Convey("prefix http", t, func() {
url := "http://i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"
So(CompleteURL(url), ShouldEqual, url)
})
Convey("prefix https", t, func() {
url := "https://i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"
So(CompleteURL(url), ShouldEqual, url)
})
Convey("prefix //", t, func() {
url := "//i0.hdslb.com/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"
So(CompleteURL(url), ShouldEqual, url)
})
Convey("prefix no prefix", t, func() {
url := "/bfs/article/3b8b57821a94af4c171218a01d9cee44dc0dccf8.jpg"
So(CompleteURL(url), ShouldEqual, "https://i0.hdslb.com"+url)
})
}
func TestNoticeState(t *testing.T) {
Convey("ToInt64", t, func() {
a := NoticeState{"lead": true, "new": true}
So(a.ToInt64(), ShouldEqual, 3)
})
Convey("ToInt64 2", t, func() {
a := NoticeState{"lead": true, "new": false}
So(a.ToInt64(), ShouldEqual, 1)
})
Convey("NewNoticeState", t, func() {
a := NoticeState{"lead": true, "new": true}
So(NewNoticeState(3), ShouldResemble, a)
})
Convey("NewNoticeState 2", t, func() {
a := NoticeState{"lead": true, "new": false}
So(NewNoticeState(1), ShouldResemble, a)
})
}

View File

@@ -0,0 +1,22 @@
package model
// RecommendAuthor .
type RecommendAuthor struct {
UpID int64 `json:"up_id"`
RecReason string `json:"rec_reason"`
RecType int `json:"rec_type"`
Tid int `json:"tid"`
Stid int `json:"second_tid"`
}
// RecommendAuthors .
type RecommendAuthors struct {
Count int `json:"count"`
Authors []*RecAuthor `json:"authors"`
}
// RecAuthor .
type RecAuthor struct {
*AccountCard
RecReason string `json:"rec_reason"`
}

View File

@@ -0,0 +1,104 @@
package model
import (
"fmt"
)
// Cards
const (
CardPrefixBangumi = "ss"
CardPrefixBangumiEp = "ep"
CardPrefixTicket = "pw"
CardPrefixMall = "sp"
CardPrefixAudio = "au"
CardPrefixArchive = "av"
CardPrefixArticle = "cv"
)
// TicketCard .
type TicketCard struct {
ID int64 `json:"id"`
Name string `json:"name"`
Image string `json:"performance_image"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Province string `json:"province_name"`
City string `json:"city_name"`
District string `json:"district_name"`
Venue string `json:"venue_name"`
PriceLow float64 `json:"price_low"`
URL string `json:"url"`
}
// MallCard .
type MallCard struct {
ID int64 `json:"itemsId"`
Name string `json:"name"`
Brief string `json:"brief"`
Images []string `json:"img"`
Price int64 `json:"price"`
Type int `json:"type"`
}
// AudioCard .
type AudioCard struct {
ID int64 `json:"song_id"`
Title string `json:"title"`
UpMid int64 `json:"up_mid"`
UpName string `json:"up_name"`
Play int64 `json:"play_num"`
Reply int64 `json:"reply_num"`
CoverURL string `json:"cover_url"`
}
// BangumiCard .
type BangumiCard struct {
ID int64 `json:"season_id"`
Type int64 `json:"season_type"`
TypeName string `json:"season_type_name"`
Image string `json:"cover"`
Title string `json:"title"`
Rating struct {
Score float64 `json:"score"`
Count int64 `json:"count"`
} `json:"rating"`
Playable bool `json:"playable"`
FollowCount int64 `json:"follow_count"`
PlayCount int64 `json:"play_count"`
}
// Cards .
type Cards struct {
Type string `json:"type,omitempty"`
TicketCard *TicketCard `json:"ticket_card,omitempty"`
BangumiCard *BangumiCard `json:"bangumi_card,omitempty"`
MallCard *MallCard `json:"mall_card,omitempty"`
AudioCard *AudioCard `json:"audio_card,omitempty"`
}
// Key .
func (c *Cards) Key() string {
var id int64
if c.TicketCard != nil {
id = c.TicketCard.ID
} else if c.BangumiCard != nil {
id = c.BangumiCard.ID
} else if c.MallCard != nil {
id = c.MallCard.ID
} else if c.AudioCard != nil {
id = c.AudioCard.ID
}
return fmt.Sprintf("%s%d", c.Type, id)
}
// Item .
func (c *Cards) Item() interface{} {
if c.TicketCard != nil {
return c.TicketCard
} else if c.BangumiCard != nil {
return c.BangumiCard
} else if c.MallCard != nil {
return c.MallCard
}
return c.AudioCard.ID
}

View File

@@ -0,0 +1,157 @@
package model
import (
"go-common/app/interface/main/creative/model/data"
"go-common/library/time"
)
// const
const (
// ReprintForbid 禁止转载.
ReprintForbid = int8(0)
// ReprintAllow 允许规范转载.
ReprintAllow = int8(1)
// NoImage 无图.
///NoImage = int8(1)
// HeadImage 头图.
HeadImage = int8(4)
)
var (
_reprint = map[int8]int8{
ReprintForbid: ReprintForbid,
ReprintAllow: ReprintAllow,
}
_tid = map[int8]int8{
TemplateText: 0,
TemplateSingleImg: 1,
TemplateMultiImg: 3,
TemplateSingleBigImg: 1,
}
)
// InReprints check reprint in all reprints.
func InReprints(rp int8) (ok bool) {
_, ok = _reprint[rp]
return
}
// InTemplateID check tid in all tids.
func InTemplateID(tid int8) (ok bool) {
_, ok = _tid[tid]
return
}
// ValidTemplate checks template id & images count.
func ValidTemplate(tid int32, imgs []string) bool {
var images []string
for _, image := range imgs {
if image != "" {
images = append(images, image)
}
}
return len(images) == int(_tid[int8(tid)])
}
// UpStat for bigdata article up stat
type UpStat struct {
View int64 `json:"view"`
Reply int64 `json:"reply"`
Like int64 `json:"like"`
Coin int64 `json:"coin"`
Fav int64 `json:"fav"`
Share int64 `json:"share"`
PreView int64 `json:"-"`
PreReply int64 `json:"-"`
PreLike int64 `json:"-"`
PreCoin int64 `json:"-"`
PreFav int64 `json:"-"`
PreShare int64 `json:"-"`
IncrView int64 `json:"incr_view"`
IncrReply int64 `json:"incr_reply"`
IncrLike int64 `json:"incr_like"`
IncrCoin int64 `json:"incr_coin"`
IncrFav int64 `json:"incr_fav"`
IncrShare int64 `json:"incr_share"`
}
// ThirtyDayArticle for article 30 days data.
type ThirtyDayArticle struct {
Category string `json:"category"`
ThirtyDay []*data.ThirtyDay `json:"thirty_day"`
}
// ArtParam param for article info input.
type ArtParam struct {
AID int64 `json:"aid"`
MID int64 `json:"mid"`
Category int64 `json:"category"`
State int32 `json:"state"`
Reprint int32 `json:"reprint"`
TemplateID int32 `json:"tid"`
Title string `json:"title"`
BannerURL string `json:"banner_url"`
Content string `json:"content"`
Summary string `json:"summary"`
Tags string `json:"tags"`
ImageURLs []string `json:"image_urls"`
OriginImageURLs []string `json:"origin_image_urls"`
RealIP string `json:"-"`
Action int `json:"action"`
Words int64 `json:"words"`
DynamicIntro string `json:"dynamic_intro"`
ActivityID int64 `json:"activity_id"`
ListID int64 `json:"list_id"`
MediaID int64 `json:"media_id"`
Spoiler int32 `json:"spoiler"`
}
// CreativeMeta article detail.
type CreativeMeta struct {
ID int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Summary string `json:"summary"`
BannerURL string `json:"banner_url"`
Reason string `json:"reason"`
TemplateID int32 `json:"template_id"`
State int32 `json:"state"`
Reprint int32 `json:"reprint"`
ImageURLs []string `json:"image_urls"`
OriginImageURLs []string `json:"origin_image_urls"`
Tags []string `json:"tags"`
Category *Category `json:"category"`
Author *Author `json:"author"`
Stats *Stats `json:"stats"`
PTime time.Time `json:"publish_time"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
ViewURL string `json:"view_url"`
EditURL string `json:"edit_url"`
IsPreview int `json:"is_preview"`
DynamicIntro string `json:"dynamic_intro"`
List *List `json:"list"`
MediaID int64 `json:"media_id"`
Spoiler int32 `json:"spoiler"`
EditTimes int `json:"edit_times"`
PreViewURL string `json:"pre_view_url"`
}
// CreativeArtList article for list.
type CreativeArtList struct {
Articles []*CreativeMeta `json:"articles"`
Type *CreationArtsType `json:"type"`
Page *ArtPage `json:"page"`
}
// CreativeDraftList draft list.
type CreativeDraftList struct {
Drafts []*CreativeMeta `json:"drafts"`
Page *ArtPage `json:"page"`
DraftURL string `json:"draft_url"`
}
// ExtMsg .
type ExtMsg struct {
Tags []*Tag `json:"tags"`
}

View File

@@ -0,0 +1,42 @@
package model
// hotspot type
const (
HotspotTypeView = 0
HotspotTypePtime = 1
)
// HotspotTypes types
var HotspotTypes = [...]int8{HotspotTypeView, HotspotTypePtime}
// Hotspot model
type Hotspot struct {
ID int64 `json:"id"`
Tag string `json:"tag"`
Title string `json:"title"`
TopArticles []int64 `json:"top_articles"`
Icon bool `json:"icon"`
Stats HotspotStats `json:"stats"`
}
// HotspotStats .
type HotspotStats struct {
Read int64 `json:"read"`
Reply int64 `json:"reply"`
Count int64 `json:"count"`
}
// SearchArt search article model
type SearchArt struct {
ID int64
PublishTime int64
Tags []string
StatsView int64
StatsReply int64
}
// HotspotResp model
type HotspotResp struct {
Hotspot *Hotspot `json:"hotspot"`
Articles []*MetaWithLike `json:"articles"`
}

View File

@@ -0,0 +1,98 @@
package model
const (
// PlatAndroid is int8 for android.
PlatAndroid = int8(0)
// PlatIPhone is int8 for iphone.
PlatIPhone = int8(1)
// PlatIPad is int8 for ipad.
PlatIPad = int8(2)
// PlatWPhone is int8 for wphone.
PlatWPhone = int8(3)
// PlatAndroidG is int8 for Android Global.
PlatAndroidG = int8(4)
// PlatIPhoneI is int8 for Iphone Global.
PlatIPhoneI = int8(5)
// PlatIPadI is int8 for IPAD Global.
PlatIPadI = int8(6)
// PlatAndroidTV is int8 for AndroidTV Global.
PlatAndroidTV = int8(7)
// PlatAndroidI is int8 for Android Global.
PlatAndroidI = int8(8)
// PlatH5 is int8 for H5
PlatH5 = int8(9)
// PlatPC is int8 for PC
PlatPC = int8(10)
//PlatOther is int8 for unknow plat
PlatOther = int8(11)
)
// Plat return plat by platStr or mobiApp
func Plat(mobiApp, device string) int8 {
switch mobiApp {
case "iphone", "iphone_b":
if device == "pad" {
return PlatIPad
}
return PlatIPhone
case "white":
return PlatIPhone
case "ipad":
return PlatIPad
case "android":
return PlatAndroid
case "win":
return PlatWPhone
case "android_G":
return PlatAndroidG
case "android_i":
return PlatAndroidI
case "iphone_i":
if device == "pad" {
return PlatIPadI
}
return PlatIPhoneI
case "ipad_i":
return PlatIPadI
case "android_tv":
return PlatAndroidTV
case "h5":
return PlatH5
case "pc":
return PlatPC
}
return PlatOther
}
// Client 成转换AI部门的client
func Client(plat int8) string {
switch plat {
case PlatIPad, PlatIPadI:
return "ipad"
case PlatIPhone, PlatIPhoneI:
return "iphone"
case PlatAndroid, PlatAndroidG, PlatAndroidI, PlatAndroidTV:
return "android"
default:
return "web"
}
}
// HistoryClient .
func HistoryClient(plat int8) (client int8) {
switch plat {
case PlatAndroid, PlatAndroidG, PlatAndroidI:
client = 3
case PlatIPhone, PlatIPhoneI:
client = 1
case PlatPC, PlatH5:
client = 2
case PlatAndroidTV:
client = 33
case PlatIPad, PlatIPadI:
client = 4
case PlatWPhone:
client = 6
}
return
}

View File

@@ -0,0 +1,90 @@
package model
import xtime "go-common/library/time"
// sort type
const (
ListSortPtime = 0
ListSortView = 1
)
// CreativeList creative list
type CreativeList struct {
*List
Total int `json:"total"`
}
// ListArtMeta .
type ListArtMeta struct {
ID int64 `json:"id"`
Title string `json:"title"`
State int `json:"state"`
PublishTime xtime.Time `json:"publish_time"`
Position int `json:"-"`
Words int64 `json:"words"`
ImageURLs []string `json:"image_urls"`
Category *Category `json:"category"`
Categories []*Category `json:"categories"`
Summary string `json:"summary"`
}
// Strong fill
func (a *ListArtMeta) Strong() {
if a == nil {
return
}
if a.ImageURLs == nil {
a.ImageURLs = []string{}
}
if a.Category == nil {
a.Category = &Category{}
}
if a.Categories == nil {
a.Categories = []*Category{}
}
}
// FullListArtMeta .
type FullListArtMeta struct {
*ListArtMeta
Stats Stats `json:"stats"`
LikeState int8 `json:"like_state"`
}
// IsNormal judge whether article's state is normal.
func (a *ListArtMeta) IsNormal() bool {
return (a != nil) && (a.State >= StateOpen)
}
// ListArticles list articles
type ListArticles struct {
List *List `json:"list"`
Articles []*ListArtMeta `json:"articles"`
Author *Author `json:"author"`
Last ListArtMeta `json:"last"`
Attention bool `json:"attention"`
}
// WebListArticles .
type WebListArticles struct {
List *List `json:"list"`
Articles []*FullListArtMeta `json:"articles"`
Author *Author `json:"author"`
Last ListArtMeta `json:"last"`
Attention bool `json:"attention"`
}
// ListInfo list info
type ListInfo struct {
List *List `json:"list"`
Last *ListArtMeta `json:"last"`
Next *ListArtMeta `json:"next"`
Now int `json:"now"`
Total int `json:"total"`
}
// UpLists .
type UpLists struct {
Lists []*List `json:"lists"`
Total int `json:"total"`
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
syntax = "proto3";
package article.service;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option (gogoproto.goproto_enum_prefix_all) = false;
option (gogoproto.goproto_getters_all) = false;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option go_package = "model";
message Category {
int64 id = 1 [(gogoproto.jsontag) = "id", (gogoproto.customname) = "ID"];
int64 parent_id = 2 [(gogoproto.jsontag) = "parent_id", (gogoproto.customname) = "ParentID"];
string name = 3 [(gogoproto.jsontag) = "name"];
int64 position = 4 [(gogoproto.jsontag) = "-"];
repeated Category children = 5 [(gogoproto.jsontag) = "children,omitempty"];
string banner_url = 6 [(gogoproto.jsontag) = "banner_url,omitempty", (gogoproto.customname) = "BannerURL"];
}
message Stats {
int64 view = 1 [(gogoproto.jsontag) = "view"];
int64 favorite = 2 [(gogoproto.jsontag) = "favorite"];
int64 like= 3 [(gogoproto.jsontag) = "like"];
int64 dislike= 4 [(gogoproto.jsontag) = "dislike"];
int64 reply= 5 [(gogoproto.jsontag) = "reply"];
int64 share= 6 [(gogoproto.jsontag) = "share"];
int64 coin= 7 [(gogoproto.jsontag) = "coin"];
int64 dynamic= 8 [(gogoproto.jsontag) = "dynamic"];
}
message Meta {
int64 id = 1 [(gogoproto.jsontag) = "id", (gogoproto.customname) = "ID"];
Category category = 2 [(gogoproto.jsontag) = "category"];
repeated Category categories = 3 [(gogoproto.jsontag) = "categories"];
string title = 4 [(gogoproto.jsontag) = "title"];
string summary = 5 [(gogoproto.jsontag) = "summary"];
string banner_url = 6 [(gogoproto.jsontag) = "banner_url", (gogoproto.customname) = "BannerURL"];
int32 template_id = 7 [(gogoproto.jsontag) = "template_id", (gogoproto.customname) = "TemplateID"];
int32 state = 8 [(gogoproto.jsontag) = "state"];
Author author = 9 [(gogoproto.jsontag) = "author"];
int32 reprint = 10 [(gogoproto.jsontag) = "reprint"];
repeated string image_urls = 11 [(gogoproto.jsontag) = "image_urls", (gogoproto.customname) = "ImageURLs"];
int64 publish_time = 12 [(gogoproto.jsontag) = "publish_time", (gogoproto.casttype) = "go-common/library/time.Time"];
int64 ctime = 13 [(gogoproto.jsontag) = "ctime", (gogoproto.casttype) = "go-common/library/time.Time"];
int64 mtime = 14 [(gogoproto.jsontag) = "mtime,omitempty", (gogoproto.casttype) = "go-common/library/time.Time"];
Stats stats = 15 [(gogoproto.jsontag) = "stats,omitempty"];
repeated Tag tags = 16 [(gogoproto.jsontag) = "tags,omitempty"];
int32 attributes = 17 [(gogoproto.jsontag) = "attributes,omitempty"];
string reason = 18 [(gogoproto.jsontag) = "reason,omitempty"];
int64 words = 19 [(gogoproto.jsontag) = "words"];
string dynamic = 20 [(gogoproto.jsontag) = "dynamic,omitempty"];
repeated string origin_image_urls = 21 [(gogoproto.jsontag) = "origin_image_urls", (gogoproto.customname) = "OriginImageURLs"];
List list = 22 [(gogoproto.jsontag) = "list"];
bool isLike = 23 [(gogoproto.jsontag) = "is_like", (gogoproto.customname) = "IsLike"];
Media media = 24 [(gogoproto.jsontag) = "media", (gogoproto.customname) = "Media"];
string apply_time = 25 [(gogoproto.jsontag) = "apply_time", (gogoproto.customname) = "ApplyTime"];
string check_time = 26 [(gogoproto.jsontag) = "check_time", (gogoproto.customname) = "CheckTime"];
}
message Media {
int32 score = 1 [(gogoproto.jsontag) = "score", (gogoproto.customname) = "Score"];
int64 media_id = 2 [(gogoproto.jsontag) = "media_id", (gogoproto.customname) = "MediaID"];
string title = 3 [(gogoproto.jsontag) = "title", (gogoproto.customname) = "Title"];
string cover = 4 [(gogoproto.jsontag) = "cover", (gogoproto.customname) = "Cover"];
string area = 5 [(gogoproto.jsontag) = "area", (gogoproto.customname) = "Area"];
int32 type_id = 6 [(gogoproto.jsontag) = "type_id", (gogoproto.customname) = "TypeID"];
string type_name = 7 [(gogoproto.jsontag) = "type_name", (gogoproto.customname) = "TypeName"];
int32 spoiler = 8 [(gogoproto.jsontag) = "spoiler", (gogoproto.customname) = "Spoiler"];
}
message OfficialVerify {
int64 type = 1 [(gogoproto.jsontag) = "type"];
string desc = 2 [(gogoproto.jsontag) = "desc"];
}
message Author {
int64 mid = 1 [(gogoproto.jsontag) = "mid"];
string name = 2 [(gogoproto.jsontag) = "name"];
string face = 3 [(gogoproto.jsontag) = "face"];
Pendant pendant = 4 [(gogoproto.jsontag) = "pendant", (gogoproto.nullable) = false];
OfficialVerify official_verify = 5 [(gogoproto.jsontag) = "official_verify", (gogoproto.nullable) = false];
Nameplate nameplate = 6 [(gogoproto.jsontag) = "nameplate", (gogoproto.nullable) = false];
VipInfo vip = 7 [(gogoproto.jsontag) = "vip", (gogoproto.nullable) = false];
}
message VipInfo {
int32 type = 1 [(gogoproto.jsontag) = "type"];
int32 status = 2 [(gogoproto.jsontag) = "status"];
int64 due_date = 3 [(gogoproto.jsontag) = "due_date"];
int32 vip_pay_type = 4 [(gogoproto.jsontag) = "vip_pay_type"];
}
message Nameplate {
int32 nid = 1 [(gogoproto.jsontag) = "nid",(gogoproto.casttype) = "int"];
string name = 2 [(gogoproto.jsontag) = "name"];
string image = 3 [(gogoproto.jsontag) = "image"];
string image_small = 4 [(gogoproto.jsontag) = "image_small"];
string level = 5 [(gogoproto.jsontag) = "level"];
string condition = 6 [(gogoproto.jsontag) = "condition"];
}
message Pendant {
int32 pid = 1 [(gogoproto.jsontag) = "pid"];
string name = 2 [(gogoproto.jsontag) = "name"];
string image = 3 [(gogoproto.jsontag) = "image"];
int32 expire = 4 [(gogoproto.jsontag) = "expire"];
}
message Tag {
int64 tid = 1 [(gogoproto.jsontag) = "tid"];
string name = 2 [(gogoproto.jsontag) = "name"];
}
message Article {
Meta meta = 1 [(gogoproto.jsontag) = "", (gogoproto.embed) = true];
string content = 2 [(gogoproto.jsontag) = "content,omitempty"];
string keywords = 3 [(gogoproto.jsontag) = "keywords", (gogoproto.customname) = "Keywords"];
}
message List {
int64 id = 1 [(gogoproto.jsontag) = "id", (gogoproto.customname) = "ID"];
int64 mid = 2[(gogoproto.jsontag) = "mid"];
string name = 3 [(gogoproto.jsontag) = "name"];
string image_url = 4 [(gogoproto.jsontag) = "image_url", (gogoproto.customname) = "ImageURL"];
int64 update_time = 5 [(gogoproto.jsontag) = "update_time", (gogoproto.casttype) = "go-common/library/time.Time"];
int64 ctime = 6 [(gogoproto.jsontag) = "ctime", (gogoproto.casttype) = "go-common/library/time.Time"];
int64 publish_time = 7 [(gogoproto.jsontag) = "publish_time", (gogoproto.casttype) = "go-common/library/time.Time"];
string summary = 8 [(gogoproto.jsontag) = "summary"];
int64 words = 9 [(gogoproto.jsontag) = "words"];
int64 read = 10 [(gogoproto.jsontag) = "read"];
int64 articles_count = 11 [(gogoproto.jsontag) = "articles_count"];
}

Some files were not shown because too many files have changed in this diff Show More