Initial commit

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

View File

@@ -0,0 +1,109 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"admin_test.go",
"block_test.go",
"business_test.go",
"config_test.go",
"dao_test.go",
"emoji_package_test.go",
"emoji_test.go",
"event_test.go",
"fold_test.go",
"http_filter_test.go",
"http_search_test.go",
"http_title_test.go",
"logReport_test.go",
"memcache_test.go",
"message_test.go",
"notice_test.go",
"redis_test.go",
"reply_content_test.go",
"reply_test.go",
"report_test.go",
"search_test.go",
"stat_test.go",
"subject_test.go",
"workflow_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/reply/conf:go_default_library",
"//app/admin/main/reply/model:go_default_library",
"//library/conf/env: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",
],
)
go_library(
name = "go_default_library",
srcs = [
"admin.go",
"block.go",
"business.go",
"config.go",
"dao.go",
"emoji.go",
"emoji_package.go",
"event.go",
"fold.go",
"http_filter.go",
"http_search.go",
"http_title.go",
"logReport.go",
"memcache.go",
"message.go",
"notice.go",
"redis.go",
"reply.go",
"reply_content.go",
"report.go",
"stat.go",
"subject.go",
"workflow.go",
],
importpath = "go-common/app/admin/main/reply/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/reply/conf:go_default_library",
"//app/admin/main/reply/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,113 @@
package dao
import (
"context"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_adminNameURL = "http://manager.bilibili.co/x/admin/manager/users/unames"
_selAdminLogByRpIDSQL = "SELECT id,oid,type,rpid,adminid,result,remark,isnew,isreport,state,ctime,mtime FROM reply_admin_log WHERE rpid=? and isnew=1"
_selAdminLogsByRpIDSQL = "SELECT id,oid,type,rpid,adminid,result,remark,isnew,isreport,state,ctime,mtime FROM reply_admin_log WHERE rpid=? order by ctime desc"
_upAdminLogSQL = "UPDATE reply_admin_log SET isnew=0,mtime=? WHERE rpID IN(%s) AND isnew=1"
_addAdminLogSQL = "INSERT INTO reply_admin_log (oid,type,rpid,adminid,result,remark,isnew,isreport,state,ctime,mtime) VALUES %s"
_addAdminLogFormat = `(%d,%d,%d,%d,'%s','%s',%d,%d,%d,'%s','%s')`
_logTimeLayout = "2006-01-02 15:04:05"
)
// AddAdminLog add admin log to mysql.
func (d *Dao) AddAdminLog(c context.Context, oids, rpIDs []int64, adminID int64, typ, isNew, isReport, state int32, result, remark string, now time.Time) (rows int64, err error) {
var vals []string
for i := range oids {
vals = append(vals, fmt.Sprintf(_addAdminLogFormat, oids[i], typ, rpIDs[i], adminID, result, remark, isNew, isReport, state, now.Format(_logTimeLayout), now.Format(_logTimeLayout)))
}
insertSQL := strings.Join(vals, ",")
res, err := d.db.Exec(c, fmt.Sprintf(_addAdminLogSQL, insertSQL))
if err != nil {
return
}
return res.RowsAffected()
}
// UpAdminNotNew update admin log to not new.
func (d *Dao) UpAdminNotNew(c context.Context, rpID []int64, now time.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upAdminLogSQL, xstr.JoinInts(rpID)), now)
if err != nil {
return
}
return res.RowsAffected()
}
// AdminLog return admin log by rpid
func (d *Dao) AdminLog(c context.Context, rpID int64) (r *model.AdminLog, err error) {
row := d.db.QueryRow(c, _selAdminLogByRpIDSQL, rpID)
r = new(model.AdminLog)
if err = row.Scan(&r.ID, &r.Oid, &r.Type, &r.ReplyID, &r.AdminID, &r.Result, &r.Remark, &r.IsNew, &r.IsReport, &r.State, &r.CTime, &r.MTime); err != nil {
return
}
return
}
// AdminLogsByRpID return operation log list.
func (d *Dao) AdminLogsByRpID(c context.Context, rpID int64) (res []*model.AdminLog, err error) {
rows, err := d.db.Query(c, _selAdminLogsByRpIDSQL, rpID)
if err != nil {
log.Error("query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.AdminLog)
if err = rows.Scan(&r.ID, &r.Oid, &r.Type, &r.ReplyID, &r.AdminID, &r.Result, &r.Remark, &r.IsNew, &r.IsReport, &r.State, &r.CTime, &r.MTime); err != nil {
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// AdminName get admin name by id.
func (d *Dao) AdminName(c context.Context, admins map[int64]string) (err error) {
if len(admins) == 0 {
return
}
var res struct {
Code int `json:"code"`
Data map[string]string `json:"data"`
}
var uids []int64
for key := range admins {
uids = append(uids, key)
}
params := url.Values{}
params.Set("uids", xstr.JoinInts(uids))
if err = d.httpClient.Get(c, _adminNameURL, "", params, &res); err != nil {
log.Error("ReportLog httpClient.Get(%s) error(%v) res(%v)", _adminNameURL, err, res)
return
}
if ec := ecode.Int(res.Code); !ecode.OK.Equal(ec) {
log.Error("ReportLog not ok(%s) error(%v) res(%v)", _adminNameURL, ec, res)
err = ec
return
}
for k, v := range res.Data {
id, err := strconv.ParseInt(k, 10, 64)
if err == nil {
admins[id] = v
} else {
log.Error("ReportLog(%s) strconv error(%v) res(%v)", _adminNameURL, err, res)
}
}
return
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestAdminLog(t *testing.T) {
var (
oids = []int64{1, 2, 3}
rpIDs = []int64{10, 20, 30}
adminID = int64(100)
typ = int32(1)
c = context.Background()
now = time.Now()
)
Convey("add admin log", t, WithDao(func(d *Dao) {
rows, err := d.AddAdminLog(c, oids, rpIDs, adminID, typ, model.AdminIsNew, model.AdminIsReport, model.AdminOperDelete, "result", "remark", now)
So(err, ShouldBeNil)
So(rows, ShouldNotEqual, 0)
t.Log(rows)
rows, err = d.UpAdminNotNew(c, rpIDs, now)
So(err, ShouldBeNil)
So(rows, ShouldNotEqual, 0)
res, err := d.AdminLogsByRpID(c, rpIDs[0])
So(err, ShouldBeNil)
So(len(res), ShouldNotEqual, 0)
}))
}

View File

@@ -0,0 +1,117 @@
package dao
import (
"context"
"encoding/json"
"net/url"
"strconv"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_blockURL string = "http://account.bilibili.co/api/member/blockAccountWithTime"
_transferURL string = "http://api.bilibili.co/x/internal/credit/blocked/case/add"
)
// BlockAccount ban an account.
func (d *Dao) BlockAccount(c context.Context, mid int64, ftime int64, notify bool, freason int32, originTitle string, originContent string, redirectURL string, adname string, remark string) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
if ftime == -1 {
params.Set("blockTimeLength", "0")
params.Set("blockForever", "1")
} else {
params.Set("blockForever", "0")
params.Set("blockTimeLength", strconv.FormatInt(ftime, 10))
}
params.Set("blockRemark", remark)
params.Set("operator", adname)
params.Set("originType", "1")
params.Set("originContent", originContent)
params.Set("reasonType", strconv.FormatInt(int64(freason), 10))
if notify {
params.Set("isNotify", "1")
} else {
params.Set("isNotify", "0")
}
params.Set("originTitle", originTitle)
params.Set("originUrl", redirectURL)
var res struct {
Code int `json:"code"`
}
if err = d.httpClient.Post(c, _blockURL, "", params, &res); err != nil {
log.Error("sendMsg error(%v)", err)
return
}
if res.Code != ecode.OK.Code() {
err = model.ErrMsgSend
log.Error("sendMsg failed(%v) error(%v)", _apiMsgSend+"?"+params.Encode(), res.Code)
}
return
}
// TransferData transefer data
type TransferData struct {
Oid int64 `json:"oid"`
Type int32 `json:"type"`
Mid int64 `json:"mid"`
RpID int64 `json:"rp_id"`
Operator string `json:"operator"`
OperatorID int64 `json:"oper_id"`
Content string `json:"origin_content"`
Reason int32 `json:"reason_type"`
Title string `json:"origin_title"`
Link string `json:"origin_url"`
Ctime int64 `json:"business_time"`
OriginType int32 `json:"origin_type"`
}
// TransferArbitration transfer report to Arbitration.
func (d *Dao) TransferArbitration(c context.Context, rps map[int64]*model.Reply, rpts map[int64]*model.Report, adid int64, adname string, titles map[int64]string, links map[int64]string) (err error) {
var data []TransferData
for _, rp := range rps {
if rpts[rp.ID] == nil {
continue
}
rpt := rpts[rp.ID]
d := TransferData{
RpID: rp.ID,
Oid: rp.Oid,
Type: rp.Type,
Mid: rp.Mid,
Operator: adname,
OperatorID: adid,
Content: rp.Content.Message,
Reason: rpt.Reason,
Ctime: int64(rp.CTime),
OriginType: 1,
Title: titles[rp.ID],
Link: links[rp.ID],
}
data = append(data, d)
}
content, err := json.Marshal(data)
if err != nil {
return
}
params := url.Values{}
params.Set("data", string(content))
var res struct {
Code int `json:"code"`
}
if err = d.httpClient.Post(c, _transferURL, "", params, &res); err != nil {
log.Error("sendMsg error(%v)", err)
return
}
if res.Code != ecode.OK.Code() {
err = model.ErrMsgSend
log.Error("sendMsg failed(%v) error(%v)", _transferURL+"?"+params.Encode(), res.Code)
}
return
}

View File

@@ -0,0 +1,52 @@
package dao
import (
"context"
"go-common/app/admin/main/reply/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoBlockAccount(t *testing.T) {
convey.Convey("BlockAccount", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
ftime = int64(0)
notify bool
freason = int32(0)
originTitle = ""
originContent = ""
redirectURL = ""
adname = ""
remark = ""
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.BlockAccount(c, mid, ftime, notify, freason, originTitle, originContent, redirectURL, adname, remark)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoTransferArbitration(t *testing.T) {
convey.Convey("TransferArbitration", t, func(ctx convey.C) {
var (
c = context.Background()
rps map[int64]*model.Reply
rpts map[int64]*model.Report
adid = int64(0)
adname = ""
titles map[int64]string
links map[int64]string
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.TransferArbitration(c, rps, rpts, adid, adname, titles, links)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,76 @@
package dao
import (
"context"
"go-common/app/admin/main/reply/model"
"go-common/library/database/sql"
)
const (
_inBussinessSQL = "INSERT INTO business (type, name, appkey, remark, alias) VALUES (?,?,?,?,?)"
_upBussinessSQL = "UPDATE business SET name=?, appkey=?, remark=?, alias=? WHERE type=?"
_upBussinessSteteSQL = "UPDATE business SET state=? WHERE type=?"
_selBussinessSQL = "SELECT type, name, appkey, remark, alias FROM business WHERE state=?"
_selOneBusinessSQL = "SELECT type, name, appkey, remark, alias FROM business WHERE type=?"
)
// InBusiness insert a business record
func (dao *Dao) InBusiness(c context.Context, tp int32, name, appkey, remark, alias string) (id int64, err error) {
res, err := dao.db.Exec(c, _inBussinessSQL, tp, name, appkey, remark, alias)
if err != nil {
return
}
return res.LastInsertId()
}
// UpBusiness update business by type
func (dao *Dao) UpBusiness(c context.Context, name, appkey, remark, alias string, tp int32) (id int64, err error) {
res, err := dao.db.Exec(c, _upBussinessSQL, name, appkey, remark, alias, tp)
if err != nil {
return
}
return res.RowsAffected()
}
// UpBusinessState logical delete a business record by set state to StateDelete
func (dao *Dao) UpBusinessState(c context.Context, state, tp int32) (id int64, err error) {
res, err := dao.db.Exec(c, _upBussinessSteteSQL, state, tp)
if err != nil {
return
}
return res.RowsAffected()
}
// Business return one business instance by type
func (dao *Dao) Business(c context.Context, tp int32) (business *model.Business, err error) {
row := dao.db.QueryRow(c, _selOneBusinessSQL, tp)
business = new(model.Business)
err = row.Scan(&business.Type, &business.Name, &business.Appkey, &business.Remark, &business.Alias)
if err != nil {
if err == sql.ErrNoRows {
err = nil
business = nil
}
}
return
}
// ListBusiness Gets gets all business records
func (dao *Dao) ListBusiness(c context.Context, state int32) (business []*model.Business, err error) {
rows, err := dao.db.Query(c, _selBussinessSQL, state)
if err != nil {
return
}
defer rows.Close()
business = make([]*model.Business, 0)
for rows.Next() {
b := new(model.Business)
if err = rows.Scan(&b.Type, &b.Name, &b.Appkey, &b.Remark, &b.Alias); err != nil {
return
}
business = append(business, b)
}
err = rows.Err()
return
}

View File

@@ -0,0 +1,81 @@
package dao
import (
"context"
"fmt"
"testing"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func delBusiness(d *Dao, id int64) error {
_delBsSQL := "Delete from business where id = %d"
_, err := d.db.Exec(context.Background(), fmt.Sprintf(_delBsSQL, id))
if err != nil {
return err
}
return nil
}
func TestBusiness(t *testing.T) {
var (
id1 int64
id2 int64
err error
bs1 = model.Business{
Type: 244,
Name: "TestName",
Appkey: "TestAppKey",
Remark: "TestRemark",
Alias: "TestAlias",
}
bs2 = model.Business{
Type: 245,
Name: "TestName",
Appkey: "TestAppKey",
Remark: "TestRemark",
Alias: "TestAlias2",
}
b []*model.Business
bu *model.Business
)
c := context.Background()
Convey("test dao business", t, WithDao(func(d *Dao) {
id1, err = d.InBusiness(c, bs1.Type, bs1.Name, bs1.Appkey, bs1.Remark, bs1.Alias)
So(err, ShouldBeNil)
So(id1, ShouldBeGreaterThan, 0)
defer delBusiness(d, id1)
id2, err = d.InBusiness(c, bs2.Type, bs2.Name, bs2.Appkey, bs2.Remark, bs2.Alias)
So(err, ShouldBeNil)
So(id2, ShouldBeGreaterThan, 0)
defer delBusiness(d, id2)
Convey("list business", WithDao(func(d *Dao) {
b, err = d.ListBusiness(c, 0)
So(err, ShouldBeNil)
So(len(b), ShouldBeGreaterThan, 0)
}))
Convey("update business", WithDao(func(d *Dao) {
bu, err = d.Business(c, 245)
So(err, ShouldBeNil)
So(bu.Name, ShouldEqual, "TestName")
_, err = d.UpBusiness(c, "TestChangeName", bu.Appkey, bu.Remark, bu.Alias, bu.Type)
So(err, ShouldBeNil)
bu, err = d.Business(c, 245)
So(err, ShouldBeNil)
So(bu.Name, ShouldEqual, "TestChangeName")
}))
Convey("update business state", WithDao(func(d *Dao) {
_, err = d.UpBusinessState(c, 1, bu.Type)
So(err, ShouldBeNil)
bu, err = d.Business(c, 245)
So(err, ShouldBeNil)
So(bu.Type, ShouldEqual, 245)
}))
}))
}

View File

@@ -0,0 +1,137 @@
package dao
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"time"
"go-common/app/admin/main/reply/model"
)
const (
_addConfigSQL = "INSERT IGNORE INTO reply_config (type, oid, adminid, operator, category, config, ctime, mtime) VALUES(?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE adminid = ?, operator = ?, config=?, mtime = ?"
_paginationConfigSQL = "SELECT id, type, oid, adminid, operator, category, config, ctime, mtime FROM reply_config"
_paginationConfigCountSQL = "SELECT count(id) FROM reply_config"
_loadConfigSQL = "SELECT id, type, oid, adminid, operator, category, config, ctime, mtime FROM reply_config WHERE type=? AND oid=? AND category = ?"
_loadConfigByIDSQL = "SELECT id, type, oid, adminid, operator, category, config, ctime, mtime FROM reply_config WHERE id = ?"
_deleteConfigSQL = "DELETE FROM reply_config where id = ?"
)
// AddConfig create a new config.
func (d *Dao) AddConfig(c context.Context, typ, category int32, oid, adminid int64, operator, config string, now time.Time) (id int64, err error) {
res, err := d.db.Exec(c, _addConfigSQL, typ, oid, adminid, operator, category, config, now, now, adminid, operator, config, now)
if err != nil {
return
}
return res.LastInsertId()
}
// LoadConfig load a config record.
func (d *Dao) LoadConfig(c context.Context, typ, category int32, oid int64) (m *model.Config, err error) {
m = new(model.Config)
row := d.db.QueryRow(c, _loadConfigSQL, typ, oid, category)
if err = row.Scan(&m.ID, &m.Type, &m.Oid, &m.AdminID, &m.Operator, &m.Category, &m.Config, &m.CTime, &m.MTime); err != nil {
if err == sql.ErrNoRows {
m = nil
err = nil
return
}
}
if m.ID > 0 && len(m.Config) > 0 {
dat := new(model.Config)
if err = json.Unmarshal([]byte(m.Config), dat); err == nil {
m.ShowEntry = dat.ShowEntry
m.ShowAdmin = dat.ShowAdmin
}
}
return
}
// LoadConfigByID load a config record by id.
func (d *Dao) LoadConfigByID(c context.Context, id int64) (m *model.Config, err error) {
m = new(model.Config)
row := d.db.QueryRow(c, _loadConfigByIDSQL, id)
if err = row.Scan(&m.ID, &m.Type, &m.Oid, &m.AdminID, &m.Operator, &m.Category, &m.Config, &m.CTime, &m.MTime); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
}
if m.ID > 0 {
dat := new(model.Config)
if err = json.Unmarshal([]byte(m.Config), dat); err == nil {
m.ShowEntry = dat.ShowEntry
m.ShowAdmin = dat.ShowAdmin
}
}
return
}
// PaginateConfig paginate config list of records indexing from start(offset) to end(offset+count) by conditions.
func (d *Dao) PaginateConfig(c context.Context, typ, category int32, oid int64, operator string, offset, count int) (configs []*model.Config, err error) {
var paginationSQLBuffer bytes.Buffer
paginationSQLBuffer.WriteString(_paginationConfigSQL)
sqlwhere, queryParams := d.constructPaginationSQLWhere(typ, category, oid, operator)
paginationSQLBuffer.WriteString(sqlwhere)
paginationSQLBuffer.WriteString(" limit ?, ?")
queryParams = append(queryParams, offset, count-1)
rows, err := d.db.Query(c, paginationSQLBuffer.String(), queryParams...)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
m := new(model.Config)
if err = rows.Scan(&m.ID, &m.Type, &m.Oid, &m.AdminID, &m.Operator, &m.Category, &m.Config, &m.CTime, &m.MTime); err != nil {
return
}
if m.ID > 0 {
dat := new(model.Config)
if err = json.Unmarshal([]byte(m.Config), dat); err == nil {
m.ShowEntry = dat.ShowEntry
m.ShowAdmin = dat.ShowAdmin
}
}
configs = append(configs, m)
}
return
}
// PaginateConfigCount returns a total count of records by conditions.
func (d *Dao) PaginateConfigCount(c context.Context, typ, category int32, oid int64, operator string) (totalCount int64, err error) {
var paginationCountSQLBuffer bytes.Buffer
paginationCountSQLBuffer.WriteString(_paginationConfigCountSQL)
sqlwhere, countParams := d.constructPaginationSQLWhere(typ, category, oid, operator)
paginationCountSQLBuffer.WriteString(sqlwhere)
row := d.db.QueryRow(c, paginationCountSQLBuffer.String(), countParams...)
if err = row.Scan(&totalCount); err != nil {
if err == sql.ErrNoRows {
err = nil
}
}
return
}
func (d *Dao) constructPaginationSQLWhere(tp, category int32, oid int64, operator string) (sqlWhere string, queryParams []interface{}) {
var sqlBuffer bytes.Buffer
sqlBuffer.WriteString(" where 1 = 1 ")
if tp > 0 && oid > 0 && category > 0 {
sqlBuffer.WriteString(" and type = ? and oid = ? and category = ?")
queryParams = append(queryParams, tp, oid, category)
}
if len(operator) > 0 {
sqlBuffer.WriteString(" and operator = ?")
queryParams = append(queryParams, operator)
}
return sqlBuffer.String(), queryParams
}
// DeleteConfig delete a reply config record by id.
func (d *Dao) DeleteConfig(c context.Context, id int64) (rows int64, err error) {
res, err := d.db.Exec(c, _deleteConfigSQL, id)
if err != nil {
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,85 @@
package dao
import (
"context"
"encoding/json"
"testing"
"time"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestAddConfig(t *testing.T) {
var (
c = context.Background()
now = time.Now()
config = &model.Config{
Oid: 1,
Type: 1,
Category: 1,
AdminID: 1,
Operator: "admin",
}
)
Convey("add a config", t, WithDao(func(d *Dao) {
configValue := map[string]int64{
"showentry": 0,
"showadmin": 1,
}
bs, err := json.Marshal(configValue)
So(err, ShouldBeNil)
config.Config = string(bs)
_, err = d.AddConfig(c, config.Type, config.Category, config.Oid, config.AdminID, config.Operator, config.Config, now)
So(err, ShouldBeNil)
}))
}
func TestLoadConfig(t *testing.T) {
var (
c = context.Background()
config = &model.Config{
Oid: 1,
Type: 1,
Category: 1,
AdminID: 1,
Operator: "admin",
}
)
Convey("load a config", t, WithDao(func(d *Dao) {
var err error
config, err = d.LoadConfig(c, config.Type, config.Category, config.Oid)
So(err, ShouldBeNil)
So(config, ShouldNotBeNil)
}))
}
func TestPaginateConfig(t *testing.T) {
var (
config = &model.Config{
Oid: 1,
Type: 1,
Category: 1,
AdminID: 1,
Operator: "admin",
}
c = context.Background()
)
Convey("load a config", t, WithDao(func(d *Dao) {
configs, err := d.PaginateConfig(c, config.Type, config.Category, config.Oid, config.Operator, 0, 20)
So(err, ShouldBeNil)
So(len(configs), ShouldNotEqual, 0)
}))
}
func TestDeleteConfig(t *testing.T) {
var (
id = int64(1)
c = context.Background()
)
Convey("load a config", t, WithDao(func(d *Dao) {
_, err := d.DeleteConfig(c, id)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,79 @@
package dao
import (
"context"
"time"
"go-common/app/admin/main/reply/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
es "go-common/library/database/elastic"
"go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
)
// Dao dao.
type Dao struct {
c *conf.Config
// db
db *sql.DB
dbSlave *sql.DB
// cache
redis *redis.Pool
redisExpire int32
mc *memcache.Pool
mcExpire int32
// http
httpClient *bm.Client
// databus
eventBus *databus.Databus
// new databus stats
statsBus *databus.Databus
statsTypes map[int32]string
es *es.Elastic
}
// New new a dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
// db
db: sql.NewMySQL(c.DB.Reply),
dbSlave: sql.NewMySQL(c.DB.ReplySlave),
// cache
redis: redis.NewPool(c.Redis.Config),
redisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),
mc: memcache.NewPool(c.Memcache.Config),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
// http
httpClient: bm.NewClient(c.HTTPClient),
// databus
eventBus: databus.New(c.Databus.Event),
es: es.NewElastic(c.Es),
}
// new databus stats
d.statsTypes = make(map[int32]string)
for name, typ := range c.StatTypes {
d.statsTypes[typ] = name
}
d.statsBus = databus.New(c.Databus.Stats)
return
}
func hit(id int64) int64 {
return id % 200
}
// BeginTran begin transaction.
func (d *Dao) BeginTran(c context.Context) (*sql.Tx, error) {
return d.db.Begin(c)
}
// Ping ping resouces is ok.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
return
}
return d.pingMC(c)
}

View File

@@ -0,0 +1,56 @@
package dao
import (
"flag"
"os"
"testing"
"go-common/app/admin/main/reply/conf"
"go-common/library/conf/env"
_ "github.com/go-sql-driver/mysql"
. "github.com/smartystreets/goconvey/convey"
)
var (
d *Dao
_d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.reply-admin")
flag.Set("conf_token", "7c141ae4ff3f31aade1c51556fd11e8a")
flag.Set("tree_id", "2124")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
env.DeployEnv = "uat"
env.Zone = "sh001"
flag.Set("conf", "../cmd/reply-admin-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
_d = New(conf.Conf)
d = _d
m.Run()
os.Exit(0)
}
func CleanCache() {
}
func WithDao(f func(d *Dao)) func() {
return func() {
Reset(func() { CleanCache() })
f(_d)
}
}

View File

@@ -0,0 +1,134 @@
package dao
import (
"context"
"strings"
"time"
"go-common/app/admin/main/reply/model"
xsql "go-common/library/database/sql"
)
const (
//state 0-上线 1-下线
_selEmojiSQL = "SELECT id,package_id,name,url,sort,state,remark from emoji order by package_id,sort"
_selEmojiByPidSQL = "SELECT id,package_id,name,url,sort,state,remark from emoji where package_id=? order by sort"
_selEmojiByNameSQL = "SELECT id from emoji where name=? "
_insertEmojiSQL = "INSERT INTO emoji (package_id, name, url, sort, state, remark, ctime, mtime ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
_upEmojiSortSQL = "UPDATE emoji SET sort=?, mtime=? where id=?"
_upEmojiStateSQL = "UPDATE emoji SET state=?, mtime=? WHERE id=?"
_upEmojiSQL = "UPDATE emoji SET name=?, remark=?, url=?, mtime=? WHERE id=?"
_delEmojiByIDSQL = "DELETE from emoji where id=?"
_delEmojiByPidSQL = "DELETE from emoji where package_id=?"
)
// EmojiList get all emoji
func (d *Dao) EmojiList(c context.Context) (emojis []*model.Emoji, err error) {
rows, err := d.db.Query(c, _selEmojiSQL)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
emo := &model.Emoji{}
if err = rows.Scan(&emo.ID, &emo.PackageID, &emo.Name, &emo.URL, &emo.Sort, &emo.State, &emo.Remark); err != nil {
return
}
emojis = append(emojis, emo)
}
err = rows.Err()
return
}
// EmojiListByPid get emoji by package_id
func (d *Dao) EmojiListByPid(c context.Context, pid int64) (emojis []*model.Emoji, err error) {
rows, err := d.db.Query(c, _selEmojiByPidSQL, pid)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
emo := &model.Emoji{}
if err = rows.Scan(&emo.ID, &emo.PackageID, &emo.Name, &emo.URL, &emo.Sort, &emo.State, &emo.Remark); err != nil {
return
}
emojis = append(emojis, emo)
}
err = rows.Err()
return
}
// EmojiByName get emoji by name
func (d *Dao) EmojiByName(c context.Context, name string) (emojis []*model.Emoji, err error) {
rows, err := d.db.Query(c, _selEmojiByNameSQL, name)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
emo := &model.Emoji{}
if err = rows.Scan(&emo.ID); err != nil {
return
}
emojis = append(emojis, emo)
}
err = rows.Err()
return
}
// CreateEmoji insert a emoji into db
func (d *Dao) CreateEmoji(c context.Context, pid int64, name string, url string, sort int32, state int32, remark string) (id int64, err error) {
result, err := d.db.Exec(c, _insertEmojiSQL, pid, name, url, sort, state, remark, time.Now(), time.Now())
if err != nil {
return
}
return result.LastInsertId()
}
// UpEmojiSort udpate emoji sort
func (d *Dao) UpEmojiSort(tx *xsql.Tx, ids string) (err error) {
idx := strings.Split(ids, ",")
for sort, id := range idx {
_, err = tx.Exec(_upEmojiSortSQL, sort, time.Now(), id)
if err != nil {
return
}
}
return nil
}
// UpEmojiStateByID update emoji state
func (d *Dao) UpEmojiStateByID(c context.Context, state int32, id int64) (idx int64, err error) {
result, err := d.db.Exec(c, _upEmojiStateSQL, state, time.Now(), id)
if err != nil {
return
}
return result.RowsAffected()
}
// UpEmoji update emoji name and remark by id
func (d *Dao) UpEmoji(c context.Context, name string, remark string, url string, id int64) (idx int64, err error) {
result, err := d.db.Exec(c, _upEmojiSQL, name, remark, url, time.Now(), id)
if err != nil {
return
}
return result.RowsAffected()
}
// DelEmojiByID delete emoji by id
func (d *Dao) DelEmojiByID(c context.Context, id int64) (idx int64, err error) {
result, err := d.db.Exec(c, _delEmojiByIDSQL, id)
if err != nil {
return
}
return result.RowsAffected()
}
// DelEmojiByPid delete emoji by package_id
func (d *Dao) DelEmojiByPid(c context.Context, pid int64) (idx int64, err error) {
result, err := d.db.Exec(c, _delEmojiByPidSQL, pid)
if err != nil {
return
}
return result.RowsAffected()
}

View File

@@ -0,0 +1,75 @@
package dao
import (
"context"
"strings"
"time"
"go-common/app/admin/main/reply/model"
xsql "go-common/library/database/sql"
)
const (
_selEmojiPackageSQL = "SELECT id,name,url,remark,sort,state from emoji_package order by sort"
_addEmojiPackageSQL = "INSERT INTO emoji_package (name, url, sort, state, remark, ctime, mtime ) VALUES (?, ?, ?, ?, ?, ?, ?)"
_upEmojiPackageSQL = "UPDATE emoji_package SET `name`=?, url=?, remark=?, state=?, mtime=? WHERE id=?"
_upEmojiPackageSortSQL = "UPDATE emoji_package SET sort=?, mtime=? where id=?"
_delEmojiPackageSQL = "DELETE from emoji_package where id=?"
)
// EmojiPackageList get all emoji_package
func (d *Dao) EmojiPackageList(c context.Context) (packs []*model.EmojiPackage, err error) {
rows, err := d.db.Query(c, _selEmojiPackageSQL)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
pack := &model.EmojiPackage{}
if err = rows.Scan(&pack.ID, &pack.Name, &pack.URL, &pack.Remark, &pack.Sort, &pack.State); err != nil {
return
}
packs = append(packs, pack)
}
err = rows.Err()
return
}
// CreateEmojiPackage insert a emoji_package into db
func (d *Dao) CreateEmojiPackage(c context.Context, name string, url string, sort int32, remark string, state int32) (id int64, err error) {
result, err := d.db.Exec(c, _addEmojiPackageSQL, name, url, sort, state, remark, time.Now(), time.Now())
if err != nil {
return
}
return result.LastInsertId()
}
// UpEmojiPackageSort udpate emojipack sort
func (d *Dao) UpEmojiPackageSort(tx *xsql.Tx, ids string) (err error) {
idx := strings.Split(ids, ",")
for sort, id := range idx {
_, err = tx.Exec(_upEmojiPackageSortSQL, sort, time.Now(), id)
if err != nil {
return
}
}
return nil
}
//UpEmojiPackage update emojipack by id
func (d *Dao) UpEmojiPackage(c context.Context, name string, url string, remark string, state int32, id int64) (idx int64, err error) {
result, err := d.db.Exec(c, _upEmojiPackageSQL, name, url, remark, state, time.Now(), id)
if err != nil {
return
}
return result.RowsAffected()
}
// DelEmojiPackage delete emoji_package by id
func (d *Dao) DelEmojiPackage(c context.Context, id int64) (idx int64, err error) {
result, err := d.db.Exec(c, _delEmojiPackageSQL, id)
if err != nil {
return
}
return result.RowsAffected()
}

View File

@@ -0,0 +1,43 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDao_EmojiPack(t *testing.T) {
Convey("CreateEmojiPackage", t, WithDao(func(d *Dao) {
id, err := d.CreateEmojiPackage(context.Background(), "[2233娘]", "www.baidu.com", 0, "", 1)
So(err, ShouldBeNil)
So(id, ShouldNotEqual, 0)
d.DelEmojiPackage(context.Background(), id)
}))
Convey("EmojiPackageList", t, WithDao(func(d *Dao) {
packs, err := d.EmojiPackageList(context.Background())
So(err, ShouldBeNil)
for _, v := range packs {
t.Logf("v.Id= %d, v.Name= %s, v.Url= %s, v.Remark= %s, v.State= %d, v.Sort= %d",
v.ID, v.Name, v.URL, v.Remark, v.State, v.Sort)
}
}))
Convey("UpEmojiPackage", t, WithDao(func(d *Dao) {
id, err := d.UpEmojiPackage(context.Background(), "[小电视x]", "xxxx", "xx", 1, 1)
So(err, ShouldBeNil)
t.Logf("id= %d", id)
}))
Convey("UpEmojiPackageSort", t, WithDao(func(d *Dao) {
tx, _ := d.BeginTran(context.Background())
err := d.UpEmojiPackageSort(tx, "1")
if err != nil {
tx.Rollback()
t.Errorf("UpEmojiPackageSort err (%v)", err)
return
}
tx.Commit()
}))
}

View File

@@ -0,0 +1,82 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDao_Emoji(t *testing.T) {
// insert emoji
Convey("CreateEmoji", t, WithDao(func(d *Dao) {
id, err := d.CreateEmoji(context.Background(), 1, "[小电视_1]", "baidu.com", 0, 0, "ssss")
So(err, ShouldBeNil)
So(id, ShouldNotEqual, 0)
defer d.DelEmojiByID(context.Background(), id)
insertID := id
//get all emoji
Convey("EmojiList", WithDao(func(d *Dao) {
data, err := d.EmojiList(context.Background())
So(err, ShouldBeNil)
for _, v := range data {
t.Logf("v.Id= %d, v.PackageID= %d, v.Name= %s, v.Url= %s, v.Remark= %s, v.State= %d, v.Sort= %d",
v.ID, v.PackageID, v.Name, v.URL, v.Remark, v.State, v.Sort)
}
}))
// get emoji by package_id
Convey("EmojiListByPid", WithDao(func(d *Dao) {
data, err := d.EmojiListByPid(context.Background(), 1)
So(err, ShouldBeNil)
for _, v := range data {
t.Logf("v.Id= %d, v.PackageID= %d, v.Name= %s, v.Url= %s, v.Remark= %s, v.State= %d, v.Sort= %d",
v.ID, v.PackageID, v.Name, v.URL, v.Remark, v.State, v.Sort)
}
}))
//update emoji sort
Convey("UpEmojiSort", WithDao(func(d *Dao) {
tx, _ := d.BeginTran(context.Background())
err := d.UpEmojiSort(tx, "2,1")
if err != nil {
tx.Rollback()
t.Errorf("UpEmojiSort err (%v)", err)
return
}
tx.Commit()
}))
//update emoji state
Convey("test UpdateEmojis", WithDao(func(d *Dao) {
id, err := d.UpEmojiStateByID(context.Background(), 1, 70)
So(err, ShouldBeNil)
So(id, ShouldNotEqual, 0)
t.Logf("id= %d", id)
}))
Convey("test select emoji by name", WithDao(func(d *Dao) {
emojis, err := d.EmojiByName(context.Background(), "[小电视_1]")
So(err, ShouldBeNil)
for _, v := range emojis {
t.Logf("v.ID= %d", v.ID)
}
}))
// update emoji remark
Convey("test SortEmojis", WithDao(func(d *Dao) {
id, err := d.UpEmoji(context.Background(), "[小电视]", "cccxxx", "google.com", insertID)
So(err, ShouldBeNil)
So(id, ShouldNotEqual, 0)
t.Logf("id= %d", id)
}))
Convey("test delEmoji", WithDao(func(d *Dao) {
id, err := d.DelEmojiByID(context.Background(), insertID)
So(err, ShouldBeNil)
So(id, ShouldNotEqual, 0)
t.Logf("id= %d", id)
}))
}))
}

View File

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

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestEvent(t *testing.T) {
var (
mid = int64(1)
sub = &model.Subject{}
rp = &model.Reply{Content: &model.ReplyContent{}}
report = &model.Report{}
c = context.Background()
)
Convey("pub a event", t, WithDao(func(d *Dao) {
err := d.PubEvent(c, model.EventReportAdd, mid, sub, rp, report)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,102 @@
package dao
import (
"context"
"fmt"
"go-common/app/admin/main/reply/model"
"go-common/library/database/sql"
)
const (
_foldedReplies = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,floor,state,attr,ctime,mtime FROM reply_%d WHERE oid=? AND type=? AND root=? AND state=12"
_countFoldedReplies = "SELECT COUNT(*) FROM reply_%d WHERE oid=? AND type=? AND root=? AND state=12"
)
// TxCountFoldedReplies ...
func (d *Dao) TxCountFoldedReplies(tx *sql.Tx, oid int64, tp int32, root int64) (count int, err error) {
if err = tx.QueryRow(fmt.Sprintf(_countFoldedReplies, hit(oid)), oid, tp, root).Scan(&count); err != nil {
if err == sql.ErrNoRows {
err = nil
}
return
}
return
}
// FoldedReplies ...
func (d *Dao) FoldedReplies(ctx context.Context, oid int64, tp int32, root int64) (rps []*model.Reply, err error) {
rows, err := d.dbSlave.Query(ctx, fmt.Sprintf(_foldedReplies, hit(oid)), oid, tp, root)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
r := new(model.Reply)
if err = rows.Scan(&r.ID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
return
}
rps = append(rps, r)
}
if err = rows.Err(); err != nil {
return
}
return
}
// RemRdsByFold ...
func (d *Dao) RemRdsByFold(ctx context.Context, roots []int64, childMap map[int64][]int64, sub *model.Subject, rpMap map[int64]*model.Reply) {
var (
keyMap = make(map[string][]int64)
)
// 评论列表缓存
keyMap[keyMainIdx(sub.Oid, sub.Type, model.SortByFloor)] = roots
keyMap[keyMainIdx(sub.Oid, sub.Type, model.SortByCount)] = roots
keyMap[keyMainIdx(sub.Oid, sub.Type, model.SortByLike)] = roots
for root, children := range childMap {
// 评论详情页缓存
keyMap[keyRootIdx(root)] = children
for _, child := range children {
// 对话列表的缓存
if rp, ok := rpMap[child]; ok && rp.Dialog != 0 {
keyMap[keyDialogIdx(rp.Dialog)] = append(keyMap[keyDialogIdx(rp.Dialog)], rp.ID)
}
}
}
d.RemReplyFromRedis(ctx, keyMap)
}
// AddRdsByFold ...
func (d *Dao) AddRdsByFold(ctx context.Context, roots []int64, childMap map[int64][]int64, sub *model.Subject, rpMap map[int64]*model.Reply) {
var (
ok bool
err error
keyMapping = make(map[string][]*model.Reply)
)
if ok, err = d.ExpireFolder(ctx, model.FolderKindSub, sub.Oid); err != nil {
return
}
if ok {
key := keyFolderIdx(model.FolderKindSub, sub.Oid)
for _, root := range roots {
if rp, ok := rpMap[root]; ok {
keyMapping[key] = append(keyMapping[key], rp)
}
}
}
// 这里不回源
for root, children := range childMap {
if ok, err = d.ExpireFolder(ctx, model.FolderKindRoot, root); err != nil {
return
}
if ok {
key := keyFolderIdx(model.FolderKindRoot, root)
for _, child := range children {
if rp, ok := rpMap[child]; ok {
keyMapping[key] = append(keyMapping[key], rp)
}
}
}
}
d.AddFolder(ctx, keyMapping)
}

View File

@@ -0,0 +1,79 @@
package dao
import (
"context"
"go-common/app/admin/main/reply/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoTxCountFoldedReplies(t *testing.T) {
convey.Convey("TxCountFoldedReplies", t, func(ctx convey.C) {
var (
tx, _ = d.BeginTran(context.Background())
oid = int64(0)
tp = int32(0)
root = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := d.TxCountFoldedReplies(tx, oid, tp, root)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFoldedReplies(t *testing.T) {
convey.Convey("FoldedReplies", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int32(0)
root = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
rps, err := d.FoldedReplies(c, oid, tp, root)
ctx.Convey("Then err should be nil.rps should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(rps, convey.ShouldBeNil)
})
})
})
}
func TestDaoRemRdsByFold(t *testing.T) {
convey.Convey("RemRdsByFold", t, func(ctx convey.C) {
var (
c = context.Background()
roots = []int64{}
childMap map[int64][]int64
sub = &model.Subject{}
rpMap map[int64]*model.Reply
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
d.RemRdsByFold(c, roots, childMap, sub, rpMap)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}
func TestDaoAddRdsByFold(t *testing.T) {
convey.Convey("AddRdsByFold", t, func(ctx convey.C) {
var (
c = context.Background()
roots = []int64{}
childMap map[int64][]int64
sub = &model.Subject{}
rpMap map[int64]*model.Reply
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
d.AddRdsByFold(c, roots, childMap, sub, rpMap)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}

View File

@@ -0,0 +1,51 @@
package dao
import (
"context"
"fmt"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_selFilteredReply = "select rpid,message FROM reply_filtered WHERE rpid in (%s)"
_filterSearch = "http://api.bilibili.co/x/admin/filter/origins"
)
// FilterContents get filtered contents from db
func (d *Dao) FilterContents(ctx context.Context, rpMaps map[int64]string) error {
if len(rpMaps) == 0 {
return nil
}
var rpids []int64
for k := range rpMaps {
rpids = append(rpids, k)
}
rows, err := d.db.Query(ctx, fmt.Sprintf(_selFilteredReply, xstr.JoinInts(rpids)))
if err != nil {
log.Error("mysql.Query error(%v)", err)
return err
}
defer rows.Close()
for rows.Next() {
var id int64
var message string
if err = rows.Scan(&id, &message); err != nil {
if err == sql.ErrNoRows {
continue
} else {
log.Error("row.Scan error(%v)", err)
return err
}
}
rpMaps[id] = message
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return err
}
return nil
}

View File

@@ -0,0 +1,22 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoFilterContents(t *testing.T) {
convey.Convey("FilterContents", t, func(ctx convey.C) {
var (
rpMaps map[int64]string
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.FilterContents(context.Background(), rpMaps)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,476 @@
package dao
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
"go-common/app/admin/main/reply/conf"
"go-common/app/admin/main/reply/model"
"go-common/library/database/elastic"
"go-common/library/log"
)
const (
// api
_apiSearch = "/api/reply/internal/search"
_apiSearchUpdate = "/api/reply/internal/update"
// index
_searchIdxReply = "reply"
_searchIdxReport = "replyreport"
_searchIdxMonitor = "replymonitor"
_searchIdxTimeFormat = "2006-01-02 15:03:04"
)
var zeroTime = time.Time{}
func (d *Dao) SearchReplyV3(c context.Context, sp *model.SearchParams, page, pageSize int64) (res *model.SearchResult, err error) {
var (
end = sp.End
begin = sp.Begin
business = "reply_list"
)
if end == zeroTime {
end = time.Now()
}
if begin == zeroTime {
begin = end.Add(-time.Hour * 24 * 30)
}
r := d.es.NewRequest(business).IndexByTime("reply_list", elastic.IndexTypeWeek, begin, end).
WhereEq("type", fmt.Sprint(sp.Type)).Order(sp.Order, sp.Sort).Pn(int(page)).Ps(int(pageSize)).
WhereRange("ctime", begin.Format("2006-01-02 15:04:05"), end.Format("2006-01-02 15:04:05"), elastic.RangeScopeLcRc)
if sp.Oid != 0 {
r = r.WhereEq("oid", strconv.FormatInt(sp.Oid, 10))
}
if sp.TypeIds != "" {
r = r.WhereIn("typeid", strings.Split(sp.TypeIds, ","))
}
if sp.Keyword != "" {
r = r.WhereLike([]string{"message"}, []string{sp.Keyword}, true, elastic.LikeLevelLow)
}
if sp.KeywordHigh != "" {
r = r.WhereLike([]string{"message_middle"}, []string{sp.KeywordHigh}, true, elastic.LikeLevelMiddle)
r = r.OrderScoreFirst(false)
}
if sp.UID != 0 {
r = r.WhereEq("mid", sp.UID)
}
if sp.Uname != "" {
r = r.WhereEq("replier", sp.Uname)
}
if sp.AdminID != 0 {
r = r.WhereEq("adminid", sp.AdminID)
}
if sp.States != "" {
r = r.WhereIn("state", strings.Split(sp.States, ","))
}
if sp.IP != 0 {
var ip = make([]byte, 4)
binary.BigEndian.PutUint32(ip, uint32(sp.IP))
r = r.WhereEq("ip", net.IPv4(ip[0], ip[1], ip[2], ip[3]).String())
}
if sp.Attr != "" {
r = r.WhereIn("attr", strings.Split(sp.Attr, ","))
}
if sp.AdminName != "" {
r = r.WhereEq("admin_name", sp.AdminName)
}
result := new(struct {
Code int
Page *model.Page
Order string
Sort string
Result []*model.SearchReply
Message string
})
log.Warn("search params: %s", r.Params())
err = r.Scan(c, &result)
if err != nil || result.Code != 0 {
log.Error("SearchReplyV3 r.Scan(%v) error:(%v)", c, err)
return
}
res = new(model.SearchResult)
res.Result = result.Result
res.Code = result.Code
res.Page = result.Page.Num
res.PageSize = result.Page.Size
if res.PageSize > 0 {
res.PageCount = result.Page.Total / result.Page.Size
}
res.Total = result.Page.Total
res.Order = result.Order
res.Message = result.Message
return
}
// SearchAdminLog search adminlog
func (d *Dao) SearchAdminLog(c context.Context, rpids []int64) (res []*model.SearchAdminLog, err error) {
if len(rpids) == 0 {
return
}
r := d.es.NewRequest("reply_admin_log").Index("replyadminlog").Pn(int(1)).Ps(len(rpids)).WhereIn("rpid", rpids)
result := new(struct {
Code int
Page *model.Page
Order string
Sort string
Result []*model.SearchAdminLog
Message string
})
log.Warn("search params: %s", r.Params())
err = r.Scan(c, &result)
if err != nil || result.Code != 0 {
log.Error("SearchAdminLog r.Scan(%v) error:(%v)", c, err)
return
}
res = result.Result
return
}
// SearchReply search reply from ES.
func (d *Dao) SearchReply(c context.Context, p *model.SearchParams, page, pageSize int64) (res *model.SearchResult, err error) {
params := url.Values{}
params.Set("appid", _searchIdxReply)
params.Set("type", fmt.Sprint(p.Type))
params.Set("sort", p.Sort)
params.Set("order", p.Order)
params.Set("page", fmt.Sprint(page))
params.Set("pagesize", fmt.Sprint(pageSize))
if p.Oid != 0 {
params.Set("oid", strconv.FormatInt(p.Oid, 10))
}
if p.TypeIds != "" {
params.Set("typeids", p.TypeIds)
}
if p.Keyword != "" {
params.Set("keyword", p.Keyword)
}
if p.UID != 0 {
params.Set("uid", strconv.FormatInt(p.UID, 10))
}
if p.Uname != "" {
params.Set("nickname", p.Uname)
}
if p.AdminID != 0 {
params.Set("adminid", strconv.FormatInt(p.AdminID, 10))
}
if p.Begin != zeroTime {
params.Set("start_time", p.Begin.Format(model.DateFormat))
}
if p.End != zeroTime {
params.Set("end_time", p.End.Format(model.DateFormat))
}
if p.States != "" {
params.Set("states", p.States)
}
if p.IP != 0 {
params.Set("ip", strconv.FormatInt(p.IP, 10))
}
if p.Attr != "" {
params.Set("attr", p.Attr)
}
if p.AdminName != "" {
params.Set("admin_name", p.AdminName)
}
res = &model.SearchResult{}
uri := conf.Conf.Host.Search + _apiSearch
if err = d.httpClient.Get(c, uri, "", params, res); err != nil {
log.Error("searchReply error(%v)", err)
return
}
if res.Code != 0 {
err = model.ErrSearchReply
log.Error("searchReply:%+v error(%v)", res, err)
}
return
}
// SearchMonitor return search monitor reply from ES.
func (d *Dao) SearchMonitor(c context.Context, sp *model.SearchMonitorParams, page, pageSize int64) (res *model.SearchMonitorResult, err error) {
var (
fields []string
keywords []string
order string
sort = "desc"
)
// NOTE:这里之前order 跟 sort 搞反了
if sp.Sort != "" {
order = sp.Sort
}
if sp.Order != "" {
sort = sp.Order
}
r := d.es.NewRequest("reply_monitor").Index(_searchIdxMonitor).
WhereEq("type", fmt.Sprint(sp.Type)).
Order(order, sort).Pn(int(page)).Ps(int(pageSize))
// mode=0 所有监控方式, mode=1 monitor, mode=2, 先审后发
if sp.Mode == 0 {
r = r.WhereOr("monitor", true).WhereOr("audit", true)
} else if sp.Mode == 1 {
r = r.WhereEq("monitor", true)
} else if sp.Mode == 2 {
r = r.WhereEq("audit", true)
}
if sp.Oid > 0 {
r = r.WhereEq("oid", fmt.Sprint(sp.Oid))
}
if sp.UID > 0 {
r = r.WhereEq("mid", fmt.Sprint(sp.UID))
}
if sp.NickName != "" {
fields = append(fields, "uname")
keywords = append(keywords, sp.NickName)
}
if sp.Keyword != "" {
fields = append(fields, "title")
keywords = append(keywords, sp.Keyword)
}
if fields != nil && keywords != nil {
r = r.WhereLike(fields, keywords, true, elastic.LikeLevelLow)
}
result := new(struct {
Code int
Page *model.Page
Order string
Sort string
Result []*model.SearchMonitor
Message string
})
res = &model.SearchMonitorResult{}
log.Warn(r.Params())
err = r.Scan(c, &result)
if err != nil || result.Code != 0 {
log.Error("r.Scan(%v) error:(%v)", c, err)
return
}
res.Result = result.Result
res.Code = result.Code
res.Page = result.Page.Num
res.PageSize = result.Page.Size
if res.PageSize > 0 {
res.PageCount = result.Page.Total / result.Page.Size
}
res.Total = result.Page.Total
res.Order = result.Order
res.Message = result.Message
oids := make([]int64, len(res.Result))
var tp int32
for idx, r := range res.Result {
oids[idx] = r.Oid
tp = int32(r.Type)
}
results, err := d.SubMCount(c, oids, tp)
if err != nil {
log.Error("SubMCount(%v,%v) error", oids, tp)
return
}
for i, reply := range res.Result {
res.Result[i].MCount = results[reply.Oid]
res.Result[i].OidStr = strconv.FormatInt(res.Result[i].Oid, 10)
}
return
}
// UpSearchMonitor update monitor to search data.
func (d *Dao) UpSearchMonitor(c context.Context, sub *model.Subject, remark string) (err error) {
m := make(map[string]interface{})
m["oid"] = sub.Oid
m["type"] = sub.Type
if sub.AttrVal(model.SubAttrMonitor) == model.AttrYes {
m["monitor"] = true
} else {
m["monitor"] = false
}
if sub.AttrVal(model.SubAttrAudit) == model.AttrYes {
m["audit"] = true
} else {
m["audit"] = false
}
m["remark"] = remark
us := d.es.NewUpdate("reply_monitor").Insert()
us.AddData("replymonitor", m)
err = us.Do(c)
if err != nil {
err = model.ErrSearchReport
log.Error("upSearchMonitor error(%v)", err)
return
}
return
}
// SearchReport search reports from ES.
func (d *Dao) SearchReport(c context.Context, sp *model.SearchReportParams, page, pageSize int64) (res *model.SearchReportResult, err error) {
params := url.Values{}
params.Set("appid", "replyreport")
params.Set("type", fmt.Sprint(sp.Type))
params.Set("page", fmt.Sprint(page))
params.Set("pagesize", fmt.Sprint(pageSize))
if sp.Oid != 0 {
params.Set("oid", fmt.Sprint(sp.Oid))
}
if sp.UID != 0 {
params.Set("uid", fmt.Sprint(sp.UID))
}
if sp.Reason != "" {
params.Set("reason", sp.Reason)
}
if sp.Typeids != "" {
params.Set("typeids", sp.Typeids)
}
if sp.Keyword != "" {
params.Set("keyword", sp.Keyword)
}
if sp.Nickname != "" {
params.Set("nickname", sp.Nickname)
}
if sp.States != "" {
params.Set("states", sp.States)
}
if sp.StartTime != "" {
params.Set("start_time", sp.StartTime)
}
if sp.EndTime != "" {
params.Set("end_time", sp.EndTime)
}
if sp.Order != "" {
params.Set("order", sp.Order)
}
if sp.Sort != "" {
params.Set("sort", sp.Sort)
}
res = &model.SearchReportResult{}
uri := conf.Conf.Host.Search + _apiSearch
if err = d.httpClient.Get(c, uri, "", params, res); err != nil {
log.Error("searchReport error(%v)", err)
return
}
if res.Code != 0 {
err = model.ErrSearchReport
log.Error("searchReport:%+v error(%v)", res, err)
}
return
}
// MonitorStats return search monitor stats from ES.
func (d *Dao) MonitorStats(c context.Context, mode, page, pageSize int64, adminIDs, sort, order, startTime, endTime string) (res *model.StatsMonitorResult, err error) {
params := url.Values{}
params.Set("appid", "replymonista")
params.Set("mode", fmt.Sprint(mode))
params.Set("page", fmt.Sprint(page))
params.Set("pagesize", fmt.Sprint(pageSize))
if adminIDs != "" {
params.Set("adminids", adminIDs)
params.Set("typeid", fmt.Sprint(model.MonitorStatsUser))
} else {
params.Set("typeid", fmt.Sprint(model.MonitorStatsAll))
}
if sort != "" {
params.Set("sort", sort)
}
if order != "" {
params.Set("order", order)
}
if startTime != "" {
params.Set("start_time", startTime)
}
if endTime != "" {
params.Set("end_time", endTime)
}
res = &model.StatsMonitorResult{}
uri := conf.Conf.Host.Search + _apiSearch
if err = d.httpClient.Get(c, uri, "", params, res); err != nil {
log.Error("monitorStats error(%v)", err)
return
}
if res.Code != 0 {
err = model.ErrSearchMonitor
log.Error("searchStats:%+v error(%v)", res, err)
}
return
}
// UpSearchReply update search reply index.
func (d *Dao) UpSearchReply(c context.Context, rps map[int64]*model.Reply, newState int32) (err error) {
if len(rps) <= 0 {
return
}
stales := d.es.NewUpdate("reply_list")
for _, rp := range rps {
m := make(map[string]interface{})
m["id"] = rp.ID
m["state"] = newState
m["mtime"] = rp.MTime.Time().Format("2006-01-02 15:04:05")
m["oid"] = rp.Oid
m["type"] = rp.Type
if rp.Content != nil {
m["message"] = rp.Content.Message
}
stales = stales.AddData(d.es.NewUpdate("reply_list").IndexByTime("reply_list", elastic.IndexTypeWeek, rp.CTime.Time()), m)
}
err = stales.Do(c)
if err != nil {
log.Error("upSearchReply update stales(%s) failed!err:=%v", stales.Params(), err)
return
}
log.Info("upSearchReply:stale:%s ret:%+v", stales.Params(), err)
return
}
// UpSearchReport update search report index.
func (d *Dao) UpSearchReport(c context.Context, rpts map[int64]*model.Report, rpState *int32) (err error) {
var res struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
params := url.Values{}
params.Set("appid", _searchIdxReport)
values := make([]map[string]interface{}, 0)
rps := make(map[int64]*model.Reply)
for _, rpt := range rpts {
if int64(rpt.ReplyCtime) != 0 && rpState != nil {
rps[rpt.RpID] = &model.Reply{
ID: rpt.RpID,
Oid: rpt.Oid,
Type: rpt.Type,
MTime: rpt.MTime,
CTime: rpt.ReplyCtime,
State: *rpState,
}
}
v := make(map[string]interface{})
v["id"] = fmt.Sprintf("%d_%d_%d", rpt.RpID, rpt.Oid, rpt.Type)
v["content"] = rpt.Content
v["reason"] = rpt.Reason
v["state"] = rpt.State
v["mtime"] = rpt.MTime.Time().Format(_searchIdxTimeFormat)
v["index_time"] = rpt.CTime.Time().Format(_searchIdxTimeFormat)
if rpt.Attr == 1 {
v["attr"] = []int{1}
} else {
v["attr"] = []int{}
}
if rpState != nil {
v["reply_state"] = *rpState
}
values = append(values, v)
}
b, _ := json.Marshal(values)
params.Set("val", string(b))
// http post
uri := conf.Conf.Host.Search + _apiSearchUpdate
if err = d.httpClient.Post(c, uri, "", params, &res); err != nil {
log.Error("upSearchReport error(%v)", err)
}
log.Info("upSearchReport:%s post:%s ret:%+v", uri, params.Encode(), res)
if len(rps) != 0 && rpState != nil {
err = d.UpSearchReply(c, rps, *rpState)
}
return
}

View File

@@ -0,0 +1,71 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/admin/main/reply/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func TestUpdateReply(t *testing.T) {
Convey("update search reply", t, WithDao(func(d *Dao) {
err := d.UpSearchReply(context.Background(), map[int64]*model.Reply{111852176: &model.Reply{
ID: 111852176,
Oid: 10098544,
Type: 1,
CTime: xtime.Time(1534412702),
MTime: xtime.Time(time.Now().Unix()),
State: 0,
}}, 3)
So(err, ShouldBeNil)
fmt.Println(err)
}))
}
func TestSearchAdminLog(t *testing.T) {
Convey("test search adminlog", t, WithDao(func(d *Dao) {
res, err := d.SearchAdminLog(context.TODO(), []int64{111843721})
So(err, ShouldBeNil)
So(len(res), ShouldBeGreaterThan, 0)
fmt.Printf("%+v", res[0])
}))
}
func TestSearchMonitor(t *testing.T) {
var (
c = context.Background()
sp = &model.SearchMonitorParams{
Mode: 0,
Type: 1,
Oid: 10099866,
Sort: "unverify_num",
}
oid int64 = 10099866
typ int32 = 1
remark = "remark"
)
Convey("test search monitor", t, WithDao(func(d *Dao) {
res, err := d.SearchMonitor(c, sp, 1, 20)
So(err, ShouldBeNil)
So(len(res.Result), ShouldBeGreaterThan, 0)
So(res.Result[0].Oid, ShouldEqual, sp.Oid)
}))
Convey("test add monitor", t, WithDao(func(d *Dao) {
sub, _ := d.Subject(c, oid, typ)
sub.AttrSet(model.AttrYes, model.SubAttrMonitor)
err := d.UpSearchMonitor(c, sub, remark)
So(err, ShouldBeNil)
}))
time.Sleep(5 * time.Second)
Convey("test search monitor", t, WithDao(func(d *Dao) {
res, err := d.SearchMonitor(c, sp, 1, 1)
So(err, ShouldBeNil)
So(len(res.Result), ShouldBeGreaterThan, 0)
So(res.Result[0].Remark, ShouldEqual, remark)
}))
}

View File

@@ -0,0 +1,330 @@
package dao
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
"go-common/library/xstr"
)
const (
// api
_apiNotice = "http://api.bilibili.co/x/internal/credit/publish/infos"
_apiBan = "http://api.bilibili.co/x/internal/credit/blocked/infos"
_apiCredit = "http://api.bilibili.co/x/internal/credit/blocked/cases"
_apiLiveVideo = "http://api.vc.bilibili.co/clip/v1/video/detail"
_apiLiveActivity = "http://api.live.bilibili.co/comment/v1/relation/get_by_id"
_apiLiveNotice = "http://api.vc.bilibili.co/news/v1/notice/info"
_apiLivePicture = "http://api.vc.bilibili.co/link_draw/v1/doc/detail"
_apiActivitySub = "http://matsuri.bilibili.co//activity/subject/url"
_apiTopic = "http://matsuri.bilibili.co/activity/page/one/%d"
_apiTopics = "http://matsuri.bilibili.co/activity/pages"
_apiDynamic = "http://api.vc.bilibili.co/dynamic_repost/v0/dynamic_repost/ftch_rp_cont?dynamic_ids[]=%d"
// link
_linkBan = "https://www.bilibili.com/blackroom/ban/%d"
_linkNotice = "https://www.bilibili.com/blackroom/notice/%d"
_linkCredit = "https://www.bilibili.com/judgement/case/%d"
_linkLiveVideo = "http://vc.bilibili.com/video/%d"
_linkLiveNotice = "http://link.bilibili.com/p/eden/news#/newsdetail?id=%d"
_linkLivePicture = "http://h.bilibili.com/ywh/%d"
_linkDynamic = "http://t.bilibili.com/%d"
)
type notice struct {
Title string `json:"title"`
}
type ban struct {
Title string `json:"punishTitle"`
}
type credit struct {
Title string `json:"punishTitle"`
}
// NoticeTitle get blackromm notice info.
func (d *Dao) NoticeTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*notice `json:"data"`
}
if err = d.httpClient.Get(c, _apiNotice, "", params, &res); err != nil {
log.Error("httpNotice(%s) error(%v)", _apiNotice, err)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_linkNotice, oid)
return
}
// BanTitle get ban info.
func (d *Dao) BanTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*ban `json:"data"`
}
if err = d.httpClient.Get(c, _apiBan, "", params, &res); err != nil {
log.Error("httpBan(%s) error(%v)", _apiBan, err)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_linkBan, oid)
return
}
// CreditTitle return link.
func (d *Dao) CreditTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("ids", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data map[int64]*credit `json:"data"`
}
if err = d.httpClient.Get(c, _apiCredit, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", _apiCredit, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("url:%s?%s code:%d", _apiCredit, params.Encode(), res.Code)
return
}
if r := res.Data[oid]; r != nil {
title = r.Title
}
link = fmt.Sprintf(_linkCredit, oid)
return
}
// LiveVideoTitle get live video title.
func (d *Dao) LiveVideoTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("video_id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Item *struct {
Description string `json:"description"`
} `json:"item"`
} `json:"data"`
}
if err = d.httpClient.Get(c, _apiLiveVideo, "", params, &res); err != nil {
log.Error("httpLiveVideoTitle(%s?%s) error(%v)", _apiLiveVideo, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil || res.Data.Item == nil {
err = fmt.Errorf("url:%s?%s code:%d", _apiLiveVideo, params.Encode(), res.Code)
return
}
title = res.Data.Item.Description
link = fmt.Sprintf(_linkLiveVideo, oid)
return
}
// LiveActivityTitle get live activity info.
func (d *Dao) LiveActivityTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, _apiLiveActivity, "", params, &res); err != nil {
log.Error("httpLiveActivityTitle(%s?%s) error(%v)", _apiLiveActivity, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("url:%s?%s code:%d", _apiLiveActivity, params.Encode(), res.Code)
return
}
title = res.Data.Name
link = res.Data.URL
return
}
// LiveNoticeTitle get live notice info.
func (d *Dao) LiveNoticeTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"title"`
} `json:"data"`
}
if err = d.httpClient.Get(c, _apiLiveNotice, "", params, &res); err != nil {
log.Error("LiveNoticeTitle(%s?%s) error(%v)", _apiLiveNotice, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("url:%s?%s code:%d", _apiLiveNotice, params.Encode(), res.Code)
return
}
title = res.Data.Title
link = fmt.Sprintf(_linkLiveNotice, oid)
return
}
// LivePictureTitle get live picture info.
func (d *Dao) LivePictureTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("doc_id", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Item *struct {
Title string `json:"title"`
Desc string `json:"description"`
} `json:"item"`
} `json:"data"`
}
if err = d.httpClient.Get(c, _apiLivePicture, "", params, &res); err != nil {
log.Error("LivePictureTitle(%s?%s) error(%v)", _apiLivePicture, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil || res.Data.Item == nil {
err = fmt.Errorf("url:%s?%s code:%d", _apiLivePicture, params.Encode(), res.Code)
return
}
title = res.Data.Item.Title
if title == "" {
title = res.Data.Item.Desc
}
link = fmt.Sprintf(_linkLivePicture, oid)
return
}
// TopicTitle get topic info.
func (d *Dao) TopicTitle(c context.Context, oid int64) (title, link string, err error) {
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"name"`
PCLink string `json:"pc_url"`
H5Link string `json:"h5_url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, fmt.Sprintf(_apiTopic, oid), "", nil, &res); err != nil {
log.Error("TopicTitle(%s) error(%v)", fmt.Sprintf(_apiTopic, oid), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", fmt.Sprintf(_apiTopic, oid), res.Code)
return
}
title = res.Data.Title
link = res.Data.PCLink
if link == "" {
link = res.Data.H5Link
}
return
}
// TopicsLink get topic info.
func (d *Dao) TopicsLink(c context.Context, links map[int64]string, isTopic bool) (err error) {
if len(links) == 0 {
return
}
var res struct {
Code int `json:"code"`
Data *struct {
List []struct {
ID int64 `json:"id"`
PCURL string `json:"pc_url"`
H5URL string `json:"h5_url"`
} `json:"list"`
} `json:"data"`
}
var ids []int64
for oid := range links {
ids = append(ids, oid)
}
params := url.Values{}
params.Set("pids", xstr.JoinInts(ids))
params.Set("all", "isOne")
if isTopic {
params.Set("mold", "1")
}
if err = d.httpClient.Get(c, _apiTopics, "", params, &res); err != nil {
log.Error("TopicsTitle(%s) error(%v)", _apiTopics, err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", _apiTopics, res.Code)
return
}
for _, data := range res.Data.List {
if data.PCURL != "" {
links[data.ID] = data.PCURL
} else {
links[data.ID] = data.H5URL
}
}
return
}
// ActivitySub return activity sub link.
func (d *Dao) ActivitySub(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
params.Set("oid", strconv.FormatInt(oid, 10))
var res struct {
Code int `json:"code"`
Data *struct {
Title string `json:"name"`
Link string `json:"act_url"`
} `json:"data"`
}
if err = d.httpClient.Get(c, _apiActivitySub, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", _apiActivitySub, params.Encode(), err)
return
}
if res.Data == nil {
err = fmt.Errorf("url:%s code:%d", _apiActivitySub, res.Code)
return
}
title = res.Data.Title
link = res.Data.Link
return
}
// DynamicTitle return link and title.
func (d *Dao) DynamicTitle(c context.Context, oid int64) (title, link string, err error) {
params := url.Values{}
uri := fmt.Sprintf(_apiDynamic, oid)
var res struct {
Code int `json:"code"`
Data *struct {
Pairs []struct {
DynamicID int64 `json:"dynamic_id"`
Content string `json:"rp_cont"`
Type int32 `json:"type"`
} `json:"pairs"`
TotalCount int64 `json:"total_count"`
} `json:"data,omitempty"`
Message string `json:"message"`
}
if err = d.httpClient.Get(c, uri, "", params, &res); err != nil {
log.Error("d.httpClient.Get(%s?%s) error(%v)", uri, params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil || len(res.Data.Pairs) == 0 {
err = fmt.Errorf("get dynamic failed!url:%s?%s code:%d message:%s pairs:%v", uri, params.Encode(), res.Code, res.Message, res.Data.Pairs)
return
}
title = res.Data.Pairs[0].Content
link = fmt.Sprintf(_linkDynamic, oid)
return
}

View File

@@ -0,0 +1,196 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoNoticeTitle(t *testing.T) {
convey.Convey("NoticeTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.NoticeTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoBanTitle(t *testing.T) {
convey.Convey("BanTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.BanTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCreditTitle(t *testing.T) {
convey.Convey("CreditTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.CreditTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(link, convey.ShouldNotBeNil)
ctx.So(title, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoLiveVideoTitle(t *testing.T) {
convey.Convey("LiveVideoTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.LiveVideoTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}
func TestDaoLiveActivityTitle(t *testing.T) {
convey.Convey("LiveActivityTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.LiveActivityTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}
func TestDaoLiveNoticeTitle(t *testing.T) {
convey.Convey("LiveNoticeTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.LiveNoticeTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}
func TestDaoLivePictureTitle(t *testing.T) {
convey.Convey("LivePictureTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.LivePictureTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}
func TestDaoTopicTitle(t *testing.T) {
convey.Convey("TopicTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.TopicTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
if err == nil {
ctx.So(err, convey.ShouldBeNil)
}
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}
func TestDaoTopicsLink(t *testing.T) {
convey.Convey("TopicsLink", t, func(ctx convey.C) {
var (
c = context.Background()
links map[int64]string
isTopic bool
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.TopicsLink(c, links, isTopic)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoActivitySub(t *testing.T) {
convey.Convey("ActivitySub", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.ActivitySub(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}
func TestDaoDynamicTitle(t *testing.T) {
convey.Convey("DynamicTitle", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
title, link, err := _d.DynamicTitle(c, oid)
ctx.Convey("Then err should be nil.title,link should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(link, convey.ShouldBeBlank)
ctx.So(title, convey.ShouldBeBlank)
})
})
})
}

View File

@@ -0,0 +1,103 @@
package dao
import (
"context"
"strconv"
"go-common/app/admin/main/reply/model"
"go-common/library/database/elastic"
"go-common/library/ecode"
"go-common/library/log"
"strings"
"time"
)
const (
// log api
_logURL = "/x/admin/search/log"
)
// ReportLogData report log data
type ReportLogData struct {
Sort string `json:"sort"`
Order string `json:"order"`
Page model.Page `json:"page"`
Result []*ReportLogResult `json:"result"`
}
// ReportLogResult ReportLogResult
type ReportLogResult struct {
AdminName string `json:"uname"`
AdminID int64 `json:"uid"`
Business int64 `json:"business"`
Type int32 `json:"type"`
Oid int64 `json:"oid"`
OidStr string `json:"oid_str"`
Action string `json:"action"`
Ctime string `json:"ctime"`
Index0 int64 `json:"int_0"`
Index1 int64 `json:"int_1"`
Index2 int64 `json:"int_2"`
Content string `json:"extra_data"`
}
// ReportLog get notice info.
func (d *Dao) ReportLog(c context.Context, sp model.LogSearchParam) (data *ReportLogData, err error) {
var (
// log_audit评论的索引是按时间分区的最早的数据是2018年的所以这里最早时间是写死的2018年
stime = time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)
etime = time.Now()
)
if sp.CtimeFrom != "" {
if stime, err = time.Parse(model.DateFormat, sp.CtimeFrom); err != nil {
log.Error("time.Parse(%v) error", sp.CtimeFrom)
stime = time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)
}
}
if sp.CtimeTo != "" {
if etime, err = time.Parse(model.DateFormat, sp.CtimeTo); err != nil {
log.Error("time.Parse(%v) error", sp.CtimeTo)
etime = time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)
}
}
action := strings.Split(sp.Action, ",")
r := d.es.NewRequest("log_audit").IndexByTime("log_audit_41", elastic.IndexTypeYear, stime, etime).WhereIn("action", action).
WhereRange("ctime", stime.Format(model.DateFormat), etime.Format(model.DateFormat), elastic.RangeScopeLcRc)
if sp.Pn <= 0 {
sp.Pn = 1
}
if sp.Ps <= 0 {
sp.Ps = 20
}
if sp.Order == "" {
sp.Order = "ctime"
}
if sp.Sort == "" {
sp.Sort = "desc"
}
r = r.Order(sp.Order, sp.Sort).Pn(int(sp.Pn)).Ps(int(sp.Ps))
if sp.Oid > 0 {
r = r.WhereEq("oid", strconv.FormatInt(sp.Oid, 10))
}
if sp.Mid > 0 {
r = r.WhereEq("int_0", sp.Mid)
}
if sp.Type > 0 {
r = r.WhereEq("type", sp.Type)
}
if sp.Other > 0 {
r = r.WhereEq("int_1", sp.Other)
}
log.Warn(r.Params())
err = r.Scan(c, &data)
if err != nil {
log.Error("r.Scan(%v) error(%v)", c, err)
return
}
if data == nil {
err = ecode.ServerErr
log.Error("log_audit error")
return
}
return
}

View File

@@ -0,0 +1,44 @@
package dao
import (
"context"
"go-common/app/admin/main/reply/model"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestReportLog(t *testing.T) {
sp := model.LogSearchParam{
Oid: 10099866,
Type: 1,
}
c := context.Background()
Convey("test top ReportLog", t, WithDao(func(d *Dao) {
sp.Action = "top"
data, err := d.ReportLog(c, sp)
So(err, ShouldBeNil)
So(data.Page.Total, ShouldEqual, 1)
}))
Convey("test monitor ReportLog", t, WithDao(func(d *Dao) {
sp.Action = "monitor"
data, err := d.ReportLog(c, sp)
So(err, ShouldBeNil)
So(data.Page.Total, ShouldEqual, 14)
}))
Convey("test subject groupby ReportLog", t, WithDao(func(d *Dao) {
sp.Action = "subject_allow,subject_forbid,subject_frozen,subject_unfrozen_allow,subject_unfrozen_forbid"
sp.Oid = 0
data, err := d.ReportLog(c, sp)
So(err, ShouldBeNil)
So(data.Page.Total, ShouldEqual, 67)
}))
Convey("test one subject ReportLog", t, WithDao(func(d *Dao) {
sp.Action = "subject_allow,subject_forbid,subject_frozen,subject_unfrozen_allow,subject_unfrozen_forbid"
sp.Appid = "log_audit"
sp.Oid = 10099866
data, err := d.ReportLog(c, sp)
So(err, ShouldBeNil)
So(data.Page.Total, ShouldEqual, 2)
}))
}

View File

@@ -0,0 +1,266 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/admin/main/reply/model"
"go-common/library/cache/memcache"
"go-common/library/log"
)
const (
_prefixSub = "s_" // sub_oid<<8|type
_prefixReply = "r_" // r_rpID
_prefixAdminTop = "at_" // at_rpID
_prefixUpperTop = "ut_" // ut_rpID
_prefixConfig = "c_%d_%d_%d" // oid_type_category
_oidOverflow = 1 << 48
)
func keyReply(rpID int64) string {
return _prefixReply + strconv.FormatInt(rpID, 10)
}
func keySubject(oid int64, typ int32) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d", _prefixSub, oid, typ)
}
return _prefixSub + strconv.FormatInt((oid<<8)|int64(typ), 10)
}
func keyConfig(oid int64, typ, category int32) string {
return fmt.Sprintf(_prefixConfig, oid, typ, category)
}
func keyAdminTop(oid int64, attr uint32) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d", _prefixAdminTop, oid, attr)
}
return _prefixAdminTop + strconv.FormatInt((oid<<8)|int64(attr), 10)
}
func keyUpperTop(oid int64, attr uint32) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d", _prefixUpperTop, oid, attr)
}
return _prefixUpperTop + strconv.FormatInt((oid<<8)|int64(attr), 10)
}
// PingMC check connection success.
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcExpire}
err = conn.Set(&item)
conn.Close()
return
}
// SubjectCache get subject from memcache.
func (d *Dao) SubjectCache(c context.Context, oid int64, typ int32) (sub *model.Subject, err error) {
key := keySubject(oid, typ)
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
sub = new(model.Subject)
if err = conn.Scan(item, sub); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
sub = nil
}
return
}
// AddSubjectCache add subject into memcache.
func (d *Dao) AddSubjectCache(c context.Context, subs ...*model.Subject) (err error) {
conn := d.mc.Get(c)
for _, sub := range subs {
key := keySubject(sub.Oid, sub.Type)
item := &memcache.Item{Key: key, Object: sub, Expiration: d.mcExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s,%v) error(%v)", key, sub, err)
}
}
conn.Close()
return
}
// DelSubjectCache delete subject from memcache.
func (d *Dao) DelSubjectCache(c context.Context, oid int64, typ int32) (err error) {
key := keySubject(oid, typ)
conn := d.mc.Get(c)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
}
}
conn.Close()
return
}
// ReplyCache get a reply from memcache.
func (d *Dao) ReplyCache(c context.Context, rpID int64) (rp *model.Reply, err error) {
key := keyReply(rpID)
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
rp = new(model.Reply)
if err = conn.Scan(item, rp); err != nil {
rp = nil
}
return
}
// RepliesCache multi get replies from memcache.
func (d *Dao) RepliesCache(c context.Context, rpIDs []int64) (rpMap map[int64]*model.Reply, missed []int64, err error) {
rpMap = make(map[int64]*model.Reply, len(rpIDs))
keys := make([]string, len(rpIDs))
mm := make(map[string]int64, len(rpIDs))
for i, rpID := range rpIDs {
key := keyReply(rpID)
keys[i] = key
mm[key] = rpID
}
conn := d.mc.Get(c)
defer conn.Close()
items, err := conn.GetMulti(keys)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
for _, item := range items {
rp := new(model.Reply)
if err = conn.Scan(item, rp); err != nil {
log.Error("conn.Scan(%s) error(%v)", item.Value, err)
continue
}
rpMap[mm[item.Key]] = rp
delete(mm, item.Key)
}
missed = make([]int64, 0, len(mm))
for _, valIn := range mm {
missed = append(missed, valIn)
}
return
}
// AddReplyCache add reply into memcache.
func (d *Dao) AddReplyCache(c context.Context, rps ...*model.Reply) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
for _, rp := range rps {
item := &memcache.Item{
Key: keyReply(rp.ID),
Object: rp,
Expiration: d.mcExpire,
Flags: memcache.FlagJSON,
}
if err = conn.Set(item); err != nil {
return
}
}
return
}
// DelReplyCache delete reply from memcache.
func (d *Dao) DelReplyCache(c context.Context, rpID int64) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(keyReply(rpID)); err != nil {
if err == memcache.ErrNotFound {
err = nil
}
}
conn.Close()
return
}
// DelConfigCache delete reply config from memcache.
func (d *Dao) DelConfigCache(c context.Context, oid int64, typ, category int32) (err error) {
key := keyConfig(oid, typ, category)
conn := d.mc.Get(c)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
}
}
conn.Close()
return
}
// TopCache get a reply from memcache.
func (d *Dao) TopCache(c context.Context, oid int64, attr uint32) (rp *model.Reply, err error) {
var key string
if attr == model.SubAttrTopAdmin {
key = keyAdminTop(oid, attr)
} else if attr == model.SubAttrTopUpper {
key = keyUpperTop(oid, attr)
}
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
}
return
}
rp = new(model.Reply)
if err = conn.Scan(item, rp); err != nil {
rp = nil
}
return
}
// DelTopCache delete topreply from memcache.
func (d *Dao) DelTopCache(c context.Context, oid int64, attr uint32) (err error) {
var key string
if attr == model.SubAttrTopAdmin {
key = keyAdminTop(oid, attr)
} else if attr == model.SubAttrTopUpper {
key = keyUpperTop(oid, attr)
}
conn := d.mc.Get(c)
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
conn.Close()
return
}
// AddTopCache add top reply into memcache.
func (d *Dao) AddTopCache(c context.Context, rp *model.Reply) (err error) {
var key string
if rp.AttrVal(model.AttrTopAdmin) == model.AttrYes {
key = keyAdminTop(rp.Oid, model.AttrTopAdmin)
} else if rp.AttrVal(model.AttrTopUpper) == model.AttrYes {
key = keyUpperTop(rp.Oid, model.AttrTopUpper)
} else {
return
}
conn := d.mc.Get(c)
defer conn.Close()
item := &memcache.Item{Key: key, Object: rp, Expiration: d.mcExpire, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
log.Error("conn.Set(%s,%v) error(%v)", key, rp, err)
}
return
}

View File

@@ -0,0 +1,84 @@
package dao
import (
"context"
"testing"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestSubjectCache(t *testing.T) {
var (
sub = &model.Subject{
Oid: 1,
Type: 1,
}
c = context.Background()
)
Convey("test subject cache", t, WithDao(func(d *Dao) {
// add
err := d.AddSubjectCache(c, sub)
So(err, ShouldBeNil)
// get
cache, err := d.SubjectCache(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(cache.Oid, ShouldEqual, sub.Oid)
// del
err = d.DelSubjectCache(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
// get
cache, err = d.SubjectCache(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(cache, ShouldBeNil)
}))
}
func TestReplyCache(t *testing.T) {
var (
rp = &model.Reply{
ID: 1,
Oid: 1,
Type: 1,
}
c = context.Background()
)
Convey("test reply cache", t, WithDao(func(d *Dao) {
// add
err := d.AddReplyCache(c, rp)
So(err, ShouldBeNil)
// get
cache, err := d.ReplyCache(c, rp.ID)
So(err, ShouldBeNil)
So(cache.ID, ShouldEqual, rp.ID)
// get
caches, miss, err := d.RepliesCache(c, []int64{rp.ID})
So(err, ShouldBeNil)
So(len(caches), ShouldEqual, 1)
So(len(miss), ShouldEqual, 0)
// del
err = d.DelReplyCache(c, rp.ID)
So(err, ShouldBeNil)
// get
cache, err = d.ReplyCache(c, rp.ID)
So(err, ShouldBeNil)
So(cache, ShouldBeNil)
}))
Convey("test top reply cache", t, WithDao(func(d *Dao) {
rp.AttrSet(model.AttrYes, model.AttrTopAdmin)
// add
err := d.AddTopCache(c, rp)
So(err, ShouldBeNil)
// get
cache, err := d.TopCache(c, rp.Oid, model.SubAttrTopAdmin)
So(err, ShouldBeNil)
So(cache.ID, ShouldEqual, rp.ID)
// del
err = d.DelTopCache(c, rp.Oid, model.SubAttrTopAdmin)
// get
cache, err = d.TopCache(c, rp.Oid, model.SubAttrTopAdmin)
So(err, ShouldBeNil)
So(cache, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,58 @@
package dao
import (
"context"
"net/url"
"strconv"
"time"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_msgReplyDel = "1_1_2"
_msgReportAccept = "1_3_1"
_msgTypeSystem = 4
// api
_apiMsgSend = "http://message.bilibili.co/api/notify/send.user.notify.do"
)
// SendReplyDelMsg send delete reply message.
func (d *Dao) SendReplyDelMsg(c context.Context, mid int64, title, msg string, now time.Time) (err error) {
return d.sendMsg(c, _msgReplyDel, title, msg, _msgTypeSystem, 0, []int64{mid}, "", now.Unix())
}
// SendReportAcceptMsg send report message.
func (d *Dao) SendReportAcceptMsg(c context.Context, mid int64, title, msg string, now time.Time) (err error) {
return d.sendMsg(c, _msgReportAccept, title, msg, _msgTypeSystem, 0, []int64{mid}, "", now.Unix())
}
func (d *Dao) sendMsg(c context.Context, mc, title, msg string, typ int, pub int64, mids []int64, info string, ts int64) (err error) {
params := url.Values{}
params.Set("type", "json")
params.Set("source", "1")
params.Set("mc", mc)
params.Set("title", title)
params.Set("data_type", strconv.Itoa(typ))
params.Set("context", msg)
params.Set("mid_list", xstr.JoinInts(mids))
params.Set("publisher", strconv.FormatInt(pub, 10))
params.Set("ext_info", info)
var res struct {
Code int `json:"code"`
}
if err = d.httpClient.Post(c, _apiMsgSend, "", params, &res); err != nil {
log.Error("sendMsg error(%v) params(%v)", err, params)
return
}
if res.Code != ecode.OK.Code() {
err = model.ErrMsgSend
log.Error("sendMsg failed(%v) error(%v)", _apiMsgSend+"?"+params.Encode(), res.Code)
}
log.Info("sendMsg(mc:%s title:%s msg:%s type:%d mids:%v error(%v)", mc, title, msg, typ, mids, err)
return
}

View File

@@ -0,0 +1,25 @@
package dao
import (
"context"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestMessage(t *testing.T) {
var (
mid = int64(1)
title = "test title"
msg = "test msg"
now = time.Now()
c = context.Background()
)
Convey("test send a message", t, WithDao(func(d *Dao) {
d.SendReplyDelMsg(c, mid, title, msg, now)
So(msg, ShouldEqual, "test msg")
d.SendReportAcceptMsg(c, mid, title, msg, now)
So(title, ShouldEqual, "test title")
}))
}

View File

@@ -0,0 +1,113 @@
package dao
import (
"context"
"go-common/app/admin/main/reply/model"
"go-common/library/database/sql"
xtime "go-common/library/time"
)
const (
_selOneNotice = "SELECT id,plat,version,condi,build,title,content,link,stime,etime,status,ctime,mtime,client_type FROM notice where id=?"
_selCountNoticeSQL = "SELECT count(*) as count FROM notice"
_selAllNoticeSQL = "SELECT id,plat,version,condi,build,title,content,link,stime,etime,status,ctime,mtime,client_type FROM notice ORDER BY stime DESC limit ?,?"
_addNoticeSQL = "INSERT INTO notice (plat,version,condi,build,title,content,link,status,stime,etime,client_type) VALUES (?,?,?,?,?,?,?,?,?,?,?)"
_updateNotcieSQL = "UPDATE notice set plat=?,version=?,condi=?,build=?,title=?,content=?,link=?,stime=?,etime=?,client_type=? where id=?"
_updateNoticeStatusSQL = "UPDATE notice set status=? where id=?"
_delNoticeSQL = "DELETE FROM notice where id=?"
_selOnlineNoticeSQL = "SELECT id,plat,version,condi,build,title,content,link,stime,etime,status,ctime,mtime,client_type FROM notice WHERE plat=? and (stime<=? and etime>?) and status=1"
)
// RangeNotice 获取发布时间与某一时间范围有交集的公告.
func (dao *Dao) RangeNotice(c context.Context, plat model.NoticePlat, stime xtime.Time, etime xtime.Time) (nts []*model.Notice, err error) {
rows, err := dao.db.Query(c, _selOnlineNoticeSQL, plat, etime.Time(), stime.Time())
if err != nil {
return
}
defer rows.Close()
nts = make([]*model.Notice, 0)
for rows.Next() {
nt := new(model.Notice)
if err = rows.Scan(&nt.ID, &nt.Plat, &nt.Version, &nt.Condition, &nt.Build, &nt.Title, &nt.Content, &nt.Link, &nt.StartTime, &nt.EndTime, &nt.Status, &nt.CreateTime, &nt.ModifyTime, &nt.ClientType); err != nil {
return
}
nts = append(nts, nt)
}
return
}
// Notice get one notice detail.
func (dao *Dao) Notice(c context.Context, id uint32) (nt *model.Notice, err error) {
row := dao.db.QueryRow(c, _selOneNotice, id)
nt = new(model.Notice)
if err = row.Scan(&nt.ID, &nt.Plat, &nt.Version, &nt.Condition, &nt.Build, &nt.Title, &nt.Content, &nt.Link, &nt.StartTime, &nt.EndTime, &nt.Status, &nt.CreateTime, &nt.ModifyTime, &nt.ClientType); err != nil {
if err == sql.ErrNoRows {
nt = nil
err = nil
}
}
return
}
// CountNotice return notice count.
func (dao *Dao) CountNotice(c context.Context) (count int64, err error) {
row := dao.db.QueryRow(c, _selCountNoticeSQL)
err = row.Scan(&count)
return
}
// ListNotice retrive reply's notice list from db by offset and count.
func (dao *Dao) ListNotice(c context.Context, offset int64, count int64) (nts []*model.Notice, err error) {
rows, err := dao.db.Query(c, _selAllNoticeSQL, offset, count)
if err != nil {
return
}
defer rows.Close()
nts = make([]*model.Notice, 0)
for rows.Next() {
nt := new(model.Notice)
if err = rows.Scan(&nt.ID, &nt.Plat, &nt.Version, &nt.Condition, &nt.Build, &nt.Title, &nt.Content, &nt.Link, &nt.StartTime, &nt.EndTime, &nt.Status, &nt.CreateTime, &nt.ModifyTime, &nt.ClientType); err != nil {
return
}
nts = append(nts, nt)
}
err = rows.Err()
return
}
// CreateNotice insert a notice into db.
func (dao *Dao) CreateNotice(c context.Context, nt *model.Notice) (rows int64, err error) {
res, err := dao.db.Exec(c, _addNoticeSQL, nt.Plat, nt.Version, nt.Condition, nt.Build, nt.Title, nt.Content, nt.Link, nt.Status, nt.StartTime, nt.EndTime, nt.ClientType)
if err != nil {
return
}
return res.LastInsertId()
}
// UpdateNotice update main notice's main fileds.
func (dao *Dao) UpdateNotice(c context.Context, nt *model.Notice) (rows int64, err error) {
res, err := dao.db.Exec(c, _updateNotcieSQL, nt.Plat, nt.Version, nt.Condition, nt.Build, nt.Title, nt.Content, nt.Link, nt.StartTime, nt.EndTime, nt.ClientType, nt.ID)
if err != nil {
return
}
return res.RowsAffected()
}
// UpdateNoticeStatus change notice's status to offline\online.
func (dao *Dao) UpdateNoticeStatus(c context.Context, status model.NoticeStatus, id uint32) (rows int64, err error) {
res, err := dao.db.Exec(c, _updateNoticeStatusSQL, status, id)
if err != nil {
return
}
return res.RowsAffected()
}
// DeleteNotice delete a notice entry from db.
func (dao *Dao) DeleteNotice(c context.Context, id uint32) (rows int64, err error) {
res, err := dao.db.Exec(c, _delNoticeSQL, id)
if err != nil {
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,120 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/admin/main/reply/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
var (
mid int64 = 1
nowTs = time.Now().Unix()
lastID int64
lastID2 int64
)
func Test_AddNotice(t *testing.T) {
c := context.Background()
nt := model.Notice{
Plat: model.PlatAndroid,
Condition: model.ConditionGT,
Build: 1113,
Title: "测试",
Status: model.StatusOffline,
Content: "测试内容",
Link: "http://www.bilibili.com",
StartTime: xtime.Time(nowTs),
EndTime: xtime.Time(nowTs + 10*3600),
ClientType: "android",
}
nt2 := model.Notice{
Plat: model.PlatAndroid,
Condition: model.ConditionLT,
Build: 2233,
Title: "测试2",
Status: model.StatusOffline,
Content: "测试内容2",
Link: "http://www.bilibili.com",
StartTime: xtime.Time(nowTs),
EndTime: xtime.Time(nowTs + 10*3600),
ClientType: "",
}
Convey("add notice", t, WithDao(func(d *Dao) {
var err error
lastID, err = d.CreateNotice(c, &nt)
So(err, ShouldBeNil)
So(lastID, ShouldBeGreaterThan, 0)
lastID2, err = d.CreateNotice(c, &nt2)
So(err, ShouldBeNil)
So(lastID2, ShouldBeGreaterThan, 0)
}))
}
func Test_ListNotice(t *testing.T) {
c := context.Background()
Convey("list notice", t, WithDao(func(d *Dao) {
nts, err := d.ListNotice(c, 1, 100)
So(err, ShouldBeNil)
So(len(nts), ShouldBeGreaterThan, 0)
So(nts[0].StartTime.Time().Unix(), ShouldBeGreaterThanOrEqualTo, nowTs)
count, err := d.CountNotice(c)
So(err, ShouldBeNil)
So(count, ShouldBeGreaterThan, 1)
}))
}
func Test_UpdateNotice(t *testing.T) {
c := context.Background()
Convey("update notice", t, WithDao(func(d *Dao) {
data, err := d.Notice(c, uint32(lastID2))
So(err, ShouldBeNil)
So(data.Title, ShouldEqual, "测试2")
data.ID = uint32(lastID2)
data.Title = "测试3"
rows, err := d.UpdateNotice(c, data)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
nt2, err := d.Notice(c, uint32(lastID2))
So(err, ShouldBeNil)
So(nt2.Title, ShouldEqual, "测试3")
rows, err = d.UpdateNoticeStatus(c, model.StatusOnline, uint32(lastID))
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
nts, err := d.RangeNotice(c, model.PlatAndroid, xtime.Time(nowTs)-3600, xtime.Time(nowTs+5*3600))
So(err, ShouldBeNil)
So(len(nts), ShouldBeGreaterThan, 0)
var isFound bool
for _, data = range nts {
if data.ID == uint32(lastID) {
isFound = true
}
}
So(isFound, ShouldBeTrue)
}))
}
func Test_DeleteNotice(t *testing.T) {
c := context.Background()
Convey("delete notice", t, WithDao(func(d *Dao) {
rows, err := d.DeleteNotice(c, uint32(lastID))
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
rows, err = d.DeleteNotice(c, uint32(lastID2))
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
}))
}

View File

@@ -0,0 +1,452 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/admin/main/reply/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_prefixIdx = "i_"
_prefixNewRootIdx = "ri_"
_prefixAuditIdx = "ai_%d_%d"
// 针对大忽悠时间被删除评论的人的MID
_prefixAdminDelMid = "mid_%d"
// f_{折叠类型,根评论还是评论区}_{评论区ID或者根评论ID}
_foldedReplyFmt = "f_%s_%d"
// dialog
_prefixDialogIdx = "d_%d"
_maxCount = 20000
)
func keyFolderIdx(kind string, ID int64) string {
return fmt.Sprintf(_foldedReplyFmt, kind, ID)
}
func keyDelMid(mid int64) string {
return fmt.Sprintf(_prefixAdminDelMid, mid)
}
// KeyMainIdx ...
func keyMainIdx(oid int64, tp, sort int32) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d_%d", _prefixIdx, oid, tp, sort)
}
return _prefixIdx + strconv.FormatInt((oid<<16)|(int64(tp)<<8)|int64(sort), 10)
}
// keyDialogIdx ...
func keyDialogIdx(dialog int64) string {
return fmt.Sprintf(_prefixDialogIdx, dialog)
}
// KeyRootIdx ...
func keyRootIdx(root int64) string {
return _prefixNewRootIdx + strconv.FormatInt(root, 10)
}
func keyIdx(oid int64, tp, sort int32) string {
if oid > _oidOverflow {
return fmt.Sprintf("%s_%d_%d_%d", _prefixIdx, oid, tp, sort)
}
return _prefixIdx + strconv.FormatInt((oid<<16)|(int64(tp)<<8)|int64(sort), 10)
}
func keyNewRootIdx(rpID int64) string {
return _prefixNewRootIdx + strconv.FormatInt(rpID, 10)
}
func keyAuditIdx(oid int64, tp int32) string {
return fmt.Sprintf(_prefixAuditIdx, oid, tp)
}
func (d *Dao) ExsistsDelMid(c context.Context, mid int64) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := keyDelMid(mid)
if ok, err = redis.Bool(conn.Do("EXISTS", key)); err != nil {
log.Error("conn.Do(EXISTS, %s) error(%v)", key, err)
}
return
}
func (d *Dao) SetDelMid(c context.Context, mid int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
// 15天内
if err = conn.Send("SETEX", keyDelMid(mid), 86400*15, 1); err != nil {
log.Error("redis SETEX(%s) error(%v)", keyDelMid(mid), err)
}
return
}
// ExpireIndex set expire time for index.
func (d *Dao) ExpireIndex(c context.Context, oid int64, typ, sort int32) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", keyIdx(oid, typ, sort), d.redisExpire)); err != nil {
log.Error("conn.Do(EXPIRE) error(%v)", err)
}
return
}
// ExpireNewChildIndex set expire time for root's index.
func (d *Dao) ExpireNewChildIndex(c context.Context, root int64) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", keyNewRootIdx(root), d.redisExpire)); err != nil {
log.Error("conn.Do(EXPIRE) error(%v)", err)
}
return
}
// TopChildReply ...
func (d *Dao) TopChildReply(c context.Context, root, child int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("ZADD", keyNewRootIdx(root), 0, child); err != nil {
return
}
return
}
// CountReplies get count of reply.
func (d *Dao) CountReplies(c context.Context, oid int64, tp, sort int32) (count int, err error) {
key := keyIdx(oid, tp, sort)
conn := d.redis.Get(c)
defer conn.Close()
if count, err = redis.Int(conn.Do("ZCARD", key)); err != nil {
log.Error("CountReplies error(%v)", err)
}
return
}
// MinScore get the lowest score from sorted set
func (d *Dao) MinScore(c context.Context, oid int64, tp int32, sort int32) (score int32, err error) {
key := keyIdx(oid, tp, sort)
conn := d.redis.Get(c)
defer conn.Close()
values, err := redis.Values(conn.Do("ZRANGE", key, 0, 0, "WITHSCORES"))
if err != nil {
log.Error("conn.Do(ZREVRANGE, %s) error(%v)", key, err)
return
}
if len(values) != 2 {
err = fmt.Errorf("redis zrange items(%v) length not 2", values)
return
}
var id int64
redis.Scan(values, &id, &score)
return
}
// AddFloorIndex add index by floor.
func (d *Dao) AddFloorIndex(c context.Context, rp *model.Reply) (err error) {
min, err := d.MinScore(c, rp.Oid, rp.Type, model.SortByFloor)
if err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , oid(%d) type(%d) err(%v)", rp.Oid, rp.Type, err)
} else if rp.Floor <= min {
return
}
key := keyIdx(rp.Oid, rp.Type, model.SortByFloor)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", key, rp.Floor, rp.ID); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive error(%v)", err)
return
}
return
}
// AddCountIndex add index by count.
func (d *Dao) AddCountIndex(c context.Context, rp *model.Reply) (err error) {
if rp.IsTop() {
return
}
var count int
if count, err = d.CountReplies(c, rp.Oid, rp.Type, model.SortByCount); err != nil {
return
} else if count >= _maxCount {
var min int32
if min, err = d.MinScore(c, rp.Oid, rp.Type, model.SortByCount); err != nil {
return
}
if rp.RCount <= min {
return
}
}
key := keyIdx(rp.Oid, rp.Type, model.SortByCount)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", key, int64(rp.RCount)<<32|(int64(rp.Floor)&0xFFFFFFFF), rp.ID); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive error(%v)", err)
return
}
return
}
// AddLikeIndex add index by like.
func (d *Dao) AddLikeIndex(c context.Context, rp *model.Reply, rpt *model.Report) (err error) {
if rp.IsTop() {
return
}
var rptCn int32
if rpt != nil {
rptCn = rpt.Count
}
score := int64((float32(rp.Like+model.WeightLike) / float32(rp.Hate+model.WeightHate+rptCn)) * 100)
score = score<<32 | (int64(rp.RCount) & 0xFFFFFFFF)
var count int
if count, err = d.CountReplies(c, rp.Oid, rp.Type, model.SortByLike); err != nil {
return
} else if count >= _maxCount {
var min int32
if min, err = d.MinScore(c, rp.Oid, rp.Type, model.SortByLike); err != nil {
return
}
if score <= int64(min) {
return
}
}
key := keyIdx(rp.Oid, rp.Type, model.SortByLike)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", key, score, rp.ID); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
return
}
// DelIndexBySort delete index by sort.
func (d *Dao) DelIndexBySort(c context.Context, rp *model.Reply, sort int32) (err error) {
key := keyIdx(rp.Oid, rp.Type, sort)
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("ZREM", key, rp.ID); err != nil {
log.Error("conn.Do(ZREM) error(%v)", err)
}
return
}
// DelReplyIndex delete reply index.
func (d *Dao) DelReplyIndex(c context.Context, rp *model.Reply) (err error) {
var (
key string
n int
)
conn := d.redis.Get(c)
defer conn.Close()
if rp.Root == 0 {
key = keyIdx(rp.Oid, rp.Type, model.SortByFloor)
err = conn.Send("ZREM", key, rp.ID)
key = keyIdx(rp.Oid, rp.Type, model.SortByCount)
err = conn.Send("ZREM", key, rp.ID)
key = keyIdx(rp.Oid, rp.Type, model.SortByLike)
err = conn.Send("ZREM", key, rp.ID)
n += 3
} else {
if rp.Dialog != 0 {
key = keyDialogIdx(rp.Dialog)
err = conn.Send("ZREM", key, rp.ID)
n++
}
key = keyNewRootIdx(rp.Root)
err = conn.Send("ZREM", key, rp.ID)
n++
}
if err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive error(%v)", err)
return
}
}
return
}
// AddNewChildIndex add root reply index by floor.
func (d *Dao) AddNewChildIndex(c context.Context, rp *model.Reply) (err error) {
key := keyNewRootIdx(rp.Root)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZADD", key, rp.Floor, rp.ID); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
log.Error("conn.Send error(%v)", err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
return
}
// DelAuditIndex delete audit reply cache.
func (d *Dao) DelAuditIndex(c context.Context, rp *model.Reply) (err error) {
key := keyAuditIdx(rp.Oid, rp.Type)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("ZREM", key, rp.ID); err != nil {
log.Error("conn.Send(ZREM %s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
return
}
// RemReplyFromRedis ...
func (d *Dao) RemReplyFromRedis(c context.Context, keyMapping map[string][]int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
count := 0
for key, rpIDs := range keyMapping {
if len(rpIDs) == 0 {
continue
}
args := make([]interface{}, 0, 1+len(rpIDs))
args = append(args, key)
for _, rpID := range rpIDs {
args = append(args, rpID)
}
if err = conn.Send("ZREM", args...); err != nil {
log.Error("conn.Send(ZREM %s) error(%v)", key, err)
return
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// ExpireFolder ...
func (d *Dao) ExpireFolder(c context.Context, kind string, ID int64) (ok bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
if ok, err = redis.Bool(conn.Do("EXPIRE", keyFolderIdx(kind, ID), d.redisExpire)); err != nil {
log.Error("conn.Do(EXPIRE) error(%v)", err)
}
return
}
// AddFolder ...
func (d *Dao) AddFolder(c context.Context, keyMapping map[string][]*model.Reply) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
count := 0
for key, rps := range keyMapping {
var args []interface{}
args = append(args, key)
for _, rp := range rps {
args = append(args, rp.Floor)
args = append(args, rp.ID)
}
if err = conn.Send("ZADD", args...); err != nil {
log.Error("conn.Send(ZADD %s) error(%v)", key, err)
return
}
count++
if err = conn.Send("EXPIRE", key, d.redisExpire); err != nil {
return
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// RemFolder ...
func (d *Dao) RemFolder(c context.Context, kind string, ID, rpID int64) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := keyFolderIdx(kind, ID)
if _, err = conn.Do("ZREM", key, rpID); err != nil {
log.Error("conn.Do(ZREM) error(%v)", err)
}
return
}

View File

@@ -0,0 +1,233 @@
package dao
import (
"context"
"go-common/app/admin/main/reply/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaokeyIdx(t *testing.T) {
convey.Convey("keyIdx", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int32(0)
sort = int32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyIdx(oid, tp, sort)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaokeyNewRootIdx(t *testing.T) {
convey.Convey("keyNewRootIdx", t, func(ctx convey.C) {
var (
rpID = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyNewRootIdx(rpID)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaokeyAuditIdx(t *testing.T) {
convey.Convey("keyAuditIdx", t, func(ctx convey.C) {
var (
oid = int64(0)
tp = int32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := keyAuditIdx(oid, tp)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoExpireIndex(t *testing.T) {
convey.Convey("ExpireIndex", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
typ = int32(0)
sort = int32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := _d.ExpireIndex(c, oid, typ, sort)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoExpireNewChildIndex(t *testing.T) {
convey.Convey("ExpireNewChildIndex", t, func(ctx convey.C) {
var (
c = context.Background()
root = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
ok, err := _d.ExpireNewChildIndex(c, root)
ctx.Convey("Then err should be nil.ok should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCountReplies(t *testing.T) {
convey.Convey("CountReplies", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int32(0)
sort = int32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
count, err := _d.CountReplies(c, oid, tp, sort)
ctx.Convey("Then err should be nil.count should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(count, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoMinScore(t *testing.T) {
convey.Convey("MinScore", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
tp = int32(0)
sort = int32(1)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
score, err := _d.MinScore(c, oid, tp, sort)
ctx.Convey("Then err should be nil.score should not be nil.", func(ctx convey.C) {
if err != nil {
ctx.So(err, convey.ShouldNotBeNil)
}
ctx.So(score, convey.ShouldEqual, 0)
})
})
})
}
func TestDaoAddFloorIndex(t *testing.T) {
convey.Convey("AddFloorIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.AddFloorIndex(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAddCountIndex(t *testing.T) {
convey.Convey("AddCountIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.AddCountIndex(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAddLikeIndex(t *testing.T) {
convey.Convey("AddLikeIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
rpt = &model.Report{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.AddLikeIndex(c, rp, rpt)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelIndexBySort(t *testing.T) {
convey.Convey("DelIndexBySort", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
sort = int32(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.DelIndexBySort(c, rp, sort)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelReplyIndex(t *testing.T) {
convey.Convey("DelReplyIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.DelReplyIndex(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAddNewChildIndex(t *testing.T) {
convey.Convey("AddNewChildIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.AddNewChildIndex(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDelAuditIndex(t *testing.T) {
convey.Convey("DelAuditIndex", t, func(ctx convey.C) {
var (
c = context.Background()
rp = &model.Reply{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.DelAuditIndex(c, rp)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,182 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/reply/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_inSQL = "INSERT IGNORE INTO reply_%d (id,oid,type,mid,root,parent,floor,state,attr,ctime,mtime) VALUES(?,?,?,?,?,?,?,?,?,?,?)"
_selReplySQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,attr,ctime,mtime FROM reply_%d WHERE id=?"
_txSelReplySQLForUpdate = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,hate,floor,state,attr,ctime,mtime FROM reply_%d WHERE id=? for update"
_selRepliesSQL = "SELECT id,oid,type,mid,root,parent,dialog,count,rcount,`like`,floor,state,attr,ctime,mtime FROM reply_%d WHERE id IN (%s)"
_incrRCountSQL = "UPDATE reply_%d SET rcount=rcount+1,mtime=? WHERE id=?"
_upReplyStateSQL = "UPDATE reply_%d SET state=?,mtime=? WHERE id=?"
_decrCntSQL = "UPDATE reply_%d SET rcount=rcount-1,mtime=? WHERE id=? AND rcount > 0"
_upAttrSQL = "UPDATE reply_%d SET attr=?,mtime=? WHERE id=?"
_selExportRepliesSQL = "SELECT id,oid,type,mid,root,parent,count,rcount,`like`,hate,floor,state,attr,T1.ctime,message from reply_%d as T1 inner join reply_content_%d as T2 on id=rpid where oid=? and type=? and T1.ctime>=? and T2.ctime<=?"
)
// InsertReply insert reply by transaction.
func (d *Dao) InsertReply(c context.Context, r *model.Reply) (id int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_inSQL, hit(r.Oid)), r.ID, r.Oid, r.Type, r.Mid, r.Root, r.Parent, r.Floor, r.State, r.Attr, r.CTime, r.MTime)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.LastInsertId()
}
// Reply get a reply from database.
func (d *Dao) Reply(c context.Context, oid, rpID int64) (r *model.Reply, err error) {
r = new(model.Reply)
row := d.db.QueryRow(c, fmt.Sprintf(_selReplySQL, hit(oid)), rpID)
if err = row.Scan(&r.ID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
if err == xsql.ErrNoRows {
r = nil
err = nil
}
}
return
}
// Replies get replies by reply ids.
func (d *Dao) Replies(c context.Context, oids []int64, rpIds []int64) (rpMap map[int64]*model.Reply, err error) {
hitMap := make(map[int64][]int64)
for i, oid := range oids {
hitMap[hit(oid)] = append(hitMap[hit(oid)], rpIds[i])
}
rpMap = make(map[int64]*model.Reply, len(rpIds))
for hit, ids := range hitMap {
var rows *xsql.Rows
rows, err = d.db.Query(c, fmt.Sprintf(_selRepliesSQL, hit, xstr.JoinInts(ids)))
if err != nil {
return
}
for rows.Next() {
r := &model.Reply{}
if err = rows.Scan(&r.ID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
rows.Close()
return
}
rpMap[r.ID] = r
}
if err = rows.Err(); err != nil {
rows.Close()
return
}
rows.Close()
}
return
}
// UpdateReplyState update reply state.
func (d *Dao) UpdateReplyState(c context.Context, oid, rpID int64, state int32) (rows int64, err error) {
now := time.Now()
res, err := d.db.Exec(c, fmt.Sprintf(_upReplyStateSQL, hit(oid)), state, now, rpID)
if err != nil {
return
}
return res.RowsAffected()
}
// TxUpdateReplyState tx update reply state.
func (d *Dao) TxUpdateReplyState(tx *xsql.Tx, oid, rpID int64, state int32, mtime time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upReplyStateSQL, hit(oid)), state, mtime, rpID)
if err != nil {
return
}
return res.RowsAffected()
}
// TxIncrReplyRCount incr rcount of reply by transaction.
func (d *Dao) TxIncrReplyRCount(tx *xsql.Tx, oid, rpID int64, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrRCountSQL, hit(oid)), now, rpID)
if err != nil {
return
}
return res.RowsAffected()
}
// TxDecrReplyRCount decr rcount of reply by transaction.
func (d *Dao) TxDecrReplyRCount(tx *xsql.Tx, oid, rpID int64, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrCntSQL, hit(oid)), now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxUpReplyAttr update subject attr.
func (d *Dao) TxUpReplyAttr(tx *xsql.Tx, oid, rpID int64, attr uint32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upAttrSQL, hit(oid)), attr, now, rpID)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxReplyForUpdate get a reply from database.
func (d *Dao) TxReplyForUpdate(tx *xsql.Tx, oid, rpID int64) (r *model.Reply, err error) {
r = new(model.Reply)
row := tx.QueryRow(fmt.Sprintf(_txSelReplySQLForUpdate, hit(oid)), rpID)
if err = row.Scan(&r.ID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
if err == xsql.ErrNoRows {
r = nil
err = nil
}
}
return
}
// TxReply get a reply from database.
func (d *Dao) TxReply(tx *xsql.Tx, oid, rpID int64) (r *model.Reply, err error) {
r = new(model.Reply)
row := tx.QueryRow(fmt.Sprintf(_selReplySQL, hit(oid)), rpID)
if err = row.Scan(&r.ID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Dialog, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.MTime); err != nil {
if err == xsql.ErrNoRows {
r = nil
err = nil
}
}
return
}
// ExportReplies export replies
func (d *Dao) ExportReplies(c context.Context, oid, mid int64, tp int8, state string, startTime, endTime time.Time) (data [][]string, err error) {
var rows *xsql.Rows
title := []string{"id", "oid", "type", "mid", "root", "parent", "count", "rcount", "like", "hate", "floor", "state", "attr", "ctime", "message"}
data = append(data, title)
query := fmt.Sprintf(_selExportRepliesSQL, hit(oid), hit(oid))
if state != "" {
query += fmt.Sprintf(" and state in (%s)", state)
}
if mid != 0 {
query += fmt.Sprintf(" and mid=%d", mid)
}
rows, err = d.dbSlave.Query(c, query, oid, tp, startTime, endTime)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
r := &model.ExportedReply{}
if err = rows.Scan(&r.ID, &r.Oid, &r.Type, &r.Mid, &r.Root, &r.Parent, &r.Count, &r.RCount, &r.Like, &r.Hate, &r.Floor, &r.State, &r.Attr, &r.CTime, &r.Message); err != nil {
rows.Close()
return
}
data = append(data, r.String())
}
if err = rows.Err(); err != nil {
return
}
return
}

View File

@@ -0,0 +1,71 @@
package dao
import (
"context"
"fmt"
"go-common/app/admin/main/reply/model"
xsql "go-common/library/database/sql"
"go-common/library/xstr"
"time"
)
const _contSharding int64 = 200
const (
_selReplyContentSQL = "SELECT rpid,message,ats,ip,plat,device,ctime,mtime FROM reply_content_%d WHERE rpid=?"
_selReplyContentsSQL = "SELECT rpid,message,ats,ip,plat,device,ctime,mtime FROM reply_content_%d WHERE rpid IN (%s)"
_selContSQL = "SELECT rpid,message,ats,ip,plat,device FROM reply_content_%d WHERE rpid=?"
_selContsSQL = "SELECT rpid,message,ats,ip,plat,device FROM reply_content_%d WHERE rpid IN (%s)"
_upContMsgSQL = "UPDATE reply_content_%d SET message=?,mtime=? WHERE rpid=?"
)
// UpReplyContent update reply content's message.
func (d *Dao) UpReplyContent(c context.Context, oid int64, rpID int64, msg string, now time.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upContMsgSQL, hit(oid)), msg, now, rpID)
if err != nil {
return
}
return res.RowsAffected()
}
// ReplyContent get a ReplyContent from database.
func (d *Dao) ReplyContent(c context.Context, oid, rpID int64) (rc *model.ReplyContent, err error) {
rc = new(model.ReplyContent)
row := d.db.QueryRow(c, fmt.Sprintf(_selReplyContentSQL, hit(oid)), rpID)
if err = row.Scan(&rc.ID, &rc.Message, &rc.Ats, &rc.IP, &rc.Plat, &rc.Device, &rc.CTime, &rc.MTime); err != nil {
if err == xsql.ErrNoRows {
rc = nil
err = nil
}
}
return
}
// ReplyContents get reply contents by ids.
func (d *Dao) ReplyContents(c context.Context, oids []int64, rpIds []int64) (rcMap map[int64]*model.ReplyContent, err error) {
hitMap := make(map[int64][]int64)
for i, oid := range oids {
hitMap[hit(oid)] = append(hitMap[hit(oid)], rpIds[i])
}
rcMap = make(map[int64]*model.ReplyContent, len(rpIds))
for hit, ids := range hitMap {
var rows *xsql.Rows
rows, err = d.db.Query(c, fmt.Sprintf(_selReplyContentsSQL, hit, xstr.JoinInts(ids)))
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
rc := &model.ReplyContent{}
if err = rows.Scan(&rc.ID, &rc.Message, &rc.Ats, &rc.IP, &rc.Plat, &rc.Device, &rc.CTime, &rc.MTime); err != nil {
return
}
rcMap[rc.ID] = rc
}
if err = rows.Err(); err != nil {
return
}
}
return
}

View File

@@ -0,0 +1,82 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/admin/main/reply/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func deleteReplyContent(d *Dao, oid int64, rpid int64) error {
_delSQL := "Delete from reply_content_%d where rpid = ?"
_, err := d.db.Exec(context.Background(), fmt.Sprintf(_delSQL, hit(oid)), rpid)
if err != nil {
return err
}
return nil
}
func TestReplyContent(t *testing.T) {
_inContSQL := "INSERT IGNORE INTO reply_content_%d (rpid,message,ats,ip,plat,device,version,ctime,mtime) VALUES(?,?,?,?,?,?,?,?,?)"
d := _d
c := context.Background()
now := time.Now()
rc := model.ReplyContent{
ID: 1,
Message: "test",
Device: "iphone",
Version: "beta",
CTime: xtime.Time(now.Unix()),
MTime: xtime.Time(now.Unix()),
}
_, err := d.db.Exec(c, fmt.Sprintf(_inContSQL, hit(6)), rc.ID, rc.Message, rc.Ats, rc.IP, rc.Plat, rc.Device, rc.Version, rc.CTime, rc.MTime)
if err != nil {
t.Logf("insert reply content %v", err)
t.FailNow()
}
defer deleteReplyContent(d, 6, 1)
rc2 := model.ReplyContent{
ID: 2,
Message: "test2",
Device: "iphone2",
Version: "beta2",
CTime: xtime.Time(now.Unix()),
MTime: xtime.Time(now.Unix()),
}
_, err = d.db.Exec(c, fmt.Sprintf(_inContSQL, hit(86)), rc2.ID, rc2.Message, rc2.Ats, rc2.IP, rc2.Plat, rc2.Device, rc2.Version, rc2.CTime, rc2.MTime)
if err != nil {
t.Logf("insert reply content %v", err)
t.FailNow()
}
defer deleteReplyContent(d, 86, 2)
Convey("reply_content test", t, WithDao(func(d *Dao) {
Convey("get update content", WithDao(func(d *Dao) {
now = time.Now()
rows, err := d.UpReplyContent(c, 86, 2, "test3", now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
}))
Convey("get reply_content", WithDao(func(d *Dao) {
content, err := d.ReplyContent(c, 86, 2)
So(err, ShouldBeNil)
So(content, ShouldNotBeNil)
So(content.ID, ShouldEqual, 2)
So(content.CTime, ShouldEqual, now.Unix())
So(content.Message, ShouldEqual, "test3")
}))
Convey("find reply_contents", WithDao(func(d *Dao) {
cMap, err := d.ReplyContents(c, []int64{6, 86}, []int64{1, 2})
So(err, ShouldBeNil)
So(len(cMap), ShouldEqual, 2)
So(cMap[1].ID, ShouldEqual, 1)
So(cMap[1].Message, ShouldEqual, "test")
So(cMap[2].ID, ShouldEqual, 2)
So(cMap[2].Message, ShouldEqual, "test3")
}))
}))
}

View File

@@ -0,0 +1,144 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/admin/main/reply/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func deleteReply(d *Dao, oid int64, rpid int64) error {
_delSQL := "Delete from reply_%d where id = ?"
_, err := d.db.Exec(context.Background(), fmt.Sprintf(_delSQL, hit(oid)), rpid)
if err != nil {
return err
}
return nil
}
func TestDataReply(t *testing.T) {
d := _d
c := context.Background()
now := time.Now()
r := model.Reply{
ID: 2,
Oid: 86,
Type: 1,
Mid: 2233,
Floor: 1,
CTime: xtime.Time(now.Unix()),
MTime: xtime.Time(now.Unix()),
}
d.InsertReply(c, &r)
defer deleteReply(d, r.Oid, r.ID)
r2 := model.Reply{
ID: 1,
Oid: 6,
Type: 1,
Mid: 2233,
Floor: 1,
CTime: xtime.Time(now.Unix()),
MTime: xtime.Time(now.Unix()),
}
d.InsertReply(c, &r2)
defer deleteReply(d, r2.Oid, r2.ID)
Convey("reply test", t, WithDao(func(d *Dao) {
Convey("get reply", WithDao(func(d *Dao) {
rp, err := d.Reply(c, 86, r.ID)
So(err, ShouldBeNil)
So(rp, ShouldNotBeNil)
So(rp.ID, ShouldEqual, r.ID)
So(rp.Mid, ShouldEqual, 2233)
}))
Convey("increase reply RCount", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
now = time.Now()
rows, err := d.TxIncrReplyRCount(t, 86, r.ID, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
rp, err := d.Reply(c, 86, r.ID)
So(err, ShouldBeNil)
So(rp, ShouldNotBeNil)
So(rp.RCount, ShouldEqual, 1)
So(rp.MTime, ShouldEqual, now.Unix())
Convey("decrease reply RCount", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
now = time.Now()
rows, err := d.TxDecrReplyRCount(t, 86, r.ID, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
rp, err := d.Reply(c, 86, r.ID)
So(err, ShouldBeNil)
So(rp, ShouldNotBeNil)
So(rp.RCount, ShouldEqual, 0)
So(rp.MTime, ShouldEqual, now.Unix())
}))
}))
Convey("set reply attr", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
now = time.Now()
rows, err := d.TxUpReplyAttr(t, 86, r.ID, 3, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
rp, err := d.Reply(c, 86, r.ID)
So(err, ShouldBeNil)
So(rp, ShouldNotBeNil)
So(rp.Attr, ShouldEqual, 3)
So(rp.MTime, ShouldEqual, now.Unix())
}))
Convey("set reply state", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
now = time.Now()
rows, err := d.TxUpdateReplyState(t, 86, r.ID, 2, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
rp, err := d.Reply(c, 86, r.ID)
So(err, ShouldBeNil)
So(rp, ShouldNotBeNil)
So(rp.State, ShouldEqual, 2)
So(rp.MTime, ShouldEqual, now.Unix())
}))
Convey("get replies", WithDao(func(d *Dao) {
rpMap, err := d.Replies(c, []int64{6, 86}, []int64{1, 2})
So(err, ShouldBeNil)
So(len(rpMap), ShouldEqual, 2)
So(rpMap[1].ID, ShouldEqual, 1)
So(rpMap[1].Oid, ShouldEqual, 6)
So(rpMap[1].Mid, ShouldEqual, 2233)
So(rpMap[2].ID, ShouldEqual, 2)
So(rpMap[2].Oid, ShouldEqual, 86)
So(rpMap[2].Mid, ShouldEqual, 2233)
}))
Convey("export replies test", WithDao(func(d *Dao) {
var (
ctime time.Time
etime time.Time
err error
)
ctime, err = time.Parse("2006-01-02", "2008-03-03")
So(err, ShouldBeNil)
etime, err = time.Parse("2006-01-02", "2018-03-03")
So(err, ShouldBeNil)
_, err = d.ExportReplies(c, 3400, 0, 3, "", ctime, etime)
So(err, ShouldBeNil)
}))
}))
}

View File

@@ -0,0 +1,207 @@
package dao
import (
"context"
sql "database/sql"
"fmt"
"time"
"go-common/app/admin/main/reply/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
)
const (
// report
_upRptStateSQL = "UPDATE reply_report_%d SET state=?,mtime=? WHERE rpid IN (%s)"
_upRptReasonSQL = "UPDATE reply_report_%d SET state=?,reason=?,content=?,mtime=? WHERE rpid IN (%s)"
_upRptAttrBitSQL = "UPDATE reply_report_%d SET attr=attr&(~(1<<?))|(?<<?),mtime=? WHERE rpid IN (%s)"
_selReportSQL = "SELECT oid,type,rpid,mid,reason,content,count,score,state,attr,ctime,mtime FROM reply_report_%d WHERE rpid=?"
_selReportsSQL = "SELECT oid,type,rpid,mid,reason,content,count,score,state,attr,ctime,mtime FROM reply_report_%d WHERE rpid IN(%s)"
_selReportOidsSQL = "SELECT oid,type,rpid,mid,reason,content,count,score,state,attr,ctime,mtime FROM reply_report_%d WHERE oid IN(?) and type=?"
// report_user
_setRptUserStateSQL = "UPDATE reply_report_user_%d SET state=?,mtime=? WHERE rpid=?"
_selRptUsersSQL = "SELECT oid,type,rpid,mid,reason,content,state,ctime,mtime FROM reply_report_user_%d WHERE rpid=? and state=?"
)
// Report get a reply report.
func (d *Dao) Report(c context.Context, oid, rpID int64) (rpt *model.Report, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_selReportSQL, hit(oid)), rpID)
rpt = new(model.Report)
if err = row.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.Count, &rpt.Score, &rpt.State, &rpt.Attr, &rpt.CTime, &rpt.MTime); err != nil {
if err == xsql.ErrNoRows {
rpt = nil
err = nil
}
}
return
}
// Reports return report map by oid.
func (d *Dao) Reports(c context.Context, oids, rpIDs []int64) (res map[int64]*model.Report, err error) {
hits := make(map[int64][]int64)
for i, oid := range oids {
hit := hit(oid)
hits[hit] = append(hits[hit], rpIDs[i])
}
res = make(map[int64]*model.Report)
for hit, ids := range hits {
var rows *xsql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_selReportsSQL, hit, xstr.JoinInts(ids))); err != nil {
return
}
for rows.Next() {
rpt := new(model.Report)
if err = rows.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.Count, &rpt.Score, &rpt.State, &rpt.Attr, &rpt.CTime, &rpt.MTime); err != nil {
rows.Close()
return
}
res[rpt.RpID] = rpt
}
if err = rows.Err(); err != nil {
rows.Close()
return
}
rows.Close()
}
return
}
// ReportByOids return report map by oid.
func (d *Dao) ReportByOids(c context.Context, typ int32, oids ...int64) (res map[int64]*model.Report, err error) {
hits := make(map[int64][]int64)
for _, oid := range oids {
hit := hit(oid)
hits[hit] = append(hits[hit], oid)
}
res = make(map[int64]*model.Report)
for hit, oids := range hits {
var rows *xsql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_selReportOidsSQL, hit), xstr.JoinInts(oids), typ); err != nil {
return
}
for rows.Next() {
rpt := new(model.Report)
if err = rows.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.Count, &rpt.Score, &rpt.State, &rpt.Attr, &rpt.CTime, &rpt.MTime); err != nil {
rows.Close()
return
}
res[rpt.RpID] = rpt
}
if err = rows.Err(); err != nil {
rows.Close()
return
}
rows.Close()
}
return
}
// UpReportsState update the report state.
func (d *Dao) UpReportsState(c context.Context, oids, rpIDs []int64, state int32, now time.Time) (rows int64, err error) {
hitMap := make(map[int64][]int64)
for i, oid := range oids {
hitMap[hit(oid)] = append(hitMap[hit(oid)], rpIDs[i])
}
for hit, ids := range hitMap {
var res sql.Result
res, err = d.db.Exec(c, fmt.Sprintf(_upRptStateSQL, hit, xstr.JoinInts(ids)), state, now)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
var row int64
row, err = res.RowsAffected()
if err != nil {
log.Error("res.RowsAffected error(%v)", err)
return
}
rows += row
}
return
}
// UpReportsStateWithReason update the report state.
func (d *Dao) UpReportsStateWithReason(c context.Context, oids, rpIDs []int64, state, reason int32, content string, now time.Time) (rows int64, err error) {
hitMap := make(map[int64][]int64)
for i, oid := range oids {
hitMap[hit(oid)] = append(hitMap[hit(oid)], rpIDs[i])
}
for hit, ids := range hitMap {
var res sql.Result
res, err = d.db.Exec(c, fmt.Sprintf(_upRptReasonSQL, hit, xstr.JoinInts(ids)), state, reason, content, now)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
var row int64
row, err = res.RowsAffected()
if err != nil {
log.Error("res.RowsAffected error(%v)", err)
return
}
rows += row
}
return
}
// UpReportsAttrBit update the report attr.
func (d *Dao) UpReportsAttrBit(c context.Context, oids, rpIDs []int64, bit uint32, val uint32, now time.Time) (rows int64, err error) {
hitMap := make(map[int64][]int64)
for i, oid := range oids {
hitMap[hit(oid)] = append(hitMap[hit(oid)], rpIDs[i])
}
for hit, ids := range hitMap {
var res sql.Result
res, err = d.db.Exec(c, fmt.Sprintf(_upRptAttrBitSQL, hit, xstr.JoinInts(ids)), bit, val, bit, now)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
var row int64
row, err = res.RowsAffected()
if err != nil {
log.Error("res.RowsAffected error(%v)", err)
return
}
rows += row
}
return
}
// ReportUsers return a report users from mysql.
func (d *Dao) ReportUsers(c context.Context, oid int64, tp int32, rpID int64) (res map[int64]*model.ReportUser, err error) {
rows, err := d.db.Query(c, fmt.Sprintf(_selRptUsersSQL, hit(oid)), rpID, model.ReportUserStateNew)
if err != nil {
log.Error("db.Query error(%v)", err)
return
}
defer rows.Close()
res = make(map[int64]*model.ReportUser)
for rows.Next() {
rpt := &model.ReportUser{}
if err = rows.Scan(&rpt.Oid, &rpt.Type, &rpt.RpID, &rpt.Mid, &rpt.Reason, &rpt.Content, &rpt.State, &rpt.CTime, &rpt.MTime); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
res[rpt.Mid] = rpt
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
return
}
return
}
// SetUserReported set a user report state by rpID.
func (d *Dao) SetUserReported(c context.Context, oid int64, tp int32, rpID int64, now time.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_setRptUserStateSQL, hit(oid)), model.ReportUserStateReported, now, rpID)
if err != nil {
log.Error("db.Exec error(%v)", err)
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,57 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestDataReport(t *testing.T) {
var (
typ = int32(12)
oid = int64(909)
rpID = int64(111845851)
oids = []int64{909}
rpIDs = []int64{111845851}
now = time.Now()
c = context.Background()
)
Convey("report info", t, WithDao(func(d *Dao) {
rpt, err := d.Report(c, oid, rpID)
So(err, ShouldBeNil)
So(rpt.RpID, ShouldEqual, rpID)
rpts, err := d.Reports(c, oids, rpIDs)
So(err, ShouldBeNil)
So(len(rpts), ShouldEqual, 1)
rptm, err := d.ReportByOids(c, typ, oids...)
So(err, ShouldBeNil)
So(len(rptm), ShouldNotEqual, 0)
}))
Convey("report state update", t, WithDao(func(d *Dao) {
_, err := d.UpReportsState(c, oids, rpIDs, model.ReportStateDelete, now)
So(err, ShouldBeNil)
rpt, err := d.Report(c, oids[0], rpIDs[0])
So(err, ShouldBeNil)
So(rpt.State, ShouldEqual, model.ReportStateDelete)
}))
Convey("report reason update", t, WithDao(func(d *Dao) {
_, err := d.UpReportsStateWithReason(c, oids, rpIDs, model.ReportStateDelete, 1, "test", now)
So(err, ShouldBeNil)
rpt, err := d.Report(c, oids[0], rpIDs[0])
So(err, ShouldBeNil)
So(rpt.State, ShouldEqual, model.ReportStateDelete)
So(rpt.Reason, ShouldEqual, 1)
So(rpt.Content, ShouldEqual, "test")
}))
Convey("report attr update", t, WithDao(func(d *Dao) {
_, err := d.UpReportsAttrBit(c, oids, rpIDs, model.ReportAttrTransferred, model.AttrYes, now)
So(err, ShouldBeNil)
rpt, err := d.Report(c, oids[0], rpIDs[0])
So(err, ShouldBeNil)
So(rpt.AttrVal(model.ReportAttrTransferred), ShouldEqual, model.AttrYes)
}))
}

View File

@@ -0,0 +1 @@
package dao

View File

@@ -0,0 +1,33 @@
package dao
import (
"context"
"strconv"
"time"
"go-common/library/log"
)
type statMsg struct {
ID int64 `json:"id"`
Timestamp int64 `json:"timestamp"`
Count int32 `json:"count"`
Type string `json:"type"`
}
// SendStats update stat.
func (d *Dao) SendStats(c context.Context, typ int32, oid int64, cnt int32) (err error) {
// new databus stats
if name, ok := d.statsTypes[typ]; ok {
m := &statMsg{
ID: oid,
Type: name,
Count: cnt,
Timestamp: time.Now().Unix(),
}
if err = d.statsBus.Send(c, strconv.FormatInt(oid, 10), m); err != nil {
log.Error("d.databus.Send(%d,%d,%d) error(%v)", typ, oid, cnt, err)
}
}
return
}

View File

@@ -0,0 +1,21 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStats(t *testing.T) {
var (
oid = int64(1)
typ = int32(1)
cnt = int32(1)
c = context.Background()
)
Convey("test stats count", t, WithDao(func(d *Dao) {
err := d.SendStats(c, typ, oid, cnt)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,282 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/reply/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/sync/errgroup"
"go-common/library/xstr"
"sync"
)
const _subShard int64 = 50
const (
_selSubjectSQL = "SELECT id,oid,type,mid,count,rcount,acount,state,attr,ctime,mtime FROM reply_subject_%d WHERE oid=? AND type=?"
_selSubjectForUpdateSQL = "SELECT id,oid,type,mid,count,rcount,acount,state,attr,ctime,mtime FROM reply_subject_%d WHERE oid=? AND type=? FOR UPDATE"
_selSubjectsSQL = "SELECT id,oid,type,mid,count,rcount,acount,state,attr,ctime,mtime FROM reply_subject_%d WHERE oid IN (%s) AND type=?"
_inSubjectSQL = "INSERT INTO reply_subject_%d (oid,type,mid,state,ctime,mtime) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE state=?,mid=?,mtime=?"
_selSubjectMCountSQL = "SELECT oid, mcount FROM reply_subject_%d WHERE oid IN(%s) AND type=?"
_upSubStateSQL = "UPDATE reply_subject_%d SET state=?,mtime=? WHERE oid=? AND type=?"
_upSubAttrSQL = "UPDATE reply_subject_%d SET attr=?,mtime=? WHERE oid=? AND type=?"
_upSubStateAndAttrSQL = "UPDATE reply_subject_%d SET state=?,attr=?,mtime=? WHERE oid=? AND type=?"
_upSubMetaSQL = "UPDATE reply_subject_%d SET meta=?,mtime=? WHERE oid=? AND type=?"
_incrSubCountSQL = "UPDATE reply_subject_%d SET count=count+1,rcount=rcount+1,acount=acount+1,mtime=? WHERE oid=? AND type=?"
_incrSubFCountSQL = "UPDATE reply_subject_%d SET count=count+1,mtime=? WHERE oid=? AND type=?"
_incrSubRCountSQL = "UPDATE reply_subject_%d SET rcount=rcount+1,mtime=? WHERE oid=? AND type=?"
_incrSubACountSQL = "UPDATE reply_subject_%d SET acount=acount+?,mtime=? WHERE oid=? AND type=?"
_decrSubRCountSQL = "UPDATE reply_subject_%d SET rcount=rcount-1,mtime=? WHERE oid=? AND type=?"
_decrSubACountSQL = "UPDATE reply_subject_%d SET acount=acount-?,mtime=? WHERE oid=? AND type=?"
_decrSubMCountSQL = "UPDATE reply_subject_%d SET mcount=mcount-1,mtime=? WHERE oid=? AND type=? AND mcount>0"
)
func subHit(id int64) int64 {
return id % _subShard
}
// SubMCount get subject mcount from mysql
func (d *Dao) SubMCount(c context.Context, oids []int64, typ int32) (res map[int64]int32, err error) {
hits := make(map[int64][]int64)
for _, oid := range oids {
hit := subHit(oid)
hits[hit] = append(hits[hit], oid)
}
res = make(map[int64]int32, len(oids))
wg, ctx := errgroup.WithContext(c)
var lock = sync.RWMutex{}
for idx, oids := range hits {
o := oids
i := idx
wg.Go(func() (err error) {
var rows *xsql.Rows
if rows, err = d.db.Query(ctx, fmt.Sprintf(_selSubjectMCountSQL, i, xstr.JoinInts(o)), typ); err != nil {
log.Error("dao.db.Query error(%v)", err)
return
}
var mcount int32
var oid int64
for rows.Next() {
if err = rows.Scan(&oid, &mcount); err != nil {
if err == xsql.ErrNoRows {
mcount = 0
oid = 0
err = nil
continue
} else {
log.Error("row.Scan error(%v)", err)
rows.Close()
return
}
}
lock.Lock()
res[oid] = mcount
lock.Unlock()
}
if err = rows.Err(); err != nil {
log.Error("rows.err error(%v)", err)
rows.Close()
return
}
rows.Close()
return
})
}
if err = wg.Wait(); err != nil {
return
}
return
}
// Subjects get subjects from mysql.
func (d *Dao) Subjects(c context.Context, oids []int64, typ int32) (subMap map[int64]*model.Subject, err error) {
hitMap := make(map[int64][]int64)
for _, oid := range oids {
hitMap[subHit(oid)] = append(hitMap[subHit(oid)], oid)
}
subMap = make(map[int64]*model.Subject)
for hit, ids := range hitMap {
var rows *xsql.Rows
rows, err = d.db.Query(c, fmt.Sprintf(_selSubjectsSQL, hit, xstr.JoinInts(ids)), typ)
if err != nil {
return
}
for rows.Next() {
m := new(model.Subject)
if err = rows.Scan(&m.ID, &m.Oid, &m.Type, &m.Mid, &m.Count, &m.RCount, &m.ACount, &m.State, &m.Attr, &m.CTime, &m.MTime); err != nil {
rows.Close()
return
}
subMap[m.Oid] = m
}
if err = rows.Err(); err != nil {
rows.Close()
return
}
rows.Close()
}
return
}
// Subject get a subject from mysql.
func (d *Dao) Subject(c context.Context, oid int64, typ int32) (m *model.Subject, err error) {
m = new(model.Subject)
row := d.db.QueryRow(c, fmt.Sprintf(_selSubjectSQL, subHit(oid)), oid, typ)
if err = row.Scan(&m.ID, &m.Oid, &m.Type, &m.Mid, &m.Count, &m.RCount, &m.ACount, &m.State, &m.Attr, &m.CTime, &m.MTime); err != nil {
if err == xsql.ErrNoRows {
m = nil
err = nil
}
}
return
}
// TxSubject get a subject from mysql.
func (d *Dao) TxSubject(tx *xsql.Tx, oid int64, typ int32) (m *model.Subject, err error) {
m = new(model.Subject)
row := tx.QueryRow(fmt.Sprintf(_selSubjectSQL, subHit(oid)), oid, typ)
if err = row.Scan(&m.ID, &m.Oid, &m.Type, &m.Mid, &m.Count, &m.RCount, &m.ACount, &m.State, &m.Attr, &m.CTime, &m.MTime); err != nil {
if err == xsql.ErrNoRows {
m = nil
err = nil
}
}
return
}
// TxSubjectForUpdate get a subject from mysql for update.
func (d *Dao) TxSubjectForUpdate(tx *xsql.Tx, oid int64, typ int32) (m *model.Subject, err error) {
m = new(model.Subject)
row := tx.QueryRow(fmt.Sprintf(_selSubjectForUpdateSQL, subHit(oid)), oid, typ)
if err = row.Scan(&m.ID, &m.Oid, &m.Type, &m.Mid, &m.Count, &m.RCount, &m.ACount, &m.State, &m.Attr, &m.CTime, &m.MTime); err != nil {
if err == xsql.ErrNoRows {
m = nil
err = nil
}
}
return
}
// AddSubject insert or update subject state.
func (d *Dao) AddSubject(c context.Context, mid, oid int64, typ, state int32, now time.Time) (id int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_inSubjectSQL, subHit(oid)), oid, typ, mid, state, now, now, state, mid, now)
if err != nil {
return
}
return res.LastInsertId()
}
// UpSubjectState update subject state.
func (d *Dao) UpSubjectState(c context.Context, oid int64, typ, state int32, now time.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upSubStateSQL, subHit(oid)), state, now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// UpSubjectAttr update subject attr.
func (d *Dao) UpSubjectAttr(c context.Context, oid int64, typ int32, attr uint32, now time.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upSubAttrSQL, subHit(oid)), attr, now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// UpStateAndAttr update subject state and attr
func (d *Dao) UpStateAndAttr(c context.Context, oid int64, typ, state int32, attr uint32, now time.Time) (rows int64, err error) {
res, err := d.db.Exec(c, fmt.Sprintf(_upSubStateAndAttrSQL, subHit(oid)), state, attr, now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxIncrSubCount incr subject count and rcount by transaction.
func (d *Dao) TxIncrSubCount(tx *xsql.Tx, oid int64, typ int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubCountSQL, subHit(oid)), now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxIncrSubFCount incr subject count and rcount by transaction.
func (d *Dao) TxIncrSubFCount(tx *xsql.Tx, oid int64, typ int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubFCountSQL, subHit(oid)), now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxIncrSubRCount incr subject rcount by transaction
func (d *Dao) TxIncrSubRCount(tx *xsql.Tx, oid int64, typ int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubRCountSQL, subHit(oid)), now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxDecrSubRCount decr subject count by transaction.
func (d *Dao) TxDecrSubRCount(tx *xsql.Tx, oid int64, typ int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrSubRCountSQL, subHit(oid)), now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxIncrSubACount incr subject acount by transaction.
func (d *Dao) TxIncrSubACount(tx *xsql.Tx, oid int64, typ int32, count int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_incrSubACountSQL, subHit(oid)), count, now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxSubDecrACount decr subject rcount by transaction.
func (d *Dao) TxSubDecrACount(tx *xsql.Tx, oid int64, typ int32, count int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrSubACountSQL, subHit(oid)), count, now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxSubDecrMCount decr subject mcount by transaction.
func (d *Dao) TxSubDecrMCount(tx *xsql.Tx, oid int64, typ int32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_decrSubMCountSQL, subHit(oid)), now, oid, typ)
if err != nil {
return
}
return res.RowsAffected()
}
// TxUpSubAttr update subject attr.
func (d *Dao) TxUpSubAttr(tx *xsql.Tx, oid int64, tp int32, attr uint32, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upSubAttrSQL, subHit(oid)), attr, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// TxUpSubMeta update subject meta.
func (d *Dao) TxUpSubMeta(tx *xsql.Tx, oid int64, tp int32, meta string, now time.Time) (rows int64, err error) {
res, err := tx.Exec(fmt.Sprintf(_upSubMetaSQL, subHit(oid)), meta, now, oid, tp)
if err != nil {
log.Error("mysqlDB.Exec error(%v)", err)
return
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,139 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func deleteSubject(d *Dao, oid int64, typ int32) error {
_delSQL := "Delete from reply_subject_%d where oid=? and type=?"
_, err := d.db.Exec(context.Background(), fmt.Sprintf(_delSQL, subHit(oid)), oid, typ)
if err != nil {
return err
}
return nil
}
func TestSubjectAttr(t *testing.T) {
var (
sub = &model.Subject{
Oid: 1,
Type: 1,
}
now = time.Now()
c = context.Background()
)
Convey("get and update a subject", t, WithDao(func(d *Dao) {
_, err := d.AddSubject(c, sub.Mid, sub.Oid, sub.Type, sub.State, now)
So(err, ShouldBeNil)
sub.AttrSet(model.AttrYes, model.SubAttrMonitor)
_, err = d.UpSubjectAttr(c, sub.Oid, sub.Type, sub.Attr, now)
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(sub.AttrVal(model.SubAttrMonitor), ShouldEqual, model.AttrYes)
}))
Convey("get and update a subject", t, WithDao(func(d *Dao) {
_, err := d.AddSubject(c, sub.Mid, sub.Oid, sub.Type, sub.State, now)
So(err, ShouldBeNil)
sub.AttrSet(model.AttrNo, model.SubAttrMonitor)
_, err = d.UpSubjectAttr(c, sub.Oid, sub.Type, sub.Attr, now)
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(sub.AttrVal(model.SubAttrMonitor), ShouldEqual, model.AttrNo)
}))
}
func TestSubjectState(t *testing.T) {
var (
sub = &model.Subject{
Oid: 1,
Type: 1,
}
now = time.Now()
c = context.Background()
)
Convey("get and update a subject", t, WithDao(func(d *Dao) {
_, err := d.AddSubject(c, sub.Mid, sub.Oid, sub.Type, sub.State, now)
So(err, ShouldBeNil)
_, err = d.UpSubjectState(c, sub.Oid, sub.Type, model.SubStateForbid, now)
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(sub.State, ShouldEqual, model.SubStateForbid)
}))
}
func TestSubjectCount(t *testing.T) {
d := _d
var (
sub = &model.Subject{
Oid: 2,
Type: 3,
}
now = time.Now()
c = context.Background()
)
_, err := d.AddSubject(c, sub.Mid, sub.Oid, sub.Type, sub.State, now)
if err != nil {
t.Logf("add subject failed!%v", err)
t.FailNow()
}
defer deleteSubject(d, 2, 3)
Convey("increase and decrease subject", t, WithDao(func(d *Dao) {
Convey("increase subject rcount", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
rows, err := d.TxIncrSubRCount(t, 2, 3, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(sub.RCount, ShouldEqual, 1)
}))
Convey("decrease subject rcount", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
rows, err := d.TxDecrSubRCount(t, 2, 3, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(sub.RCount, ShouldEqual, 0)
}))
Convey("increase subject acount", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
rows, err := d.TxIncrSubACount(t, 2, 3, 123, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(sub.ACount, ShouldEqual, 123)
}))
Convey("decrease subject acount", WithDao(func(d *Dao) {
t, err := d.BeginTran(c)
So(err, ShouldBeNil)
rows, err := d.TxSubDecrACount(t, 2, 3, 23, now)
So(err, ShouldBeNil)
So(rows, ShouldBeGreaterThan, 0)
err = t.Commit()
So(err, ShouldBeNil)
sub, err = d.Subject(c, sub.Oid, sub.Type)
So(err, ShouldBeNil)
So(sub.ACount, ShouldEqual, 100)
}))
}))
}

View File

@@ -0,0 +1,36 @@
package dao
import (
"context"
"go-common/library/ecode"
"net/url"
"strconv"
"go-common/library/log"
)
const (
_workflowDel = "http://api.bilibili.co/x/internal/workflow/appeal/v3/delete"
)
// FilterContent get filtered contents by ids.
func (d *Dao) DelReport(c context.Context, oid int64, rpid int64) (err error) {
var res struct {
Code int `json:"code"`
}
params := url.Values{}
params.Set("business", "13")
params.Set("oid", strconv.FormatInt(oid, 10))
params.Set("eid", strconv.FormatInt(rpid, 10))
if err = d.httpClient.Post(c, _workflowDel, "", params, &res); err != nil {
log.Error("DelReport(%s,%s) error(%v)", _workflowDel, params.Encode(), err)
return
}
if res.Code != 0 {
log.Error("DelReport(%s,%s) error(%v)", _workflowDel, params.Encode(), err)
err = ecode.Int(res.Code)
return
}
log.Info("DelReport success!(%s,%s,%d)", _workflowDel, params.Encode())
return
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoDelReport(t *testing.T) {
convey.Convey("DelReport", t, func(ctx convey.C) {
var (
c = context.Background()
oid = int64(0)
rpid = int64(0)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := _d.DelReport(c, oid, rpid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}