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,74 @@
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/job/main/member/conf:go_default_library",
"//app/job/main/member/model:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"base.go",
"cache_delay.go",
"check.go",
"exp.go",
"fixer.go",
"member.go",
"moral.go",
"realname.go",
"service.go",
"subproc.go",
],
importpath = "go-common/app/job/main/member/service",
tags = ["automanaged"],
deps = [
"//app/interface/main/account/service/realname/crypto:go_default_library",
"//app/job/main/member/conf:go_default_library",
"//app/job/main/member/dao:go_default_library",
"//app/job/main/member/model:go_default_library",
"//app/job/main/member/model/queue:go_default_library",
"//app/job/main/member/service/block:go_default_library",
"//app/service/main/member/api/gorpc:go_default_library",
"//app/service/main/member/model:go_default_library",
"//app/service/main/share/model:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/ip:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/databusutil:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/golang.org/x/time/rate:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/member/service/block:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,52 @@
package service
import (
"context"
"go-common/library/log"
)
// BaseInfo get user's base info.
// func (s *Service) BaseInfo(c context.Context, mid int64) (info *model.BaseInfo, err error) {
// if mid <= 0 {
// log.Info("s.BaseInfo(%d) mid not valid number!", mid)
// return
// }
// var mc = true
// if info, err = s.dao.BaseInfoCache(c, mid); err != nil {
// mc = false
// err = nil // ignore error
// }
// if info != nil {
// if info.Mid == 0 {
// log.Info("s.BaseInfo(%d) mid not exist!", mid)
// }
// return
// }
// if info, err = s.dao.BaseInfo(c, mid); err != nil {
// log.Error("s.dao.BaseInfo(%d) error(%v)", mid, err)
// return
// }
// if info == nil {
// info = &model.BaseInfo{}
// log.Info("s.BaseInfo(%d) mid not exist!", mid)
// return
// }
// if mc {
// s.dao.SetBaseInfoCache(context.TODO(), mid, info)
// }
// log.Info("s.BaseInfo(%d) info(%+v)", mid, info)
// return
// }
func (s *Service) updateAccFace(c context.Context, mid int64) error {
base, err := s.dao.BaseInfo(c, mid)
if err != nil {
log.Error("updateAccFace s.dao.BaseInfoWithoutDomain(%d) error(%v)", mid, err)
return err
}
if base == nil {
return nil
}
return s.dao.UpdateAccFace(c, mid, base.Face)
}

View File

@@ -0,0 +1,57 @@
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/job/main/member/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"audit_log.go",
"blackhouse.go",
"limit.go",
"msg.go",
"service.go",
],
importpath = "go-common/app/job/main/member/service/block",
tags = ["automanaged"],
deps = [
"//app/job/main/member/conf:go_default_library",
"//app/job/main/member/dao/block:go_default_library",
"//app/job/main/member/model/block:go_default_library",
"//library/cache:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report: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/job/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,201 @@
package block
import (
"context"
"encoding/json"
"fmt"
"time"
model "go-common/app/job/main/member/model/block"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/queue/databus"
"github.com/pkg/errors"
)
func (s *Service) creditExpireHandler(c context.Context) {
if s.conf.BlockProperty.CreditExpireCheckLimit <= 0 {
log.Error("conf.Conf.Property.creditExpireCheckLimit [%d] <= 0", s.conf.BlockProperty.CreditExpireCheckLimit)
return
}
var (
mids = make([]int64, s.conf.BlockProperty.CreditExpireCheckLimit)
startID int64
err error
)
for len(mids) >= s.conf.BlockProperty.CreditExpireCheckLimit {
log.Info("black house expire handle startID (%d)", startID)
if startID, mids, err = s.dao.UserStatusList(c, model.BlockStatusCredit, startID, s.conf.BlockProperty.CreditExpireCheckLimit); err != nil {
log.Error("%+v", err)
return
}
for _, mid := range mids {
log.Info("Start handle black house mid (%d)", mid)
var ok bool
if ok, err = s.creditExpireCheck(c, mid); err != nil {
log.Error("%+v", err)
continue
}
if ok {
log.Info("Start remove black house mid (%d)", mid)
if err = s.creditExpireRemove(c, mid); err != nil {
log.Error("error: %+v, mid: %d", err, mid)
}
}
}
}
}
func (s *Service) creditExpireCheck(c context.Context, mid int64) (ok bool, err error) {
var (
his *model.DBHistory
ex *model.DBExtra
)
if his, err = s.dao.UserLastHistory(c, mid); err != nil {
return
}
if his == nil {
return
}
log.Info("Credit check his (%+v)", his)
if his.Action != model.BlockActionLimit {
return
}
if ex, err = s.dao.UserExtra(c, mid); err != nil {
return
}
if ex == nil {
return
}
log.Info("Credit check extra (%+v)", his)
if ex.ActionTime.Before(his.StartTime) {
return
}
if his.StartTime.Add(time.Duration(his.Duration) * time.Second).After(time.Now()) {
return
}
ok = true
return
}
func (s *Service) creditExpireRemove(c context.Context, mid int64) (err error) {
var (
db = &model.DBHistory{
MID: mid,
AdminID: -1,
AdminName: "sys",
Source: model.BlockSourceRemove,
Area: model.BlockAreaNone,
Reason: "小黑屋自动解封",
Comment: "小黑屋自动解封",
Action: model.BlockActionSelfRemove,
StartTime: time.Now(),
Duration: 0,
Notify: false,
}
tx *xsql.Tx
)
if tx, err = s.dao.BeginTX(c); err != nil {
return
}
if err = s.dao.TxInsertHistory(c, tx, db); err != nil {
tx.Rollback()
return
}
count, err := s.dao.TxUpsertUser(c, tx, mid, model.BlockStatusFalse)
if err != nil || count == 0 {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
err = errors.WithStack(err)
}
s.mission(func() {
if err := s.notifyRemoveMSG(context.TODO(), []int64{mid}); err != nil {
log.Error("%+v", err)
}
})
s.cache.Save(func() {
if err := s.dao.DeleteUserCache(context.TODO(), mid); err != nil {
log.Error("%+v", err)
}
if databusErr := s.dao.AccountNotify(context.TODO(), mid); databusErr != nil {
log.Error("%+v", databusErr)
}
})
return
}
func (s *Service) notifyRemoveMSG(c context.Context, mids []int64) (err error) {
code, title, content := s.MSGRemoveInfo()
if err = s.dao.SendSysMsg(c, code, mids, title, content, ""); err != nil {
return
}
return
}
// databus
func (s *Service) creditsubproc() {
defer func() {
if x := recover(); x != nil {
log.Error("%+v", errors.WithStack(fmt.Errorf("s.creditsubproc panic(%v)", x)))
go s.creditsubproc()
log.Info("s.creditsubproc recover")
}
}()
var (
msg *databus.Message
eventMSG *model.CreditAnswerMSG
err error
msgChan = s.creditSub.Messages()
c = context.TODO()
)
for msg = range msgChan {
if err = msg.Commit(); err != nil {
log.Error("msg.Commit error(%v)", err)
}
eventMSG = &model.CreditAnswerMSG{}
if err = json.Unmarshal([]byte(msg.Value), eventMSG); err != nil {
log.Error("%+v", errors.WithStack(err))
continue
}
if err = s.handleCreditAnswerMSG(c, eventMSG); err != nil {
log.Error("%+v", err)
continue
}
log.Info("s.handleCreditAnswerMSG(%v) msg", eventMSG)
}
log.Info("creditsubproc end")
}
func (s *Service) handleCreditAnswerMSG(c context.Context, msg *model.CreditAnswerMSG) (err error) {
if msg.MID <= 0 {
return
}
var (
extra = &model.DBExtra{
MID: msg.MID,
CreditAnswerFlag: true,
ActionTime: msg.MTime.Time(),
}
checkFlag bool
)
if err = s.dao.InsertExtra(c, extra); err != nil {
return
}
// 及时检查解封
log.Info("Start check black house mid (%d) from answer", extra.MID)
if checkFlag, err = s.creditExpireCheck(c, extra.MID); err != nil {
return
}
if checkFlag {
log.Info("Start remove black house mid (%d)", extra.MID)
if err = s.creditExpireRemove(c, extra.MID); err != nil {
log.Error("error: %+v, mid: %d", err, extra.MID)
return
}
}
return
}

View File

@@ -0,0 +1,118 @@
package block
import (
"context"
"time"
model "go-common/app/job/main/member/model/block"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
func (s *Service) limitExpireHandler(c context.Context) {
if s.conf.BlockProperty.LimitExpireCheckLimit <= 0 {
log.Error("conf.Conf.Property.LimitExpireCheckLimit [%d] <= 0", s.conf.BlockProperty.LimitExpireCheckLimit)
return
}
var (
mids = make([]int64, s.conf.BlockProperty.LimitExpireCheckLimit)
startID int64
err error
)
for len(mids) >= s.conf.BlockProperty.LimitExpireCheckLimit {
log.Info("limit expire handle startID (%d)", startID)
if startID, mids, err = s.dao.UserStatusList(c, model.BlockStatusLimit, startID, s.conf.BlockProperty.LimitExpireCheckLimit); err != nil {
log.Error("%+v", err)
return
}
for _, mid := range mids {
var ok bool
if ok, err = s.limitExpireCheck(c, mid); err != nil {
log.Error("%+v", err)
continue
}
if ok {
if err = s.limitExpireRemove(c, mid); err != nil {
log.Error("error: %+v, mid: %d", err, mid)
continue
}
}
time.Sleep(time.Millisecond * 100)
}
}
}
func (s *Service) limitExpireCheck(c context.Context, mid int64) (ok bool, err error) {
var (
his *model.DBHistory
)
if his, err = s.dao.UserLastHistory(c, mid); err != nil {
return
}
if his == nil {
return
}
if his.Action != model.BlockActionLimit {
return
}
if his.StartTime.Add(time.Duration(his.Duration) * time.Second).After(time.Now()) {
return
}
ok = true
return
}
func (s *Service) limitExpireRemove(c context.Context, mid int64) (err error) {
var (
_reason, _comment = "系统自动解封", "系统自动解封"
stime = time.Now()
db = &model.DBHistory{
MID: mid,
AdminID: model.BlockJOBManagerID,
AdminName: model.BlockJOBManagerName,
Source: model.BlockSourceRemove,
Area: model.BlockAreaNone,
Reason: _reason,
Comment: _comment,
Action: model.BlockActionSelfRemove,
StartTime: stime,
Duration: 0,
Notify: false,
}
tx *xsql.Tx
)
if tx, err = s.dao.BeginTX(c); err != nil {
return
}
if err = s.dao.TxInsertHistory(c, tx, db); err != nil {
tx.Rollback()
return
}
count, err := s.dao.TxUpsertUser(c, tx, mid, model.BlockStatusFalse)
if err != nil || count == 0 {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
err = errors.WithStack(err)
}
s.mission(func() {
if err := s.notifyRemoveMSG(context.TODO(), []int64{mid}); err != nil {
log.Error("%+v", err)
}
})
s.mission(func() {
s.AddAuditLog(context.TODO(), model.BlockActionSelfRemove, model.BlockJOBManagerID, model.BlockJOBManagerName, []int64{mid}, 0, model.BlockSourceRemove, model.BlockAreaNone, _reason, _comment, false, stime)
})
s.cache.Save(func() {
if err := s.dao.DeleteUserCache(context.TODO(), mid); err != nil {
log.Error("%+v", err)
}
if databusErr := s.dao.AccountNotify(context.TODO(), mid); databusErr != nil {
log.Error("%+v", databusErr)
}
})
return
}

View File

@@ -0,0 +1,9 @@
package block
// MSGRemoveInfo get msg info
func (s *Service) MSGRemoveInfo() (code string, title, content string) {
code = s.conf.BlockProperty.MSG.BlockRemove.Code
title = s.conf.BlockProperty.MSG.BlockRemove.Title
content = s.conf.BlockProperty.MSG.BlockRemove.Content
return
}

View File

@@ -0,0 +1,114 @@
package block
import (
"context"
"fmt"
"time"
"go-common/app/job/main/member/conf"
"go-common/app/job/main/member/dao/block"
"go-common/library/cache"
"go-common/library/log"
"go-common/library/queue/databus"
"github.com/pkg/errors"
)
// Service struct
type Service struct {
dao *block.Dao
conf *conf.Config
cache *cache.Cache
missch chan func()
creditSub *databus.Databus
}
// New init
func New(conf *conf.Config, dao *block.Dao, creditSub *databus.Databus) (s *Service) {
s = &Service{
dao: dao,
conf: conf,
cache: cache.New(1, 10240),
missch: make(chan func(), 10240),
creditSub: creditSub,
}
// 自动解禁检查
if s.conf.BlockProperty.Flag.ExpireCheck {
go s.limitcheckproc()
go s.creditcheckproc()
}
// 小黑屋答题状态订阅
if s.conf.BlockProperty.Flag.CreditSub {
go s.creditsubproc()
}
go s.missproc()
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()
}
func (s *Service) limitcheckproc() {
defer func() {
if x := recover(); x != nil {
log.Error("service.limitcheckproc panic(%v)", x)
go s.limitcheckproc()
log.Info("service.limitcheckproc recover")
}
}()
for {
log.Info("limit check start")
s.limitExpireHandler(context.TODO())
log.Info("limit check end")
time.Sleep(time.Duration(s.conf.BlockProperty.LimitExpireCheckTick))
}
}
func (s *Service) creditcheckproc() {
defer func() {
if x := recover(); x != nil {
log.Error("%+v", errors.WithStack(fmt.Errorf("service.creditcheckproc panic(%v)", x)))
go s.creditcheckproc()
log.Info("service.creditcheckproc recover")
}
}()
for {
log.Info("black house check start")
s.creditExpireHandler(context.TODO())
log.Info("black house check end")
time.Sleep(time.Duration(s.conf.BlockProperty.CreditExpireCheckTick))
}
}
func (s *Service) missproc() {
defer func() {
if x := recover(); x != nil {
log.Error("service.missproc panic(%v)", x)
go s.missproc()
log.Info("service.missproc recover")
}
}()
for {
f := <-s.missch
f()
}
}
func (s *Service) mission(f func()) {
select {
case s.missch <- f:
default:
log.Error("s.missch full")
}
}

View File

@@ -0,0 +1,34 @@
package block
import (
"context"
"flag"
"os"
"testing"
"go-common/app/job/main/member/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
c context.Context
)
func TestMain(m *testing.M) {
defer os.Exit(0)
flag.Set("conf", "../cmd/member-job-dev.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()
})
}

View File

@@ -0,0 +1,96 @@
package service
import (
"context"
"time"
"go-common/app/job/main/member/model/queue"
"go-common/library/log"
)
// Item is
type Item struct {
Mid int64
Time time.Time
Action string
}
// Compare is
func (i *Item) Compare(other queue.Item) int {
o := asItem(other)
if o == nil {
return -1
}
if i.Time.Equal(o.Time) {
return 0
}
if i.Time.After(o.Time) {
return 1
}
return -1
}
// HashCode is
func (i *Item) HashCode() int64 {
return i.Mid
}
func asItem(in queue.Item) *Item {
o, ok := in.(*Item)
if !ok {
return nil
}
return o
}
func asItems(in []queue.Item) []*Item {
out := make([]*Item, 0, len(in))
for _, i := range in {
item := asItem(i)
if item == nil {
continue
}
out = append(out, item)
}
return out
}
func (s *Service) cachedelayproc(ctx context.Context) {
fiveSeconds := time.Second * 5
t := time.NewTicker(fiveSeconds)
delayed := func(t time.Time) bool {
top := asItem(s.cachepq.Peek())
if top == nil {
log.Info("Empty cache queue top at: %v", t)
return false
}
if t.Sub(top.Time) < fiveSeconds {
log.Info("Top item is in five seconds, skip and waiting for next tick")
return false
}
return true
}
for ti := range t.C {
if !delayed(ti) {
continue
}
for {
qitems, err := s.cachepq.Get(1)
if err != nil {
log.Error("Failed to get queue items from cache queue: %+v", err)
return
}
items := asItems(qitems)
for _, it := range items {
log.Info("Notify purge cache in delay queue with mid: %d", it.Mid)
s.dao.NotifyPurgeCache(ctx, it.Mid, it.Action)
}
if s.cachepq.Empty() || !delayed(time.Now()) {
break
}
}
}
}

View File

@@ -0,0 +1,89 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/job/main/member/model"
"go-common/library/log"
)
func (s *Service) checkExpAdd(c context.Context, mid int64, event string, now time.Time) (exp *model.NewExp, eo *model.ExpOper, added, ok bool, err error) {
if eo, ok = model.ExpFlagOper[event]; !ok {
log.Info("s.checkExpAdd(%d) oper(%s) not found", mid, event)
return
}
var base *model.BaseInfo
if base, err = s.dao.BaseInfo(c, mid); err != nil {
log.Error("s.dao.BaseInfo(%d) error(%v)", mid, err)
return
}
if base == nil {
err = fmt.Errorf("No base info with mid(%v)", mid)
log.Error("Failed to checkExpAdd with mid(%d) error: %+v", mid, err)
return
}
if ok = !(base.Rank < 10000); !ok {
log.Info("s.checkExpAdd(%d) mid.Rank<10000", mid)
return
}
if added, err = s.dao.ExpAdded(c, mid, now.Day(), eo.Oper); err != nil || added {
log.Info("s.dao.ExpAdded(%d) error(%v) added(%v)", mid, err, added)
return
}
if exp, err = s.dao.SelExp(c, mid); err != nil {
log.Error("s.dao.SelExp(%d) error(%v)", mid, err)
return
}
if now.Unix()-int64(exp.Addtime) < 24*60*60 {
added = exp.Flag&eo.Flag == eo.Flag
return
}
if err = s.dao.InitExp(c, mid); err != nil {
log.Error("s.dao.InitExp(%d) error(%v)", mid, err)
return
}
exp.FlagDailyReset(now)
if err = s.dao.UpdateExpFlag(c, mid, exp.Flag, exp.Addtime); err != nil {
log.Error("s.dao.UpdateExpFlag(%d) flag(%d) addtime(%v)", mid, exp.Flag, exp.Addtime)
return
}
added = exp.Flag&eo.Flag == eo.Flag
return
}
// CheckExpInit check init user exp if exp not exist.
func (s *Service) CheckExpInit(c context.Context, mid int64) (opers []*model.ExpOper, err error) {
var aso *model.MemberAso
if aso, err = s.dao.AsoStatus(c, mid); err != nil {
log.Error("s.dao.AsoStatus(%d) error(%v)", mid, err)
return
}
if aso.Spacesta >= 0 && len(aso.Email) != 0 {
opers = append(opers, model.ExpFlagOper["email"])
}
if len(aso.Telphone) != 0 {
opers = append(opers, model.ExpFlagOper["phone"])
}
if aso.SafeQs != 0 {
opers = append(opers, model.ExpFlagOper["safe"])
}
var ri *model.RealnameInfo
if ri, err = s.dao.RealnameInfo(c, mid); err != nil {
log.Error("s.dao.RealnameInfo(%d) error(%+v)", mid, err)
return
}
if ri != nil && ri.Status.IsPass() {
opers = append(opers, model.ExpFlagOper["identify"])
}
log.Info("exp init opers with mid: %d: %+v", mid, opers)
return
}
func sameAccInfo(base *model.BaseInfo, res *model.AccountInfo) (same bool) {
return sameName(base, res)
}
func sameName(base *model.BaseInfo, res *model.AccountInfo) bool {
return base.Name == res.Name
}

View File

@@ -0,0 +1,170 @@
package service
import (
"context"
"encoding/json"
"time"
"go-common/app/job/main/member/model"
"go-common/library/log"
)
const (
expMulti = 100
level1 = 1
level2 = 200
level3 = 1500
level4 = 4500
level5 = 10800
level6 = 28800
)
func (s *Service) initExp(c context.Context, mid int64) (err error) {
var opers []*model.ExpOper
if opers, err = s.CheckExpInit(c, mid); err != nil {
log.Error("s.CheckExpInit(%d) error(%v)", mid, err)
return
}
if len(opers) == 0 {
log.Info("s.CheckExpInit(%d) opers eq(0) continue", mid)
return
}
var exp *model.NewExp
if exp, err = s.dao.SelExp(c, mid); err != nil {
log.Error("s.dao.SelExp(%d) error(%v)", mid, err)
return
}
if exp.Mid == 0 {
if err = s.dao.InitExp(c, mid); err != nil {
log.Error("s.dao.InitExp(%d) init user exp completed error(%v)", mid, err)
return
}
}
var (
rows int64
now = time.Now().Unix()
)
for _, oper := range opers {
if rows, err = s.dao.UpdateExpAped(c, mid, oper.Count*100, oper.Flag); err != nil {
log.Error("s.dao.UpdateExpAped(%d) error(%v)", mid, err)
return
}
if rows == 0 {
log.Info("s.dao.UpdateExpAped(%d) exp(%d) flag(%d) rows affected eq(0) continue", mid, oper.Count*100, oper.Flag)
continue
}
if err = s.dao.DatabusAddLog(c, mid, exp.Exp/100, (exp.Exp+oper.Count*100)/100, now, oper.Oper, oper.Reason, ""); err != nil {
log.Error("s.dao.DatabusAddLog(%d) fromExp(%d) toExp(%d) ts(%d) oper(%s) reason(%s) error(%v)", mid, exp.Exp/100, (exp.Exp+oper.Count*100)/100, now, oper.Oper, oper.Reason, err)
err = nil
continue
} else {
log.Info("s.dao.DatabusAddLog(%d) fromExp(%d) toExp(%d) ts(%d) oper(%s) reason(%s) msg published", mid, exp.Exp/100, (exp.Exp+oper.Count*100)/100, now, oper.Oper, oper.Reason)
exp.Exp = exp.Exp + oper.Count*100
now++
}
}
return
}
func (s *Service) delayUpdateExp() {
s.limiter.UpdateExp.Wait(context.Background())
}
func (s *Service) addExp(c context.Context, e *model.AddExp) (err error) {
if e.Mid <= 0 {
return
}
now := time.Unix(e.Ts, 0)
exp, eo, added, ok, err := s.checkExpAdd(c, e.Mid, e.Event, now)
if err != nil || added || !ok {
log.Info("s.checkExpAdd(%d) event(%s) result added(%v) ok(%v) err(%v)", e.Mid, e.Event, added, ok, err)
return
}
// 写数据库限速,防止写入过大导致主从延迟
s.delayUpdateExp()
var rows int64
if rows, err = s.dao.UpdateExpAped(c, e.Mid, eo.Count*100, eo.Flag); err != nil {
log.Error("s.dao.UpdateExpAped(%d) exp(%d) flag(%d) error(%v) ", e.Mid, eo.Count*100, eo.Flag, err)
return
}
if rows == 0 {
log.Info("s.dao.UpdateExpAped(%d) exp(%d) flag(%d) rows affected eq(0) continue!", e.Mid, eo.Count*100, eo.Flag)
return
}
if _, err = s.dao.SetExpAdded(context.Background(), e.Mid, now.Day(), eo.Oper); err != nil {
log.Error("s.dao.SetExpAdded(%d) oper(%s)", e.Mid, eo.Oper)
err = nil
}
if err = s.dao.DatabusAddLog(context.Background(), e.Mid, (exp.Exp)/100, (exp.Exp+eo.Count*100)/100, e.Ts, eo.Oper, eo.Reason, e.IP); err != nil {
log.Error("s.dao.DatabusAddLog(%d) oper(%s) reason(%s) error(%v)", e.Mid, eo.Oper, eo.Reason, err)
err = nil
} else {
log.Info("s.dao.DatabusAddLog(%d) oper(%s) reason(%s) msg published!", e.Mid, eo.Oper, eo.Reason)
}
return
}
func (s *Service) awardDo(ms []interface{}) {
for _, m := range ms {
l, ok := m.(*model.LoginLogIPString)
if !ok {
continue
}
s.addExp(context.TODO(), &model.AddExp{
Mid: l.Mid,
IP: l.Loginip,
Ts: l.Timestamp,
Event: "login",
})
s.recoverMoral(context.TODO(), l.Mid)
log.Info("consumer mid:%d,ts: %d", l.Mid, l.Timestamp)
}
}
func isExpAndLevelChange(mu *model.Message) (bool, bool) {
if mu.Action == "insert" {
return true, true
}
if len(mu.Old) <= 0 || len(mu.New) <= 0 {
return false, false
}
old := &model.ExpMessage{}
new := &model.ExpMessage{}
if err := json.Unmarshal(mu.New, new); err != nil {
return false, false
}
if err := json.Unmarshal(mu.Old, old); err != nil {
return false, false
}
expChange := false
levelChange := false
if old.Exp != new.Exp {
expChange = true
}
if level(old.Exp) != level(new.Exp) {
levelChange = true
}
return expChange, levelChange
}
func level(exp int64) int8 {
exp = exp / expMulti
switch {
case exp < level1:
return 0
case exp < level2:
return 1
case exp < level3:
return 2
case exp < level4:
return 3
case exp < level5:
return 4
case exp < level6:
return 5
default:
return 6
}
}

View File

@@ -0,0 +1,117 @@
package service
import (
"context"
"encoding/json"
"sync/atomic"
"time"
"go-common/app/job/main/member/model"
"go-common/library/log"
"github.com/pkg/errors"
)
var (
csclice []chan int64
maxmid int64 = 310000000
scanned int64
errCount int64
)
func (s *Service) makeChan(num int) {
csclice = make([]chan int64, num)
for i := 0; i < num; i++ {
csclice[i] = make(chan int64, 10000)
}
}
// dataCheckMids check mid
func (s *Service) dataCheckMids() {
var (
i int64
)
if s.c.SyncRange.End > maxmid {
s.c.SyncRange.End = maxmid
}
if s.c.SyncRange.Start < 0 {
s.c.SyncRange.Start = 0
}
for i = s.c.SyncRange.Start; i < s.c.SyncRange.End; i++ {
csclice[i%30] <- i
}
}
// dataFixer
func (s *Service) dataFixer(cs chan int64) {
for {
mids := make([]int64, 0, 10)
for mid := range cs {
mids = append(mids, mid)
if len(mids) >= 5 {
break
}
atomic.AddInt64(&scanned, 1)
}
s.fix(mids)
}
}
func (s *Service) fix(mids []int64) {
var (
err error
accs = make(map[int64]*model.AccountInfo)
errs = make(map[int64]map[string]bool)
c = context.TODO()
base *model.BaseInfo
)
func() {
defer func() {
if r := recover(); r != nil {
r = errors.WithStack(r.(error))
log.Error("fixer: wocao jingran recover le error(%+v)", r)
time.Sleep(10 * time.Second)
}
time.Sleep(10 * time.Millisecond)
}()
if accs, errs, err = s.dao.Accounts(c, mids); err != nil {
log.Error("fixer: dao.AccountInfo mid(%v) res(%v) error(%v)", mids, accs, err)
return
}
for mid, res := range accs {
log.Error("fixer: mid(%d) res(%+v)", mid, res)
if base, err = s.dao.BaseInfo(c, mid); err != nil {
log.Error("fixer: s.dao.BaseInfo mid(%d) err(%v)", mid, err)
continue
}
if base == nil {
log.Error("fixer: dataCheckErr mid(%d) res(%v),base(%v),detail(%v)", mid, res, base)
continue
}
// all fields are same
if sameAccInfo(base, res) {
log.Info("fixer: sameAccInfo mid(%d) result true continue", mid)
continue
}
// increase errCount and logging
bs, _ := json.Marshal(base)
jres, _ := json.Marshal(res)
atomic.AddInt64(&errCount, 1)
log.Error("fixer: dataCheckFail mid(%d) base(%s),res(%s),errCount(%d)", mid, bs, jres, atomic.LoadInt64(&errCount))
if _, ok := errs[mid]; !ok {
log.Error("fixer,errs[%v] is not ok", mid)
continue
}
if asoOK := errs[mid]["asoOK"]; asoOK && !sameName(base, res) && len(res.Name) > 0 {
s.dao.SetName(c, mid, res.Name)
}
}
log.Info("fixer: dataCheckRight mids(%v) scanned(%d) errCount(%d)", mids, scanned, atomic.LoadInt64(&errCount))
}()
}

View File

@@ -0,0 +1,24 @@
package service
import (
"context"
"go-common/library/log"
)
// setName set user name.
func (s *Service) setName(mid int64) (err error) {
var (
name string
)
if name, err = s.dao.Name(context.TODO(), mid); err != nil {
log.Error("s.dao.Name(%d) error(%v)", mid, err)
return
}
if len(name) > 0 {
if err = s.dao.SetName(context.TODO(), mid, name); err != nil {
return
}
}
return
}

View File

@@ -0,0 +1,82 @@
package service
import (
"context"
"fmt"
"strconv"
"time"
"go-common/app/job/main/member/model"
smodel "go-common/app/service/main/member/model"
"go-common/library/log"
)
func (s *Service) recoverMoral(c context.Context, mid int64) (err error) {
var (
moral *smodel.Moral
rowsAffected int64
)
if moral, err = s.dao.SelMoral(c, mid); err != nil {
return
}
if moral == nil || moral.Moral >= smodel.DefaultMoral {
log.Info("recoverMoral ignore, moral(%v)", moral)
return
}
now := time.Now()
deltaDays := deltaDays(now, moral.LastRecoverDate.Time())
if deltaDays <= 0 {
return
}
deltaMoral := deltaDays * 100
if moral.Moral+deltaMoral > smodel.DefaultMoral {
deltaMoral = smodel.DefaultMoral - moral.Moral
}
if rowsAffected, err = s.dao.RecoverMoral(c, mid, deltaMoral, deltaMoral, now.Format("2006-01-02")); err != nil {
return
}
if rowsAffected == 0 {
return
}
// report log
ul := &model.UserLog{
Mid: mid,
IP: "127.0.0.1",
TS: now.Unix(),
LogID: model.UUID4(),
Content: map[string]string{
"from_moral": strconv.FormatInt(moral.Moral, 10),
"to_moral": strconv.FormatInt(deltaMoral+moral.Moral, 10),
"origin": strconv.FormatInt(smodel.ManualRecoveryType, 10),
"status": strconv.FormatInt(smodel.RevocableMoralStatus, 10),
"remark": fmt.Sprintf("自动恢复(%d)天", deltaDays),
"operater": "系统",
"reason": fmt.Sprintf("时间:%d天", deltaDays),
},
}
s.dao.AddMoralLog(c, ul)
// origin log
// content := make(map[string][]byte, 10)
// content["mid"] = []byte(strconv.FormatInt(mid, 10))
// content["ip"] = []byte("127.0.0.1")
// content["from_moral"] = []byte(strconv.FormatInt(moral.Moral, 10))
// content["to_moral"] = []byte(strconv.FormatInt(deltaMoral+moral.Moral, 10))
// content["origin"] = []byte(strconv.FormatInt(smodel.ManualRecoveryType, 10))
// content["status"] = []byte(strconv.FormatInt(smodel.RevocableMoralStatus, 10))
// content["remark"] = []byte(fmt.Sprintf("自动恢复(%d)天", deltaDays))
// content["operater"] = []byte("系统")
// content["reason"] = []byte(fmt.Sprintf("时间:%d天", deltaDays))
// if err = s.dao.AddLog(c, mid, now.Unix(), content, model.TableMoralLog); err != nil {
// log.Error("recoverMoral mid: %v from_moral:%v,to_moral:%v error(%v)", mid, content["from_moral"], content["to_moral"], err)
// } else {
// log.Info("recoverMoral mid: %v from_moral:%v,to_moral:%v error(%v)", mid, content["from_moral"], content["to_moral"], err)
// }
s.dao.DelMoralCache(c, mid)
return
}
func deltaDays(now, old time.Time) int64 {
return int64(now.Sub(old).Hours() / 24)
}

View File

@@ -0,0 +1,261 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
"go-common/app/job/main/member/conf"
"go-common/app/job/main/member/model"
memmodel "go-common/app/service/main/member/model"
"go-common/library/log"
"go-common/library/net/ip"
"github.com/pkg/errors"
)
// constrs for gender
const (
_genderMale = "male"
_genderFemale = "female"
)
// realname alipay polling
func (s *Service) realnamealipaycheckproc() {
defer func() {
if x := recover(); x != nil {
log.Error("%+v", errors.WithStack(fmt.Errorf("service.realnamealipaycheckproc panic(%v)", x)))
go s.realnamealipaycheckproc()
log.Info("service.realnamealipaycheckproc recover")
}
}()
for {
var (
to = time.Now()
from = to.Add(-2 * time.Duration(conf.Conf.Biz.RealnameAlipayCheckTick))
expiredTime = from
startTime = expiredTime.AddDate(0, -1, 0)
)
log.Info("realname alipay check start from : %s , to : %s", from, to)
s.realnameAlipayCheckHandler(context.Background(), from, to)
// to = from
// from = to.Add(-2 * time.Duration(conf.Conf.Biz.RealnameAlipayCheckTick))
log.Info("realname alipay handle expired end startTime : %s , expiredTime : %s", startTime, expiredTime)
s.realnameAlipayExpiredHandler(context.Background(), startTime, expiredTime)
time.Sleep(time.Duration(conf.Conf.Biz.RealnameAlipayCheckTick))
}
}
// realnameAlipayCheckHandler 轮询时间段 [from,to] 中,未完成阿里实名的实名申请
func (s *Service) realnameAlipayCheckHandler(c context.Context, from, to time.Time) {
if conf.Conf.Biz.RealnameAlipayCheckLimit <= 0 {
log.Error("conf.Conf.Property.realnameAlipayCheckHandler [%d] <= 0", conf.Conf.Biz.RealnameAlipayCheckLimit)
return
}
var (
applys = make([]*model.RealnameAlipayApply, conf.Conf.Biz.RealnameAlipayCheckLimit)
startID int64
err error
)
for len(applys) >= conf.Conf.Biz.RealnameAlipayCheckLimit {
if startID, applys, err = s.dao.RealnameAlipayApplyList(c, startID, model.RealnameApplyStatusPending, from, to, conf.Conf.Biz.RealnameAlipayCheckLimit); err != nil {
log.Error("%+v", err)
return
}
for _, apply := range applys {
log.Info("Start check realname alipay apply mid (%d) bizno (%s)", apply.MID, apply.Bizno)
if err = s.realnameAlipayConfirm(c, apply); err != nil {
log.Error("%+v", err)
continue
}
}
}
for len(applys) >= conf.Conf.Biz.RealnameAlipayCheckLimit {
if startID, applys, err = s.dao.RealnameAlipayApplyList(c, startID, model.RealnameApplyStatusBack, from, to, conf.Conf.Biz.RealnameAlipayCheckLimit); err != nil {
log.Error("%+v", err)
return
}
for _, apply := range applys {
log.Info("Start check realname alipay apply mid (%d) bizno (%s)", apply.MID, apply.Bizno)
if err = s.realnameAlipayConfirm(c, apply); err != nil {
log.Error("%+v", err)
continue
}
}
}
}
func (s *Service) realnameAlipayConfirm(c context.Context, apply *model.RealnameAlipayApply) (err error) {
if apply.Bizno == "" {
return
}
var (
pass bool
reason string
)
if pass, reason, err = s.alipayQuery(c, apply.Bizno); err != nil {
return
}
// rpc call
var (
rpcConfirmArg = &memmodel.ArgRealnameAlipayConfirm{
MID: apply.MID,
Pass: pass,
Reason: reason,
}
)
if err = s.memrpc.RealnameAlipayConfirm(c, rpcConfirmArg); err != nil {
return
}
log.Info("Succeed to confirm realname alipay with arg: %+v", rpcConfirmArg)
if pass {
expArg := &model.AddExp{
Mid: apply.MID,
IP: ip.InternalIP(),
Ts: time.Now().Unix(),
Event: "identify",
}
if expErr := s.addExp(context.TODO(), expArg); expErr != nil {
log.Error("realname exp error(%+v) ", expErr)
return
}
log.Info("realname exp success(%+v)", expArg)
}
return
}
func (s *Service) alipayQuery(c context.Context, bizno string) (pass bool, reason string, err error) {
var (
param url.Values
biz struct {
Bizno string `json:"biz_no"`
}
)
biz.Bizno = bizno
if param, err = s.alipayParam("zhima.customer.certification.query", biz, ""); err != nil {
return
}
if pass, reason, err = s.dao.AlipayQuery(c, param); err != nil {
return
}
return
}
// alipayParam 构造阿里请求parambiz为 biz_content struct
func (s *Service) alipayParam(method string, biz interface{}, returnURL string) (p url.Values, err error) {
var (
sign string
bizBytes []byte
)
if bizBytes, err = json.Marshal(biz); err != nil {
err = errors.WithStack(err)
return
}
p = url.Values{}
p.Set("app_id", conf.Conf.Biz.RealnameAlipayAppID)
p.Set("method", method)
p.Set("charset", "utf-8")
p.Set("sign_type", "RSA2")
p.Set("timestamp", time.Now().Format("2006-01-02 15:04:05"))
p.Set("version", "1.0")
p.Set("biz_content", string(bizBytes))
if returnURL != "" {
p.Set("return_url", returnURL)
}
if sign, err = s.alipayCryptor.SignParam(p); err != nil {
return
}
p.Set("sign", sign)
return
}
// rejectExpiredRealnameAlipay 自动驳回超过两天还没有通过芝麻认证的实名认证
func (s *Service) realnameAlipayExpiredHandler(c context.Context, startTime, expiredTime time.Time) {
if conf.Conf.Biz.RealnameAlipayCheckLimit <= 0 {
log.Error("conf.Conf.Property.realnameAlipayCheckHandler [%d] <= 0", conf.Conf.Biz.RealnameAlipayCheckLimit)
return
}
var (
applys []*model.RealnameAlipayApply
startID int64
err error
)
// 每次查询一个月里100条过期的未处理的位处理的芝麻认证数据进行驳回
for {
log.Info("realname handle startID (%d)", startID)
startID, applys, err = s.dao.RealnameAlipayApplyList(c, startID, model.RealnameApplyStatusPending, startTime, expiredTime, conf.Conf.Biz.RealnameAlipayCheckLimit)
if err != nil {
log.Error("realnameAlipayExpiredHandler search err(%+v)", err)
return
}
// 没有查询到预期的过期数据,则停止循环,等待下一次检查
if len(applys) == 0 {
log.Error("realnameAlipayExpiredHandler search no row in result")
return
}
// 循环驳回验证超时的芝麻认证
for _, apply := range applys {
log.Info("Start expire realname alipay apply mid (%d) bizno (%s)", apply.MID, apply.Bizno)
var (
rpcConfirmArg = &memmodel.ArgRealnameAlipayConfirm{
MID: apply.MID,
Pass: false,
Reason: "超时自动驳回",
}
)
if err = s.memrpc.RealnameAlipayConfirm(c, rpcConfirmArg); err != nil {
log.Error("realnameAlipayExpiredHandler reject err(%+v)", err)
continue
}
}
time.Sleep(10 * time.Millisecond)
}
}
// ParseIdentity to birthday and gender
func ParseIdentity(id string) (birthday time.Time, gender string, err error) {
var (
ystr, mstr, dstr, gstr string
y, m, d, g int
)
switch len(id) {
case 15:
ystr, mstr, dstr = "19"+id[6:8], id[8:10], id[10:12]
gstr = id[14:15]
case 18:
ystr, mstr, dstr = id[6:10], id[10:12], id[12:14]
gstr = id[16:17]
default:
err = errors.Errorf("identity id invalid : %s", id)
return
}
if y, err = strconv.Atoi(ystr); err != nil {
err = errors.WithStack(err)
return
}
if m, err = strconv.Atoi(mstr); err != nil {
err = errors.WithStack(err)
return
}
if d, err = strconv.Atoi(dstr); err != nil {
err = errors.WithStack(err)
return
}
if g, err = strconv.Atoi(gstr); err != nil {
err = errors.WithStack(err)
return
}
birthday = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local)
if g%2 == 1 {
gender = _genderMale
} else {
gender = _genderFemale
}
return
}

View File

@@ -0,0 +1,167 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net"
"go-common/app/interface/main/account/service/realname/crypto"
"go-common/app/job/main/member/conf"
"go-common/app/job/main/member/dao"
"go-common/app/job/main/member/model"
"go-common/app/job/main/member/model/queue"
"go-common/app/job/main/member/service/block"
memrpc "go-common/app/service/main/member/api/gorpc"
"go-common/library/log"
"go-common/library/log/infoc"
"go-common/library/queue/databus"
"go-common/library/queue/databus/databusutil"
"golang.org/x/time/rate"
)
// Service struct of service.
type Service struct {
c *conf.Config
dao *dao.Dao
block *block.Service
ds *databus.Databus
accDs *databus.Databus
passortDs *databus.Databus
logDatabus *databus.Databus
expDatabus *databus.Databus
realnameDatabus *databus.Databus
shareMidDatabus *databus.Databus
loginGroup *databusutil.Group
awardGroup *databusutil.Group
memrpc *memrpc.Service
cachepq *queue.PriorityQueue
alipayCryptor *crypto.Alipay
limiter *limiter
ParsedRealnameInfoc *infoc.Infoc
}
type limiter struct {
UpdateExp *rate.Limiter
}
// New create service instance and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
ds: databus.New(c.DataBus),
accDs: databus.New(c.AccDataBus),
passortDs: databus.New(c.PassortDataBus),
logDatabus: databus.New(c.LogDatabus),
expDatabus: databus.New(c.ExpDatabus),
realnameDatabus: databus.New(c.RealnameDatabus),
shareMidDatabus: databus.New(c.ShareMidDatabus),
alipayCryptor: crypto.NewAlipay(string(c.RealnameAlipayPub), string(c.RealnameAlipayBiliPriv)),
loginGroup: databusutil.NewGroup(c.Databusutil, databus.New(c.LoginDatabus).Messages()),
awardGroup: databusutil.NewGroup(c.Databusutil, databus.New(c.AwardDatabus).Messages()),
memrpc: memrpc.New(nil),
cachepq: queue.NewPriorityQueue(1024, false),
limiter: &limiter{
UpdateExp: rate.NewLimiter(200, 10),
},
ParsedRealnameInfoc: infoc.New(c.ParsedRealnameInfoc),
}
s.dao = dao.New(c)
s.block = block.New(c, s.dao.BlockImpl(), databus.New(c.BlockCreditDatabus))
s.loginGroup.New = newMsg
s.loginGroup.Split = split
s.loginGroup.Do = s.awardDo
s.awardGroup.New = newMsg
s.awardGroup.Split = split
s.awardGroup.Do = s.awardDo
s.loginGroup.Start()
s.awardGroup.Start()
go s.passportSubproc()
go s.realnameSubproc()
go s.realnamealipaycheckproc()
go s.cachedelayproc(context.Background())
go s.shareMidproc()
accproc := int32(10)
expproc := int32(1)
if c.Biz.AccprocCount > accproc {
accproc = c.Biz.AccprocCount
}
if c.Biz.ExpprocCount > expproc {
expproc = c.Biz.ExpprocCount
}
log.Info("Starting %d account sub proc", accproc)
for i := 0; i < int(accproc); i++ {
go s.subproc()
go s.accSubproc()
go s.logproc()
}
log.Info("Starting %d exp sub proc", expproc)
for i := 0; i < int(expproc); i++ {
go s.expproc()
}
if s.c.FeatureGates.DataFixer && s.dao.LeaderEleciton(context.Background()) {
fmt.Println("Leader elected")
// 数据检查
s.makeChan(30)
go s.dataCheckMids()
for i := 0; i < 60; i++ {
go s.dataFixer(csclice[i%30])
}
}
return
}
func split(msg *databus.Message, data interface{}) int {
t, ok := data.(*model.LoginLogIPString)
if !ok {
return 0
}
return int(t.Mid)
}
func newMsg(msg *databus.Message) (res interface{}, err error) {
llm := new(model.LoginLogIPString)
ll := new(model.LoginLog)
if err = json.Unmarshal(msg.Value, &llm); err != nil {
if err = json.Unmarshal(msg.Value, &ll); err != nil {
msg.Commit()
log.Error("json.Unmarshal(%s) error(%v)", msg.Value, err)
return
}
llm.Mid = ll.Mid
llm.Timestamp = ll.Timestamp
llm.Loginip = inetNtoA(uint32(ll.Loginip))
}
res = llm
return
}
func inetNtoA(sum uint32) string {
ip := make(net.IP, net.IPv4len)
ip[0] = byte((sum >> 24) & 0xFF)
ip[1] = byte((sum >> 16) & 0xFF)
ip[2] = byte((sum >> 8) & 0xFF)
ip[3] = byte(sum & 0xFF)
return ip.String()
}
// Ping check service health.
func (s *Service) Ping(c context.Context) error {
return s.dao.Ping(c)
}
// Close kafka consumer close.
func (s *Service) Close() (err error) {
if err = s.ds.Close(); err != nil {
log.Error("s.ds.Close(),err(%v)", err)
}
if err = s.accDs.Close(); err != nil {
log.Error("s.accDs.Close(),err(%v)", err)
}
if err = s.passortDs.Close(); err != nil {
log.Error("s.passportDs.Close(),err(%v)", err)
}
s.block.Close()
return
}

View File

@@ -0,0 +1,46 @@
package service
import (
"context"
"flag"
"testing"
"time"
"go-common/app/job/main/member/conf"
"go-common/app/job/main/member/model"
"go-common/library/log"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func initConf() {
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Xlog)
defer log.Close()
}
func init() {
flag.Set("conf", "../cmd/member-job-dev.toml")
initConf()
s = New(conf.Conf)
}
func TestAddexp(t *testing.T) {
Convey("addexp", t, func() {
err := s.addExp(context.Background(), &model.AddExp{Mid: 1})
So(err, ShouldBeNil)
})
}
func TestRecoverMoral(t *testing.T) {
time.Sleep(time.Second * 2)
Convey("recoverMoral", t, func() {
err := s.recoverMoral(context.Background(), 2)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,504 @@
package service
import (
"context"
"encoding/json"
"runtime/debug"
"strings"
"time"
"go-common/app/job/main/member/model"
share "go-common/app/service/main/share/model"
"go-common/library/log"
"go-common/library/net/ip"
"github.com/pkg/errors"
)
const (
_DedeMember = "dede_member"
_AsoAccount = "aso_account"
_DedeMemberPerson = "dede_member_person"
_DedeMemberSpace = "dede_member_space"
_MemberBaseInfo = "user_base_"
_MemberMoral = "user_moral"
_memberExp = "user_exp_"
_DedeMemberTags = "dede_member_tags"
_dedeMemberMoral = "dede_member_moral"
_memberRealnameApply = "realname_apply"
_memberRealnameInfo = "realname_info"
_retry = 3
)
var (
_shareVideoType = map[int]struct{}{
1: {},
3: {},
}
)
// subproc databus sub
func (s *Service) subproc() {
var err error
var c = context.TODO()
for res := range s.ds.Messages() {
mu := &model.Message{}
if err = json.Unmarshal(res.Value, mu); err != nil {
log.Error("member-job,json.Unmarshal (%v) error(%v)", string(res.Value), err)
continue
}
for i := 0; i < _retry; i++ {
if strings.HasPrefix(mu.Table, _MemberBaseInfo) {
var (
rank = &struct {
Mid int64 `json:"mid"`
Rank int64 `json:"rank"`
}{}
)
oldRank := int64(5000)
if mu.Old != nil && len(mu.Old) != 0 {
if err = json.Unmarshal(mu.Old, rank); err != nil {
log.Error("json.Unmarsha(%s) error(%v)", string(mu.Old), err)
break
}
oldRank = rank.Rank
}
if err = json.Unmarshal(mu.New, rank); err != nil {
log.Error("json.Unmarsha(%s) error(%v)", string(mu.New), err)
break
}
newRank := rank.Rank
if oldRank <= 5000 && newRank >= 10000 {
if err = s.initExp(c, rank.Mid); err != nil {
log.Error("s.initExp(%d) error(%v)", rank.Mid, err)
}
}
if err = s.dao.DelBaseInfoCache(c, rank.Mid); err != nil {
continue
}
// sync face to old account .
s.updateAccFace(c, rank.Mid) //todo delete
// TODO: with update face or name to purge cache at the same time
if err = s.dao.NotifyPurgeCache(c, rank.Mid, model.ActUpdateFace); err != nil {
log.Error("s.dao.NotifyPurgeCache(%d, %s) error(%v)", rank.Mid, model.ActUpdateFace, err)
break
}
item := &Item{
Mid: rank.Mid,
Time: time.Now(),
Action: model.ActUpdateUname,
}
if err = s.cachepq.Put(item); err != nil {
log.Error("Failed to put into cachepq with item: %+v: %+v", item, err)
err = nil
}
log.Info("Notify to purge cache with mid(%d) action(%s) message(old: %s, new: %s)", rank.Mid, model.ActUpdateFace, string(mu.Old), string(mu.New))
} else if mu.Table == _MemberMoral {
var p = &model.MemberMid{}
if err = json.Unmarshal(mu.New, p); err != nil {
log.Error("member-job,json.Unmarshal (%v) error(%v)", string(mu.New), err)
break
}
if err = s.dao.DelMoralCache(c, p.Mid); err != nil {
continue
}
if err = s.dao.NotifyPurgeCache(c, p.Mid, model.ActUpdateMoral); err != nil {
log.Error("s.dao.NotifyPurgeCache(%d, %s) error(%v)", p.Mid, model.ActUpdateMoral, err)
break
}
} else if mu.Table == _memberRealnameInfo || mu.Table == _memberRealnameApply {
var p = &struct {
Mid int64 `json:"mid"`
Status model.RealnameApplyStatus `json:"status"`
}{}
if err = json.Unmarshal(mu.New, p); err != nil {
log.Error("member-job,json.Unmarshal (%v) error(%+v)", string(mu.New), err)
break
}
if err = s.dao.DeleteRealnameCache(c, p.Mid); err != nil {
log.Error("Delete RealnameCache cache err : %+v", err)
continue
}
if err = s.dao.NotifyPurgeCache(c, p.Mid, "updateRealname"); err != nil {
log.Error("s.dao.NotifyPurgeCache(%d, %s) error(%+v)", p.Mid, "updateRealname", err)
break
}
log.Info("Notify to purge realname cache with mid(%d) action(%s) message(old: %s, new: %s)", p.Mid, "updateRealname", string(mu.Old), string(mu.New))
if p.Status.IsPass() {
// 尝试补发一次经验
s.addExp(context.TODO(), &model.AddExp{
Mid: p.Mid,
IP: ip.InternalIP(),
Ts: time.Now().Unix(),
Event: "identify",
})
}
if mu.Table == _memberRealnameInfo {
s.syncParsedRealnameInfo(c, p.Mid)
}
} else if strings.HasPrefix(mu.Table, _memberExp) {
var (
p = &model.MemberMid{}
exp *model.NewExp
)
if err = json.Unmarshal(mu.New, p); err != nil {
log.Error("s.subproc() table(%s) json.Unmarshal() error(%v)", mu.Table, err)
break
}
if exp, err = s.dao.SelExp(c, p.Mid); err != nil {
log.Error("s.dao.SelNewExp(%d) error(%v)", p.Mid, err)
break
}
if err = s.dao.SetExpCache(c, exp.Mid, exp.Exp); err != nil {
log.Error("Failed to set exp cache: %+v: %+v", exp, err)
break
}
log.Info("s.dao.SetExpCache(%d) set exp cache complete. exp(%d)", exp.Mid, exp.Exp)
expChange, levelChange := isExpAndLevelChange(mu)
if expChange {
log.Info("Notify to purge cache with mid(%d) action(%s) message(old: %s, new: %s)", p.Mid, model.ActUpdateExp, string(mu.Old), string(mu.New))
if err = s.dao.NotifyPurgeCache(c, p.Mid, model.ActUpdateExp); err != nil {
log.Error("s.dao.NotifyPurgeCache(%d, %s) error(%v)", p.Mid, model.ActUpdateExp, err)
break
}
}
if levelChange {
log.Info("Notify to purge cache with mid(%d) action(%s) message(old: %s, new: %s)", p.Mid, model.ActUpdateLevel, string(mu.Old), string(mu.New))
if err = s.dao.NotifyPurgeCache(c, p.Mid, model.ActUpdateLevel); err != nil {
log.Error("s.dao.NotifyPurgeCache(%d, %s) error(%v)", p.Mid, model.ActUpdateLevel, err)
break
}
}
}
if err == nil {
break
}
}
if err = res.Commit(); err != nil {
log.Error("databus.Commit err(%v)", err)
}
log.Info("subproc key:%v,topic: %v, part:%v offset:%v,message %s,", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
}
// subproc databus sub
func (s *Service) accSubproc() {
var (
err error
)
for res := range s.accDs.Messages() {
mu := &model.Message{}
ms := &struct {
Mid int64 `json:"mid"`
}{}
if err = json.Unmarshal(res.Value, mu); err != nil {
log.Error("member-job,json.Unmarshal (%v) error(%v)", string(res.Value), err)
continue
}
if err = json.Unmarshal(mu.New, ms); err != nil {
log.Error("json.Unmarsha(%s) error(%v)", string(mu.New), ms)
continue
}
for num := 0; num < 3; num++ {
switch {
//sex,face,rank
case strings.HasPrefix(mu.Table, _DedeMember) && len(mu.Table) < 14:
//err = s.setFace(c, ms.Mid)
//birthday,dating,place,marital
case mu.Table == _DedeMemberPerson:
//sign
case mu.Table == _DedeMemberSpace:
//err = s.setSign(c, ms.Mid)
//tag
case strings.HasPrefix(mu.Table, _DedeMemberTags):
//moral
case mu.Table == _dedeMemberMoral:
}
if err != nil {
log.Error("accSubproc err(%v)", err)
time.Sleep(1 * time.Second)
continue
}
break
}
if err = res.Commit(); err != nil {
log.Error("databus.Commit err(%v)", err)
}
log.Info("subproc key:%v,topic: %v, part:%v offset:%v,message %s,", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
}
// subproc databus sub.
func (s *Service) passportSubproc() {
var err error
for res := range s.passortDs.Messages() {
mu := &model.Message{}
ms := &struct {
Mid int64 `json:"mid"`
}{}
if err = json.Unmarshal(res.Value, mu); err != nil {
log.Error("member-job,json.Unmarshal (%v) error(%v)", string(res.Value), err)
continue
}
if err = json.Unmarshal(mu.New, ms); err != nil {
log.Error("json.Unmarsha(%s) error(%v)", string(mu.New), ms)
continue
}
for num := 0; num < 3; num++ {
//name
if mu.Table == _AsoAccount {
if mu.Action != "delete" {
err = s.setName(ms.Mid)
}
}
if err != nil {
log.Error("passportSubproc err(%v)", err)
time.Sleep(1 * time.Second)
continue
}
break
}
if err = res.Commit(); err != nil {
log.Error("databus.Commit err(%v)", err)
}
log.Info("subproc key:%v,topic: %v, part:%v offset:%v,message %s,", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
}
func (s *Service) logproc() {
for {
mu := <-s.logDatabus.Messages()
l := &model.UserLog{}
err := json.Unmarshal(mu.Value, l)
if err != nil {
log.Error("Failed to parse log databus message value: value(%s): err: %+v", string(mu.Value), err)
continue
}
// send log to report
s.dao.AddExpLog(context.TODO(), l)
// send log to origin hbase
// content := make(map[string][]byte, len(l.Content))
// for k, v := range l.Content {
// content[k] = []byte(v)
// }
// content["ip"] = []byte(l.IP)
// for i := 0; i < 3; i++ {
// err := s.dao.AddLog(context.TODO(), l.Mid, l.TS, content, model.TableExpLog)
// if err == nil {
// break
// }
// log.Error("addlog mid %d err %v", l.Mid, err)
// time.Sleep(time.Millisecond * 500)
// }
log.Info("consumer key:%s,message:%s", mu.Key, mu.Value)
mu.Commit()
}
}
func (s *Service) expproc() {
for {
mu := <-s.expDatabus.Messages()
ex := new(model.AddExp)
err := json.Unmarshal(mu.Value, ex)
if err != nil {
log.Error("s.expproc() json.Unmarshal error(%v)", err)
mu.Commit()
continue
}
try := 0
success := false
for {
if err = s.addExp(context.TODO(), ex); err == nil {
success = true
break
}
try++
if try > 3 {
log.Error("Failed to add exp, try 3 times mid: %d error: %+v", ex.Mid, err)
mu.Commit()
break
}
time.Sleep(time.Millisecond * 500)
}
if !success {
continue
}
// 如果是一个观看视频的消息就尝试补发一下登录奖励
if ex.Event == "view" {
s.addExp(context.TODO(), &model.AddExp{
Mid: ex.Mid,
IP: ex.IP,
Ts: ex.Ts,
Event: "login",
})
s.recoverMoral(context.TODO(), ex.Mid)
}
log.Info("expproc consumer key:%s,value: %s", mu.Key, mu.Value)
mu.Commit()
}
}
func isVideoShare(shareType int) bool {
_, ok := _shareVideoType[shareType]
return ok
}
func (s *Service) shareMidproc() {
for {
mu := <-s.shareMidDatabus.Messages()
sh := new(share.MIDShare)
err := json.Unmarshal(mu.Value, sh)
if err != nil {
log.Error("s.shareMidproc() json.Unmarshal error(%v)", err)
mu.Commit()
continue
}
if !isVideoShare(sh.TP) {
log.Warn("Not a video share, skip to add exp: %+v", sh)
mu.Commit()
continue
}
try := 0
success := false
ex := &model.AddExp{
Event: "share",
Mid: sh.MID,
IP: ip.InternalIP(),
Ts: sh.Time,
}
for {
if err = s.addExp(context.TODO(), ex); err == nil {
success = true
break
}
try++
if try > 3 {
log.Error("Failed to add share exp, try 3 times mid: %d error: %+v", ex.Mid, err)
mu.Commit()
break
}
time.Sleep(time.Millisecond * 500)
}
if !success {
continue
}
log.Info("shareMidproc consumer key:%s,value: %s", mu.Key, mu.Value)
mu.Commit()
}
}
func (s *Service) realnameSubproc() {
defer func() {
if x := recover(); x != nil {
log.Error("realnameSubproc panic(%+v) :\n %s", x, debug.Stack())
go s.realnameSubproc()
}
}()
log.Info("realnameSubproc run")
var (
c = context.TODO()
err error
)
for res := range s.realnameDatabus.Messages() {
msg := &model.Message{}
if err = json.Unmarshal(res.Value, msg); err != nil {
log.Error("member-job,json.Unmarshal (%v) error(%v)", string(res.Value), errors.WithStack(err))
continue
}
switch msg.Table {
case "dede_identification_card_apply":
if msg.Action == "delete" {
log.Error("dede_identification_card_apply got delete msg (%s)", msg.New)
continue
}
ms := &model.RealnameApplyMessage{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "dede_identification_card_apply , %s", msg.New)
log.Error("%+v", err)
continue
}
log.Info("upsert realname apply : (%+v)", ms)
if err = s.dao.UpdateRealnameFromMSG(c, ms); err != nil {
log.Error("%+v", err)
continue
}
if err = s.dao.DeleteRealnameCache(c, ms.MID); err != nil {
log.Error("Delete RealnameApplyStatus cache err : %+v", err)
continue
}
if err = s.dao.NotifyPurgeCache(c, ms.MID, "updateRealname"); err != nil {
log.Error("s.dao.NotifyPurgeCache(%d, %s) error(%+v)", ms.MID, "updateRealname", err)
continue
}
log.Info("Notify to purge realname cache with mid(%d) action(%s) message(old: %s, new: %s)", ms.MID, "updateRealname", string(msg.Old), string(msg.New))
case "dede_identification_card_apply_img":
if msg.Action == "delete" {
log.Error("dede_identification_card_apply_img got delete msg (%s)", msg.New)
continue
}
ms := &model.RealnameApplyImgMessage{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "dede_identification_card_apply_img , %s", msg.New)
log.Error("%+v", err)
continue
}
log.Info("upsert realname apply img : (%+v)", ms)
if err = s.dao.UpsertRealnameApplyImg(c, ms); err != nil {
log.Error("%+v", err)
continue
}
}
if err = res.Commit(); err != nil {
err = errors.Wrapf(err, "realnameSubproc commit")
log.Error("%+v", err)
}
log.Info("Realname subproc key:%v,topic: %v, part:%v offset:%v,message %s,", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
}
func (s *Service) syncParsedRealnameInfo(ctx context.Context, mid int64) {
info, err := s.dao.RealnameInfo(ctx, mid)
if err != nil {
log.Error("Failed to fetch realname info with mid: %d: %+v", mid, err)
return
}
if info.Country != model.RealnameCountryChina ||
info.CardType != model.RealnameCardTypeIdentity {
log.Info("Skip to sync parsed realname info with mid: %d", mid)
return
}
card, err := info.DecryptedCard()
if err != nil {
log.Error("Failed to decrypt realname card with mid: %d: %+v", mid, err)
return
}
birth, gender, err := ParseIdentity(card)
if err != nil {
log.Error("Failed to parse idenitfy with mid: %d: %+v", mid, err)
return
}
// sync to hive
log.Infov(ctx,
log.KV("action", "Syning realname parsed info"),
log.KV("mid", info.MID),
log.KV("birthday", birth.Format("2006-01-02")),
log.KV("status", info.Status),
log.KV("gender", gender),
log.KV("mtime", info.MTime.Format("2006-01-02 15:04:05")),
)
s.ParsedRealnameInfoc.Infov(ctx,
birth.Format("2006-01-02"),
info.MTime.Format("2006-01-02 15:04:05"),
int64(info.Status),
int64(info.MID),
gender,
)
}