Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@ -0,0 +1,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})
})
}))
}