Create & Init Project...
This commit is contained in:
119
app/interface/openplatform/article/dao/BUILD
Normal file
119
app/interface/openplatform/article/dao/BUILD
Normal 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"],
|
||||
)
|
87
app/interface/openplatform/article/dao/activity.go
Normal file
87
app/interface/openplatform/article/dao/activity.go
Normal 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
|
||||
}
|
10
app/interface/openplatform/article/dao/anniversary_mc.go
Normal file
10
app/interface/openplatform/article/dao/anniversary_mc.go
Normal 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)
|
||||
}
|
39
app/interface/openplatform/article/dao/author.go
Normal file
39
app/interface/openplatform/article/dao/author.go
Normal 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
|
||||
}
|
101
app/interface/openplatform/article/dao/berserker.go
Normal file
101
app/interface/openplatform/article/dao/berserker.go
Normal 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[:]))
|
||||
}
|
126
app/interface/openplatform/article/dao/bfs.go
Normal file
126
app/interface/openplatform/article/dao/bfs.go
Normal 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
|
||||
}
|
238
app/interface/openplatform/article/dao/bigdata.go
Normal file
238
app/interface/openplatform/article/dao/bigdata.go
Normal 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
|
||||
}
|
71
app/interface/openplatform/article/dao/bigdata_test.go
Normal file
71
app/interface/openplatform/article/dao/bigdata_test.go
Normal 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)
|
||||
})
|
||||
}
|
131
app/interface/openplatform/article/dao/cache.go
Normal file
131
app/interface/openplatform/article/dao/cache.go
Normal 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)
|
||||
}
|
139
app/interface/openplatform/article/dao/cards.go
Normal file
139
app/interface/openplatform/article/dao/cards.go
Normal 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
|
||||
}
|
172
app/interface/openplatform/article/dao/cards_test.go
Normal file
172
app/interface/openplatform/article/dao/cards_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
509
app/interface/openplatform/article/dao/creation.go
Normal file
509
app/interface/openplatform/article/dao/creation.go
Normal 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
|
||||
}
|
68
app/interface/openplatform/article/dao/creation_mc.go
Normal file
68
app/interface/openplatform/article/dao/creation_mc.go
Normal 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
|
||||
}
|
30
app/interface/openplatform/article/dao/creation_mc_test.go
Normal file
30
app/interface/openplatform/article/dao/creation_mc_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
128
app/interface/openplatform/article/dao/creation_test.go
Normal file
128
app/interface/openplatform/article/dao/creation_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
443
app/interface/openplatform/article/dao/dao.cache.go
Normal file
443
app/interface/openplatform/article/dao/dao.cache.go
Normal 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
|
||||
}
|
266
app/interface/openplatform/article/dao/dao.go
Normal file
266
app/interface/openplatform/article/dao/dao.go
Normal 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()
|
||||
}
|
91
app/interface/openplatform/article/dao/dao_test.go
Normal file
91
app/interface/openplatform/article/dao/dao_test.go
Normal 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()
|
||||
}
|
48
app/interface/openplatform/article/dao/databus.go
Normal file
48
app/interface/openplatform/article/dao/databus.go
Normal 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
|
||||
}
|
41
app/interface/openplatform/article/dao/dynamic.go
Normal file
41
app/interface/openplatform/article/dao/dynamic.go
Normal 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
|
||||
}
|
60
app/interface/openplatform/article/dao/list_mc.go
Normal file
60
app/interface/openplatform/article/dao/list_mc.go
Normal 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
|
||||
}
|
164
app/interface/openplatform/article/dao/list_mc_test.go
Normal file
164
app/interface/openplatform/article/dao/list_mc_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
867
app/interface/openplatform/article/dao/mc.cache.go
Normal file
867
app/interface/openplatform/article/dao/mc.cache.go
Normal 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
|
||||
}
|
104
app/interface/openplatform/article/dao/media.go
Normal file
104
app/interface/openplatform/article/dao/media.go
Normal 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
|
||||
}
|
663
app/interface/openplatform/article/dao/memcached.go
Normal file
663
app/interface/openplatform/article/dao/memcached.go
Normal 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
|
||||
}
|
90
app/interface/openplatform/article/dao/memcached_test.go
Normal file
90
app/interface/openplatform/article/dao/memcached_test.go
Normal 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)
|
||||
})
|
||||
}
|
41
app/interface/openplatform/article/dao/message.go
Normal file
41
app/interface/openplatform/article/dao/message.go
Normal 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
|
||||
}
|
322
app/interface/openplatform/article/dao/mysql.go
Normal file
322
app/interface/openplatform/article/dao/mysql.go
Normal 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
|
||||
}
|
304
app/interface/openplatform/article/dao/mysql_article.go
Normal file
304
app/interface/openplatform/article/dao/mysql_article.go
Normal 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
|
||||
}
|
66
app/interface/openplatform/article/dao/mysql_article_test.go
Normal file
66
app/interface/openplatform/article/dao/mysql_article_test.go
Normal 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)
|
||||
}))
|
||||
}
|
115
app/interface/openplatform/article/dao/mysql_author.go
Normal file
115
app/interface/openplatform/article/dao/mysql_author.go
Normal 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
|
||||
}
|
58
app/interface/openplatform/article/dao/mysql_author_test.go
Normal file
58
app/interface/openplatform/article/dao/mysql_author_test.go
Normal 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)
|
||||
})
|
||||
}
|
69
app/interface/openplatform/article/dao/mysql_complaint.go
Normal file
69
app/interface/openplatform/article/dao/mysql_complaint.go
Normal 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
|
||||
}
|
216
app/interface/openplatform/article/dao/mysql_draft.go
Normal file
216
app/interface/openplatform/article/dao/mysql_draft.go
Normal 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
|
||||
}
|
610
app/interface/openplatform/article/dao/mysql_list.go
Normal file
610
app/interface/openplatform/article/dao/mysql_list.go
Normal 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
|
||||
}
|
148
app/interface/openplatform/article/dao/mysql_list_test.go
Normal file
148
app/interface/openplatform/article/dao/mysql_list_test.go
Normal 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)
|
||||
})
|
||||
}
|
87
app/interface/openplatform/article/dao/mysql_recommend.go
Normal file
87
app/interface/openplatform/article/dao/mysql_recommend.go
Normal 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
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
86
app/interface/openplatform/article/dao/mysql_test.go
Normal file
86
app/interface/openplatform/article/dao/mysql_test.go
Normal 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)
|
||||
})
|
||||
}
|
70
app/interface/openplatform/article/dao/mysql_upper.go
Normal file
70
app/interface/openplatform/article/dao/mysql_upper.go
Normal 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
|
||||
}
|
34
app/interface/openplatform/article/dao/mysql_upper_test.go
Normal file
34
app/interface/openplatform/article/dao/mysql_upper_test.go
Normal 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)
|
||||
}))
|
||||
}
|
53
app/interface/openplatform/article/dao/rank.go
Normal file
53
app/interface/openplatform/article/dao/rank.go
Normal 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
|
||||
}
|
20
app/interface/openplatform/article/dao/rank_test.go
Normal file
20
app/interface/openplatform/article/dao/rank_test.go
Normal 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)
|
||||
})
|
||||
}
|
484
app/interface/openplatform/article/dao/redis.go
Normal file
484
app/interface/openplatform/article/dao/redis.go
Normal 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
|
||||
}
|
81
app/interface/openplatform/article/dao/redis_like.go
Normal file
81
app/interface/openplatform/article/dao/redis_like.go
Normal 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
|
||||
}
|
30
app/interface/openplatform/article/dao/redis_like_test.go
Normal file
30
app/interface/openplatform/article/dao/redis_like_test.go
Normal 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)
|
||||
})
|
||||
}))
|
||||
}
|
46
app/interface/openplatform/article/dao/redis_read.go
Normal file
46
app/interface/openplatform/article/dao/redis_read.go
Normal 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
|
||||
}
|
126
app/interface/openplatform/article/dao/redis_sort.go
Normal file
126
app/interface/openplatform/article/dao/redis_sort.go
Normal 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
|
||||
}
|
64
app/interface/openplatform/article/dao/redis_sort_test.go
Normal file
64
app/interface/openplatform/article/dao/redis_sort_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
121
app/interface/openplatform/article/dao/redis_test.go
Normal file
121
app/interface/openplatform/article/dao/redis_test.go
Normal 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})
|
||||
})
|
||||
}))
|
||||
}
|
Reference in New Issue
Block a user