go-common/app/admin/main/member/service/service.go
2019-04-22 18:49:16 +08:00

406 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}