Create & Init Project...

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

View File

@@ -0,0 +1,101 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"member_test.go",
"official_test.go",
"review_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"member.go",
"monitor.go",
"official.go",
"realname.go",
"review.go",
"review_audit.go",
"service.go",
],
importpath = "go-common/app/admin/main/member/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/dao:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//app/admin/main/member/model/bom:go_default_library",
"//app/admin/main/member/service/block:go_default_library",
"//app/interface/main/account/service/realname/crypto:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/coin/api/gorpc:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/figure/rpc/client:go_default_library",
"//app/service/main/member/api/gorpc:go_default_library",
"//app/service/main/member/model:go_default_library",
"//app/service/main/member/model/block:go_default_library",
"//app/service/main/member/service/crypto:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/rpc/client:go_default_library",
"//app/service/main/spy/model:go_default_library",
"//app/service/main/spy/rpc/client:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/stat/prom:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/nfnt/resize:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/member/service/block:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,66 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"audit_log.go",
"block.go",
"msg.go",
"notify.go",
"rpc.go",
"service.go",
],
importpath = "go-common/app/admin/main/member/service/block",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/dao/block:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/figure/model:go_default_library",
"//app/service/main/figure/rpc/client:go_default_library",
"//app/service/main/spy/model:go_default_library",
"//app/service/main/spy/rpc/client:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//vendor/github.com/pkg/errors: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,48 @@
package block
import (
"context"
"strconv"
"time"
model "go-common/app/admin/main/member/model/block"
"go-common/library/log"
manager "go-common/library/queue/databus/report"
)
// AddAuditLog .
func (s *Service) AddAuditLog(c context.Context, tp model.BlockAction, uid int64, uname string, oids []int64, duration time.Duration, source model.BlockSource, area model.BlockArea, reason, comment string, notify bool, stime time.Time) error {
var (
err error
dur = int64(duration / time.Second)
notifyStr = strconv.FormatBool(notify)
)
for _, oid := range oids {
managerInfo := &manager.ManagerInfo{
UID: uid,
Uname: uname,
Business: model.BlockLogBizID,
Type: int(tp),
Action: tp.String(),
Oid: oid,
Ctime: time.Now(),
Index: []interface{}{dur, uint8(source), uint8(area), reason, comment, notifyStr},
Content: map[string]interface{}{
"duration": dur,
"source": source,
"area": area,
"reason": reason,
"comment": comment,
"notify": notifyStr,
"action_time": stime.Unix(),
"remove_time": stime.Add(time.Second * time.Duration(dur)).Unix(),
},
}
if err = manager.Manager(managerInfo); err != nil {
log.Error("manager.Manager(%+v) error(%+v)", managerInfo, err)
continue
}
log.Info("s.managerSendLog(%+v)", managerInfo)
}
return err
}

View File

@@ -0,0 +1,320 @@
package block
import (
"context"
"sync"
"time"
model "go-common/app/admin/main/member/model/block"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/sync/errgroup"
"github.com/pkg/errors"
)
// Search .
func (s *Service) Search(c context.Context, mids []int64) (infos []*model.BlockInfo, err error) {
var (
users []*model.DBUser
userDetails []*model.DBUserDetail
eg errgroup.Group
mapMu sync.Mutex
userMap = make(map[int64]*model.BlockInfo)
)
if users, err = s.dao.Users(c, mids); err != nil {
return
}
if userDetails, err = s.dao.UserDetails(c, mids); err != nil {
return
}
infos = make([]*model.BlockInfo, 0, len(mids))
for _, m := range mids {
mid := m
eg.Go(func() (err error) {
info := &model.BlockInfo{
MID: mid,
}
// 1. account 数据
if info.Nickname, info.TelStatus, info.Level, info.RegTime, err = s.AccountInfo(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
}
if info.Nickname == "" {
log.Info("user mid(%d) not found", info.MID)
return
}
// 2. 封禁状态
for i := range users {
if users[i].MID == mid {
info.ParseStatus(users[i])
break
}
}
// 3. 封禁次数
for i := range userDetails {
if userDetails[i].MID == mid {
info.BlockCount = userDetails[i].BlockCount
break
}
}
// 4. spy 分值
if info.SpyScore, err = s.SpyScore(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.SpyScore = -1
}
// 5. figure 排名
if info.FigureRank, err = s.FigureRank(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.FigureRank = -1
}
// 6. extra 额外账号信息
if info.Tel, err = s.telInfo(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.Tel = "N/A"
}
if info.Mail, err = s.mailInfo(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.Mail = "N/A"
}
if info.Username, err = s.userID(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.Username = "N/A"
}
mapMu.Lock()
userMap[info.MID] = info
mapMu.Unlock()
return
})
}
eg.Wait()
for _, mid := range mids {
if info, ok := userMap[mid]; ok {
infos = append(infos, info)
}
}
return
}
// History .
func (s *Service) History(c context.Context, mid int64, ps, pn int, desc bool) (history *model.BlockDetail, err error) {
var (
start = (pn - 1) * ps
limit = ps
dbHistory []*model.DBHistory
dbUser *model.DBUser
)
history = &model.BlockDetail{}
if dbUser, err = s.dao.User(c, mid); err != nil {
return
}
if dbUser != nil {
history.Status = dbUser.Status
}
if history.Total, err = s.dao.HistoryCount(c, mid); err != nil {
return
}
if dbHistory, err = s.dao.History(c, mid, start, limit, desc); err != nil {
return
}
history.History = make([]*model.BlockHistory, 0, len(dbHistory))
for i := range dbHistory {
his := &model.BlockHistory{}
his.ParseDB(dbHistory[i])
history.History = append(history.History, his)
}
return
}
// BatchBlock .
func (s *Service) BatchBlock(c context.Context, p *model.ParamBatchBlock) (err error) {
var (
tx *xsql.Tx
duration = time.Duration(p.Duration) * time.Hour * 24
source model.BlockSource
stime = time.Now()
)
if tx, err = s.dao.BeginTX(c); err != nil {
return
}
if p.Source == model.BlockMgrSourceSys {
// 系统封禁
source = model.BlockSourceSys
} else if p.Source == model.BlockMgrSourceCredit {
// 小黑屋封禁
source = model.BlockSourceBlackHouse
if err = s.dao.BlackhouseBlock(context.Background(), p); err != nil {
return
}
}
for _, mid := range p.MIDs {
if err = s.action(c, tx, mid, p.AdminID, p.AdminName, source, p.Area, p.Reason, p.Comment, p.Action, duration, p.Notify, stime); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
return
}
// 发送站内信
if p.Notify {
s.mission(func() {
if notifyErr := s.notifyMSG(context.Background(), p.MIDs, source, p.Action, p.Area, p.Reason, p.Duration); notifyErr != nil {
log.Error("%+v", notifyErr)
return
}
})
}
s.mission(func() {
s.AddAuditLog(context.Background(), p.Action, p.AdminID, p.AdminName, p.MIDs, duration, source, p.Area, p.Reason, p.Comment, p.Notify, stime)
})
s.asyncPurgeCache(p.MIDs)
return
}
// BatchRemove .
func (s *Service) BatchRemove(c context.Context, p *model.ParamBatchRemove) (err error) {
var (
tx *xsql.Tx
stime = time.Now()
)
if tx, err = s.dao.BeginTX(c); err != nil {
return
}
for _, mid := range p.MIDs {
if err = s.action(c, tx, mid, p.AdminID, p.AdminName, model.BlockSourceManager, model.BlockAreaNone, "", p.Comment, model.BlockActionAdminRemove, 0, p.Notify, stime); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
return
}
// 发送站内信
if p.Notify {
s.mission(func() {
if notifyErr := s.notifyMSG(context.Background(), p.MIDs, model.BlockSourceManager, model.BlockActionAdminRemove, model.BlockAreaNone, "", 0); notifyErr != nil {
log.Error("%+v", notifyErr)
return
}
})
}
s.mission(func() {
s.AddAuditLog(context.Background(), model.BlockActionAdminRemove, p.AdminID, p.AdminName, p.MIDs, 0, model.BlockSourceManager, model.BlockAreaNone, "", p.Comment, p.Notify, stime)
})
s.asyncPurgeCache(p.MIDs)
return
}
// notifyMSG .
func (s *Service) notifyMSG(c context.Context, mids []int64, source model.BlockSource, action model.BlockAction, area model.BlockArea, reason string, days int64) (err error) {
code, title, content := s.MSGInfo(source, action, area, reason, days)
log.Info("block admin title : %s , content : %s , mids : %+v", title, content, mids)
if err = s.dao.SendSysMsg(context.Background(), code, mids, title, content, ""); err != nil {
return
}
return
}
func (s *Service) action(c context.Context, tx *xsql.Tx, mid int64, adminID int64, adminName string, source model.BlockSource, area model.BlockArea, reason, comment string, action model.BlockAction, duration time.Duration, notify bool, stime time.Time) (err error) {
var (
db = &model.DBHistory{
MID: mid,
AdminID: adminID,
AdminName: adminName,
Source: source,
Area: area,
Reason: reason,
Comment: comment,
Action: action,
StartTime: stime,
Duration: int64(duration / time.Second),
Notify: notify,
}
blockStatus model.BlockStatus
)
if err = s.dao.TxInsertHistory(c, tx, db); err != nil {
return
}
switch action {
case model.BlockActionAdminRemove, model.BlockActionSelfRemove:
blockStatus = model.BlockStatusFalse
case model.BlockActionLimit:
switch source {
case model.BlockSourceBlackHouse:
blockStatus = model.BlockStatusCredit
default:
blockStatus = model.BlockStatusLimit
}
s.mission(func() {
if err = s.dao.UpdateAddBlockCount(context.Background(), mid); err != nil {
log.Error("%+v", err)
}
})
case model.BlockActionForever:
blockStatus = model.BlockStatusForever
s.mission(func() {
if err = s.dao.UpdateAddBlockCount(context.Background(), mid); err != nil {
log.Error("%+v", err)
}
})
default:
err = errors.Errorf("unknown block action [%d]", action)
return
}
if err = s.dao.TxUpdateUser(c, tx, mid, blockStatus); err != nil {
return
}
return
}
func (s *Service) userID(c context.Context, mid int64) (id string, err error) {
return "N/A", nil
}
func (s *Service) mailInfo(c context.Context, mid int64) (mail string, err error) {
if mail, err = s.dao.MailInfo(c, mid); mail == "" {
mail = "N/A"
}
return
}
// TelInfo .
func (s *Service) telInfo(c context.Context, mid int64) (tel string, err error) {
if tel, err = s.dao.TelInfo(c, mid); err != nil {
return
}
if len(tel) == 0 {
tel = "N/A"
return
}
if len(tel) < 4 {
tel = tel[:1] + "****"
return
}
tel = tel[:3] + "****" + tel[len(tel)-4:]
return
}
func (s *Service) asyncPurgeCache(mids []int64) {
_ = s.cache.Do(context.Background(), func(ctx context.Context) {
for _, mid := range mids {
if cacheErr := s.dao.DeleteUserCache(ctx, mid); cacheErr != nil {
log.Error("%+v", cacheErr)
}
if cacheErr := s.dao.DeleteUserDetailCache(ctx, mid); cacheErr != nil {
log.Error("%+v", cacheErr)
}
if databusErr := s.accountNotify(ctx, mid); databusErr != nil {
log.Error("%+v", databusErr)
}
}
})
}

View File

@@ -0,0 +1,85 @@
package block
import (
"fmt"
model "go-common/app/admin/main/member/model/block"
"go-common/library/log"
)
const (
_creditLimit = `抱歉,你的账号因“%s%s”现已进行封禁%d天处理账号解封需要满足以下两个条件:1.账号封禁时间已满。2.完成解封答题( #{点击进入解封答题}{"http://www.bilibili.com/blackroom/releaseexame.html"} )全部完成后解封。封禁期间将无法投稿、发送及回复消息,无法发布评论、弹幕,无法对他人评论进行回复、赞踩操作,无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常,还请遵守社区规范,共同维护良好的社区氛围!`
_creditForever = `抱歉,你的账号因“%s%s”现已进行永久封禁处理。封禁期间将无法投稿、发送及回复消息无法发布评论、弹幕无法对他人评论进行回复、赞踩操作无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常还请遵守社区规范共同维护良好的社区氛围`
_sysLimit = `抱歉,你的账号因“%s”现已进行封禁%d天处理账号解封需要满足以下两个条件:1.账号封禁时间已满。2.完成解封答题( #{点击进入解封答题}{"http://www.bilibili.com/blackroom/releaseexame.html"} )全部完成后解封。封禁期间将无法投稿、发送及回复消息,无法发布评论、弹幕,无法对他人评论进行回复、赞踩操作,无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常,还请遵守社区规范,共同维护良好的社区氛围!`
_sysForever = `抱歉,你的账号因“%s”现已进行永久封禁处理。封禁期间将无法投稿、发送及回复消息无法发布评论、弹幕无法对他人评论进行回复、赞踩操作无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常还请遵守社区规范共同维护良好的社区氛围`
_remove = `你的账号已经解除封禁,封禁期间禁止使用的各项社区功能已经恢复。请遵守社区规范,共同维护良好的社区氛围。`
)
// MSGInfo get msg info
func (s *Service) MSGInfo(source model.BlockSource, action model.BlockAction, area model.BlockArea, reason string, days int64) (code string, title, content string) {
// 小黑屋封禁
if source == model.BlockSourceBlackHouse {
areaStr := area.String()
if areaStr != "" {
areaStr = fmt.Sprintf("在%s中", areaStr)
}
if action == model.BlockActionLimit {
code = "2_3_2"
title = "账号违规处理通知"
content = fmt.Sprintf(_creditLimit, areaStr, s.convertReason(reason), days)
return
}
if action == model.BlockActionForever {
code = "2_3_3"
title = "账号违规处理通知"
content = fmt.Sprintf(_creditForever, areaStr, s.convertReason(reason))
return
}
}
// 系统封禁
if source == model.BlockSourceSys {
if action == model.BlockActionLimit {
code = "2_3_4"
title = "账号违规处理通知"
content = fmt.Sprintf(_sysLimit, s.convertReason(reason), days)
return
}
if action == model.BlockActionForever {
code = "2_3_5"
title = "账号违规处理通知"
content = fmt.Sprintf(_sysForever, s.convertReason(reason))
return
}
}
if action == model.BlockActionAdminRemove || action == model.BlockActionSelfRemove {
code = "2_3_6"
title = "账号封禁解除通知"
content = _remove
return
}
log.Error("s.MSGInfo unkown source[%v] action[%v] area[%v] reason[%s] days[%d]", source, action, area, reason, days)
return
}
func (s *Service) convertReason(reason string) string {
switch reason {
case "账号资料相关违规":
return "账号资料违规"
case "作品投稿违规":
return "作品投稿违规"
case "异常注册账号":
return "异常注册"
case "异常答题账号":
return "异常答题"
case "异常数据行为":
return "异常数据行为"
case "发布违规信息":
return "发布违规信息"
case "其他自动封禁", "手动封禁":
return "违反社区规则"
default:
return reason
}
}

View File

@@ -0,0 +1,18 @@
package block
import (
"context"
"strconv"
"go-common/app/admin/main/member/model/block"
"github.com/pkg/errors"
)
func (s *Service) accountNotify(c context.Context, mid int64) (err error) {
msg := &block.AccountNotify{UID: mid, Action: "blockUser"}
if err = s.accountNotifyPub.Send(c, strconv.FormatInt(msg.UID, 10), msg); err != nil {
err = errors.Errorf("mid(%d) s.accountNotify.Send(%+v) error(%+v)", msg.UID, msg, err)
}
return
}

View File

@@ -0,0 +1,72 @@
package block
import (
"context"
account "go-common/app/service/main/account/api"
mdlfigure "go-common/app/service/main/figure/model"
mdlspy "go-common/app/service/main/spy/model"
"go-common/library/net/metadata"
"github.com/pkg/errors"
)
// SpyScore .
func (s *Service) SpyScore(c context.Context, mid int64) (score int8, err error) {
var (
arg = &mdlspy.ArgUserScore{
Mid: mid,
}
res *mdlspy.UserScore
)
if res, err = s.spyRPC.UserScore(c, arg); err != nil {
err = errors.WithStack(err)
return
}
if res == nil {
return
}
score = res.Score
return
}
// FigureRank .
func (s *Service) FigureRank(c context.Context, mid int64) (rank int8, err error) {
var (
arg = &mdlfigure.ArgUserFigure{
Mid: mid,
}
res *mdlfigure.FigureWithRank
)
if res, err = s.figureRPC.UserFigure(c, arg); err != nil {
err = errors.WithStack(err)
return
}
if res == nil {
rank = 100
return
}
rank = res.Percentage
return
}
// AccountInfo .
func (s *Service) AccountInfo(c context.Context, mid int64) (nickname string, tel int32, level int32, regTime int64, err error) {
var (
arg = &account.MidReq{
Mid: mid,
RealIp: metadata.String(c, metadata.RemoteIP),
}
profileReply *account.ProfileReply
)
if profileReply, err = s.accountClient.Profile3(c, arg); err != nil {
err = errors.WithStack(err)
return
}
res := profileReply.Profile
nickname = res.Name
tel = res.TelStatus
level = res.Level
regTime = int64(res.JoinTime)
return
}

View File

@@ -0,0 +1,75 @@
package block
import (
"context"
"runtime/debug"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/dao/block"
account "go-common/app/service/main/account/api"
rpcfigure "go-common/app/service/main/figure/rpc/client"
rpcspy "go-common/app/service/main/spy/rpc/client"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline/fanout"
)
// Service struct
type Service struct {
conf *conf.Config
dao *block.Dao
cache *fanout.Fanout
spyRPC *rpcspy.Service
figureRPC *rpcfigure.Service
accountClient account.AccountClient
missch chan func()
accountNotifyPub *databus.Databus
}
// New init
func New(conf *conf.Config, dao *block.Dao, spyRPC *rpcspy.Service, figureRPC *rpcfigure.Service,
accountClient account.AccountClient, accountNotifyPub *databus.Databus) (s *Service) {
s = &Service{
conf: conf,
dao: dao,
cache: fanout.New("memberAdminCache", fanout.Worker(1), fanout.Buffer(10240)),
missch: make(chan func(), 10240),
accountNotifyPub: accountNotifyPub,
spyRPC: spyRPC,
figureRPC: figureRPC,
accountClient: accountClient,
}
go s.missproc()
return s
}
func (s *Service) missproc() {
defer func() {
if x := recover(); x != nil {
log.Error("service.missproc panic(%+v) : %s", x, debug.Stack())
go s.missproc()
}
}()
for {
f := <-s.missch
f()
}
}
func (s *Service) mission(f func()) {
select {
case s.missch <- f:
default:
log.Error("s.missch full")
}
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,65 @@
package block
import (
"context"
"flag"
"os"
"testing"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/model/block"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
c = context.Background()
)
func TestMain(m *testing.M) {
defer os.Exit(0)
flag.Set("conf", "../cmd/member-admin-test.toml")
var err error
if err = conf.Init(); err != nil {
panic(err)
}
m.Run()
}
func TestService(t *testing.T) {
Convey("", t, func() {
s.Ping(c)
s.Close()
})
}
func TestBlock(t *testing.T) {
Convey("block", t, func() {
var (
p = &block.ParamBatchBlock{
MIDs: []int64{1, 2, 3, 4},
AdminID: 233,
AdminName: "233",
Source: 1,
Area: block.BlockAreaNone,
Reason: "test",
Comment: "test",
Action: block.BlockActionLimit,
Duration: 1,
Notify: false,
}
pm = &block.ParamBatchRemove{
MIDs: []int64{1, 2, 3, 4},
AdminID: 233,
AdminName: "233",
Comment: "test",
Notify: false,
}
)
err := s.BatchBlock(c, p)
So(err, ShouldBeNil)
err = s.BatchRemove(c, pm)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,718 @@
package service
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"sort"
"strconv"
"time"
"go-common/app/admin/main/member/model"
"go-common/app/admin/main/member/model/bom"
coin "go-common/app/service/main/coin/model"
member "go-common/app/service/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
"github.com/pkg/errors"
)
const (
_logActionBaseAudit = "base_audit"
_logActionDeleteSign = "delete_sign"
_logActionRankUpdate = "rank_set"
_logActionCoinUpdate = "coin_set"
_logActionExpUpdate = "exp_set"
_logActionMoralUpdate = "moral_set"
)
func (s *Service) batchBase(ctx context.Context, mids []int64) (map[int64]*model.Base, error) {
bs := make(map[int64]*model.Base, len(mids))
for _, mid := range mids {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
bs[b.Mid] = b
}
return bs, nil
}
// BaseReview is.
func (s *Service) BaseReview(ctx context.Context, arg *model.ArgBaseReview) ([]*model.BaseReview, error) {
mids := arg.Mids()
if len(mids) == 0 {
return nil, ecode.RequestErr
}
if len(mids) > 200 {
//mids = mids[:200]
return nil, ecode.SearchMidOverLimit
}
var mrs []*model.BaseReview
for _, mid := range mids {
base, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
logs, err := s.dao.SearchUserAuditLog(ctx, mid)
if err != nil {
log.Error("Failed to search user audit log, mid: %d error: %v", mid, err)
continue
}
fixFaceLogs(logs.Result)
mr := &model.BaseReview{
Base: *base,
Logs: logs.Result,
}
mrs = append(mrs, mr)
}
if err := s.reviewAddit(ctx, mids, mrs); err != nil {
log.Error("Failed to fetch review violation count with mids: %+v: %+v", mids, err)
}
return mrs, nil
}
func fixFaceLogs(auditLogs []model.AuditLog) {
for i, v := range auditLogs {
if v.Type != model.BaseAuditTypeFace {
continue
}
// 对头像进行重新签名
ext := new(struct {
Old string `json:"old"`
New string `json:"new"`
})
if err := json.Unmarshal([]byte(v.Extra), &ext); err != nil {
log.Error("Failed to unmarshal extra, additLog: %+v error: %v", v, err)
continue
}
ext.New = model.BuildFaceURL(ext.New)
extraDataBytes, err := json.Marshal(ext)
if err != nil {
log.Error("FaceExtra (%+v) json marshal err(%v)", ext, err)
continue
}
auditLogs[i].Extra = string(extraDataBytes)
}
}
// ClearFace is
func (s *Service) ClearFace(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
if err = s.dao.UpFace(ctx, mid, ""); err != nil {
log.Error("Failed to clear face mid %d error: %+v", mid, err)
continue
}
privFace, err := s.mvToPrivate(ctx, urlPath(b.Face))
if err != nil {
log.Error("Failed to mv face To private bucket, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.Message(ctx, "违规头像处理通知", "抱歉,由于你的头像涉嫌违规,已被修改。如有疑问请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.IncrViolationCount(ctx, mid); err != nil {
log.Error("Failed to increase violation count mid: %d error: %+v", mid, err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: model.BaseAuditTypeFace,
Oid: mid,
Action: _logActionBaseAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old": b.Face,
"new": model.BuildFaceURL(privFace),
},
})
}
return nil
}
// ClearSign is
func (s *Service) ClearSign(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
if err = s.dao.UpSign(ctx, mid, ""); err != nil {
log.Error("Failed to clear sign mid %d error: %+v", mid, err)
continue
}
if err = s.dao.Message(ctx, "违规签名处理通知", "抱歉,由于你的签名涉嫌违规,已被修改。如有疑问请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.IncrViolationCount(ctx, mid); err != nil {
log.Error("Failed to increase violation count mid: %d error: %+v", mid, err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: model.BaseAuditTypeSign,
Oid: mid,
Action: _logActionBaseAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old": b.Sign,
"new": "",
},
})
}
return nil
}
// ClearName is
func (s *Service) ClearName(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
defaultName := fmt.Sprintf("bili_%d", mid)
if err = s.dao.UpdateUname(ctx, mid, defaultName); err != nil {
log.Error("Failed to clear name mid %d error: %+v", mid, err)
continue
}
if err = s.dao.Message(ctx, "违规昵称处理通知", "抱歉,由于你的昵称涉嫌违规,已被修改。如有疑问请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.IncrViolationCount(ctx, mid); err != nil {
log.Error("Failed to increase violation count mid: %d error: %+v", mid, err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: model.BaseAuditTypeName,
Oid: mid,
Action: _logActionBaseAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old": b.Name,
"new": defaultName,
},
})
}
return nil
}
// Members is.
func (s *Service) Members(ctx context.Context, arg *model.ArgList) (*model.MemberPagination, error) {
searched, err := s.dao.SearchMember(ctx, arg)
if err != nil {
return nil, err
}
mids := searched.Mids()
bs, err := s.batchBase(ctx, mids)
if err != nil {
return nil, err
}
result := make([]*model.Base, 0, len(mids))
for _, mid := range mids {
if b, ok := bs[mid]; ok {
result = append(result, b)
}
}
page := &model.MemberPagination{
CommonPagination: searched.Pagination(),
Members: result,
}
return page, nil
}
// MemberProfile is.
func (s *Service) MemberProfile(ctx context.Context, mid int64) (*model.Profile, error) {
p := model.NewProfile()
// base
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base with mid: %d: %+v", mid, err)
return nil, err
}
p.Base = *b
// detail
// remove later
p.Detail = model.Detail{
Mid: b.Mid,
Birthday: b.Birthday,
}
// exp
e, err := s.dao.Exp(ctx, mid)
if err != nil {
log.Error("Failed to retrive user exp with mid: %d: %+v", mid, err)
}
if e != nil {
p.Exp = *e
p.Level.FromExp(e)
}
// moral
mo, err := s.dao.Moral(ctx, mid)
if err != nil {
log.Error("Failed to retrive user moral with mid: %d: %+v", mid, err)
}
if mo != nil {
p.Moral = *mo
}
// official
of, err := s.dao.Official(ctx, mid)
if err != nil {
log.Error("Failed to retrive user official with mid: %d: %+v", mid, err)
}
if of != nil {
p.Official = *of
}
// coin
co, err := s.coinRPC.UserCoins(ctx, &coin.ArgCoinInfo{Mid: mid})
if err != nil {
log.Error("Failed to retrive user coins with mid: %d: %+v", mid, err)
}
p.Coin = model.Coin{Coins: co}
// addit
ad, err := s.dao.UserAddit(ctx, mid)
if err != nil {
log.Error("Failed to retrive user addit with mid: %d: %+v", mid, err)
}
if ad != nil {
p.Addit = *ad
}
// realname
dr, err := s.dao.RealnameInfo(ctx, mid)
if err != nil {
log.Error("Failed to retrive user realname with mid: %d: %+v", mid, err)
}
if dr != nil {
p.Realanme.ParseInfo(dr)
} else {
p.Realanme.State = model.RealnameApplyStateNone
}
return p, nil
}
// DelSign is.
func (s *Service) DelSign(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
err := s.dao.UpSign(ctx, mid, "")
if err != nil {
log.Error("Failed to delete sign mid: %d: %+v", mid, err)
continue
}
if err := s.dao.Message(ctx, "违规签名处理通知", "抱歉,由于你的个性签名内容涉嫌违规,我们已将你的个性签名清空,如有问题请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", mid, err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: mid,
Action: _logActionDeleteSign,
Ctime: time.Now(),
})
}
return nil
}
// SetMoral is.
func (s *Service) SetMoral(ctx context.Context, arg *model.ArgMoralSet) error {
moral, err := s.dao.Moral(ctx, arg.Mid)
if err != nil {
return errors.WithStack(err)
}
newMoral := int64(arg.Moral * 100)
delta := newMoral - moral.Moral
if err = s.memberRPC.AddMoral(ctx,
&member.ArgUpdateMoral{
Mid: arg.Mid,
Delta: delta,
Operator: arg.Operator,
Reason: arg.Reason,
Remark: "管理后台",
Origin: member.ManualChangeType,
IP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionMoralUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_moral": newMoral,
"delta": delta,
},
})
return nil
}
// SetExp is.
func (s *Service) SetExp(ctx context.Context, arg *model.ArgExpSet) error {
exp, err := func() (*model.Exp, error) {
exp, err := s.dao.Exp(ctx, arg.Mid)
if err != nil {
if err == ecode.NothingFound {
return &model.Exp{Mid: arg.Mid}, nil
}
return nil, errors.WithStack(err)
}
return exp, nil
}()
if err != nil {
return err
}
delta := arg.Exp - float64(exp.Exp/100)
if err = s.memberRPC.UpdateExp(ctx,
&member.ArgAddExp{
Mid: arg.Mid,
Count: delta,
Operate: arg.Operator,
Reason: arg.Reason,
IP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionExpUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_exp": arg.Exp,
"delta": delta,
},
})
return nil
}
// SetRank is.
func (s *Service) SetRank(ctx context.Context, arg *model.ArgRankSet) error {
if err := s.memberRPC.SetRank(ctx,
&member.ArgUpdateRank{
Mid: arg.Mid,
Rank: arg.Rank,
RemoteIP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionRankUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_rank": arg.Rank,
"reason": arg.Reason,
},
})
return nil
}
// SetCoin is.
func (s *Service) SetCoin(ctx context.Context, arg *model.ArgCoinSet) error {
coins, err := s.coinRPC.UserCoins(ctx, &coin.ArgCoinInfo{Mid: arg.Mid, RealIP: arg.IP})
if err != nil {
return errors.WithStack(err)
}
reason := "系统操作"
if arg.Reason != "" {
reason = fmt.Sprintf("系统操作:%s", arg.Reason)
}
delta := arg.Coins - coins
if _, err = s.coinRPC.ModifyCoin(ctx,
&coin.ArgModifyCoin{
Mid: arg.Mid,
Operator: arg.Operator,
Reason: reason,
Count: delta,
IP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionCoinUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_coins": arg.Coins,
"delta": delta,
"reason": reason,
},
})
return nil
}
// SetAdditRemark is.
func (s *Service) SetAdditRemark(ctx context.Context, arg *model.ArgAdditRemarkSet) error {
return s.dao.UpAdditRemark(ctx, arg.Mid, arg.Remark)
}
// PubExpMsg is.
func (s *Service) PubExpMsg(ctx context.Context, arg *model.ArgPubExpMsg) (err error) {
msg := &model.AddExpMsg{
Event: arg.Event,
Mid: arg.Mid,
IP: arg.IP,
Ts: arg.Ts,
}
return s.dao.PubExpMsg(ctx, msg)
}
// ExpLog is.
func (s *Service) ExpLog(ctx context.Context, mid int64) ([]*model.UserLog, error) {
return nil, ecode.MethodNotAllowed
}
func filterByStatus(status ...int8) func(*model.FaceRecord) bool {
ss := make(map[int8]struct{}, len(status))
for _, s := range status {
ss[s] = struct{}{}
}
return func(fr *model.FaceRecord) bool {
_, ok := ss[fr.Status]
return ok
}
}
func filterByMid(mid int64) func(*model.FaceRecord) bool {
return func(fr *model.FaceRecord) bool {
return fr.Mid == mid
}
}
func filterByOP(operator string) func(*model.FaceRecord) bool {
return func(fr *model.FaceRecord) bool {
return fr.Operator == operator
}
}
// FaceHistory is.
func (s *Service) FaceHistory(ctx context.Context, arg *model.ArgFaceHistory) (*model.FaceRecordPagination, error) {
list, err := s.faceHistory(ctx, arg)
if err != nil {
return nil, err
}
plist := list.Paginate(arg.PS*(arg.PN-1), arg.PS)
page := &model.FaceRecordPagination{
Records: plist,
CommonPagination: &model.CommonPagination{
Page: model.Page{
Num: arg.PN,
Size: arg.PS,
Total: len(list),
},
},
}
return page, nil
}
func (s *Service) faceHistory(ctx context.Context, arg *model.ArgFaceHistory) (res model.FaceRecordList, err error) {
switch arg.Mode() {
case "op":
res, err = s.dao.FaceHistoryByOP(ctx, arg)
if err != nil {
return nil, err
}
if arg.Mid > 0 {
res = res.Filter(filterByMid(arg.Mid))
}
case "mid":
res, err = s.dao.FaceHistoryByMid(ctx, arg)
if err != nil {
return nil, err
}
if arg.Operator != "" {
res = res.Filter(filterByOP(arg.Operator))
}
}
res = res.Filter(filterByStatus(arg.Status...))
sort.Slice(res, func(i, j int) bool {
return res[i].ModifyTime > res[j].ModifyTime
})
for _, r := range res {
r.BuildFaceURL()
}
return
}
// MoralLog is.
func (s *Service) MoralLog(ctx context.Context, mid int64) ([]*model.UserLog, error) {
return nil, ecode.MethodNotAllowed
}
func (s *Service) reviewAddit(ctx context.Context, mids []int64, mrs []*model.BaseReview) error {
uas, err := s.dao.BatchUserAddit(ctx, mids)
if err != nil {
return err
}
for _, mr := range mrs {
if ua, ok := uas[mr.Mid]; ok {
mr.Addit = *ua
}
}
return nil
}
// BatchFormal is
func (s *Service) BatchFormal(ctx context.Context, arg *model.ArgBatchFormal) error {
fp := bom.NewReader(bytes.NewReader(arg.FileData))
reader := csv.NewReader(fp)
ip := metadata.String(ctx, metadata.RemoteIP)
columns, err := reader.Read()
if err != nil {
log.Error("Failed to read columns from csv: %+v", err)
return ecode.RequestErr
}
findMidPositon := func() (int, error) {
for i, col := range columns {
if col == "mid" {
return i, nil
}
}
return 0, errors.New("No mid column")
}
midPosition, err := findMidPositon()
if err != nil {
log.Error("Failed to find mid column: %+v", err)
return ecode.RequestErr
}
mids := make([]int64, 0)
for {
record, rerr := reader.Read()
if rerr == io.EOF {
break
}
if rerr != nil {
log.Error("Failed to parse csv: %+v", errors.WithStack(rerr))
return ecode.RequestErr
}
if len(record) < midPosition {
log.Warn("Skip record due to no suitable position: %+v", record)
continue
}
mid, perr := strconv.ParseInt(record[midPosition], 10, 64)
if perr != nil {
log.Warn("Failed to parse mid on data: %+v: %+v", record[midPosition], perr)
continue
}
mids = append(mids, mid)
}
bases, err := s.batchBase(ctx, mids)
if err != nil {
log.Error("Failed to query bases with mids: %+v: %+v", mids, err)
return ecode.RequestErr
}
for _, mid := range mids {
base, ok := bases[mid]
if !ok {
log.Warn("No such user with mid: %d", mid)
continue
}
if base.Rank >= 10000 {
log.Warn("Rank already exceeded 10000 on mid: %d: %+v", mid, base)
continue
}
rankArg := &model.ArgRankSet{
Mid: mid,
Rank: 10000,
Operator: arg.Operator,
OperatorID: arg.OperatorID,
IP: ip,
}
if err := s.SetRank(ctx, rankArg); err != nil {
log.Warn("Failed to set rank with mid: %d: %+v", mid, err)
continue
}
// 通过发放一次每日登录的经验奖励消息来使用户等级直升 lv1
expArg := &model.ArgPubExpMsg{
Mid: mid,
IP: ip,
Ts: time.Now().Unix(),
Event: "login",
}
if err := s.PubExpMsg(ctx, expArg); err != nil {
log.Warn("Failed to pub exp message with mid: %d: %+v", mid, err)
continue
}
}
return nil
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/member/model"
"github.com/smartystreets/goconvey/convey"
)
func TestMemberList(t *testing.T) {
convey.Convey("MemberList", t, func() {
results, err := s.Members(context.Background(), &model.ArgList{
Keyword: "123",
})
convey.So(err, convey.ShouldBeNil)
convey.So(results, convey.ShouldNotBeNil)
})
}
func TestMemberProfile(t *testing.T) {
convey.Convey("MemberProfile", t, func() {
result, err := s.MemberProfile(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}
func TestExpLog(t *testing.T) {
convey.Convey("ExpLog", t, func() {
result, err := s.ExpLog(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,95 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/member/model"
"go-common/library/log"
"go-common/library/queue/databus/report"
)
const (
_logActionMonitorAdd = "monitor_user_add"
_logActionMonitorDel = "monitor_user_del"
)
// Monitors is.
func (s *Service) Monitors(ctx context.Context, arg *model.ArgMonitor) ([]*model.Monitor, int, error) {
includeDeleted := false
if arg.Mid > 0 {
includeDeleted = true
}
mns, total, err := s.dao.Monitors(ctx, arg.Mid, includeDeleted, arg.Pn, arg.Ps)
if err != nil {
return nil, 0, err
}
s.monitorsName(ctx, mns)
return mns, total, nil
}
// AddMonitor is.
func (s *Service) AddMonitor(ctx context.Context, arg *model.ArgAddMonitor) error {
remark := fmt.Sprintf("加入监控列表:%s", arg.Remark)
if err := s.dao.AddMonitor(ctx, arg.Mid, arg.Operator, remark); err != nil {
return err
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionMonitorAdd,
Ctime: time.Now(),
// extra
Index: []interface{}{},
Content: map[string]interface{}{
"remark": remark,
},
})
return nil
}
// DelMonitor is.
func (s *Service) DelMonitor(ctx context.Context, arg *model.ArgDelMonitor) error {
remark := fmt.Sprintf("移出监控列表:%s", arg.Remark)
if err := s.dao.DelMonitor(ctx, arg.Mid, arg.Operator, remark); err != nil {
return err
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionMonitorDel,
Ctime: time.Now(),
// extra
Index: []interface{}{},
Content: map[string]interface{}{
"remark": remark,
},
})
return nil
}
func (s *Service) monitorsName(ctx context.Context, mns []*model.Monitor) {
mids := make([]int64, 0, len(mns))
for _, mn := range mns {
mids = append(mids, mn.Mid)
}
bs, err := s.dao.Bases(ctx, mids)
if err != nil {
log.Error("Failed to fetch bases with mids: %+v: %+v", mids, err)
return
}
for _, mn := range mns {
b, ok := bs[mn.Mid]
if !ok {
continue
}
mn.Name = b.Name
}
}

View File

@@ -0,0 +1,436 @@
package service
import (
"context"
"encoding/json"
"strings"
"time"
"go-common/app/admin/main/member/model"
"go-common/app/service/main/member/model/block"
spymodel "go-common/app/service/main/spy/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus/report"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
const (
_logActionAudit = "official_doc_audit"
_logActionEdit = "official_doc_edit"
_logActionEditName = "official_doc_edit_name"
)
func i64Toi8(in []int64) []int8 {
out := make([]int8, 0, len(in))
for _, i := range in {
out = append(out, int8(i))
}
return out
}
func i8Toi64(in []int8) []int64 {
out := make([]int64, 0, len(in))
for _, i := range in {
out = append(out, int64(i))
}
return out
}
func actOr(act ...string) string {
return strings.Join(act, ",")
}
func (s *Service) officialName(ctx context.Context, ofs []*model.Official) {
mids := make([]int64, 0, len(ofs))
for _, o := range ofs {
mids = append(mids, o.Mid)
}
ofds, err := s.dao.OfficialDocsByMids(ctx, mids)
if err != nil {
log.Error("Failed to s.dao.OfficialDocsByMids(%+v): %+v", mids, err)
return
}
for _, o := range ofs {
od, ok := ofds[o.Mid]
if !ok {
continue
}
o.Name = od.Name
}
}
// Officials is.
func (s *Service) Officials(ctx context.Context, arg *model.ArgOfficial) ([]*model.Official, int, error) {
if len(arg.Role) == 0 {
arg.Role = i8Toi64(model.AllRoles)
}
if arg.ETime == 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
ofs, total, err := s.dao.Officials(ctx, arg.Mid, i64Toi8(arg.Role), arg.STime.Time(), arg.ETime.Time(), arg.Pn, arg.Ps)
if err != nil {
return nil, 0, err
}
// 需要展示昵称
s.officialName(ctx, ofs)
return ofs, total, err
}
func (s *Service) blockResult(ctx context.Context, mid int64) (*model.BlockResult, error) {
info, err := s.memberRPC.BlockInfo(ctx, &block.RPCArgInfo{MID: mid})
if err != nil {
err = errors.Wrapf(err, "%v", mid)
return nil, err
}
block := &model.BlockResult{
MID: info.MID,
BlockStatus: info.BlockStatus,
StartTime: info.StartTime,
EndTime: info.EndTime,
}
return block, nil
}
// OfficialDoc is.
func (s *Service) OfficialDoc(ctx context.Context, mid int64) (od *model.OfficialDoc, logs *model.SearchLogResult, block *model.BlockResult, spys []*spymodel.Statistics, realname *model.Realname, sameCreditCodeMids []int64, err error) {
if od, err = s.dao.OfficialDoc(ctx, mid); err != nil {
return
}
if od == nil {
od = &model.OfficialDoc{
Mid: mid,
OfficialExtra: &model.OfficialExtra{},
}
}
logs, err = s.dao.SearchLog(ctx, 0, mid, "", actOr(_logActionAudit, _logActionEdit))
if err != nil {
log.Error("Failed to s.dao.SearchLog(%+v): %+v", mid, err)
return
}
block, err = s.blockResult(ctx, mid)
if err != nil {
log.Error("Failed to s.blockResult(%+v): %+v", mid, err)
return
}
arg := &spymodel.ArgStat{Mid: mid}
spys, err = s.spyRPC.StatByID(ctx, arg)
if err != nil {
log.Error("Failed to s.spyRPC.StatByID: mid(%d): %+v", od.Mid, err)
return
}
realname, err = s.officialRealname(ctx, mid)
if err != nil {
log.Error("Failed to get official realname with mid: %d: %+v", od.Mid, err)
return
}
// 查询使用相同社会信用代码的mid
sameCreditCodeMids = make([]int64, 0)
if od.OfficialExtra.CreditCode != "" {
func() {
addits, err := s.OfficialDocAddits(ctx, "credit_code", od.OfficialExtra.CreditCode)
if err != nil {
log.Error("Failed to get official addit with mid: %d: %+v", od.Mid, err)
return
}
for _, addit := range addits {
if addit.Mid != od.Mid {
sameCreditCodeMids = append(sameCreditCodeMids, addit.Mid)
}
}
}()
}
return
}
func (s *Service) officialRealname(ctx context.Context, mid int64) (*model.Realname, error) {
realname := &model.Realname{
State: model.RealnameApplyStateNone,
}
dr, err := s.dao.RealnameInfo(ctx, mid)
if err != nil {
log.Error("Failed to get realname info with mid: %d: %+v", mid, err)
return realname, nil
}
if dr != nil {
realname.ParseInfo(dr)
}
imagesByMain := func() {
apply, err := s.dao.LastPassedRealnameMainApply(ctx, mid)
if err != nil {
log.Error("Failed to get last passed realname main apply with mid: %d: %+v", mid, err)
return
}
images, err := s.dao.RealnameApplyIMG(ctx, []int64{apply.HandIMG, apply.FrontIMG, apply.BackIMG})
if err != nil {
log.Error("Failed to get realname apply image by apply: %+v: %+v", apply, err)
return
}
for _, image := range images {
realname.ParseDBApplyIMG(image.IMGData)
}
}
imagesByAlipay := func() {
apply, err := s.dao.LastPassedRealnameAlipayApply(ctx, mid)
if err != nil {
log.Error("Failed to get last passed realname alipay apply with mid: %d: %+v", mid, err)
return
}
realname.ParseDBApplyIMG(apply.IMG)
}
switch dr.Channel {
case model.ChannelMain.DBChannel():
imagesByMain()
case model.ChannelAlipay.DBChannel():
imagesByAlipay()
default:
log.Error("Failed to get realname apply images by realname info: %+v", dr)
}
return realname, nil
}
// OfficialDocs is.
func (s *Service) OfficialDocs(ctx context.Context, arg *model.ArgOfficialDoc) ([]*model.OfficialDoc, int, error) {
if len(arg.Role) == 0 {
arg.Role = i8Toi64(model.AllRoles)
}
if len(arg.State) == 0 {
arg.State = i8Toi64(model.AllStates)
}
if arg.ETime == 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
return s.dao.OfficialDocs(ctx, arg.Mid, i64Toi8(arg.Role), i64Toi8(arg.State), arg.Uname, arg.STime.Time(), arg.ETime.Time(), arg.Pn, arg.Ps)
}
// OfficialDocAudit is.
func (s *Service) OfficialDocAudit(ctx context.Context, arg *model.ArgOfficialAudit) (err error) {
od, err := s.dao.OfficialDoc(ctx, arg.Mid)
if err != nil {
return
}
if arg.State == model.OfficialStatePass {
if err = s.updateUname(ctx, od.Mid, od.Name, arg.UID, arg.Uname); err != nil {
log.Error("Failed to update uname: mid(%d), name(%s): %+v", od.Mid, od.Name, err)
return
}
}
if err = s.dao.OfficialDocAudit(ctx, arg.Mid, arg.State, arg.Uname, arg.IsInternal, arg.Reason); err != nil {
return
}
od, err = s.dao.OfficialDoc(ctx, arg.Mid)
if err != nil {
return
}
role := int8(model.OfficialRoleUnauth)
cnt := `对不起,您的官方认证申请未通过,未通过原因:"` + arg.Reason + `,重新申请点#{这里}{"https://account.bilibili.com/account/official/home"}`
if arg.State == model.OfficialStatePass {
role = od.Role
cnt = "恭喜您的官方认证申请已经通过啦o(* ̄▽ ̄*)o"
}
if _, err = s.dao.OfficialEdit(ctx, arg.Mid, role, od.Title, od.Desc); err != nil {
return
}
if err = s.dao.Message(ctx, "官方认证审核通知", cnt, []int64{arg.Mid}); err != nil {
log.Error("Failed to send message: %+v", err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Uname,
UID: arg.UID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{arg.State, int64(od.CTime), od.Role, od.Name, od.Title, od.Desc},
Content: map[string]interface{}{
"reason": arg.Reason,
"name": od.Name,
"extra": od.Extra,
"role": od.Role,
"title": od.Title,
"desc": od.Desc,
"state": arg.State,
"doc_ctime": int64(od.CTime),
},
})
return
}
// OfficialDocEdit is.
func (s *Service) OfficialDocEdit(ctx context.Context, arg *model.ArgOfficialEdit) (err error) {
od, _ := s.dao.OfficialDoc(ctx, arg.Mid)
if od == nil {
od = &model.OfficialDoc{
Mid: arg.Mid,
Role: arg.Role,
OfficialExtra: &model.OfficialExtra{},
}
}
od.State = int8(model.OfficialStatePass)
if arg.Role == model.OfficialRoleUnauth {
od.State = int8(model.OfficialStateNoPass)
}
od.Name = arg.Name
od.Uname = arg.Uname
od.Telephone = arg.Telephone
od.Email = arg.Email
od.Address = arg.Address
od.Supplement = arg.Supplement
od.Company = arg.Company
od.Operator = arg.Operator
od.CreditCode = arg.CreditCode
od.Organization = arg.Organization
od.OrganizationType = arg.OrganizationType
od.BusinessLicense = arg.BusinessLicense
od.BusinessLevel = arg.BusinessLevel
od.BusinessScale = arg.BusinessScale
od.BusinessAuth = arg.BusinessAuth
od.OfficalSite = arg.OfficalSite
od.RegisteredCapital = arg.RegisteredCapital
extra, err := json.Marshal(od.OfficialExtra)
if err != nil {
err = errors.Wrap(err, "official doc edit")
return
}
if err = s.updateUname(ctx, arg.Mid, arg.Name, arg.UID, arg.Uname); err != nil {
log.Error("Failed to update uname: mid(%d), name(%s): %+v", arg.Mid, arg.Name, err)
err = ecode.MemberNameFormatErr
return
}
if err = s.dao.OfficialDocEdit(ctx, arg.Mid, arg.Name, arg.Role, od.State, arg.Title, arg.Desc, string(extra), arg.Uname, arg.IsInternal); err != nil {
log.Error("Failed to update official doc: %+v", err)
err = ecode.RequestErr
return
}
if _, err = s.dao.OfficialEdit(ctx, arg.Mid, arg.Role, arg.Title, arg.Desc); err != nil {
return
}
report.Manager(&report.ManagerInfo{
Uname: arg.Uname,
UID: arg.UID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionEdit,
Ctime: time.Now(),
// extra
Index: []interface{}{od.State, int64(od.CTime), arg.Role, arg.Name, arg.Title, arg.Desc},
Content: map[string]interface{}{
"extra": string(extra),
"name": arg.Name,
"role": arg.Role,
"title": arg.Title,
"desc": arg.Desc,
"state": od.State,
"doc_ctime": int64(od.CTime),
},
})
if arg.SendMessage {
if merr := s.dao.Message(ctx, arg.MessageTitle, arg.MessageContent, []int64{arg.Mid}); merr != nil {
log.Error("Failed to send message: %+v", merr)
}
}
return
}
// OfficialDocSubmit is.
func (s *Service) OfficialDocSubmit(ctx context.Context, arg *model.ArgOfficialSubmit) (err error) {
od := &model.OfficialDoc{
Mid: arg.Mid,
Name: arg.Name,
State: int8(model.OfficialStateWait),
Role: arg.Role,
Title: arg.Title,
Desc: arg.Desc,
Uname: arg.Uname,
IsInternal: arg.IsInternal,
SubmitSource: arg.SubmitSource,
OfficialExtra: &model.OfficialExtra{
Realname: arg.Realname,
Operator: arg.Operator,
Telephone: arg.Telephone,
Email: arg.Email,
Address: arg.Address,
Company: arg.Company,
CreditCode: arg.CreditCode,
Organization: arg.Organization,
OrganizationType: arg.OrganizationType,
BusinessLicense: arg.BusinessLicense,
BusinessScale: arg.BusinessScale,
BusinessLevel: arg.BusinessLevel,
BusinessAuth: arg.BusinessAuth,
Supplement: arg.Supplement,
Professional: arg.Professional,
Identification: arg.Identification,
OfficalSite: arg.OfficalSite,
RegisteredCapital: arg.RegisteredCapital,
},
}
if !od.Validate() {
log.Error("Failed to validate official doc: %+v", od)
err = ecode.RequestErr
return
}
return s.dao.OfficialDocSubmit(ctx, od.Mid, od.Name, od.Role, int8(model.OfficialStateWait), od.Title, od.Desc, od.OfficialExtra.String(), od.Uname, od.IsInternal, od.SubmitSource)
}
func (s *Service) updateUname(ctx context.Context, mid int64, name string, adminID int64, adminName string) error {
b, err := s.dao.Base(ctx, mid)
if err != nil {
return err
}
if b.Name == name {
return nil
}
if err := s.dao.UpdateUname(ctx, mid, name); err != nil {
log.Error("Failed to update uname to aso: mid(%d), name(%s): %+v", mid, name, err)
return err
}
if err := s.dao.UpName(ctx, mid, name); err != nil {
log.Error("Failed to update uname to member: mid(%d), name(%s): %+v", mid, name, err)
return err
}
report.Manager(&report.ManagerInfo{
Uname: adminName,
UID: adminID,
Business: model.ManagerLogID,
Type: 0,
Oid: mid,
Action: _logActionEditName,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old_name": b.Name,
"new_name": name,
},
})
return nil
}
// OfficialDocAddits find mids by property and value
func (s *Service) OfficialDocAddits(ctx context.Context, property string, vstring string) ([]*model.OfficialDocAddit, error) {
if property == "" {
return nil, ecode.RequestErr
}
addits, err := s.dao.OfficialDocAddits(ctx, property, vstring)
return addits, err
}

View File

@@ -0,0 +1,84 @@
package service
import (
"context"
"testing"
"time"
"go-common/app/admin/main/member/model"
xtime "go-common/library/time"
"github.com/smartystreets/goconvey/convey"
)
func TestOfficials(t *testing.T) {
convey.Convey("Officials", t, func() {
o, total, err := s.Officials(context.Background(), &model.ArgOfficial{
Mid: 123,
Role: []int64{1},
ETime: xtime.Time(time.Now().Unix()),
Pn: 1,
Ps: 20,
})
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(total, convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDoc(t *testing.T) {
convey.Convey("OfficialDoc", t, func() {
o, logs, block, spy, realname, mids, err := s.OfficialDoc(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(logs, convey.ShouldNotBeNil)
convey.So(block, convey.ShouldNotBeNil)
convey.So(spy, convey.ShouldNotBeNil)
convey.So(realname, convey.ShouldNotBeNil)
convey.So(mids, convey.ShouldNotBeNil)
})
}
func TestOfficialDocs(t *testing.T) {
convey.Convey("OfficialDocs", t, func() {
o, total, err := s.OfficialDocs(context.Background(), &model.ArgOfficialDoc{
Mid: 123,
Role: []int64{1},
State: []int64{1},
ETime: xtime.Time(time.Now().Unix()),
Pn: 1,
Ps: 20,
})
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(total, convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDocAudit(t *testing.T) {
convey.Convey("OfficialDocAudit", t, func() {
err := s.OfficialDocAudit(context.Background(), &model.ArgOfficialAudit{
Mid: 123,
State: 1,
UID: 111,
Uname: "guan",
Reason: "xxx",
})
convey.So(err, convey.ShouldBeNil)
})
}
func TestOfficialDocEdit(t *testing.T) {
convey.Convey("OfficialDocEdit", t, func() {
err := s.OfficialDocEdit(context.Background(), &model.ArgOfficialEdit{
Mid: 123,
Name: "guan",
Role: 1,
Title: "title",
Desc: "desc",
UID: 111,
Uname: "guan",
})
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,772 @@
package service
import (
"bufio"
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"image"
"image/jpeg"
"image/png"
"io/ioutil"
mrand "math/rand"
"os"
"strconv"
"strings"
"syscall"
"time"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/model"
memmdl "go-common/app/service/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
"github.com/nfnt/resize"
"github.com/pkg/errors"
)
// consts
const (
_512KiloBytes = 512 * 1024
)
// RealnameList .
func (s *Service) RealnameList(ctx context.Context, arg *model.ArgRealnameList) (list []*model.RespRealnameApply, total int, err error) {
list = make([]*model.RespRealnameApply, 0)
switch arg.Channel {
case model.ChannelMain:
return s.realnameMainList(ctx, arg)
case model.ChannelAlipay:
return s.realnameAlipayList(ctx, arg)
}
return
}
func (s *Service) realnameMainList(ctx context.Context, arg *model.ArgRealnameList) (list []*model.RespRealnameApply, total int, err error) {
var (
dl []*model.DBRealnameApply
)
if arg.Card != "" {
// 如果查询证件号, 则通过证件号MD5 realname_info 查询到 对应的 mids
var (
infos []*model.DBRealnameInfo
mids []int64
md5 = cardMD5(arg.Card, arg.DBCardType(), arg.Country)
)
log.Info("realnameMainList card: %s, md5: %s", arg.Card, md5)
if infos, err = s.dao.RealnameInfoByCardMD5(ctx, md5, arg.State.DBStatus(), model.ChannelMain.DBChannel()); err != nil {
return
}
log.Info("realnameMainList infos : %+v", infos)
if len(infos) <= 0 {
return
}
for _, i := range infos {
log.Info("realnameMainList info: %+v", i)
mids = append(mids, i.MID)
}
log.Info("realnameMainList mids: %+v", mids)
if dl, total, err = s.dao.RealnameMainList(ctx, mids, arg.DBCardType(), arg.DBCountry(), arg.OPName, arg.TSFrom, arg.TSTo, arg.DBState(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
} else {
var (
mids []int64
)
if arg.MID > 0 {
mids = append(mids, arg.MID)
}
if dl, total, err = s.dao.RealnameMainList(ctx, mids, arg.DBCardType(), arg.DBCountry(), arg.OPName, arg.TSFrom, arg.TSTo, arg.DBState(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
}
var (
midMap = make(map[int64]int) // map[mid]count
mids []int64
imgIDs []int64
)
// 审核 db 数据解析进 list
for _, d := range dl {
midMap[d.MID] = 0
var (
r = &model.RespRealnameApply{}
)
r.ParseDBMainApply(d)
imgIDs = append(imgIDs, d.HandIMG, d.FrontIMG, d.BackIMG)
list = append(list, r)
}
// 没有数据则返回
if len(midMap) <= 0 {
return
}
// 获取实名申请次数
for mid := range midMap {
if midMap[mid], err = s.dao.RealnameApplyCount(ctx, mid); err != nil {
return
}
mids = append(mids, mid)
}
// 获取mid的昵称 & 等级信息
var (
memsArg = &memmdl.ArgMemberMids{
Mids: mids,
}
memMap map[int64]*memmdl.Member
imgMap map[int64]*model.DBRealnameApplyIMG
)
if memMap, err = s.memberRPC.Members(ctx, memsArg); err != nil {
err = errors.WithStack(err)
return
}
// 获取证件照信息
if imgMap, err = s.dao.RealnameApplyIMG(ctx, imgIDs); err != nil {
return
}
for _, ra := range list {
if mem, ok := memMap[ra.MID]; ok {
ra.ParseMember(mem)
}
for _, id := range ra.IMGIDs {
if img, ok := imgMap[id]; ok {
ra.ParseDBApplyIMG(img.IMGData)
}
}
ra.Times = midMap[ra.MID]
}
return
}
func (s *Service) realnameAlipayList(ctx context.Context, arg *model.ArgRealnameList) (list []*model.RespRealnameApply, total int, err error) {
var (
dl []*model.DBRealnameAlipayApply
)
if arg.Card != "" {
// 如果查询证件号, 则通过证件号MD5 realname_info 查询到 对应的 mids
var (
infos []*model.DBRealnameInfo
mids []int64
md5 = cardMD5(arg.Card, arg.DBCardType(), arg.Country)
)
log.Info("realnameAlipayList card: %s, md5: %s", arg.Card, md5)
if infos, err = s.dao.RealnameInfoByCardMD5(ctx, md5, arg.State.DBStatus(), model.ChannelAlipay.DBChannel()); err != nil {
return
}
log.Info("realnameAlipayList infos : %+v", infos)
if len(infos) <= 0 {
return
}
for _, i := range infos {
log.Info("realnameAlipayList info: %+v", i)
mids = append(mids, i.MID)
}
log.Info("realnameAlipayList mids: %+v", mids)
if dl, total, err = s.dao.RealnameAlipayList(ctx, mids, 0, 0, arg.State.DBStatus(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
} else {
var (
mids []int64
)
if arg.MID > 0 {
mids = append(mids, arg.MID)
}
if dl, total, err = s.dao.RealnameAlipayList(ctx, mids, arg.TSFrom, arg.TSTo, arg.State.DBStatus(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
}
log.Info("realnameAlipayList dl: %+v, total: %d", dl, total)
var (
midMap = make(map[int64]int)
)
// append to list
for _, d := range dl {
midMap[d.MID] = 0
var (
r = &model.RespRealnameApply{}
)
r.ParseDBAlipayApply(d)
list = append(list, r)
}
if len(midMap) <= 0 {
return
}
var mids []int64
for mid := range midMap {
if midMap[mid], err = s.dao.RealnameApplyCount(ctx, mid); err != nil {
return
}
mids = append(mids, mid)
}
var (
memsArg = &memmdl.ArgMemberMids{
Mids: mids,
}
memMap map[int64]*memmdl.Member
)
if memMap, err = s.memberRPC.Members(ctx, memsArg); err != nil {
err = errors.WithStack(err)
return
}
for _, ra := range list {
if mem, ok := memMap[ra.MID]; ok {
ra.ParseMember(mem)
}
ra.Times = midMap[ra.MID]
}
return
}
func cardMD5(card string, cardType int, country int) (res string) {
if card == "" || cardType < 0 || country < 0 {
return
}
var (
lowerCode = strings.ToLower(card)
key = fmt.Sprintf("%s_%s_%d_%d", model.RealnameSalt, lowerCode, cardType, country)
)
return fmt.Sprintf("%x", md5.Sum([]byte(key)))
}
// RealnamePendingList .
func (s *Service) RealnamePendingList(ctx context.Context, arg *model.ArgRealnamePendingList) (list []*model.RespRealnameApply, total int, err error) {
var (
larg = &model.ArgRealnameList{
Channel: arg.Channel,
State: model.RealnameApplyStatePending,
TSFrom: time.Now().Add(-time.Hour * 24 * 7).Unix(),
PS: arg.PS,
PN: arg.PN,
}
)
return s.RealnameList(ctx, larg)
}
// RealnameAuditApply .
func (s *Service) RealnameAuditApply(ctx context.Context, arg *model.ArgRealnameAuditApply, adminName string, adminID int64) (err error) {
var (
mid int64
)
// 1. check the apply state
switch arg.Channel {
case model.ChannelMain:
var apply *model.DBRealnameApply
if apply, err = s.dao.RealnameMainApply(ctx, arg.ID); err != nil {
return
}
if apply.IsPassed() {
return
}
mid = apply.MID
case model.ChannelAlipay:
var apply *model.DBRealnameAlipayApply
if apply, err = s.dao.RealnameAlipayApply(ctx, arg.ID); err != nil {
return
}
if apply.Status == model.RealnameApplyStateNone.DBStatus() || apply.Status == model.RealnameApplyStateRejective.DBStatus() {
return
}
mid = apply.MID
}
var (
state = 0
msgTitle = ""
msgContent = ""
mc = "2_2_1"
expNotify = false
)
switch arg.Action {
case model.RealnameActionPass:
state = model.RealnameApplyStatePassed.DBStatus()
msgTitle = "您提交的实名认证已审核通过"
msgContent = "恭喜,您提交的实名认证已通过审核"
expNotify = true
case model.RealnameActionReject:
state = model.RealnameApplyStateRejective.DBStatus()
msgTitle = "您提交的实名认证未通过审核"
msgContent = fmt.Sprintf(`抱歉,您提交的实名认证未通过审核,驳回原因:%s。请修改后重新提交实名认证。`, arg.Reason)
default:
err = ecode.RequestErr
return
}
// 2. do something
switch arg.Channel {
case model.ChannelMain:
if err = s.dao.UpdateOldRealnameApply(ctx, arg.ID, state, adminName, adminID, time.Now(), arg.Reason); err != nil {
return
}
case model.ChannelAlipay:
if err = s.dao.UpdateRealnameAlipayApply(ctx, arg.ID, adminID, adminName, state, arg.Reason); err != nil {
return
}
if err = s.dao.UpdateRealnameInfo(ctx, mid, state, arg.Reason); err != nil {
return
}
}
go func() {
if err := s.dao.RawMessage(context.Background(), mc, msgTitle, msgContent, []int64{mid}); err != nil {
log.Error("%+v", err)
}
if expNotify {
expMsg := &model.AddExpMsg{
Event: "identify",
Mid: mid,
IP: metadata.String(ctx, metadata.RemoteIP),
Ts: time.Now().Unix(),
}
if err := s.dao.PubExpMsg(ctx, expMsg); err != nil {
log.Error("%+v", err)
}
}
}()
return
}
// RealnameReasonList .
func (s *Service) RealnameReasonList(ctx context.Context, arg *model.ArgRealnameReasonList) (list []string, total int, err error) {
return s.dao.RealnameReasonList(ctx)
}
// RealnameSetReason .
func (s *Service) RealnameSetReason(ctx context.Context, arg *model.ArgRealnameSetReason) (err error) {
return s.dao.UpdateRealnameReason(ctx, arg.Reasons)
}
// RealnameSearchCard .
func (s *Service) RealnameSearchCard(ctx context.Context, cards []string, cardType int, country int) (data map[string]int64, err error) {
var (
hashmap = make(map[string]string) //map[hash]card
hashes = make([]string, 0)
list []*model.DBRealnameInfo
)
for _, card := range cards {
hash := cardMD5(card, cardType, country)
hashmap[hash] = card
hashes = append(hashes, hash)
}
if list, err = s.dao.RealnameSearchCards(ctx, hashes); err != nil {
return
}
data = make(map[string]int64)
for _, l := range list {
if rawCode, ok := hashmap[l.CardMD5]; ok {
data[rawCode] = l.MID
}
}
return
}
// RealnameUnbind is.
func (s *Service) RealnameUnbind(ctx context.Context, mid int64, adminName string, adminID int64) (err error) {
var (
info *model.DBRealnameInfo
)
if info, err = s.dao.RealnameInfo(ctx, mid); err != nil {
return
}
if info == nil {
err = ecode.RealnameAlipayApplyInvalid
return
}
if info.Status != model.RealnameApplyStatePassed.DBStatus() {
return
}
if err = s.dao.UpdateRealnameInfo(ctx, mid, model.RealnameApplyStateRejective.DBStatus(), "管理后台解绑"); err != nil {
return
}
switch info.Channel {
case model.ChannelMain.DBChannel():
if err = s.dao.RejectRealnameMainApply(ctx, mid, adminName, adminID, "管理后台解绑"); err != nil {
return
}
case model.ChannelAlipay.DBChannel():
if err = s.dao.RejectRealnameAlipayApply(ctx, mid, adminName, adminID, "管理后台解绑"); err != nil {
return
}
default:
log.Warn("Failed to reject realname apply: unrecognized channel: %+v", info)
}
go func() {
r := &report.ManagerInfo{
Uname: adminName,
UID: adminID,
Business: model.RealnameManagerLogID,
Type: 0,
Oid: mid,
Action: model.LogActionRealnameUnbind,
Ctime: time.Now(),
}
if err = report.Manager(r); err != nil {
log.Error("Send manager log failed : %+v , report : %+v", err, r)
err = nil
return
}
log.Info("Send manager log success report : %+v", r)
}()
return
}
// RealnameImage return img
func (s *Service) RealnameImage(ctx context.Context, token string) ([]byte, error) {
filePath := fmt.Sprintf("%s/%s.txt", conf.Conf.Realname.DataDir, token)
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
log.Info("file : %s , not found", filePath)
return nil, ecode.RequestErr
}
file, err := os.Open(filePath)
if err != nil {
return nil, errors.WithStack(err)
}
defer file.Close()
img, err := ioutil.ReadAll(file)
if err != nil {
return nil, errors.WithStack(err)
}
return s.mainCryptor.IMGDecrypt(img)
}
// FetchRealnameImage is
func (s *Service) FetchRealnameImage(ctx context.Context, token string) ([]byte, error) {
img, err := s.dao.GetRealnameImageCache(ctx, asIMGData(token))
if err == nil && len(img) > 0 {
return img, nil
}
if err != nil {
log.Warn("Failed to get realname image from cache: %s: %+v", token, err)
}
img, err = s.RealnameImage(ctx, token)
if err != nil {
return nil, err
}
if len(img) <= _512KiloBytes {
return img, nil
}
striped, err := StripImage(img)
if err != nil {
log.Warn("Failed to strip image: %+v", err)
return img, nil
}
return striped, nil
}
// RealnameImagePreview return preview img
func (s *Service) RealnameImagePreview(ctx context.Context, token string, borderSize uint) (data []byte, err error) {
var (
src []byte
)
if src, err = s.RealnameImage(ctx, token); err != nil {
return
}
if len(src) == 0 {
return
}
var (
img image.Image
imgWidth, imgHeight int
imgFormat string
sr = bytes.NewReader(src)
)
if img, imgFormat, err = image.Decode(sr); err != nil {
log.Warn("Failed to decode image: %+v, return origin image data directly", err)
return src, nil
}
imgWidth, imgHeight = img.Bounds().Dx(), img.Bounds().Dy()
log.Info("Decode img : %s , format : %s , width : %d , height : %d ", token, imgFormat, imgWidth, imgHeight)
if imgFormat != "png" && imgFormat != "jpg" && imgFormat != "jpeg" {
return
}
if imgWidth > imgHeight {
img = resize.Resize(borderSize, 0, img, resize.Lanczos3)
} else {
img = resize.Resize(0, borderSize, img, resize.Lanczos3)
}
var (
bb bytes.Buffer
bw = bufio.NewWriter(&bb)
)
switch imgFormat {
case "jpg", "jpeg":
if err = jpeg.Encode(bw, img, nil); err != nil {
err = errors.WithStack(err)
return
}
case "png":
if err = png.Encode(bw, img); err != nil {
err = errors.WithStack(err)
return
}
}
data = bb.Bytes()
return
}
// RealnameExcel export user realname info
func (s *Service) RealnameExcel(ctx context.Context, mids []int64) ([]*model.RealnameExport, error) {
infos, err := s.dao.BatchRealnameInfo(ctx, mids)
if err != nil {
log.Warn("Failed to get realname info with mids: %+v: %+v", mids, err)
// keep an empty infos
infos = make(map[int64]*model.DBRealnameInfo)
}
pinfos, err := s.dao.PassportQueryByMidsChunked(ctx, mids, 100)
if err != nil {
log.Warn("Failed to get passport query by mids: %+v: %+v", mids, err)
// keep an empty infos
pinfos = make(map[int64]*model.PassportQueryByMidResult)
}
res := make([]*model.RealnameExport, 0, len(mids))
for _, mid := range mids {
export := &model.RealnameExport{
Mid: mid,
}
// passport
func() {
p, ok := pinfos[mid]
if !ok {
log.Warn("Failed to get passport info with mid: %d", mid)
return
}
export.UserID = p.Userid
export.Uname = p.Name
export.Tel = p.Tel
}()
// realname
func() {
info, ok := infos[mid]
if !ok {
log.Warn("Failed to get realname info with mid: %d", mid)
return
}
export.Realname = info.Realname
export.CardType = info.CardType
cardDecode, err := model.CardDecrypt(info.Card)
if err != nil {
log.Error("Failed to decrypt card: %s: %+v", info.Card, err)
return
}
export.CardNum = cardDecode
}()
res = append(res, export)
}
return res, nil
}
// RealnameSubmit is
func (s *Service) RealnameSubmit(ctx context.Context, arg *model.ArgRealnameSubmit) error {
encryptedCardNum, err := s.realnameCrypto.CardEncrypt([]byte(arg.CardNum))
if err != nil {
return err
}
_ = func() error {
front := &model.DBRealnameApplyIMG{IMGData: asIMGData(arg.FrontImageToken)}
if err := s.dao.AddRealnameIMG(ctx, front); err != nil {
return err
}
back := &model.DBRealnameApplyIMG{IMGData: asIMGData(arg.BackImageToken)}
if err := s.dao.AddRealnameIMG(ctx, back); err != nil {
return err
}
apply := &model.DBRealnameApply{
MID: arg.Mid,
Realname: arg.Realname,
Country: arg.Country,
CardType: arg.CardType,
CardNum: string(encryptedCardNum),
CardMD5: cardMD5(arg.CardNum, int(arg.CardType), int(arg.Country)),
FrontIMG: front.ID,
BackIMG: back.ID,
Status: model.RealnameApplyStatePassed.DBStatus(),
Operator: arg.Operator,
OperatorID: arg.OperatorID,
OperatorTime: time.Now(),
}
if arg.HandImageToken != "" {
hand := &model.DBRealnameApplyIMG{IMGData: asIMGData(arg.HandImageToken)}
if err := s.dao.AddRealnameIMG(ctx, hand); err != nil {
return err
}
apply.HandIMG = hand.ID
}
if err := s.dao.AddRealnameApply(ctx, apply); err != nil {
return err
}
info := &model.DBRealnameInfo{
MID: apply.MID,
Channel: model.ChannelMain.DBChannel(),
Realname: apply.Realname,
Country: apply.Country,
CardType: apply.CardType,
Card: apply.CardNum,
CardMD5: apply.CardMD5,
Status: model.RealnameApplyStatePassed.DBStatus(),
Reason: fmt.Sprintf("管理后台提交:%s", arg.Remark),
}
return s.dao.SubmitRealnameInfo(ctx, info)
}
toOld := func() error {
front := &model.DeDeIdentificationCardApplyImg{IMGData: asIMGData(arg.FrontImageToken)}
if err := s.dao.AddOldRealnameIMG(ctx, front); err != nil {
return err
}
back := &model.DeDeIdentificationCardApplyImg{IMGData: asIMGData(arg.BackImageToken)}
if err := s.dao.AddOldRealnameIMG(ctx, back); err != nil {
return err
}
apply := &model.DeDeIdentificationCardApply{
MID: arg.Mid,
Realname: arg.Realname,
Type: arg.CardType,
CardData: string(encryptedCardNum),
CardForSearch: cardMD5(arg.CardNum, int(arg.CardType), int(arg.Country)),
FrontImg: front.ID,
BackImg: back.ID,
ApplyTime: int32(time.Now().Unix()),
Operator: arg.Operator,
OperatorTime: int32(time.Now().Unix()),
Status: int8(model.RealnameApplyStatePassed.DBStatus()),
Remark: fmt.Sprintf("管理后台提交:%s", arg.Remark),
}
if arg.HandImageToken != "" {
hand := &model.DeDeIdentificationCardApplyImg{IMGData: asIMGData(arg.HandImageToken)}
if err := s.dao.AddOldRealnameIMG(ctx, hand); err != nil {
return err
}
apply.FrontImg2 = hand.ID
}
if err := s.dao.AddOldRealnameApply(ctx, apply); err != nil {
return err
}
info := &model.DBRealnameInfo{
MID: apply.MID,
Channel: model.ChannelMain.DBChannel(),
Realname: apply.Realname,
Country: arg.Country,
CardType: arg.CardType,
Card: apply.CardData,
CardMD5: apply.CardForSearch,
Status: model.RealnameApplyStatePassed.DBStatus(),
Reason: fmt.Sprintf("管理后台提交:%s", arg.Remark),
}
return s.dao.SubmitRealnameInfo(ctx, info)
}
if err := toOld(); err != nil {
return err
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.RealnameManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: model.LogActionRealnameSubmit,
Ctime: time.Now(),
})
return nil
}
// RealnameFileUpload is
func (s *Service) RealnameFileUpload(ctx context.Context, mid int64, data []byte) (src string, err error) {
var (
md5Engine = md5.New()
hashMID string
hashRand string
fileName string
dirPath string
dateStr string
)
md5Engine.Write([]byte(strconv.FormatInt(mid, 10)))
hashMID = hex.EncodeToString(md5Engine.Sum(nil))
md5Engine.Reset()
md5Engine.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10)))
md5Engine.Write([]byte(strconv.FormatInt(mrand.Int63n(1000000), 10)))
hashRand = hex.EncodeToString(md5Engine.Sum(nil))
fileName = fmt.Sprintf("%s_%s.txt", hashMID[:6], hashRand)
dateStr = time.Now().Format("20060102")
dirPath = fmt.Sprintf("%s/%s/", s.c.Realname.DataDir, dateStr)
var (
dataFile *os.File
writeFileSize int
encrptedData []byte
)
_, err = os.Stat(dirPath)
if os.IsNotExist(err) {
mask := syscall.Umask(0)
defer syscall.Umask(mask)
if err = os.MkdirAll(dirPath, 0777); err != nil {
err = errors.WithStack(err)
return
}
}
if encrptedData, err = s.mainCryptor.IMGEncrypt(data); err != nil {
err = errors.WithStack(err)
return
}
if dataFile, err = os.Create(dirPath + fileName); err != nil {
err = errors.Wrapf(err, "create file %s failed", dirPath+fileName)
return
}
defer dataFile.Close()
if writeFileSize, err = dataFile.Write(encrptedData); err != nil {
err = errors.Wrapf(err, "write file %s size %d failed", dirPath+fileName, len(encrptedData))
return
}
if writeFileSize != len(encrptedData) {
err = errors.Errorf("Write file data to %s , expected %d actual %d", dirPath+fileName, len(encrptedData), writeFileSize)
return
}
src = fmt.Sprintf("%s/%s", dateStr, strings.TrimSuffix(fileName, ".txt"))
return
}
func asIMGData(imgToken string) string {
return model.RealnameImgPrefix + imgToken + model.RealnameImgSuffix
}
func asIMGToken(IMGData string) string {
token := strings.TrimPrefix(IMGData, "/idenfiles/")
token = strings.TrimSuffix(token, ".txt")
return token
}
// StripImage is
func StripImage(raw []byte) ([]byte, error) {
i, format, err := image.Decode(bytes.NewReader(raw))
if err != nil {
return nil, errors.WithStack(err)
}
out := &bytes.Buffer{}
switch format {
case "jpg", "jpeg":
if err := jpeg.Encode(out, i, &jpeg.Options{Quality: jpeg.DefaultQuality}); err != nil {
return nil, errors.WithStack(err)
}
default:
return nil, errors.Errorf("Unsupported type: %s", format)
}
return out.Bytes(), nil
}

View File

@@ -0,0 +1,242 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/member/model"
"go-common/app/admin/main/member/model/block"
relation "go-common/app/service/main/relation/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
xtime "go-common/library/time"
)
const (
_logActionUserPropertyAudit = "user_property_review_audit"
)
// Reviews is.
func (s *Service) Reviews(ctx context.Context, arg *model.ArgReviewList) ([]*model.UserPropertyReview, int, error) {
bySearch := func() ([]*model.UserPropertyReview, int, error) {
stime := arg.STime.Time().Format("2006-01-02 15:04:05")
if arg.ETime == 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
etime := arg.ETime.Time().Format("2006-01-02 15:04:05")
property := int8ToInt(arg.Property)
state := int8ToInt(arg.State)
result, err := s.dao.SearchUserPropertyReview(ctx, arg.Mid, property, state, arg.IsMonitor, arg.IsDesc, arg.Operator, stime, etime, arg.Pn, arg.Ps)
if err != nil {
return nil, 0, err
}
ids := result.IDs()
rws, err := s.dao.ReviewByIDs(ctx, ids, arg.State)
if err != nil {
return nil, 0, err
}
rws = arrange(rws, ids)
return rws, result.Total(), nil
}
byDB := func() ([]*model.UserPropertyReview, int, error) {
return s.dao.Reviews(ctx, arg.Mid, arg.Property, arg.State, arg.IsMonitor, arg.IsDesc, arg.Operator, arg.STime, arg.ETime, arg.Pn, arg.Ps)
}
rws, total, err := bySearch()
if arg.ForceDB {
log.Info("Force user property review query to db")
rws, total, err = byDB()
}
if err != nil {
return nil, 0, err
}
for _, rw := range rws {
if rw.Property == model.ReviewPropertyFace {
rw.BuildFaceURL()
}
}
s.reviewsName(ctx, rws)
s.reviewsFaceReject(ctx, rws)
s.reviewsRelationStat(ctx, rws)
return rws, total, err
}
func (s *Service) onReviewSuccess(ctx context.Context, waitRws []*model.UserPropertyReview, arg *model.ArgReviewAudit) error {
if !arg.BlockUser {
return nil
}
blockArg := &block.ParamBatchBlock{
MIDs: waitRwMids(waitRws),
AdminName: arg.Operator,
AdminID: arg.OperatorID,
Source: arg.Source,
Area: arg.Area,
Reason: arg.Reason,
Comment: arg.Comment,
Action: arg.Action,
Duration: arg.Duration,
Notify: arg.Notify,
}
if !blockArg.Validate() {
log.Error("Failed to validate block parama, arg: %v", blockArg)
return ecode.RequestErr
}
if err := s.block.BatchBlock(ctx, blockArg); err != nil {
log.Error("Failed to batch block, error: %v, arg: %v", err, blockArg)
return err
}
return nil
}
// ReviewAudit is.
func (s *Service) ReviewAudit(ctx context.Context, arg *model.ArgReviewAudit) error {
waitRws, err := s.dao.ReviewByIDs(ctx, arg.ID, []int8{model.ReviewStateWait})
if err != nil {
return err
}
if err := s.dao.ReviewAudit(ctx, arg.ID, arg.State, arg.Remark, arg.Operator); err != nil {
return err
}
for _, r := range waitRws {
ak := auditKey(r.Property, r.IsMonitor)
handler, ok := s.auditHandlers[ak]
if !ok {
log.Warn("Unable to handle property update: review: %+v audit: %+v", r, arg)
continue
}
if err := handler(ctx, r, arg); err != nil {
log.Error("Failed to trigger review audit event: review: %+v error: %+v", r, err)
remark := fmt.Sprintf("操作异常:%s, 备注: %s", ecode.Cause(err).Message(), arg.Remark)
if err = s.dao.UpdateRemark(ctx, r.ID, remark); err != nil {
log.Error("Failed to update remark error: %v", err)
}
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: r.Mid,
Action: _logActionUserPropertyAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{r.ID, arg.State, 0, arg.Remark, "", ""},
Content: map[string]interface{}{
"remark": arg.Remark,
"state": arg.State,
"id": r.ID,
"mid": r.Mid,
},
})
}
s.onReviewSuccess(ctx, waitRws, arg)
return nil
}
// Review is.
func (s *Service) Review(ctx context.Context, arg *model.ArgReview) (*model.UserPropertyReview, error) {
r, err := s.dao.Review(ctx, arg.ID)
if err != nil {
return nil, err
}
r.Block, err = s.block.History(ctx, r.Mid, 2, 1, true)
if err != nil {
log.Error("Failed to get block review info, error: %v, mid: %v", err, r.Mid)
err = nil
}
return r, nil
}
func (s *Service) reviewsName(ctx context.Context, rws []*model.UserPropertyReview) {
mids := make([]int64, 0, len(rws))
for _, rw := range rws {
mids = append(mids, rw.Mid)
}
bs, err := s.dao.Bases(ctx, mids)
if err != nil {
log.Error("Failed to fetch bases with mids: %+v: %+v", mids, err)
return
}
for _, rw := range rws {
b, ok := bs[rw.Mid]
if !ok {
continue
}
rw.Name = b.Name
}
}
func (s *Service) reviewsFaceReject(ctx context.Context, rws []*model.UserPropertyReview) {
mids := make([]int64, 0, len(rws))
for _, rw := range rws {
mids = append(mids, rw.Mid)
}
frs, err := s.dao.BatchUserAddit(ctx, mids)
if err != nil {
log.Error("Failed to fetch FaceRejects with mids: %+v: %+v", mids, err)
return
}
for _, rw := range rws {
if fr, ok := frs[rw.Mid]; ok {
rw.FaceReject = fr.FaceReject
}
}
}
func (s *Service) reviewsRelationStat(ctx context.Context, rws []*model.UserPropertyReview) {
mids := make([]int64, 0, len(rws))
for _, rw := range rws {
mids = append(mids, rw.Mid)
}
stats, err := s.relationRPC.Stats(ctx, &relation.ArgMids{
Mids: mids,
RealIP: metadata.String(ctx, metadata.RemoteIP),
})
if err != nil {
log.Error("Failed to fetch relation stat with mids: %+v: %+v", mids, err)
return
}
for _, rw := range rws {
stat, ok := stats[rw.Mid]
if !ok {
continue
}
rw.Follower = stat.Follower
}
}
func int8ToInt(in []int8) []int {
res := []int{}
for _, i := range in {
res = append(res, int(i))
}
return res
}
func arrange(rws []*model.UserPropertyReview, ids []int64) []*model.UserPropertyReview {
res := []*model.UserPropertyReview{}
tmp := make(map[int64]*model.UserPropertyReview, len(ids))
for _, rw := range rws {
tmp[rw.ID] = rw
}
for _, id := range ids {
if rw, ok := tmp[id]; ok {
res = append(res, rw)
}
}
return res
}
func waitRwMids(waitRws []*model.UserPropertyReview) []int64 {
mids := make([]int64, 0, len(waitRws))
for _, w := range waitRws {
mids = append(mids, w.Mid)
}
return mids
}

View File

@@ -0,0 +1,151 @@
package service
import (
"context"
"fmt"
"math/rand"
"net/http"
"net/url"
"path"
"go-common/app/admin/main/member/model"
comodel "go-common/app/service/main/coin/model"
"go-common/library/ecode"
"go-common/library/log"
)
var upNameCostCoins = 6.0
type auditHandler func(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error
func (s *Service) initAuditHandler() {
s.auditHandlers[auditKey(model.ReviewPropertySign, true)] = s.onSignAudit
s.auditHandlers[auditKey(model.ReviewPropertyName, true)] = s.onNameAudit
s.auditHandlers[auditKey(model.ReviewPropertyFace, true)] = s.onFaceMonitorAudit
s.auditHandlers[auditKey(model.ReviewPropertyFace, false)] = s.onFaceAudit
}
func auditKey(property int8, isMonitor bool) string {
return fmt.Sprintf("%d-%t", property, isMonitor)
}
func (s *Service) onSignAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStatePass:
return s.dao.UpSign(ctx, origin.Mid, origin.New)
case model.ReviewStateNoPass:
if err := s.dao.Message(ctx, "违规签名处理通知", "抱歉,由于你的签名涉嫌违规,系统已将你的签名回退。如有疑问请联系客服。", []int64{origin.Mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", origin.Mid, err)
}
return nil
}
return nil
}
func (s *Service) onNameAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStatePass:
if origin.NickFree() {
return s.dao.UpdateUname(ctx, origin.Mid, origin.New)
}
coins, err := s.coinRPC.UserCoins(ctx, &comodel.ArgCoinInfo{Mid: origin.Mid})
if err != nil {
return err
}
if coins < upNameCostCoins {
return ecode.UpdateUnameMoneyIsNot
}
if err := s.dao.UpdateUname(ctx, origin.Mid, origin.New); err != nil {
log.Error("faild to update Name, mid: %d, name: %s, %+v", origin.Mid, origin.New, err)
return err
}
arg := &comodel.ArgModifyCoin{
Mid: origin.Mid,
Count: -upNameCostCoins,
Reason: fmt.Sprintf("UPDATE:NICK:%s=>%s", origin.Old, origin.New),
}
if _, err := s.coinRPC.ModifyCoin(ctx, arg); err != nil {
log.Error("faild to modify coin, arg: %+v, %+v", arg, err)
return err
}
case model.ReviewStateNoPass:
if err := s.dao.Message(ctx, "违规昵称处理通知", "抱歉,由于你的昵称涉嫌违规,系统已将你的昵称回退。如有疑问请联系客服。", []int64{origin.Mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", origin.Mid, err)
}
return nil
}
return nil
}
func (s *Service) onFaceAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStateNoPass:
if err := s.dao.UpFace(ctx, origin.Mid, ""); err != nil {
return err
}
return s.faceReject(ctx, origin, arg)
}
return nil
}
func (s *Service) onFaceMonitorAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStatePass:
return s.dao.UpFace(ctx, origin.Mid, origin.New)
case model.ReviewStateNoPass:
return s.faceReject(ctx, origin, arg)
}
return nil
}
func (s *Service) mvToPrivate(ctx context.Context, face string) (string, error) {
file, err := s.dao.Image(buildURL(face))
if err != nil {
return "", err
}
ftype := http.DetectContentType(file)
privURL, err := s.dao.UploadImage(ctx, ftype, file, s.c.FacePriBFS)
if err != nil {
return "", err
}
if err := s.dao.DelImage(ctx, path.Base(privURL), s.c.FaceBFS); err != nil {
log.Error("s.dao.DelImage(%v) error(%+v)", privURL, err)
}
return urlPath(privURL), nil
}
func buildURL(path string) string {
return fmt.Sprintf("http://i%d.hdslb.com%s", rand.Int63n(3), path)
}
func urlPath(in string) string {
URL, err := url.Parse(in)
if err != nil {
return ""
}
return URL.Path
}
func (s *Service) faceReject(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
privFace, err := s.mvToPrivate(ctx, origin.New)
if err != nil {
log.Error("s.mvToPrivate(%d) error(%+v)", origin.Mid, err)
return err
}
if err := s.dao.UpdateReviewFace(ctx, origin.ID, privFace); err != nil {
log.Error("Failed to update review face: id: %d face: %s: %+v", origin.ID, privFace, err)
return err
}
if err := s.dao.MvArchivedFaceToPriv(ctx, origin.New, privFace, arg.Operator, arg.Remark); err != nil {
log.Error("mv archived face to private bucket mid(%d) error(%v)", origin.Mid, err)
return err
}
if err := s.dao.Message(ctx, "违规头像处理通知", "抱歉,由于你的头像涉嫌违规,系统已将你的头像回退。如有疑问请联系客服。", []int64{origin.Mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", origin.Mid, err)
}
if err := s.dao.IncrFaceReject(ctx, origin.Mid); err != nil {
log.Error("IncrFaceReject faild: mid: %d: %+v", origin.Mid, err)
}
return nil
}

View File

@@ -0,0 +1,19 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/member/model"
"github.com/smartystreets/goconvey/convey"
)
func TestService_Reviews(t *testing.T) {
convey.Convey("Reviews", t, func() {
o, logs, err := s.Reviews(context.Background(), &model.ArgReviewList{Property: []int8{1}, IsDesc: true, STime: 10000, Pn: 1, Ps: 10})
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(logs, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,405 @@
package service
import (
"context"
"encoding/json"
"fmt"
"path"
"time"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/dao"
"go-common/app/admin/main/member/model"
"go-common/app/admin/main/member/service/block"
acccrypto "go-common/app/interface/main/account/service/realname/crypto"
account "go-common/app/service/main/account/api"
coinrpc "go-common/app/service/main/coin/api/gorpc"
rpcfigure "go-common/app/service/main/figure/rpc/client"
memberrpc "go-common/app/service/main/member/api/gorpc"
"go-common/app/service/main/member/service/crypto"
rpcrelation "go-common/app/service/main/relation/rpc/client"
rpcspy "go-common/app/service/main/spy/rpc/client"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/stat/prom"
xtime "go-common/library/time"
"github.com/robfig/cron"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
block *block.Service
auditHandlers map[string]auditHandler
coinRPC *coinrpc.Service
memberRPC *memberrpc.Service
spyRPC *rpcspy.Service
figureRPC *rpcfigure.Service
accountClient account.AccountClient
cron *cron.Cron
relationRPC *rpcrelation.Service
realnameCrypto *crypto.Realname
mainCryptor *acccrypto.Main
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
coinRPC: coinrpc.New(c.RPCClient.Coin),
memberRPC: memberrpc.New(c.RPCClient.Member),
figureRPC: rpcfigure.New(c.RPCClient.Figure),
spyRPC: rpcspy.New(c.RPCClient.Spy),
relationRPC: rpcrelation.New(c.RPCClient.Relation),
auditHandlers: make(map[string]auditHandler),
cron: cron.New(),
realnameCrypto: crypto.NewRealname(string(c.Realname.RsaPub), string(c.Realname.RsaPriv)),
mainCryptor: acccrypto.NewMain(string(c.Realname.RsaPub), string(c.Realname.RsaPriv)),
}
var err error
if s.accountClient, err = account.NewClient(c.RPCClient.Account); err != nil {
panic(err)
}
s.block = block.New(c, s.dao.BlockImpl(), s.spyRPC, s.figureRPC, s.accountClient, databus.New(c.AccountNotify))
s.initAuditHandler()
s.initCron()
s.cron.Start()
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
s.block.Close()
}
func (s *Service) initCron() {
s.cron.AddFunc("0 */5 * * * *", func() { s.notifyAudit(context.Background()) }) // 用于发送审核数据给目标用户
s.cron.AddFunc("0 */5 * * * *", func() { s.promAuditTotal(context.Background()) }) // 用于上报审核数据给promethues
s.cron.AddFunc("0 */1 * * * *", func() { s.cacheRecentRealnameImage(context.Background()) }) // 用于缓存实名认证的图片数据
s.cron.AddFunc("0 */2 * * * *", func() { s.faceCheckproc(context.Background(), -10*time.Minute, "two minute") }) // 用于AI头像审核首次每隔2分钟审核10分钟内的头像
s.cron.AddFunc("0 */60 * * * *", func() { s.faceCheckproc(context.Background(), -6*time.Hour, "per hour") }) // 用于AI头像审核重新审核每小时重新审核一下6小时内的头像
s.cron.AddFunc("0 */5 * * * *", func() { s.faceAutoPassproc(context.Background()) }) // 头像自动审核每隔5分钟检查一次超过48小时未处理的头像并自动通过
}
// notifyAudit
func (s *Service) notifyAudit(ctx context.Context) {
now := time.Now()
log.Info("start notify audit at: %+v", now)
locked, err := s.dao.TryLockReviewNotify(ctx, now)
if err != nil {
log.Error("Failed to lock review notify at: %+v: %+v", now, err)
return
}
if !locked {
log.Warn("Already locked by other instance at: %+v", now)
return
}
stime := now.Add(-time.Hour * 24 * 7) // 只计算 7 天内的数据
// 绝对锁上了
faceNotify := func() error {
total, firstAt, err := s.faceAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch face audit notify content: %+v", err)
return err
}
log.Info("faceAuditNotifyContent success: total(%v),firstAt(%v)", total, firstAt)
title := fmt.Sprintf("头像审核提醒;消息时间:%s", now.Format("2006-01-02 15:04:05"))
firstAtStr := "null"
if firstAt != nil {
firstAtStr = firstAt.Format("2006-01-02 15:04:05")
}
content := fmt.Sprintf(
"头像审核提醒;消息时间:%s\n头像审核积压%d 条;最早进审时间:%s",
now.Format("2006-01-02 15:04:05"),
total,
firstAtStr,
)
return s.dao.MerakNotify(ctx, title, content)
}
if err := faceNotify(); err != nil {
log.Error("Failed to notify face review stat: %+v", err)
}
monitorNotify := func() error {
total, firstAt, err := s.monitorAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch monitor audit notify content: %+v", err)
return err
}
log.Info("monitorAuditNotifyContent success: total(%v),firstAt(%v)", total, firstAt)
title := fmt.Sprintf("用户信息监控提醒;消息时间:%s", now.Format("2006-01-02 15:04:05"))
firstAtStr := "null"
if firstAt != nil {
firstAtStr = firstAt.Format("2006-01-02 15:04:05")
}
content := fmt.Sprintf(
"用户信息监控提醒;消息时间:%s\n用户信息监控积压%d 条;最早进审时间:%s",
now.Format("2006-01-02 15:04:05"),
total,
firstAtStr,
)
return s.dao.MerakNotify(ctx, title, content)
}
if err := monitorNotify(); err != nil {
log.Error("Failed to notify monitor review stat: %+v", err)
}
// 实名认证待审核通知
realnameNotify := func() error {
total, firstAt, err := s.realnameAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch realname audit notify content: %+v", err)
return err
}
log.Info("realnameAuditNotifyContent success: total(%v),firstAt(%v)", total, firstAt)
title := fmt.Sprintf("实名认证审核提醒;消息时间:%s", now.Format("2006-01-02 15:04:05"))
firstAtStr := "null"
if firstAt != nil {
firstAtStr = firstAt.Format("2006-01-02 15:04:05")
}
content := fmt.Sprintf(
"实名认证审核提醒;消息时间:%s\n实名认证审核积压%d 条;最早进审时间:%s",
now.Format("2006-01-02 15:04:05"),
total,
firstAtStr,
)
return s.dao.MerakNotify(ctx, title, content)
}
if err := realnameNotify(); err != nil {
log.Error("Failed to notify realname list stat: %+v", err)
}
log.Info("end notify audit at: %+v", now)
}
// promAuditTotal
func (s *Service) promAuditTotal(ctx context.Context) {
stime := time.Now().Add(-time.Hour * 24 * 7) // 只计算 7 天内的数据
log.Info("promAuditTotal start %+v", time.Now())
faceAudit := func() {
faceTotal, _, err := s.faceAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch face audit notify content: %+v", err)
return
}
prom.BusinessInfoCount.State("faceAudit-needAudit", int64(faceTotal))
}
monitorAudit := func() {
monitorTotal, _, err := s.monitorAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch monitor audit notify content: %+v", err)
return
}
prom.BusinessInfoCount.State("monitorAudit-needAudit", int64(monitorTotal))
}
realnameAudit := func() {
realnameTotal, _, err := s.realnameAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch realname audit notify content: %+v", err)
return
}
prom.BusinessInfoCount.State("realnameAudit-needAudit", int64(realnameTotal))
}
faceAudit()
monitorAudit()
realnameAudit()
log.Info("promAuditTotal end %+v", time.Now())
}
func (s *Service) faceAuditNotifyContent(ctx context.Context, stime time.Time) (int, *time.Time, error) {
arg := &model.ArgReviewList{
State: []int8{0},
IsMonitor: false,
Property: []int8{model.ReviewPropertyFace},
IsDesc: false,
Pn: 1,
Ps: 1,
STime: xtime.Time(stime.Unix()),
ForceDB: false,
}
reviews, total, err := s.Reviews(ctx, arg)
if err != nil {
return 0, nil, err
}
if len(reviews) <= 0 {
return 0, nil, nil
}
firstAt := reviews[0].CTime.Time()
return total, &firstAt, nil
}
func (s *Service) monitorAuditNotifyContent(ctx context.Context, stime time.Time) (int, *time.Time, error) {
arg := &model.ArgReviewList{
State: []int8{0},
IsMonitor: true,
IsDesc: false,
Pn: 1,
Ps: 1,
STime: xtime.Time(stime.Unix()),
ForceDB: false,
}
reviews, total, err := s.Reviews(ctx, arg)
if err != nil {
return 0, nil, err
}
if len(reviews) <= 0 {
return 0, nil, nil
}
firstAt := reviews[0].CTime.Time()
return total, &firstAt, nil
}
func (s *Service) realnameAuditNotifyContent(ctx context.Context, stime time.Time) (int, *time.Time, error) {
arg := &model.ArgRealnameList{
Channel: "main", //main : 主站 alipay : 支付宝
TSFrom: stime.Unix(),
State: model.RealnameApplyStatePending,
IsDesc: false,
PN: 1,
PS: 1,
}
mainList, total, err := s.realnameMainList(ctx, arg)
if err != nil {
return 0, nil, err
}
if len(mainList) <= 0 {
return 0, nil, nil
}
firstAt := time.Unix(mainList[0].CreateTS, 0)
return total, &firstAt, nil
}
func (s *Service) faceAutoPassproc(ctx context.Context) {
now := time.Now()
log.Info("faceAutoPassproc start %+v", now)
etime := now.AddDate(0, 0, -2)
if err := s.faceAutoPass(ctx, etime); err != nil {
log.Error("Failed to face auto pass, error: %+v", err)
}
}
func (s *Service) faceAutoPass(ctx context.Context, etime time.Time) error {
property := []int{model.ReviewPropertyFace}
state := []int{model.ReviewStateWait, model.ReviewStateQueuing}
result, err := s.dao.SearchUserPropertyReview(ctx, 0, property,
state, false, false, "", "", etime.Format("2006-01-02 15:04:05"), 1, 100)
if err != nil {
return err
}
ids := result.IDs()
if len(ids) == 0 {
log.Info("face auto pass empty result list, end time: %v", etime)
return nil
}
if err = s.dao.FaceAutoPass(ctx, ids, xtime.Time(etime.Unix())); err != nil {
return err
}
return nil
}
func (s *Service) faceCheckproc(ctx context.Context, duration time.Duration, tag string) {
now := time.Now()
stime := now.Add(duration).Unix()
etime := now.Unix()
log.Info("faceCheckproc:%v start %+v", tag, now)
if err := s.faceAuditAI(ctx, stime, etime); err != nil {
log.Error("Failed to check face, error: %+v", err)
}
}
func (s *Service) faceAuditAI(ctx context.Context, stime, etime int64) error {
rws, err := s.dao.QueuingFaceReviewsByTime(ctx, xtime.Time(stime), xtime.Time(etime))
if err != nil {
log.Warn("Failed to get recent user_property_review image: %+v", err)
return err
}
for _, rw := range rws {
fcr, err := s.faceCheckRes(ctx, path.Base(rw.New))
if err != nil {
log.Error("Failed to get face check res, rw: %+v, error: %+v", rw, err)
continue
}
state := int8(model.ReviewStateWait)
if fcr.Valid() {
state = model.ReviewStatePass
}
remark := fmt.Sprintf("AI: %s", fcr.String())
if err = s.dao.AuditQueuingFace(ctx, rw.ID, remark, state); err != nil {
log.Error("Failed to audit queuing face, rw: %+v, error: %+v", rw, err)
continue
}
log.Info("face check success, rw: %+v", rw)
}
log.Info("faceCheckproc end")
return nil
}
func (s *Service) faceCheckRes(ctx context.Context, fileName string) (*model.FaceCheckRes, error) {
res, err := s.dao.SearchFaceCheckRes(ctx, fileName)
if err != nil {
return nil, err
}
if len(res.Result) == 0 {
return nil, ecode.NothingFound
}
userLog := res.Result[0]
fcr, err := parseFaceCheckRes(userLog.Extra)
if err != nil {
log.Error("Failed to parse faceCheckRes, userLog: %+v error: %+v", userLog, err)
return nil, err
}
return fcr, nil
}
func parseFaceCheckRes(in string) (*model.FaceCheckRes, error) {
res := &model.FaceCheckRes{}
err := json.Unmarshal([]byte(in), res)
if err != nil {
return nil, err
}
return res, nil
}
// BlockImpl is
func (s *Service) BlockImpl() *block.Service {
return s.block
}
func (s *Service) cacheRecentRealnameImage(ctx context.Context) {
images, err := s.dao.RecentRealnameApplyImg(ctx, time.Minute*2)
if err != nil {
log.Warn("Failed to get recent realname apply image: %+v", err)
return
}
for _, image := range images {
data, _ := s.dao.GetRealnameImageCache(ctx, image.IMGData)
if len(data) > 0 {
log.Info("This image has already been cached: %s", image.IMGData)
continue
}
data, err := s.FetchRealnameImage(ctx, asIMGToken(image.IMGData))
if err != nil {
log.Warn("Failed to fetch realname image to cache: %s: %+v", image.IMGData, err)
continue
}
if err := s.dao.SetRealnameImageCache(ctx, image.IMGData, data); err != nil {
log.Warn("Failed to set realname image cache: %s: %+v", image.IMGData, err)
continue
}
log.Info("Succeeded to cache realname image: %s", image.IMGData)
}
}

View File

@@ -0,0 +1,40 @@
package service
import (
"context"
"flag"
"testing"
"time"
"go-common/app/admin/main/member/conf"
"github.com/smartystreets/goconvey/convey"
)
var s *Service
func init() {
flag.Parse()
flag.Set("conf", "../cmd/member-admin-test.toml")
if err := conf.Init(); err != nil {
panic(err)
}
s = New(conf.Conf)
}
func TestPing(t *testing.T) {
convey.Convey("Ping", t, func() {
err := s.Ping(context.Background())
convey.So(err, convey.ShouldBeNil)
})
}
func TestFaceCheck(t *testing.T) {
convey.Convey("faceCheck", t, func() {
etime := time.Now().Unix()
stime := time.Now().AddDate(0, 0, -2).Unix()
err := s.faceAuditAI(context.Background(), stime, etime)
convey.So(err, convey.ShouldBeNil)
})
}