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,21 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/interface/main/answer/cmd:all-srcs",
"//app/interface/main/answer/conf:all-srcs",
"//app/interface/main/answer/dao:all-srcs",
"//app/interface/main/answer/http:all-srcs",
"//app/interface/main/answer/model:all-srcs",
"//app/interface/main/answer/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,201 @@
#### answer 答题
### v2.3.4
> 1.captcha 验证码
### v2.3.3
> 1. result 增加缓存
> 2. fix pendant
### v2.3.2
> 1. 答题分区简繁翻译
### v2.3.1
> 1.account grpc
### v2.3.0
> 1. 简繁翻译
### v2.2.1
> 1. 分享动态数据
### v2.2.0
> 1.用户行为日志
### v2.1.1
1. 对外展示方案二的切换至方案一
### v2.1.0
> 1. 自动发挂件
### v2.0.4
> 1. fix pendant history
### v2.0.2
> 1. 兼容老挂件显示
### v2.0.1
> 1. 答题v3 兼容领取老挂件
### v2.0.0
> 1. 答题v3
### v1.10.0
> 1. 提交基础题检查手机号绑定状态
### v1.9.0
> 1. 答题推荐
### v1.8.1
> 1. add IsSameUser
### v1.8.0
> 1. 改为 metadata RemoteIP
### v1.7.1
> 1. geetest ust slow
### v1.7.0
> 1. use new auth
### v1.6.2
> 1. fix ans nil return err
### v1.6.1
> 1. del config reload
### v1.6.0
> 1. geetest failback validate
### v1.5.1
> 1. fix ans nil return err
### v1.5.0
> 1. update infoc sdk
### v1.4.11
> 1. add geetest err log
### v1.4.10
> 1. update status 503 to code -503
### v1.4.9
> 1. fix ans nil return err
### v1.4.8
> 1. fix check base result
### v1.4.7
> 1. 上报答题数据提前
2. 移动转正重试至answer-interface
### v1.4.6
> 1. 使用新的 account service 和 member service
### v1.4.5
> 1. mysql return panic by timeout
### v1.4.4
> 1. fix CheckQueCaptcha err panic
### v1.4.3
> 1. fix CheckQueCaptcha err panic
### v1.4.2
> 1. fix answer err panic
### v1.4.1
> 1. 对接转正接口
### v1.4.0
> 1. answer rpc 迁移到 answer http interface
### v1.3.16
> 1. account v7
### v1.3.15
> 1. remove stastd
### v1.3.14
> 1. 答题api v2
### v1.3.13
> 1. 转正rank判断兼容
### v1.3.12
> 1. 对接新挂件api
### v1.3.11
> 1. 修复发挂件逻辑
### v1.3.10
> 1. 转正异常databus
### v1.3.9
> 1. 附加题check
### v1.3.8
> 1. 附加题答题地址
### v1.3.7
> 1. old hid query
### v1.3.6
> 1.答题增加附加题part
### v1.3.5
> 1.删除未使用的identify
#### v1.3.4
> 1.answer RPC client add prom
#### v1.3.3
> 1.删除无用代码
#### v1.3.2
> 1.修复自选题题目load不全
#### v1.3.1
> 1.增加自选题日志
#### v1.3.0
> 1.接入反作弊系统
#### v1.2.3
> 1.修复无用初始化
#### v1.2.2
> 1.修复答题过快封禁时间
#### v1.2.1
> 1.修复分院帽提交题目
#### v1.2.0
> 1.move answer-service into Kratos
#### v1.1.0
> 1.merge member-service into Kratos
#### v1.0.7
> 1.排行榜昵称
#### v1.0.6
> 1.答题转正重试
#### v1.0.5
> 1.新增异地登录功能
#### v1.0.4
> 1.修复h5不显示答题
#### v1.0.3
> 1.答题图片bfs
#### v1.0.2
> 1.接入prom
#### v1.0.1
> 1.修复封禁时长
#### v1.0.0
> 1.基础api

View File

@@ -0,0 +1,13 @@
# Owner
liangkai
zhaogangtao
# Author
guhao
zhangshengchao
zhaogangtao
# Reviewer
liangkai
guhao
zhangshengchao

View File

@@ -0,0 +1,18 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- guhao
- liangkai
- zhangshengchao
- zhaogangtao
labels:
- interface
- interface/main/answer
- main
options:
no_parent_owners: true
reviewers:
- guhao
- liangkai
- zhangshengchao
- zhaogangtao

View File

@@ -0,0 +1,2 @@
### member service
用户账号会员系统的服务层。

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["answer-test.toml"],
importpath = "go-common/app/interface/main/answer/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/http:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/text/translate/chinese: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,195 @@
# This is a TOML document. Boom.
version = "1.0.0"
[bm]
addr = "0.0.0.0:6991"
timeout = "1s"
[rpcClient]
[rpcClient.member]
timeout = "1s"
[rpcClient.account]
timeout = "10s"
[mysql]
addr = "127.0.0.1:3306"
dsn = "root:123456@tcp(127.0.0.1:3306)/bilibili_answer?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
idleTimeout ="4h"
queryTimeout = "1000ms"
execTimeout = "1000ms"
tranTimeout = "2000ms"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[memcache]
name = "answer"
proto = "tcp"
addr = "172.18.33.60:11234"
idle = 5
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"
answerBolckExpire = "12h"
[redis]
name = "answer"
proto = "tcp"
addr = "127.0.0.1:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "720h"
ansCountExpire = "12h"
ansAddFlagCountExpire = "1m"
[Captcha]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
dial = "500ms"
timeout = "1s"
keepAlive = "60s"
timer = 1000
[Captcha.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[httpClient]
[httpClient.normal]
dial = "1s"
timeout = "1s"
keepAlive = "60s"
timer = 10
key = "53e2fa226f5ad348"
secret = "3cf6bd1b0ff671021da5f424fea4b04a"
[httpClient.normal.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[httpClient.slow]
dial = "500ms"
timeout = "1s"
keepAlive = "60s"
timer = 10
key = "zxnh4k92dwe61t27"
secret = "dnu3bwpxyswqwf1ixpsczthury1nqiew"
[httpClient.slow.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[host]
api = "http://api.bilibili.co"
geetest = "http://api.geetest.com"
account = "http://account.bilibili.com"
extraIds = "http://172.18.33.147:3000/laogai/question/exam/danmu"
[geetest]
[geetest.PC]
captchaId = "a703e7d11eda930ca87be02c1a80ff97"
privateKey = "df5eacf78b4afc79a052f199939e946a"
[geetest.H5]
captchaId = "bf666f87e5cca24ddb4c1dbeb70912b8"
privateKey = "d1e0bc5eadc754f8cd522c6fed5e175d"
[infoc2]
taskID = "000078"
proto = "tcp"
addr = "172.19.100.20:5401"
chanSize = 1024
[dataBus]
[dataBus.extraAnswer]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "AccAnswer-BigData-P"
topic= "AccAnswer-T"
action="pub"
name = "answer-interface/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[dataBus.accountFormal]
key = "875d05ffa843e297"
secret= "24d052bae2247fc59ee925d46ffd61e2"
group= "Answer-MainAccountLaw-P"
topic= "Answer-T"
action="pub"
name = "answer-interface/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 1
active = 1
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[antispam]
on=true
second=2
n=1
hour=12
m=5
[antispam.redis]
name = "answer"
proto = "tcp"
addr = "172.16.33.54:6379"
idle = 100
active = 100
dialTimeout = "500ms"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
expire = "10m"
[answer]
debug=true
duration=30
blockedTimestamp=0
baseNum = 4
proNum = 5
baseExtraPassNum=5
baseExtraNoPassNum=5
baseExtraScore=2
baseExtraPassCount=3
extraNum = 10
maxRetries = 10
captchaTokenURL = "http://api.bilibili.co/x/internal/v1/captcha/token"
captchaVerifyURL = "http://api.bilibili.co/x/internal/v1/captcha/verify"
[question]
tcQestTick = "1m"
rankQestTick = "10m"
[backoff]
maxDelay = 60
baseDelay = 5
factor = 1.6
jitter = 0.2

View File

@@ -0,0 +1,47 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/interface/main/answer/conf"
"go-common/app/interface/main/answer/http"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/queue/databus/report"
"go-common/library/text/translate/chinese"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
ecode.Init(conf.Conf.Ecode)
chinese.Init()
http.Init(conf.Conf)
report.InitUser(conf.Conf.Report)
// signal handler
log.Info("answer-interface start")
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("answer get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT:
time.Sleep(time.Second * 2)
log.Info("answer-interface exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/interface/main/answer/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/auth:go_default_library",
"//library/net/netutil:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml: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,184 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/log/infoc"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/auth"
"go-common/library/net/netutil"
"go-common/library/net/rpc"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
)
// Conf global variable.
var (
Conf = &Config{}
client *conf.Client
confPath string
)
// Config struct of conf.
type Config struct {
App *bm.App
Host *Host
Log *log.Config
Tracer *trace.Config
Infoc2 *infoc.Config
Ecode *ecode.Config
BM *bm.ServerConfig
HTTPClient *HTTPClient
RPCClient *RPC
DataBus *DataSource
Mysql *sql.Config
Memcache *Memcache
Redis *Redis
AuthN *auth.Config
Antispam *antispam.Config
Geetest *Geetest
Answer *Answer
Question *Question
Backoff *netutil.BackoffConfig
Report *databus.Config
AccountRPC *warden.ClientConfig
Captcha *bm.ClientConfig
}
// RPC config
type RPC struct {
Member *rpc.ClientConfig
Account *rpc.ClientConfig
}
// Answer conf.
type Answer struct {
Captcha bool // true:only use bili captcha
Debug bool
Duration int64
BlockedTimestamp int64
BaseNum int
BaseExtraPassNum int
BaseExtraNoPassNum int
ProNum int
BaseExtraScore int
BaseExtraPassCount int
ExtraNum int
MaxRetries int
CaptchaTokenURL string
CaptchaVerifyURL string
}
// Redis conf.
type Redis struct {
*redis.Config
Expire time.Duration
AnsCountExpire time.Duration
AnsAddFlagCountExpire time.Duration
}
// Memcache conf.
type Memcache struct {
*memcache.Config
Expire time.Duration
AnswerBolckExpire time.Duration
}
// Question conf.
type Question struct {
// question total count tick
TcQestTick time.Duration
RankQestTick time.Duration
}
// HTTPClient conf.
type HTTPClient struct {
Normal *bm.ClientConfig
Slow *bm.ClientConfig
}
// Host conf.
type Host struct {
Geetest string
Account string
ExtraIds string
API string
}
// Geetest geetest id & key
type Geetest struct {
PC GeetestConfig
H5 GeetestConfig
}
// GeetestConfig conf.
type GeetestConfig struct {
CaptchaID string
PrivateKEY string
}
// App bilibili intranet authorization.
type App struct {
Key string
Secret string
}
// DataSource .
type DataSource struct {
ExtraAnswer *databus.Config
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init int config
func Init() error {
if confPath != "" {
return local()
}
return remote()
}

View File

@@ -0,0 +1,73 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"captcha.go",
"dao.go",
"databus.go",
"memcache.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/interface/main/answer/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/xstr: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",
"//app/interface/main/answer/dao/account:all-srcs",
"//app/interface/main/answer/dao/geetest:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"captcha_test.go",
"dao_test.go",
"memcache_test.go",
"mysql_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["pendant_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//vendor/github.com/go-sql-driver/mysql:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["pendant.go"],
importpath = "go-common/app/interface/main/answer/dao/account",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,114 @@
package account
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/app/interface/main/answer/conf"
"go-common/app/interface/main/answer/model"
"go-common/library/cache/memcache"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"github.com/pkg/errors"
)
// Dao is elec dao.
type Dao struct {
c *conf.Config
mc *memcache.Pool
client *bm.Client
pendant string
beFormal string
extraIds string
}
const (
_wasFormal = -659
)
// New pendant dao
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache.Config),
client: bm.NewClient(c.HTTPClient.Normal),
pendant: c.Host.API + _multiGivePendant,
beFormal: c.Host.Account + _beFormal,
extraIds: c.Host.ExtraIds,
}
return
}
const (
_multiGivePendant = "/x/internal/pendant/multiGrantByMid"
_beFormal = "/api/internal/member/beFormal"
)
// GivePendant send user pendant
func (d *Dao) GivePendant(c context.Context, mid int64, pid int64, days int, ip string) (err error) {
log.Info(" GivePendant (%d,%d,%d) ", mid, pid, days)
params := url.Values{}
params.Set("mids", strconv.FormatInt(mid, 10))
params.Set("pid", strconv.FormatInt(pid, 10))
params.Set("expire", strconv.FormatInt(int64(days), 10))
var res struct {
Code int `json:"code"`
}
if err = d.client.Post(c, d.pendant, ip, params, &res); err != nil {
log.Error("pendant url(%s) error(%v)", d.pendant+"?"+params.Encode(), err)
log.Error("GivePendant(%d,%d),err:%+v", mid, pid, err)
return
}
if res.Code != 0 {
err = fmt.Errorf("pendant GivePendant failed(%v)", res.Code)
log.Error(" d.client.Get(%s) error(%v)", d.pendant+"?"+params.Encode(), err)
return
}
return
}
// BeFormal become a full member
func (d *Dao) BeFormal(c context.Context, mid int64, ip string) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
var res struct {
Code int `json:"code"`
}
if err = d.client.Post(c, d.beFormal, ip, params, &res); err != nil {
err = errors.Wrapf(err, "beFormal url(%s)", d.beFormal+"?"+params.Encode())
log.Error("BeFormal(%d),err:%+v", mid, err)
return
}
if res.Code != 0 && res.Code != _wasFormal {
err = errors.WithStack(fmt.Errorf("beFormal(%d) failed(%v)", mid, res.Code))
log.Error("BeFormal(%d),res:%+v", mid, res)
return
}
log.Info("beFormal suc(%d) ", mid)
return
}
// ExtraIds BigData Extra Question ids.
func (d *Dao) ExtraIds(c context.Context, mid int64, ip string) (done []int64, pend []int64, err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
var res struct {
Code int `json:"code"`
Data *model.ExtraBigData
}
if err = d.client.Get(c, d.extraIds, ip, params, &res); err != nil {
log.Error("ExtraIds url(%s) error(%v)", d.extraIds+"?"+params.Encode(), err)
return
}
if res.Code != 0 || res.Data == nil {
err = fmt.Errorf("ExtraIds failed(%v)", res.Code)
log.Error(" d.client.Get(%s) res(%v) error(%v)", d.extraIds+"?"+params.Encode(), res, err)
return
}
done = res.Data.Done
pend = res.Data.Pend
return
}

View File

@@ -0,0 +1,74 @@
package account
import (
"context"
"flag"
"os"
"testing"
"go-common/app/interface/main/answer/conf"
_ "github.com/go-sql-driver/mysql"
. "github.com/smartystreets/goconvey/convey"
)
var d *Dao
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account-law.answer")
flag.Set("conf_appid", "main.account-law.answer")
flag.Set("conf_token", "ba3ee255695e8d7b46782268ddc9c8a3")
flag.Set("tree_id", "25260")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_env", "10")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../../cmd/answer-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
// Test_BeFormal .
func Test_BeFormal(t *testing.T) {
Convey("Test_BeFormal", t, func() {
var (
c = context.Background()
mid = int64(100000245)
)
d.BeFormal(c, mid, "127.0.0.1")
})
}
// Test_GivePendant .
func Test_GivePendant(t *testing.T) {
Convey("Test_GivePendant", t, func() {
err := d.GivePendant(context.Background(), 21432418, 1, 1, "127.0.0.1")
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestDaoExtraIds
func Test_ExtraIds(t *testing.T) {
Convey("Test_GivePendant", t, func() {
var (
err error
c = context.Background()
mid = int64(100000245)
ds, ps []int64
)
ds, ps, err = d.ExtraIds(c, mid, "127.0.0.1")
So(err, ShouldBeNil)
So(ds, ShouldNotBeNil)
So(ps, ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,56 @@
package dao
import (
"context"
"net/url"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
// Captcha Captcha.
func (d *Dao) Captcha(c context.Context) (string, string, error) {
params := url.Values{}
params.Set("bid", "answer")
res := &struct {
Code int `json:"code"`
Data struct {
Token string `json:"token"`
URL string `json:"url"`
} `json:"data"`
Msg string `json:"message"`
TTL int `json:"ttl"`
}{}
ip := metadata.String(c, metadata.RemoteIP)
if err := d.captcha.Get(c, d.c.Answer.CaptchaTokenURL, ip, params, res); err != nil {
log.Error("s.captcha.Get(%s) error(%v)", d.c.Answer.CaptchaTokenURL+"?"+params.Encode(), err)
return "", "", err
}
if res.Code != 0 {
log.Error("s.captcha.Get(%s?%s) code:%d", d.c.Answer.CaptchaTokenURL, params.Encode(), res.Code)
return "", "", ecode.Int(res.Code)
}
return res.Data.Token, res.Data.URL, nil
}
// Verify Verify.
func (d *Dao) Verify(c context.Context, token, code, ip string) error {
params := url.Values{}
params.Set("token", token)
params.Set("code", code)
res := &struct {
Code int `json:"code"`
Msg string `json:"message"`
TTL int `json:"ttl"`
}{}
if err := d.captcha.Post(c, d.c.Answer.CaptchaVerifyURL, ip, params, res); err != nil {
log.Error("s.captcha.POST(%s) error(%v)", d.c.Answer.CaptchaVerifyURL+"?"+params.Encode(), err)
return err
}
if res.Code != 0 {
log.Error("s.captcha.POST(%s?%s) code:%d", d.c.Answer.CaptchaVerifyURL, params.Encode(), res.Code)
return ecode.Int(res.Code)
}
return nil
}

View File

@@ -0,0 +1,40 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestCaptcha(t *testing.T) {
convey.Convey("Captcha", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, p2, err := d.Captcha(c)
ctx.Convey("Then err should be nil.p1,p2 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p2, convey.ShouldNotBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestVerify(t *testing.T) {
convey.Convey("Verify", t, func(ctx convey.C) {
var (
c = context.Background()
token = ""
code = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.Verify(c, token, code, "")
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,58 @@
package dao
import (
"time"
"go-common/app/interface/main/answer/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/sql"
httpx "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
)
// Dao struct answer history of Dao
type Dao struct {
c *conf.Config
db *sql.DB
redis *redis.Pool
mc *memcache.Pool
mcExpire int32
redisExpire int32
ansCountExpire int32
ansAddFlagExpire int32
answerBlockExpire int32
dbExtraAnswerRet *databus.Databus
captcha *httpx.Client
}
// New new a Dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
db: sql.NewMySQL(c.Mysql),
redis: redis.NewPool(c.Redis.Config),
mc: memcache.NewPool(c.Memcache.Config),
redisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),
ansCountExpire: int32(time.Duration(c.Redis.AnsCountExpire) / time.Second),
ansAddFlagExpire: int32(time.Duration(c.Redis.AnsCountExpire) / time.Second),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
answerBlockExpire: int32(time.Duration(c.Memcache.AnswerBolckExpire) / time.Second),
dbExtraAnswerRet: databus.New(c.DataBus.ExtraAnswer),
captcha: httpx.NewClient(c.Captcha),
}
return
}
// Close close connections of mc, redis, db.
func (d *Dao) Close() {
if d.redis != nil {
d.redis.Close()
}
if d.mc != nil {
d.mc.Close()
}
if d.db != nil {
d.db.Close()
}
}

View File

@@ -0,0 +1,38 @@
package dao
import (
"flag"
"os"
"testing"
"go-common/app/interface/main/answer/conf"
)
var (
d *Dao
mid int64 = 7593623
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account-law.answer")
flag.Set("conf_appid", "main.account-law.answer")
flag.Set("conf_token", "ba3ee255695e8d7b46782268ddc9c8a3")
flag.Set("tree_id", "25260")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_env", "10")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/answer-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,17 @@
package dao
import (
"context"
"strconv"
"go-common/library/log"
)
// PubExtraRet pub extra msg into databus.
func (d *Dao) PubExtraRet(c context.Context, mid int64, msg interface{}) (err error) {
key := strconv.FormatInt(mid, 10)
if err = d.dbExtraAnswerRet.Send(c, key, msg); err != nil {
log.Error("d.dbExtraAnswerRet.Send(%s, %v) error (%v)", key, msg, err)
}
return
}

View File

@@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["dao_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//vendor/github.com/bouk/monkey:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/gopkg.in/h2non/gock.v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["dao.go"],
importpath = "go-common/app/interface/main/answer/dao/geetest",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/model:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster: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,151 @@
package geetest
import (
"context"
"crypto/tls"
"errors"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"go-common/app/interface/main/answer/conf"
"go-common/app/interface/main/answer/model"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
const (
_register = "/register.php"
_validate = "/validate.php"
)
// Dao is account dao.
type Dao struct {
c *conf.Config
// url
registerURI string
validateURI string
// http client
client *http.Client
clientx *bm.Client
}
// New new a dao.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
registerURI: c.Host.Geetest + _register,
validateURI: c.Host.Geetest + _validate,
// http client
client: NewClient(c.HTTPClient),
clientx: bm.NewClient(c.HTTPClient.Slow),
}
return
}
// PreProcess preprocessing the geetest and get to challenge
func (d *Dao) PreProcess(c context.Context, mid int64, ip, geeType string, gc conf.GeetestConfig, newCaptcha int) (challenge string, err error) {
var (
req *http.Request
res *http.Response
bs []byte
params url.Values
)
params = url.Values{}
params.Set("user_id", strconv.FormatInt(mid, 10))
params.Set("new_captcha", strconv.Itoa(newCaptcha))
params.Set("ip_address", ip)
params.Set("client_type", geeType)
params.Set("gt", gc.CaptchaID)
if req, err = http.NewRequest("GET", d.registerURI+"?"+params.Encode(), nil); err != nil {
log.Error("d.preprocess uri(%s) params(%s) error(%v)", d.validateURI, params.Encode(), err)
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if res, err = d.client.Do(req); err != nil {
log.Error("client.Do(%s) error(%v)", d.validateURI+"?"+params.Encode(), err)
return
}
defer res.Body.Close()
if res.StatusCode >= http.StatusInternalServerError {
err = errors.New("http status code 5xx")
log.Error("gtServerErr uri(%s) error(%v)", d.validateURI+"?"+params.Encode(), err)
return
}
if bs, err = ioutil.ReadAll(res.Body); err != nil {
log.Error("ioutil.ReadAll(%s) uri(%s) error(%v)", bs, d.validateURI+"?"+params.Encode(), err)
return
}
if len(bs) != 32 {
log.Error("d.preprocess len(%s) the length not equate 32byte", string(bs))
return
}
challenge = string(bs)
return
}
// Validate recheck the challenge code and get to seccode
func (d *Dao) Validate(c context.Context, challenge, seccode, clientType, ip, captchaID string, mid int64) (res *model.ValidateRes, err error) {
params := url.Values{}
params.Set("seccode", seccode)
params.Set("challenge", challenge)
params.Set("captchaid", captchaID)
params.Set("client_type", clientType)
params.Set("ip_address", ip)
params.Set("json_format", "1")
params.Set("sdk", "golang_3.0.0")
params.Set("user_id", strconv.FormatInt(mid, 10))
params.Set("timestamp", strconv.FormatInt(time.Now().Unix(), 10))
req, err := http.NewRequest("POST", d.validateURI, strings.NewReader(params.Encode()))
if err != nil {
log.Error("http.NewRequest error(%v) | uri(%s) params(%s)", err, d.validateURI, params.Encode())
return
}
log.Info("Validate(%v) start", params)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if err = d.clientx.Do(c, req, &res); err != nil {
log.Error("d.client.Do error(%v) | uri(%s) params(%s) res(%v)", err, d.validateURI, params.Encode(), res)
return
}
log.Info("Validate(%v) suc", res)
return
}
// NewClient new a http client.
func NewClient(c *conf.HTTPClient) (client *http.Client) {
var (
transport *http.Transport
dialer *net.Dialer
)
dialer = &net.Dialer{
Timeout: time.Duration(c.Slow.Dial),
KeepAlive: time.Duration(c.Slow.KeepAlive),
}
transport = &http.Transport{
DialContext: dialer.DialContext,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{
Transport: transport,
}
return
}
// GeeConfig get geetest config.
func (d *Dao) GeeConfig(t string, c *conf.Geetest) (gc conf.GeetestConfig, geetype string) {
switch t {
case "pc":
gc = c.PC
case "h5":
gc = c.H5
default:
gc = c.PC
}
geetype = "web"
return
}

View File

@@ -0,0 +1,237 @@
package geetest
import (
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
"go-common/app/interface/main/answer/conf"
"github.com/bouk/monkey"
"github.com/smartystreets/goconvey/convey"
gock "gopkg.in/h2non/gock.v1"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account-law.answer")
flag.Set("conf_appid", "main.account-law.answer")
flag.Set("conf_token", "ba3ee255695e8d7b46782268ddc9c8a3")
flag.Set("tree_id", "25260")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_env", "10")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../../cmd/answer-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
return r
}
func TestDaoPreProcess(t *testing.T) {
var (
c = context.Background()
mid = int64(2205)
ip = "127.0.0,1"
geeType = "1222"
gc = conf.GeetestConfig{CaptchaID: "22"}
newCaptcha = 1
)
convey.Convey("PreProcess", t, func(ctx convey.C) {
ctx.Convey("req, err = http.NewRequest;err!=nil", func(ctx convey.C) {
monkey.Patch(http.NewRequest, func(_ string, _ string, _ io.Reader) (_ *http.Request, _ error) {
return nil, fmt.Errorf("Error")
})
_, err := d.PreProcess(c, mid, ip, geeType, gc, newCaptcha)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
ctx.Convey("req, err = http.NewRequest;err==nil", func(ctx convey.C) {
ctx.Convey("res, err = d.client.Do; err != nil", func(ctx convey.C) {
monkey.PatchInstanceMethod(reflect.TypeOf(d.client), "Do", func(_ *http.Client, _ *http.Request) (_ *http.Response, _ error) {
return nil, fmt.Errorf("Error")
})
_, err := d.PreProcess(c, mid, ip, geeType, gc, newCaptcha)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
ctx.Convey("res, err = d.client.Do; err == nil", func(ctx convey.C) {
params := url.Values{}
params.Set("user_id", strconv.FormatInt(mid, 10))
params.Set("new_captcha", strconv.Itoa(newCaptcha))
params.Set("ip_address", ip)
params.Set("client_type", geeType)
params.Set("gt", gc.CaptchaID)
d.client.Transport = gock.DefaultTransport
ctx.Convey("res.StatusCode >= http.StatusInternalServerError", func(ctx convey.C) {
httpMock("GET", d.registerURI+"?"+params.Encode()).Reply(501)
_, err := d.PreProcess(c, mid, ip, geeType, gc, newCaptcha)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
ctx.Convey("res.StatusCode < http.StatusInternalServerError", func(ctx convey.C) {
httpMock("GET", d.registerURI+"?"+params.Encode()).Reply(200).SetHeaders(map[string]string{
"StatusCode": "200",
})
ctx.Convey("bs, err = ioutil.ReadAll(res.Body); err != nil ", func(ctx convey.C) {
monkey.Patch(ioutil.ReadAll, func(_ io.Reader) (_ []byte, _ error) {
return nil, fmt.Errorf("Error")
})
_, err := d.PreProcess(c, mid, ip, geeType, gc, newCaptcha)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
ctx.Convey("bs, err = ioutil.ReadAll(res.Body); err == nil ", func(ctx convey.C) {
ctx.Convey("len(bs) != 32 ", func(ctx convey.C) {
challenge, err := d.PreProcess(c, mid, ip, geeType, gc, newCaptcha)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(challenge, convey.ShouldBeEmpty)
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("len(bs) == 32 ", func(ctx convey.C) {
var (
str = "testeeeeeeeeeeyyyyyyyyyyyyrrrrrr"
bs = []byte(str)
)
monkey.Patch(ioutil.ReadAll, func(_ io.Reader) (_ []byte, _ error) {
return bs, nil
})
challenge, err := d.PreProcess(c, mid, ip, geeType, gc, newCaptcha)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(challenge, convey.ShouldNotBeNil)
ctx.So(err, convey.ShouldBeNil)
})
})
})
})
})
})
ctx.Reset(func() {
gock.OffAll()
d.client.Transport = http.DefaultClient.Transport
monkey.UnpatchAll()
})
})
}
func TestDaoValidate(t *testing.T) {
var (
c = context.Background()
challenge = "1"
seccode = "127.0.0,1"
clientType = ""
ip = "1222"
captchaID = "22"
mid = int64(14771787)
)
convey.Convey("Validate", t, func(ctx convey.C) {
ctx.Convey("req, err = http.NewRequest;err!=nil", func(ctx convey.C) {
monkey.Patch(http.NewRequest, func(_ string, _ string, _ io.Reader) (_ *http.Request, _ error) {
return nil, fmt.Errorf("Error")
})
_, err := d.Validate(c, challenge, seccode, clientType, ip, captchaID, mid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
ctx.Convey("req, err = http.NewRequest;err==nil", func(ctx convey.C) {
ctx.Convey("res, err = d.client.Do; err != nil", func(ctx convey.C) {
monkey.PatchInstanceMethod(reflect.TypeOf(d.client), "Do", func(_ *http.Client, _ *http.Request) (_ *http.Response, _ error) {
return nil, fmt.Errorf("Error")
})
_, err := d.Validate(c, challenge, seccode, clientType, ip, captchaID, mid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
ctx.Convey("res, err = d.client.Do; err == nil", func(ctx convey.C) {
params := url.Values{}
params.Set("seccode", seccode)
params.Set("challenge", challenge)
params.Set("captchaid", captchaID)
params.Set("client_type", clientType)
params.Set("ip_address", ip)
params.Set("json_format", "1")
params.Set("sdk", "golang_3.0.0")
params.Set("user_id", strconv.FormatInt(mid, 10))
params.Set("timestamp", strconv.FormatInt(time.Now().Unix(), 10))
// *bm.Client
d.clientx.SetTransport(gock.DefaultTransport)
httpMock("POST", d.validateURI).Reply(200).JSON(`{"code":0}`)
_, err := d.Validate(c, challenge, seccode, clientType, ip, captchaID, mid)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
ctx.Reset(func() {
gock.OffAll()
d.clientx.SetTransport(http.DefaultClient.Transport)
monkey.UnpatchAll()
})
})
}
func TestDaoGeeConfig(t *testing.T) {
var gtc = &conf.Geetest{PC: conf.GeetestConfig{CaptchaID: "22", PrivateKEY: "123"}, H5: conf.GeetestConfig{CaptchaID: "22", PrivateKEY: "456"}}
convey.Convey("GeeConfi", t, func(ctx convey.C) {
ctx.Convey("t=pc", func(ctx convey.C) {
var t = "pc"
gc, geetype := d.GeeConfig(t, gtc)
ctx.Convey("gc=gtc.PC,geetype =web", func(ctx convey.C) {
ctx.So(gc, convey.ShouldResemble, gtc.PC)
ctx.So(geetype, convey.ShouldResemble, "web")
})
})
ctx.Convey("t=h5", func(ctx convey.C) {
var t = "h5"
gc, geetype := d.GeeConfig(t, gtc)
ctx.Convey("gc=gtc.H5,geetype =web", func(ctx convey.C) {
ctx.So(gc, convey.ShouldResemble, gtc.H5)
ctx.So(geetype, convey.ShouldResemble, "web")
})
})
ctx.Convey("t=", func(ctx convey.C) {
var t = ""
gc, geetype := d.GeeConfig(t, gtc)
ctx.Convey("gc=gtc.PC,geetype =web", func(ctx convey.C) {
ctx.So(gc, convey.ShouldResemble, gtc.PC)
ctx.So(geetype, convey.ShouldResemble, "web")
})
})
})
}

View File

@@ -0,0 +1,275 @@
package dao
import (
"context"
"fmt"
"go-common/app/interface/main/answer/model"
"go-common/library/cache/memcache"
"go-common/library/log"
)
const (
_answerTimeKey = "v3_at_%d" // key of user's answer limit time
_answerHistoryKey = "v3_ah_%d" // key of user's answer history
_answerQidListKey = "v3_aqbi_%d"
_answerExtraQidListKey = "v3_aql_%d_%d"
_answerBlockKey = "v3_ablk_%d" // key of user's answer block flag
_answerHistory = "hid_%d" // ah_hid
)
var (
_blockFlag = []byte("1")
)
func answerQidListKey(mid int64, t int8) (key string) {
switch t {
case model.BaseExtraPassQ:
key = fmt.Sprintf(_answerExtraQidListKey, t, mid)
case model.BaseExtraNoPassQ:
key = fmt.Sprintf(_answerExtraQidListKey, t, mid)
default:
key = fmt.Sprintf(_answerQidListKey, mid)
}
return
}
// pingMC ping memcache.
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{
Key: "ping",
Value: []byte{1},
Expiration: d.mcExpire,
}); err != nil {
log.Error("conn.Set(ping, 1) error(%v)", err)
}
return
}
// ExpireCache get user's answer stime and base answer error times cache.
func (d *Dao) ExpireCache(c context.Context, mid int64) (at *model.AnswerTime, err error) {
key := fmt.Sprintf(_answerTimeKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("conn.Get(%s) error(%v)", key, err)
return
}
at = &model.AnswerTime{}
if err = conn.Scan(item, at); err != nil {
log.Error("conn.Scan(%s) error(%v)", string(item.Value), err)
}
return
}
// SetExpireCache set user's answer stime and base answer error times cache.
func (d *Dao) SetExpireCache(c context.Context, mid int64, at *model.AnswerTime) (err error) {
key := fmt.Sprintf(_answerTimeKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{
Key: key,
Object: at,
Flags: memcache.FlagJSON,
Expiration: d.mcExpire,
}); err != nil {
log.Error("conn.Set(%s, %v) error(%v)", key, at, err)
}
return
}
// DelExpireCache delete user's answer stime and base answer error times cache.
func (d *Dao) DelExpireCache(c context.Context, mid int64) (err error) {
key := fmt.Sprintf(_answerTimeKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("conn.Delete(%s) error(%v)", key, err)
}
return
}
// HistoryCache get user's answer history cache
func (d *Dao) HistoryCache(c context.Context, mid int64) (ah *model.AnswerHistory, err error) {
key := fmt.Sprintf(_answerHistoryKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("conn.Get(%s) error(%v)", key, err)
return
}
ah = &model.AnswerHistory{}
if err = conn.Scan(item, ah); err != nil {
log.Error("conn.Scan(%s) error(%v)", string(item.Value), err)
}
return
}
// SetHistoryCache set user's answer history cache
func (d *Dao) SetHistoryCache(c context.Context, mid int64, ah *model.AnswerHistory) (err error) {
key := fmt.Sprintf(_answerHistoryKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{
Key: key,
Object: ah,
Flags: memcache.FlagJSON,
Expiration: d.mcExpire,
}); err != nil {
log.Error("conn.Set(%s, %v) error(%v)", key, ah, err)
}
return
}
// DelHistoryCache delete user's answer history cache
func (d *Dao) DelHistoryCache(c context.Context, mid int64) (err error) {
key := fmt.Sprintf(_answerHistoryKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("DelHistoryCache(%d),err:%+v", mid, err)
}
return
}
// IdsCache get user's base question ids
func (d *Dao) IdsCache(c context.Context, mid int64, t int8) (ids []int64, err error) {
key := answerQidListKey(mid, t)
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("d.IdsCache(%d,%d) error(%v)", mid, t, err)
return
}
if err = conn.Scan(item, &ids); err != nil {
log.Error("conn.Scan(%s) error(%v)", string(item.Value), err)
}
return
}
// SetIdsCache set user's base question ids
func (d *Dao) SetIdsCache(c context.Context, mid int64, ids []int64, t int8) (err error) {
key := answerQidListKey(mid, t)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{
Key: key,
Object: ids,
Flags: memcache.FlagJSON,
Expiration: d.mcExpire,
}); err != nil {
log.Error("conn.Set(%s, %v) error(%v)", key, ids, err)
}
return
}
// DelIdsCache delete user's base question ids
func (d *Dao) DelIdsCache(c context.Context, mid int64, t int8) (err error) {
key := answerQidListKey(mid, t)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("DelIdsCache(%d,%d),err:%+v", mid, t, err)
}
return
}
// SetBlockCache set user's block.
func (d *Dao) SetBlockCache(c context.Context, mid int64) (err error) {
key := fmt.Sprintf(_answerBlockKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{
Key: key,
Value: _blockFlag,
Expiration: d.answerBlockExpire,
}); err != nil {
log.Error("conn.Store(%s, %v) error(%v)", key, string(_blockFlag), err)
}
return
}
// CheckBlockCache check user's block.
func (d *Dao) CheckBlockCache(c context.Context, mid int64) (exist bool, err error) {
key := fmt.Sprintf(_answerBlockKey, mid)
conn := d.mc.Get(c)
defer conn.Close()
_, err = conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("conn.Get(%s) error(%v)", key, err)
return
}
exist = true
return
}
// HidCache get user's answer history cache
func (d *Dao) HidCache(c context.Context, hid int64) (ah *model.AnswerHistory, err error) {
key := fmt.Sprintf(_answerHistory, hid)
conn := d.mc.Get(c)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
log.Error("conn.Get(%s) error(%v)", key, err)
return
}
ah = &model.AnswerHistory{}
if err = conn.Scan(item, ah); err != nil {
log.Error("conn.Scan(%s) error(%v)", string(item.Value), err)
}
return
}
// SetHidCache set user's answer history cache
func (d *Dao) SetHidCache(c context.Context, ah *model.AnswerHistory) (err error) {
key := fmt.Sprintf(_answerHistory, ah.Hid)
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{
Key: key,
Object: ah,
Flags: memcache.FlagJSON,
Expiration: d.mcExpire,
}); err != nil {
log.Error("conn.Set(%s, %v) error(%v)", key, ah, err)
}
return
}

View File

@@ -0,0 +1,110 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/interface/main/answer/model"
"github.com/smartystreets/goconvey/convey"
)
func TestAnswerQidListKey(t *testing.T) {
convey.Convey("answerQidListKey", t, func() {
key := answerQidListKey(7593623, model.BaseExtraPassQ)
convey.So(key, convey.ShouldNotBeNil)
})
}
func TestDaopingMC(t *testing.T) {
convey.Convey("pingMC", t, func() {
err := d.pingMC(context.Background())
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoSetExpireCache(t *testing.T) {
at := &model.AnswerTime{Etimes: 1}
convey.Convey("SetExpireCache", t, func() {
err := d.SetExpireCache(context.Background(), mid, at)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("ExpireCache", t, func() {
at, err := d.ExpireCache(context.Background(), mid)
convey.So(err, convey.ShouldBeNil)
convey.So(at, convey.ShouldNotBeNil)
})
}
func TestDaoDelExpireCache(t *testing.T) {
convey.Convey("DelExpireCache", t, func() {
err := d.DelExpireCache(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoSetHistoryCache(t *testing.T) {
his := &model.AnswerHistory{Hid: time.Now().Unix()}
convey.Convey("SetHistoryCache", t, func() {
err := d.SetHistoryCache(context.Background(), mid, his)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("HistoryCache", t, func() {
ah, err := d.HistoryCache(context.Background(), mid)
convey.So(err, convey.ShouldBeNil)
convey.So(ah, convey.ShouldNotBeNil)
})
}
func TestDaoSetIdsCache(t *testing.T) {
convey.Convey("SetIdsCache", t, func() {
err := d.SetIdsCache(context.Background(), mid, []int64{1, 2, 3}, 0)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("IdsCache", t, func() {
ids, err := d.IdsCache(context.Background(), mid, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ids, convey.ShouldNotBeNil)
})
convey.Convey("DelHistoryCache", t, func() {
err := d.DelHistoryCache(context.Background(), mid)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoDelIdsCache(t *testing.T) {
convey.Convey("DelIdsCache", t, func() {
err := d.DelIdsCache(context.Background(), 0, 0)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoSetBlockCache(t *testing.T) {
convey.Convey("SetBlockCache", t, func() {
err := d.SetBlockCache(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoCheckBlockCache(t *testing.T) {
convey.Convey("CheckBlockCache", t, func() {
exist, err := d.CheckBlockCache(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(exist, convey.ShouldNotBeNil)
})
}
func TestDaoHidCache(t *testing.T) {
hid := time.Now().Unix()
his := &model.AnswerHistory{Hid: hid}
convey.Convey("SetHidCache", t, func() {
err := d.SetHidCache(context.Background(), his)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("HidCache", t, func() {
ah, err := d.HidCache(context.Background(), hid)
convey.So(err, convey.ShouldBeNil)
convey.So(ah, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,319 @@
package dao
import (
"context"
xsql "database/sql"
"fmt"
"math/rand"
"time"
"go-common/app/interface/main/answer/model"
"go-common/library/database/sql"
"go-common/library/log"
"go-common/library/xstr"
"github.com/pkg/errors"
)
const (
_shard = 10
_getAnswerHistorySQL = "SELECT id,hid,mid,start_time,step_one_err_num,step_one_complete_time,step_two_start_time,complete_time,complete_result,score,is_pass_captcha,is_first_pass,passed_level,rank_id,step_extra_start_time,step_extra_complete_time,extra_score FROM answer_history_%d WHERE mid=? ORDER BY id DESC LIMIT 1"
_getHistoryByHidSQL = "SELECT id,hid,mid,start_time,step_one_err_num,step_one_complete_time,step_two_start_time,complete_time,complete_result,score,is_pass_captcha,is_first_pass,passed_level,rank_id,step_extra_start_time,step_extra_complete_time,extra_score FROM answer_history_%d WHERE hid=? ORDER BY id DESC LIMIT 1"
_sharingIndexSQL = "SELECT table_index FROM answer_history_mapping WHERE hid = ?"
_addAnswerHistorySQL = "INSERT INTO answer_history_%d (hid,mid,start_time,step_one_err_num,step_one_complete_time) VALUES (?,?,?,?,?)"
_setAnswerHistorySQL = "UPDATE answer_history_%d SET complete_result=?,complete_time=?,score=?,is_first_pass=?,passed_level=?,rank_id=? WHERE id=?"
_updateAnswerLevelSQL = "UPDATE answer_history_%d SET is_first_pass = ?,passed_level = ? WHERE id = ? "
_updateCaptchaSQL = "UPDATE answer_history_%d SET is_pass_captcha = ? WHERE id = ? "
_updateStepTwoTimeSQL = "UPDATE answer_history_%d SET step_two_start_time = ? WHERE id = ? "
_updateExtraStartTimeSQL = "UPDATE answer_history_%d SET step_extra_start_time = ? WHERE id = ? "
_updateExtraRetSQL = "UPDATE answer_history_%d SET step_extra_complete_time = ?,extra_score = ? WHERE id = ? "
_pendanHistorySQL = "SELECT hid,status FROM answer_pendant_history WHERE mid = ?"
_inPendantHistorySQL = "INSERT INTO answer_pendant_history (hid,mid) VALUES (?,?)"
_upPendantHistorySQL = "UPDATE answer_pendant_history SET status = 1 WHERE mid = ? AND hid = ? AND status = 0"
_allTypesSQL = "SELECT id,typename,parentid,lablename FROM ans_v3_question_type ORDER BY parentid;"
_questionByIdsSQL = "SELECT id,type_id,question,ans1,ans2,ans3,ans4,mid FROM ans_v3_question WHERE id IN (%s)"
_questionTypeSQL = "SELECT id,type_id FROM ans_v3_question WHERE state = ?"
_questionExtraByIdsSQL = "SELECT id,question,ans,status,origin_id,av_id,source,ctime,mtime FROM answer_extra_question WHERE state = 1 AND id IN (%s)"
_questionExtraTypeSQL = "SELECT id,ans FROM answer_extra_question WHERE isdel = 1 and state = 1 limit ?"
)
func hit(id int64) int64 {
return id % _shard
}
// PendantHistory .
func (d *Dao) PendantHistory(c context.Context, mid int64) (hid int64, status int8, err error) {
row := d.db.QueryRow(c, _pendanHistorySQL, mid)
if err = row.Scan(&hid, &status); err != nil {
if err == sql.ErrNoRows {
return 0, 0, nil
}
log.Error("PendantHistory(%d),error:%+v", mid, err)
return 0, 0, errors.WithStack(err)
}
return hid, status, nil
}
// History get user's answer history by mid.
func (d *Dao) History(c context.Context, mid int64) (res *model.AnswerHistory, err error) {
res = &model.AnswerHistory{}
row := d.db.QueryRow(c, fmt.Sprintf(_getAnswerHistorySQL, hit(mid)), mid)
if err = row.Scan(&res.ID, &res.Hid, &res.Mid, &res.StartTime, &res.StepOneErrTimes, &res.StepOneCompleteTime, &res.StepTwoStartTime, &res.CompleteTime,
&res.CompleteResult, &res.Score, &res.IsPassCaptcha, &res.IsFirstPass, &res.PassedLevel, &res.RankID, &res.StepExtraStartTime, &res.StepExtraCompleteTime, &res.StepExtraScore); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("History(%d),error:%+v", mid, err)
}
}
return
}
// HistoryByHid get user's answer history by mid.
func (d *Dao) HistoryByHid(c context.Context, hid int64) (res *model.AnswerHistory, err error) {
res = &model.AnswerHistory{}
row := d.db.QueryRow(c, fmt.Sprintf(_getHistoryByHidSQL, hit(hid)), hid)
if err = row.Scan(&res.ID, &res.Hid, &res.Mid, &res.StartTime, &res.StepOneErrTimes, &res.StepOneCompleteTime, &res.StepTwoStartTime, &res.CompleteTime,
&res.CompleteResult, &res.Score, &res.IsPassCaptcha, &res.IsFirstPass, &res.PassedLevel, &res.RankID, &res.StepExtraStartTime, &res.StepExtraCompleteTime, &res.StepExtraScore); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("HistoryByHid(%d),error:%+v", hid, err)
}
}
return
}
// OldHistory get user's answer history by hid and sharing index.
func (d *Dao) OldHistory(c context.Context, hid, idx int64) (res *model.AnswerHistory, err error) {
res = &model.AnswerHistory{}
row := d.db.QueryRow(c, fmt.Sprintf(_getHistoryByHidSQL, idx), hid)
if err = row.Scan(&res.ID, &res.Hid, &res.Mid, &res.StartTime, &res.StepOneErrTimes, &res.StepOneCompleteTime, &res.StepTwoStartTime, &res.CompleteTime,
&res.CompleteResult, &res.Score, &res.IsPassCaptcha, &res.IsFirstPass, &res.PassedLevel, &res.RankID, &res.StepExtraStartTime, &res.StepExtraCompleteTime, &res.StepExtraScore); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("OldHistory(%d,%d),error:%+v", hid, idx, err)
}
}
return
}
// SharingIndexByHid get old history sharingIndex by hid
func (d *Dao) SharingIndexByHid(c context.Context, hid int64) (res int64, err error) {
row := d.db.QueryRow(c, _sharingIndexSQL, hid)
if err = row.Scan(&res); err != nil {
if err == sql.ErrNoRows {
err = nil
} else {
log.Error("OldHistory(%d),error:%+v", hid, err)
}
}
return
}
// AddPendantHistory .
func (d *Dao) AddPendantHistory(c context.Context, mid, hid int64) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _inPendantHistorySQL, hid, mid); err != nil {
log.Error("AddPendantHistory(%d,%d),error:%+v", mid, hid, err)
return
}
return res.RowsAffected()
}
// SetHistory set user's answer history by id.
func (d *Dao) SetHistory(c context.Context, mid int64, his *model.AnswerHistory) (affected int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_setAnswerHistorySQL, hit(mid)), his.CompleteResult, his.CompleteTime, his.Score, his.IsFirstPass, his.PassedLevel, his.RankID, his.ID); err != nil {
log.Error("setAnswerHistory: db.Exec(%d, %v) error(%v)", mid, his, err)
return
}
return res.RowsAffected()
}
// AddHistory add user's answer history by id.
func (d *Dao) AddHistory(c context.Context, mid int64, his *model.AnswerHistory) (affected int64, hid string, err error) {
var res xsql.Result
hid = fmt.Sprintf("%d%04d%02d", time.Now().Unix(), rand.Intn(9999), hit(mid))
if res, err = d.db.Exec(c, fmt.Sprintf(_addAnswerHistorySQL, hit(mid)), hid, his.Mid, his.StartTime, his.StepOneErrTimes, his.StepOneCompleteTime); err != nil {
log.Error("addAnswerHistory: db.Exec(%d, %v) error(%v)", mid, his, err)
return
}
affected, err = res.RowsAffected()
return
}
// UpdateLevel update answer history passedLevel and isFirstPass.
func (d *Dao) UpdateLevel(c context.Context, id int64, mid int64, isFirstPass, passedLevel int8) (ret int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_updateAnswerLevelSQL, hit(mid)), isFirstPass, passedLevel, id); err != nil {
log.Error("UpdateLevel: db.Exec(%d,%d,%d,%d) error(%v)", id, mid, isFirstPass, passedLevel, err)
return
}
return res.RowsAffected()
}
// UpdateCaptcha update answer history captcha.
func (d *Dao) UpdateCaptcha(c context.Context, id int64, mid int64, isPassCaptcha int8) (ret int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_updateCaptchaSQL, hit(mid)), isPassCaptcha, id); err != nil {
log.Error("UpdateCaptcha: db.Exec(%d, %d, %d) error(%v)", id, mid, isPassCaptcha, err)
return
}
return res.RowsAffected()
}
// UpdateStepTwoTime .
func (d *Dao) UpdateStepTwoTime(c context.Context, id int64, mid int64, t time.Time) (ret int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_updateStepTwoTimeSQL, hit(mid)), t, id); err != nil {
log.Error("UpdateCaptcha: db.Exec(%d,%d,%s) error(%v)", id, mid, t, err)
return
}
return res.RowsAffected()
}
// UpdateExtraStartTime update extra start time.
func (d *Dao) UpdateExtraStartTime(c context.Context, id int64, mid int64, t time.Time) (ret int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_updateExtraStartTimeSQL, hit(mid)), t, id); err != nil {
log.Error("updateExtraStartTime: db.Exec(%d, %d, %s) error(%v)", id, mid, t, err)
return
}
return res.RowsAffected()
}
// UpdateExtraRet update extra start time.
func (d *Dao) UpdateExtraRet(c context.Context, id int64, mid int64, t int64, extraScore int64) (ret int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, fmt.Sprintf(_updateExtraRetSQL, hit(mid)), t, extraScore, id); err != nil {
log.Error("updateExtraRetSQL: db.Exec(%d, %d, %d, %d) error(%v)", id, mid, t, extraScore, err)
return
}
return res.RowsAffected()
}
// UpPendantHistory update pendant history.
func (d *Dao) UpPendantHistory(c context.Context, mid, hid int64) (ret int64, err error) {
var res xsql.Result
if res, err = d.db.Exec(c, _upPendantHistorySQL, mid, hid); err != nil {
log.Error("UpPendantHistory(%d,%d),error:%+v", mid, hid, err)
return
}
return res.RowsAffected()
}
// QidsExtraByState get extra question ids by check
func (d *Dao) QidsExtraByState(c context.Context, size int) (res []*model.ExtraQst, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _questionExtraTypeSQL, size); err != nil {
return
}
defer rows.Close()
for rows.Next() {
r := new(model.ExtraQst)
if err = rows.Scan(&r.ID, &r.Ans); err != nil {
log.Error("QidsExtraByState(%d),error:%+v", size, err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// ExtraByIds get extra question in idstr
func (d *Dao) ExtraByIds(c context.Context, ids []int64) (res map[int64]*model.ExtraQst, err error) {
var rows *sql.Rows
res = make(map[int64]*model.ExtraQst, len(ids))
idStr := xstr.JoinInts(ids)
if rows, err = d.db.Query(c, fmt.Sprintf(_questionExtraByIdsSQL, idStr)); err != nil {
log.Error("d.questionExtraByIds.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.ExtraQst)
if err = rows.Scan(&r.ID, &r.Question, &r.Ans, &r.Status, &r.OriginID, &r.AvID, &r.Source, &r.Ctime, &r.Mtime); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res[r.ID] = r
}
err = rows.Err()
return
}
// Types get all types
func (d *Dao) Types(c context.Context) (res []*model.TypeInfo, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _allTypesSQL); err != nil {
log.Error("d.allTypesStmt.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.TypeInfo)
if err = rows.Scan(&r.ID, &r.Name, &r.Parentid, &r.LabelName); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// ByIds get question in idStr
func (d *Dao) ByIds(c context.Context, ids []int64) (res map[int64]*model.Question, err error) {
var rows *sql.Rows
res = make(map[int64]*model.Question, len(ids))
idStr := xstr.JoinInts(ids)
if rows, err = d.db.Query(c, fmt.Sprintf(_questionByIdsSQL, idStr)); err != nil {
log.Error("d.queryQuestionByIdsStmt.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.Question)
ans := make([]string, 4)
if err = rows.Scan(&r.ID, &r.TypeID, &r.Question, &ans[0], &ans[1], &ans[2], &ans[3], &r.Mid); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
r.Ans = ans
res[r.ID] = r
}
err = rows.Err()
return
}
// QidsByState get question ids by check
func (d *Dao) QidsByState(c context.Context, state int8) (res []*model.Question, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _questionTypeSQL, state); err != nil {
log.Error("d.questionTypeStmt.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.Question)
if err = rows.Scan(&r.ID, &r.TypeID); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}

View File

@@ -0,0 +1,177 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/interface/main/answer/model"
"github.com/smartystreets/goconvey/convey"
)
func TestHit(t *testing.T) {
convey.Convey("hit", t, func() {
p1 := hit(0)
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestDaoPendantHistory(t *testing.T) {
convey.Convey("PendantHistory", t, func() {
hid, status, err := d.PendantHistory(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(status, convey.ShouldNotBeNil)
convey.So(hid, convey.ShouldNotBeNil)
})
}
func TestDaoHistory(t *testing.T) {
convey.Convey("History", t, func() {
res, err := d.History(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestDaoHistoryByHid(t *testing.T) {
convey.Convey("HistoryByHid", t, func() {
res, err := d.HistoryByHid(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestDaoOldHistory(t *testing.T) {
convey.Convey("OldHistory", t, func() {
res, err := d.OldHistory(context.Background(), 0, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestDaoSharingIndexByHid(t *testing.T) {
convey.Convey("SharingIndexByHid", t, func() {
res, err := d.SharingIndexByHid(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestDaoAddPendantHistory(t *testing.T) {
convey.Convey("AddPendantHistory", t, func() {
affected, err := d.AddPendantHistory(context.Background(), time.Now().Unix(), time.Now().Unix())
convey.So(err, convey.ShouldBeNil)
convey.So(affected, convey.ShouldNotBeNil)
})
}
func TestDaoHistorySuit(t *testing.T) {
var (
err error
affected int64
hid = time.Now().Unix()
)
ans := &model.AnswerHistory{
Hid: hid,
}
convey.Convey("AddHistory", t, func() {
affected, _, err = d.AddHistory(context.Background(), 0, ans)
convey.So(err, convey.ShouldBeNil)
convey.So(affected, convey.ShouldNotBeNil)
})
convey.Convey("HistoryByHid", t, func() {
ans, err = d.HistoryByHid(context.Background(), hid)
convey.So(err, convey.ShouldBeNil)
convey.So(affected, convey.ShouldNotBeNil)
})
convey.Convey("SetHistory", t, func() {
affected, err = d.SetHistory(context.Background(), 0, ans)
convey.So(err, convey.ShouldBeNil)
convey.So(affected, convey.ShouldNotBeNil)
})
}
func TestDaoUpdateLevel(t *testing.T) {
convey.Convey("UpdateLevel", t, func() {
ret, err := d.UpdateLevel(context.Background(), 0, 0, 0, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestDaoUpdateCaptcha(t *testing.T) {
convey.Convey("UpdateCaptcha", t, func() {
ret, err := d.UpdateCaptcha(context.Background(), 0, 0, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestDaoUpdateStepTwoTime(t *testing.T) {
convey.Convey("UpdateStepTwoTime", t, func() {
ret, err := d.UpdateStepTwoTime(context.Background(), 0, 0, time.Now())
convey.So(err, convey.ShouldBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestDaoUpdateExtraStartTime(t *testing.T) {
convey.Convey("UpdateExtraStartTime", t, func() {
ret, err := d.UpdateExtraStartTime(context.Background(), 0, 0, time.Now())
convey.So(err, convey.ShouldBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestDaoUpdateExtraRet(t *testing.T) {
convey.Convey("UpdateExtraRet", t, func() {
ret, err := d.UpdateExtraRet(context.Background(), 0, 0, 0, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestDaoUpPendantHistory(t *testing.T) {
convey.Convey("UpPendantHistory", t, func() {
ret, err := d.UpPendantHistory(context.Background(), 0, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestDaoQidsExtraByState(t *testing.T) {
convey.Convey("QidsExtraByState", t, func() {
_, err := d.QidsExtraByState(context.Background(), 1)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoExtraByIds(t *testing.T) {
convey.Convey("ExtraByIds", t, func() {
res, err := d.ExtraByIds(context.Background(), []int64{1, 2, 3})
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestDaoTypes(t *testing.T) {
convey.Convey("Types", t, func() {
_, err := d.Types(context.Background())
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoByIds(t *testing.T) {
convey.Convey("ByIds", t, func() {
_, err := d.ByIds(context.Background(), []int64{1, 2, 3})
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoQidsByState(t *testing.T) {
convey.Convey("QidsByState", t, func() {
_, err := d.QidsByState(context.Background(), 1)
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,111 @@
package dao
import (
"context"
"strconv"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_qidByTypeID = "v3_qus_tids_"
_extraQidByTypeID = "v3_eq_t_"
)
func qusByType(tid int) string {
return _qidByTypeID + strconv.FormatInt(int64(tid), 10)
}
func extraQidByType(tid int8) string {
return _extraQidByTypeID + strconv.FormatInt(int64(tid), 10)
}
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
// QidByType get question by type.
func (d *Dao) QidByType(c context.Context, tid int, num uint8) (ids []int64, err error) {
key := qusByType(tid)
conn := d.redis.Get(c)
defer conn.Close()
if ids, err = redis.Int64s(conn.Do("SRANDMEMBER", key, num)); err != nil {
log.Error("RandBaseQs conn.Send('SRANDMEMBER', %s, %d) error(%v)", key, num, err)
}
return
}
// SetQids set question ids.
func (d *Dao) SetQids(c context.Context, qs []int64, typeID int) (err error) {
if len(qs) == 0 {
return
}
key := qusByType(typeID)
conn := d.redis.Get(c)
defer conn.Close()
args := make([]interface{}, 0, len(qs)+1)
args = append(args, key)
for _, q := range qs {
args = append(args, q)
}
if _, err = conn.Do("SADD", args...); err != nil {
log.Error("conn.Send(SADD, %v) error(%v)", args, err)
}
return
}
// SetExtraQids set extra question ids.
func (d *Dao) SetExtraQids(c context.Context, qs []int64, ans int8) (err error) {
if len(qs) == 0 {
return
}
key := extraQidByType(ans)
conn := d.redis.Get(c)
defer conn.Close()
args := make([]interface{}, 0, len(qs)+1)
args = append(args, key)
for _, q := range qs {
args = append(args, q)
}
if _, err = conn.Do("SADD", args...); err != nil {
log.Error("conn.Send(SADD, %v) error(%v)", args, err)
}
return
}
// DelQidsCache del qids cahce.
func (d *Dao) DelQidsCache(c context.Context, typeID int) (err error) {
key := qusByType(typeID)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Send(DEL, %s) error(%v)", key, err)
}
return
}
// DelExtraQidsCache del extra qids cahce.
func (d *Dao) DelExtraQidsCache(c context.Context, ans int8) (err error) {
key := extraQidByType(ans)
conn := d.redis.Get(c)
defer conn.Close()
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Send(DEL, %s) error(%v)", key, err)
}
return
}
// ExtraQidByType extra qis by type.
func (d *Dao) ExtraQidByType(c context.Context, ans int8, num uint8) (ids []int64, err error) {
key := extraQidByType(ans)
conn := d.redis.Get(c)
defer conn.Close()
if ids, err = redis.Int64s(conn.Do("SRANDMEMBER", key, num)); err != nil {
log.Error("ExtraQidByType conn.Send('SRANDMEMBER', %s, %d) error(%v)", key, num, err)
}
return
}

View File

@@ -0,0 +1,64 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestQusByType(t *testing.T) {
convey.Convey("qusByType", t, func() {
p1 := qusByType(0)
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestExtraQidByType(t *testing.T) {
convey.Convey("extraQidByType", t, func() {
p1 := extraQidByType(0)
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestDaopingRedis(t *testing.T) {
convey.Convey("pingRedis", t, func() {
err := d.pingRedis(context.Background())
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoSetQids(t *testing.T) {
convey.Convey("SetQids", t, func() {
err := d.SetQids(context.Background(), []int64{1, 2, 3}, 0)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("QidByType", t, func() {
ids, err := d.QidByType(context.Background(), 0, 3)
convey.So(err, convey.ShouldBeNil)
convey.So(ids, convey.ShouldNotBeNil)
})
}
func TestDaoSetExtraQids(t *testing.T) {
convey.Convey("SetExtraQids", t, func() {
err := d.SetExtraQids(context.Background(), []int64{1, 2, 3}, 0)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("ExtraQidByType", t, func() {
ids, err := d.ExtraQidByType(context.Background(), 0, 2)
convey.So(err, convey.ShouldBeNil)
convey.So(ids, convey.ShouldNotBeNil)
})
convey.Convey("DelQidsCache", t, func() {
err := d.DelQidsCache(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDaoDelExtraQidsCache(t *testing.T) {
convey.Convey("DelExtraQidsCache", t, func() {
err := d.DelExtraQidsCache(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"answer.go",
"captcha.go",
"http.go",
"local.go",
"pendant.go",
],
importpath = "go-common/app/interface/main/answer/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/model:go_default_library",
"//app/interface/main/answer/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/auth:go_default_library",
"//library/xstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,207 @@
package http
import (
"fmt"
"strconv"
"strings"
"go-common/app/interface/main/answer/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/xstr"
)
func localized(c *bm.Context) string {
langs := detectLocalizedWeb(c)
if len(langs) == 0 {
return model.LangZhCN
}
switch langs[0] {
case model.LangZhTW:
return model.LangZhTW
case model.LangZhHK:
return model.LangZhTW
default:
return model.LangZhCN
}
}
func checkBirthDay(c *bm.Context) {
var mid, _ = c.Get("mid")
if ok := svc.CheckBirthday(c, mid.(int64)); !ok {
c.JSON(nil, ecode.MemberBirthdayInfoIsNull)
return
}
c.JSON(nil, nil)
}
// checkPro check second step answers
func checkPro(c *bm.Context) {
var (
err error
ids []int64
params = c.Request.Form
qIds = params.Get("qs_ids")
mid, _ = c.Get("mid")
ansHash = make(map[int64]string)
)
qidArr := strings.Split(qIds, ",")
for _, qid := range qidArr {
id, _ := strconv.ParseInt(qid, 10, 64)
ansHash[id] = params.Get("ans_hash_" + qid)
ids = append(ids, id)
}
hid, err := svc.ProCheck(c, mid.(int64), ids, ansHash, localized(c))
if err != nil {
log.Error("svc.ProCheck(%d,%s,%+v) error(%+v)", mid.(int64), xstr.JoinInts(ids), ansHash, err)
c.JSON(nil, err)
return
}
c.JSON(fmt.Sprintf(model.ProPassed, hid), nil)
}
// checkBase check first step answers
func checkBase(c *bm.Context) {
var (
err error
ids []int64
params = c.Request.Form
qIds = params.Get("qs_ids")
mid, _ = c.Get("mid")
ansHash = make(map[int64]string)
)
qidsArr := strings.Split(qIds, ",")
for _, qid := range qidsArr {
id, _ := strconv.ParseInt(qid, 10, 64)
ansHash[id] = params.Get("ans_hash_" + qid)
ids = append(ids, id)
}
req, err := svc.CheckBase(c, mid.(int64), ids, ansHash, localized(c))
if err != nil {
log.Error("svc.BaseCheck(%d,%s,%+v) error(%+v)", mid.(int64), xstr.JoinInts(ids), ansHash, err)
c.JSON(nil, err)
return
}
res := make(map[string]interface{})
if req != nil && len(req.QidList) > 0 {
res["next"] = false
res["ids"] = req.QidList
c.JSON(res, nil)
return
}
res["next"] = true
c.JSON(res, nil)
}
// checkExtra extra question check.
func checkExtra(c *bm.Context) {
var (
ids []int64
params = c.Request.Form
qIds = params.Get("qs_ids")
mid, ok = c.Get("mid")
ansHash = make(map[int64]string)
ua = c.Request.Header.Get("User-Agent")
refer = c.Request.Header.Get("Referer")
)
if !ok {
c.JSON(nil, ecode.RequestErr)
return
}
qidsArr := strings.Split(qIds, ",")
for _, qid := range qidsArr {
id, _ := strconv.ParseInt(qid, 10, 64)
h := params.Get("ans_hash_" + qid)
if h != "" {
ansHash[id] = params.Get("ans_hash_" + qid)
ids = append(ids, id)
}
}
buvid := c.Request.Header.Get("Buvid")
if buvid == "" {
cookie, _ := c.Request.Cookie("buvid3")
if cookie != nil {
buvid = cookie.Value
}
}
c.JSON(nil, svc.ExtraCheck(c, mid.(int64), ids, ansHash, ua, localized(c), refer, buvid))
}
// getBaseQ get first step questions
func baseQus(c *bm.Context) {
var (
mid, ok = c.Get("mid")
mobile = strings.Contains(c.Request.UserAgent(), model.MobileUserAgentFlag)
)
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
c.JSON(svc.BaseQ(c, mid.(int64), localized(c), mobile))
}
// getProType get second step question types
func proType(c *bm.Context) {
mid, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
c.JSON(svc.ProType(c, mid.(int64), localized(c)))
}
// getQstByType get second step questions
func proQus(c *bm.Context) {
var (
params = c.Request.Form
mid, ok = c.Get("mid")
tIdsStr = params.Get("type_ids")
mobile = strings.Contains(c.Request.UserAgent(), model.MobileUserAgentFlag)
)
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
c.JSON(svc.ConvertProQues(c, mid.(int64), tIdsStr, localized(c), mobile))
}
// extraQus extra question.
func extraQus(c *bm.Context) {
var (
mid, ok = c.Get("mid")
mobile = strings.Contains(c.Request.UserAgent(), model.MobileUserAgentFlag)
)
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
c.JSON(svc.ConvertExtraQs(c, mid.(int64), localized(c), mobile))
}
func cool(c *bm.Context) {
var (
err error
mid int64
hid int64
params = c.Request.Form
hidStr = params.Get("id")
)
if midI, ok := c.Get("mid"); ok {
mid = midI.(int64)
}
if hid, err = strconv.ParseInt(hidStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(svc.Cool(c, hid, mid))
}
func extraScore(c *bm.Context) {
mid, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.NoLogin)
return
}
c.JSON(svc.ExtraScore(c, mid.(int64)))
}

View File

@@ -0,0 +1,95 @@
package http
import (
"fmt"
"strconv"
"strings"
"go-common/app/interface/main/answer/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func validate(c *bm.Context) {
var (
mid, _ = c.Get("mid")
params = c.Request.Form
challenge = params.Get("geetest_challenge")
validate = params.Get("geetest_validate")
seccode = params.Get("geetest_seccode")
success = params.Get("geetest_success")
captchaType = params.Get("captcha_type") // typ == "gt"
cookie = c.Request.Header.Get("cookie")
mobile = strings.Contains(c.Request.UserAgent(), model.MobileUserAgentFlag)
ct string
sid string
)
if mobile {
ct = model.PlatH5
} else {
ct = model.PlatPC
}
successi, err := strconv.Atoi(success)
if err != nil {
successi = 1
}
sidCookie, err := c.Request.Cookie("sid")
if err != nil {
log.Warn("cookie do not contains sid error(%v)", err)
} else {
sid = sidCookie.Value
}
comargs := map[string]string{
"ua": c.Request.Header.Get("User-Agent"),
"buvid": c.Request.Header.Get("Buvid"),
"refer": c.Request.Header.Get("Referer"),
"url": c.Request.Header.Get("URL"),
"sid": sid,
}
if captchaType == model.BiliCaptcha {
validate = params.Get("token")
seccode = params.Get("code")
if validate == "" || seccode == "" {
log.Error("validate(%+v) ", params)
c.JSON(nil, ecode.RequestErr)
}
}
req, err := svc.Validate(c, challenge, validate, seccode, ct, successi, mid.(int64), cookie, captchaType, comargs)
if err != nil {
log.Error("svc.Validate(%d,%d,%s,%s,%s,%s,%s,%s) error(%+v)", mid.(int64), successi, challenge, validate, seccode, ct,
cookie, comargs, err)
c.JSON(nil, err)
return
}
cool, err := svc.Cool(c, req.HistoryID, mid.(int64))
if err != nil {
c.JSON(nil, err)
return
}
cool.Hid = req.HistoryID
cool.URL = fmt.Sprintf(model.ProPassed, req.HistoryID)
c.JSON(cool, nil)
}
// captcha get captcha
func captcha(c *bm.Context) {
var (
err error
mid, _ = c.Get("mid")
mobile = strings.Contains(c.Request.UserAgent(), model.MobileUserAgentFlag)
ct string
)
if mobile {
ct = model.PlatH5
} else {
ct = model.PlatPC
}
req, err := svc.Captcha(c, mid.(int64), ct, 1)
if err != nil {
log.Error("svc.QueCaptcha(%d,%s) error(%+v)", mid.(int64), ct, err)
c.JSON(nil, err)
return
}
c.JSON(req, nil)
}

View File

@@ -0,0 +1,59 @@
package http
import (
"go-common/app/interface/main/answer/conf"
"go-common/app/interface/main/answer/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
"go-common/library/net/http/blademaster/middleware/auth"
)
var (
authSvc *auth.Auth
antSvc *antispam.Antispam
svc *service.Service
)
// Init init
func Init(c *conf.Config) {
initService(c)
engineOuter := bm.DefaultServer(c.BM)
outerRouter(engineOuter)
if err := engineOuter.Start(); err != nil {
log.Error("engineOuter.Start() error(%v)", err)
panic(err)
}
}
// initService init services.
func initService(c *conf.Config) {
authSvc = auth.New(c.AuthN)
antSvc = antispam.New(c.Antispam)
svc = service.New(c)
}
// outerRouter init outer router api path.
func outerRouter(e *bm.Engine) {
e.Ping(ping)
og := e.Group("/x/answer", bm.CORS())
{
// 答题流程排序
og.GET("/v3/base", authSvc.UserWeb, antSvc.Handler(), baseQus)
og.POST("/v3/base/check", authSvc.User, checkBase)
og.GET("/v3/extra", authSvc.User, antSvc.Handler(), extraQus)
og.POST("/v3/extra/check", authSvc.UserWeb, checkExtra)
og.GET("/v3/extra/score", authSvc.User, extraScore)
og.GET("/user/birthday", authSvc.User, checkBirthDay)
og.GET("/v3/pro/type", authSvc.User, proType)
og.GET("/v3/pro", authSvc.UserWeb, antSvc.Handler(), proQus)
og.POST("/v3/pro/check", authSvc.User, checkPro)
og.GET("/v3/captcha/gt", authSvc.User, antSvc.Handler(), captcha)
og.POST("/v3/captcha/check", authSvc.User, validate)
og.GET("/v3/result", authSvc.GuestWeb, cool)
og.POST("/rec/pendant", authSvc.User, pendantRec)
}
}
func ping(c *bm.Context) {
}

View File

@@ -0,0 +1,60 @@
// Package http detect localized from http header
// and set localized info to metadata as key 'locale'
// The locale used following the specification defined at
// http://www.rfc-editor.org/rfc/bcp/bcp47.txt.
// Examples are: "en-US", "fr-CH", "es-MX"
package http
import (
"net/url"
"sort"
"strconv"
"strings"
bm "go-common/library/net/http/blademaster"
)
type language struct {
Name string
Weighting float64
}
// detectLocalizedWeb detect locale from HTTP Accept-Language header
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language
func detectLocalizedWeb(c *bm.Context) (langs []string) {
parseLang := func(s string) (language, error) {
seps := strings.SplitN(s, ";", 2)
lang := language{Name: seps[0]}
if len(seps) == 1 {
lang.Weighting = 1
return lang, nil
}
params, err := url.ParseQuery(seps[1])
if err != nil {
return lang, err
}
lang.Weighting, err = strconv.ParseFloat(params.Get("q"), 32)
return lang, err
}
items := strings.Split(c.Request.Header.Get("Accept-Language"), ",")
if items[0] == "" {
return
}
// three language is most common accept language send by browser
languages := make([]language, 0, len(items))
for _, s := range items {
l, err := parseLang(s)
if err != nil {
//TODO(weicheng): deal with error
continue
}
languages = append(languages, l)
}
sort.Slice(languages, func(i, j int) bool {
return languages[i].Weighting > languages[j].Weighting
})
for i := range languages {
langs = append(langs, languages[i].Name)
}
return
}

View File

@@ -0,0 +1,21 @@
package http
import (
"go-common/app/interface/main/answer/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func pendantRec(c *bm.Context) {
arg := new(model.ReqPendant)
mid, ok := c.Get("mid")
if !ok {
c.JSON(nil, ecode.AccountNotLogin)
return
}
if err := c.Bind(arg); err != nil {
return
}
arg.MID = mid.(int64)
c.JSON(nil, svc.PendantRec(c, arg))
}

View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"ansque.go",
"answer.go",
"answer_state.go",
"captcha.go",
"databus.go",
"identify.go",
"pendant.go",
"question.go",
],
importpath = "go-common/app/interface/main/answer/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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 model
// AnsQueDetail .
type AnsQueDetail struct {
ID int64 `json:"qs_id"`
AnsImg string `json:"ans_img"`
QsHeight float64 `json:"qs_h"`
QsPositionY float64 `json:"qs_y"`
Ans1Hash string `json:"ans1_hash"`
Ans2Hash string `json:"ans2_hash"`
Ans3Hash string `json:"ans3_hash"`
Ans4Hash string `json:"ans4_hash"`
Ans0Height float64 `json:"ans0_h"`
Ans0PositionY float64 `json:"ans0_y"`
Ans1Height float64 `json:"ans1_h"`
Ans1PositionY float64 `json:"ans1_y"`
Ans2Height float64 `json:"ans2_h"`
Ans2PositionY float64 `json:"ans2_y"`
Ans3Height float64 `json:"ans3_h"`
Ans3PositionY float64 `json:"ans3_y"`
}
// AnsQueDetailList .
type AnsQueDetailList struct {
CurrentTime int64 `json:"current_time"`
EndTime int64 `json:"end_time"`
QuesList []*AnsQueDetail `json:"items"`
}
// AnsProType .
type AnsProType struct {
List []*AnsTypeList `json:"list"`
CurrentTime int64 `json:"current_time"`
EndTime int64 `json:"end_time"`
Repro string `json:"repro"`
}
// AnsTypeList .
type AnsTypeList struct {
Name string `json:"name"`
Fields []*AnsType `json:"fields"`
}
// AnsType info.
type AnsType struct {
ID int64 `json:"id"`
Name string `json:"name"`
}

View File

@@ -0,0 +1,250 @@
package model
import (
"time"
)
// answer constants
const (
LangZhCN = "zh-CN"
LangZhTW = "zh-TW"
LangZhHK = "zh-HK"
)
// answer constants
const (
UserInfoRank = 5000
PenDantDays = 7 //答题优秀设置挂件的天数
ExtraAnsA = "符合规范"
ExtraAnsB = "不符合规范"
)
// Score info.
const (
FullScore = 100
Score85 = 85
Score60 = 60
Score0 = 0
)
// Rank info.
const (
RankTop int = 122
)
// question type.
const (
Q int8 = iota
BaseExtraNoPassQ // 1 extra no pass
BaseExtraPassQ // 2 extra pass
)
// extra question ans.
const (
UnKownQ int8 = iota
ViolationQ
NormalQ
)
// answer captcha pass
const (
CaptchaNopass int8 = iota
CaptchaPass
)
// BaseQues question record
type BaseQues struct {
Question string
Check int8
Ctime time.Time
}
// MyQues my question
type MyQues struct {
Count int64
List []*BaseQues
}
// RankInfo rank
type RankInfo struct {
Mid int64 `json:"mid"`
Face string `json:"face"`
Uname string `json:"uname"`
Num int64 `json:"num"`
Nameplate *NameplateInfo `json:"nameplate"`
}
// NameplateInfo .
type NameplateInfo struct {
Nid int `json:"nid"`
Name string `json:"name"`
Image string `json:"image"`
ImageSmall string `json:"image_small"`
Level string `json:"level"`
Condition string `json:"condition"`
}
// TypeInfo type info
type TypeInfo struct {
ID, Parentid int64
Name string
LabelName string
Subs []*SubType
}
// ProTypes .
type ProTypes struct {
List []*TypeInfo
CurrentTime, EndTime time.Time
Repro bool
}
// SubType sub type info
type SubType struct {
ID int64 `json:"id"`
Name string `json:"name"`
LabelName string `json:"-"`
}
// AnsQue .
type AnsQue struct {
ID int64
Img string
Height float64
PositionY float64 // background-position-y
Ans []*AnsPosition
}
// AnsPosition .
type AnsPosition struct {
AnsHash string
Height float64 // height
PositionY float64 // background-position-y
}
// AnsQuesList .
type AnsQuesList struct {
CurrentTime, EndTime time.Time
QuesList []*AnsQue
}
// AnsCheck .
type AnsCheck struct {
QidList []int64
HistoryID int64
Pass bool
}
// CaptchaReq Captcha request.
type CaptchaReq struct {
Mid int64
IP string
ClientType string
NewCaptcha int
}
// CaptchaCheckReq Captcha check request.
type CaptchaCheckReq struct {
Mid int64
IP string
Challenge string
ClientType string
Validate string
Seccode string
Success int
Cookie string
Comargs map[string]string
}
// QueReq request
type QueReq struct {
ID int64
}
// AnsCool .
type AnsCool struct {
Hid int64 `json:"hid,omitempty"`
URL string `json:"url,omitempty"`
Name string `json:"uname"`
Face string `json:"face"`
Powers []*CoolPower `json:"power_result"`
Score int8 `json:"score"`
Rank *CoolRank `json:"rank"`
Share *CoolShare `json:"share"`
CanShowRankBtn bool `json:"can_show_rank_btn"`
IsSameUser bool `json:"is_same_user"`
ViewMore string `json:"view_more"`
VideoInfo *CoolVideo `json:"video_info"`
IsFirstPass int8 `json:"is_first_pass"`
Level int8 `json:"level"`
MainTids []int `json:"main_tids"`
SubTids []int `json:"sub_tids"`
}
// CoolPower .
type CoolPower struct {
Num int64 `json:"num"`
Name string `json:"name"`
}
// CoolRank .
type CoolRank struct {
ID int `json:"id"`
Name string `json:"name"`
Img string `json:"img"`
}
// CoolShare .
type CoolShare struct {
Content string `json:"content"`
ShortContent string `json:"short_content"`
}
// CoolVideo .
type CoolVideo struct {
URL string `json:"url"`
Name string `json:"name"`
Img string `json:"img"`
WatchNum string `json:"watch_num"`
UpNum string `json:"up_num"`
}
// AnswerHistory info.
type AnswerHistory struct {
ID int64 `json:"id"`
Hid int64 `json:"hid"`
Mid int64 `json:"mid"`
StartTime time.Time `json:"start_time"`
StepOneErrTimes int8 `json:"step_one_err_times"`
StepOneCompleteTime int64 `json:"step_one_complete_time"`
StepExtraStartTime time.Time `json:"step_extra_start_time"`
StepExtraCompleteTime int64 `json:"step_extra_complete_time"`
StepExtraScore int64 `json:"step_extra_score"`
StepTwoStartTime time.Time `json:"step_two_start_time"`
CompleteTime time.Time `json:"complete_time"`
CompleteResult string `json:"complete_result"`
Score int8 `json:"score"`
IsFirstPass int8 `json:"is_first_pass"`
IsPassCaptcha int8 `json:"is_pass_captcha"`
PassedLevel int8 `json:"passed_level"`
RankID int `json:"rank_id"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// AnswerTime info.
type AnswerTime struct {
Stime time.Time `json:"stime"` // answer start time
Etimes int8 `json:"etimes"` // base answer error times
}
// AnsHash .
type AnsHash struct {
AnsHashName string
AnsHashVal string
}
// ExtraScoreReply .
type ExtraScoreReply struct {
Score int64 `json:"score"`
}

View File

@@ -0,0 +1,33 @@
package model
// const .
const (
MobileUserAgentFlag = "Mobile"
ProPassed = "/v3/result/%d"
MinType = 3
MaxType = 10
BlockMsg = "12小时内无法再次答题!"
DefPageSize = 10
DefPageNo = 1
NoParam = -1
OfficialType = 6
ShowOfficialRank = 31300
// ans info.
AnsSize = 4 // 题目答案数
HashSalt = "bilirqeust"
// platfrom
PlatH5 = "h5"
PlatPC = "pc"
DefBirthday1 = "1930-01-01"
DefBirthday2 = "1980-01-01"
// rank
FlagAll = "all"
FlagMonth = "m"
)

View File

@@ -0,0 +1,24 @@
package model
// var
var (
// Geetest captcha_type .
Geetest = "gt"
BiliCaptcha = "bili"
)
// ProcessRes geetest Captcha resp info.
type ProcessRes struct {
Success int8 `json:"success"`
CaptchaID string `json:"gt"`
Challenge string `json:"challenge"`
NewCaptcha int `json:"new_captcha"`
CaptchaType string `json:"type,omitempty"`
Token string `json:"token,omitempty"`
URL string `json:"url,omitempty"`
}
// ValidateRes info.
type ValidateRes struct {
Seccode string `json:"seccode"`
}

View File

@@ -0,0 +1,51 @@
package model
import "time"
// const .
const (
AnswerLogID = 15
AnswerUpdate = "answer_update"
BasePass = "basePass"
ExtraStartTime = "extraStartTime"
ExtraCheck = "extraCheck"
ProQues = "proQues"
ProCheck = "proCheck"
Captcha = "captchaPass"
Level = "level"
)
// DataBusResult databus.
type DataBusResult struct {
Mid int64 `json:"mid"` // 用户 ID
Buvid string `json:"buvid"` // 设备标识符 前端传入
IP string `json:"ip"` // 用户 IP 地址
Ua string `json:"ua"` // 客户端 User Agent
Refer string `json:"refer"` // 页面跳转来源地址 Refer
Score int8 `json:"score"` // 答题总分数
Hid int64 `json:"hid"` // hid
Rs []*Rs
}
// Rs def.
type Rs struct {
ID int64 `json:"id"` // 题目自增 ID
Question string `json:"question"` // 问题内容
Ans int8 `json:"ans"` // 用户答案
TrueAns int8 `json:"trueAns"` // 真实答案 答案0:未知 1:违规 2:不违规
AvID int64 `json:"av_id"` // 相关视频id
Status int8 `json:"status"` // 问题状态 1:未申核 2:已审核
Source int8 `json:"source"` // 问题来源 0:未知1:评论 2:弹幕
Ctime time.Time `json:"ctime"` // 创建时间
Mtime time.Time `json:"mtime"` // 修改时间
}
// Formal user formal info.
type Formal struct {
Mid int64 `json:"mid"` // 用户 ID
Hid int64 `json:"history_id"` // 答题历史 ID
Cookie string `json:"cookie"` // cookie
IP string `json:"ip"` // cookie
PassTime time.Time `json:"pass_time"` // 通过时间
}

View File

@@ -0,0 +1,17 @@
package model
// const .
const (
IdentifyOk = 0
IdentifyNoInfo = 1
PhoneOk = 0
Phone17x = 1
PhoneEmpty = 2
)
// IdentifyInfo .
type IdentifyInfo struct {
Identify int8 `json:"'identify'"`
Phone int8 `json:"phone"`
}

View File

@@ -0,0 +1,19 @@
package model
// const .
const (
PendantNotGet = 0
PendantGet = 1
)
// Pendant .
type Pendant struct {
Pid int `json:"pid"`
Name string `json:"name"`
}
// ReqPendant .
type ReqPendant struct {
HID int64 `form:"hid" validate:"required"`
MID int64
}

View File

@@ -0,0 +1,109 @@
package model
import (
"time"
)
// state
const (
WaitCheck = int8(0)
PassCheck = int8(1)
NoPassCheck = int8(2)
)
// size
const (
MaxQuestion = 120
MinQuestion = 6
MaxAns = 100
MinAns = 2
MaxTips = 100
MinTips = 2
MaxLoadQueSize = 100000
)
// media type
const (
TextMediaType = int8(1)
ImageMediaType = int8(2)
TopRankSize = 10
MyQuestionSize = 12
AttrUnknown = -1
)
// QuestionRPC stion question info.
type QuestionRPC struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
IP uint32 `json:"ip"`
TypeID int8 `json:"type"`
MediaType int8 `json:"media_type"`
Check int8 `json:"check"`
Source int8 `json:"source"`
Question string `json:"question"`
Ans1 string `json:"ans1"`
Ans2 string `json:"ans2"`
Ans3 string `json:"ans3"`
Ans4 string `json:"ans4"`
Tips string `json:"tips"`
AvID int32 `json:"av_id"`
Ctime time.Time `json:"ctime"`
}
// Question question info rpc.
type Question struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
IP string `json:"ip"`
TypeID int8 `json:"type"`
MediaType int8 `json:"media_type"`
Check int8 `json:"check"`
Source int8 `json:"source"`
Question string `json:"question"`
Ans1 string `json:"ans1"`
Ans2 string `json:"ans2"`
Ans3 string `json:"ans3"`
Ans4 string `json:"ans4"`
Ans []string `json:"-"`
Tips string `json:"tips"`
AvID int32 `json:"av_id"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// ExtraQst etc.
type ExtraQst struct {
ID int64 `json:"id"`
Question string `json:"question"`
Ans int8 `json:"ans"`
Status int8 `json:"status"`
OriginID int64 `json:"origin_id"`
AvID int64 `json:"av_id"`
Source int8 `json:"source"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// ImgPosition .
type ImgPosition struct {
Ans0H float64 `json:"ans_0_h"`
Ans0Y float64 `json:"ans_0_y"`
Ans1H float64 `json:"ans_1_h"`
Ans1Y float64 `json:"ans_1_y"`
Ans2H float64 `json:"ans_2_h"`
Ans2Y float64 `json:"ans_2_y"`
Ans3H float64 `json:"ans_3_h"`
Ans3Y float64 `json:"ans_3_y"`
Ans4H float64 `json:"ans_4_h"`
Ans4Y float64 `json:"ans_4_y"`
QsH float64 `json:"qs_h"`
QsY float64 `json:"qs_y"`
}
// ExtraBigData ret.
type ExtraBigData struct {
Done []int64 `json:"done"`
Pend []int64 `json:"pend"`
}

View File

@@ -0,0 +1,77 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"answer_test.go",
"answercheck_test.go",
"common_test.go",
"geetest_test.go",
"service_test.go",
"user_info_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"answer.go",
"answercheck.go",
"common.go",
"geetest.go",
"rank_share.go",
"service.go",
"user_info.go",
],
importpath = "go-common/app/interface/main/answer/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/answer/conf:go_default_library",
"//app/interface/main/answer/dao:go_default_library",
"//app/interface/main/answer/dao/account:go_default_library",
"//app/interface/main/answer/dao/geetest:go_default_library",
"//app/interface/main/answer/model:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/member/api/gorpc:go_default_library",
"//app/service/main/member/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/log/anticheat:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/text/translate/chinese:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,819 @@
package service
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"math/rand"
"strconv"
"strings"
"time"
"unicode/utf8"
"go-common/app/interface/main/answer/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/text/translate/chinese"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
const (
_hashSalt = "bilirqeust"
// _ansURI = "/answer/img?qs_id=%v&ans1_hash=%s&ans2_hash=%s&ans3_hash=%s&ans4_hash=%s"
_baseTypeID = 36 // 官方基础题库
_rankBtn = 7 * 24 * time.Hour
_minType = 3
_maxType = 10
)
var (
_typeIdsMapping = map[int][]int{
100001: {15, 16, 17},
100002: {29, 30},
100003: {12, 13},
// 22: 21,
// 24: 23,
// 35: 31, 36: 31,
// 32: 30, 33: 30, 34: 30,
// 29: 28, 37: 28, 7: 28, 8: 28,
// 41: 5, 42: 5, 43: 5, 44: 5, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 50: 5, 51: 5, 52: 5,
}
// 对外展示方式1
_typeMap1 = []*model.TypeInfo{
{Name: "游戏", Subs: []*model.SubType{
{ID: 8, Name: "动作射击"},
{ID: 9, Name: "冒险格斗"},
{ID: 100003, Name: "策略模拟"},
// {ID: 13, Name: "策略模拟"},
{ID: 14, Name: "音乐体育"},
}},
{Name: "影视", Subs: []*model.SubType{
{ID: 15, Name: "纪录片"},
{ID: 16, Name: "电影"},
{ID: 17, Name: "电视剧"},
}},
{Name: "科技", Subs: []*model.SubType{
{ID: 18, Name: "军事"},
{ID: 19, Name: "地理"},
{ID: 20, Name: "历史"},
{ID: 21, Name: "文学"},
{ID: 22, Name: "数学"},
{ID: 23, Name: "物理"},
{ID: 24, Name: "化学"},
{ID: 25, Name: "生物"},
{ID: 26, Name: "数码科技"},
}},
{Name: "动画", Subs: []*model.SubType{
{ID: 27, Name: "国创"},
{ID: 28, Name: "番剧"},
}},
{Name: "艺术", Subs: []*model.SubType{
{ID: 100002, Name: "音乐"},
// {ID: 30, Name: "音乐"},
{ID: 31, Name: "绘画"},
}},
{Name: "流行前线", Subs: []*model.SubType{
{ID: 32, Name: "娱乐"},
{ID: 33, Name: "时尚"},
{ID: 34, Name: "运动"},
}},
{Name: "鬼畜", Subs: []*model.SubType{
{ID: 35, Name: "鬼畜"},
}},
}
// 推荐分区映射
_recTypeIDMap = map[int]map[string][]int{
124: {"main_tid": []int{1, 167}, "sub_tid": []int{3, 129}},
127: {"main_tid": []int{1, 167}, "sub_tid": []int{3, 4}},
126: {"main_tid": []int{3, 119}, "sub_tid": []int{1}},
123: {"main_tid": []int{3}, "sub_tid": []int{129, 119}},
121: {"main_tid": []int{36, 177}, "sub_tid": []int{160}},
125: {"main_tid": []int{4}, "sub_tid": []int{129}},
129: {"main_tid": []int{36, 177}, "sub_tid": []int{160}},
130: {"main_tid": []int{23, 11}, "sub_tid": []int{160}},
128: {"main_tid": []int{119}, "sub_tid": []int{160}},
}
)
// BaseQ base question.
func (s *Service) BaseQ(c context.Context, mid int64, lang string, mobile bool) (res *model.AnsQueDetailList, err error) {
var aqs *model.AnsQuesList
if aqs, err = s.BaseQs(c, mid, lang, mobile); err != nil {
err = errors.Wrapf(err, "s.ansRPC.BaseQs(%d,%t)", mid, mobile)
return
}
res = s.convertModel(aqs)
return
}
// BaseQs get base question
func (s *Service) BaseQs(c context.Context, mid int64, lang string, mobile bool) (rqs *model.AnsQuesList, err error) {
var (
ids []int64
now = time.Now()
)
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
h, err := s.history(c, mid)
if err == nil && h != nil {
if h.StartTime.Add(s.answerDuration()).After(now) && h.Score == 0 {
if h.StepExtraCompleteTime != 0 {
err = ecode.AnswerProNoPass // extra question pass
return
}
err = ecode.AnswerExtraNoPass
return
}
if h.Score > 0 && h.IsPassCaptcha == 0 {
err = ecode.AnswerCaptchaNoPassed
return
}
}
ids, err = s.answerDao.IdsCache(c, mid, model.Q)
if err != nil || len(ids) != s.c.Answer.BaseNum {
ids, err = s.answerDao.QidByType(c, _baseTypeID, uint8(s.c.Answer.BaseNum))
if err != nil {
log.Error("s.answerDao.QidByType(%d,%d) error(%v)", _baseTypeID, s.c.Answer.BaseNum, err)
return
}
if len(ids) == 0 {
err = ecode.AnswerQsNumErr
log.Error("qidByType ids len(%d) is 0", len(ids))
return
}
}
rqs, err = s.concatData(c, mid, ids, lang, mobile, s.c.Answer.BaseNum)
if err != nil {
log.Error("BaseQs s.concatData(%d, %d, %d) error(%v)", c, mid, ids, err)
return
}
at := &model.AnswerTime{
Stime: now,
Etimes: 0,
}
err = s.answerDao.SetExpireCache(c, mid, at)
rqs.CurrentTime = at.Stime
rqs.EndTime = at.Stime.Add(s.answerDuration())
s.answerDao.DelIdsCache(c, mid, model.BaseExtraPassQ)
s.answerDao.DelIdsCache(c, mid, model.BaseExtraNoPassQ)
return
}
// ConvertExtraQs extra question.
func (s *Service) ConvertExtraQs(c context.Context, mid int64, lang string, mobile bool) (res *model.AnsQueDetailList, err error) {
var ans *model.AnsQuesList
if ans, err = s.ExtraQs(c, mid, lang, mobile); err != nil {
err = errors.Wrapf(err, "s.ansRPC.ExtraQues(%d,%t)", mid, mobile)
return
}
res = s.convertExtraModel(ans)
return
}
// ExtraQs extra question.
func (s *Service) ExtraQs(c context.Context, mid int64, lang string, mobile bool) (rqs *model.AnsQuesList, err error) {
var (
ids []int64
passids []int64
npassids []int64
now = time.Now()
)
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
h, err := s.checkExtraState(c, mid, now)
if err != nil {
return
}
// keep on answer
passids, _ = s.answerDao.IdsCache(c, mid, model.BaseExtraPassQ)
npassids, err = s.answerDao.IdsCache(c, mid, model.BaseExtraNoPassQ)
if err != nil || len(passids) != s.c.Answer.BaseExtraPassNum || len(npassids) != s.c.Answer.BaseExtraNoPassNum {
var (
ok bool
)
ok, passids, npassids = s.extraQueByBigData(c, mid, "")
if !ok {
// if bigdata get extra mid faild
passids, err = s.answerDao.ExtraQidByType(c, model.BaseExtraPassQ, uint8(s.c.Answer.BaseExtraPassNum))
if err != nil {
log.Error("s.answerDao.ExtraQidByType(%d, %d, %d) error(%v)", model.BaseExtraPassQ, s.c.Answer.BaseExtraPassNum, len(passids), err)
return
}
if len(passids) != s.c.Answer.BaseExtraPassNum {
err = ecode.AnswerQsNumErr
log.Warn("passids lenth(%d) neq BaseExtraPassNum(%d)", len(passids), s.c.Answer.BaseExtraPassNum)
return
}
npassids, err = s.answerDao.ExtraQidByType(c, model.BaseExtraNoPassQ, uint8(s.c.Answer.BaseExtraNoPassNum))
if err != nil {
log.Error("s.answerDao.ExtraQidByType(%d, %d, %d) error(%v)", model.BaseExtraNoPassQ, s.c.Answer.BaseExtraNoPassNum, len(npassids), err)
return
}
if len(npassids) != s.c.Answer.BaseExtraNoPassNum {
err = ecode.AnswerQsNumErr
log.Warn("npassids lenth(%d) neq BaseExtraNoPassNum(%d)", len(npassids), s.c.Answer.BaseExtraNoPassNum)
return
}
}
}
ids = append(passids, npassids...)
rqs, err = s.concatExtraData(c, mid, ids, passids, npassids, lang, mobile, s.c.Answer.BaseExtraPassNum+s.c.Answer.BaseExtraNoPassNum)
if err != nil {
log.Error("BaseExtraQs s.concatExtraData(%d, %d, %d) error(%v)", c, mid, ids, err)
return
}
rqs.CurrentTime = now
rqs.EndTime = h.StartTime.Add(s.answerDuration())
if _, err = s.answerDao.UpdateExtraStartTime(c, h.ID, mid, now); err != nil {
log.Error("s.answerDao.UpdateExtraStartTime( %d, %d) error(%v)", h.ID, mid, err)
return
}
h.StepExtraStartTime = now
h.Mtime = now
s.userActionLog(mid, model.ExtraStartTime, h)
s.answerDao.DelHistoryCache(c, mid)
return
}
func (s *Service) checkExtraState(c context.Context, mid int64, now time.Time) (h *model.AnswerHistory, err error) {
h, err = s.history(c, mid)
if err != nil {
log.Error("s.history(%v) is nil error(%v)", h, err)
err = ecode.AnswerBaseNotPassed
return
}
if h != nil {
if h.Score > 0 && h.IsPassCaptcha == 0 {
err = ecode.AnswerCaptchaNoPassed
return
}
// if base pass
if h.StartTime.Add(s.answerDuration()).After(now) && h.Score == 0 {
if h.StepExtraCompleteTime != 0 {
err = ecode.AnswerProNoPass
}
return
}
err = ecode.AnswerBaseNotPassed
return
}
err = ecode.AnswerBaseNotPassed
return
}
// ProTypes get promotion types.
func (s *Service) proTypes(c context.Context, mid int64) (res *model.ProTypes, err error) {
var (
repro bool
ah *model.AnswerHistory
now = time.Now()
)
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
if ah, err = s.checkBase(c, mid, now); err != nil {
return
}
qsidsMc, err := s.answerDao.IdsCache(c, mid, model.Q)
if err == nil && len(qsidsMc) == s.c.Answer.ProNum {
repro = true
}
res = &model.ProTypes{List: _typeMap1, EndTime: ah.StartTime.Add(s.answerDuration()), CurrentTime: now, Repro: repro}
return
}
// ProType type.
func (s *Service) ProType(c context.Context, mid int64, lang string) (res *model.AnsProType, err error) {
var (
repro string
list = []*model.AnsTypeList{}
)
rpcRes, err := s.proTypes(c, mid)
if err != nil {
log.Error("s.proTypes(%+d) error (%v)", mid, err)
return
}
for _, vt := range rpcRes.List {
var sub = []*model.AnsType{}
for _, vst := range vt.Subs {
ansType := &model.AnsType{ID: vst.ID, Name: vst.Name}
if lang == model.LangZhTW {
ansType.Name = chinese.Convert(c, ansType.Name)
}
sub = append(sub, ansType)
}
ansTypeList := &model.AnsTypeList{Name: vt.Name, Fields: sub}
if lang == model.LangZhTW {
ansTypeList.Name = chinese.Convert(c, ansTypeList.Name)
}
list = append(list, ansTypeList)
}
if rpcRes.Repro {
repro = "yes"
} else {
repro = "no"
}
res = &model.AnsProType{List: list, CurrentTime: rpcRes.CurrentTime.Unix(), EndTime: rpcRes.EndTime.Unix(), Repro: repro}
return
}
// ConvertProQues pro question.
func (s *Service) ConvertProQues(c context.Context, mid int64, tIds string, lang string, mobile bool) (res []*model.AnsQueDetail, err error) {
var (
ans *model.AnsQuesList
ansdl *model.AnsQueDetailList
)
if ans, err = s.ProQues(c, mid, tIds, lang, mobile); err != nil {
err = errors.Wrapf(err, "s.ProQues(%d,%v,%t)", mid, tIds, mobile)
return
}
ansdl = s.convertModel(ans)
res = ansdl.QuesList
return
}
// ProQues question info.
func (s *Service) ProQues(c context.Context, mid int64, qtsStr string, lang string, mobile bool) (rqs *model.AnsQuesList, err error) {
var (
ah *model.AnswerHistory
now = time.Now()
allQids []int64
tIds, realTIDs []int
)
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
if ah, err = s.checkBase(c, mid, now); err != nil {
return
}
allQids, err = s.answerDao.IdsCache(c, mid, model.Q)
if err != nil || len(allQids) != s.c.Answer.ProNum {
tIDStrArr := strings.Split(qtsStr, ",")
if len(tIDStrArr) < _minType || len(tIDStrArr) > _maxType {
err = ecode.AnswerTypeIDsErr
return
}
if tIds, err = sliceAtoi(tIDStrArr); err != nil {
err = ecode.AnswerTypeIDsErr
return
}
for _, qt := range tIds {
if qt <= 0 {
err = ecode.RequestErr
return
}
if mapIDS, ok := _typeIdsMapping[qt]; ok {
realTIDs = append(realTIDs, mapIDS...)
continue
}
realTIDs = append(realTIDs, qt)
}
num := math.Ceil(float64(s.c.Answer.ProNum) / float64(len(realTIDs)))
log.Warn("realTIDs:%v", realTIDs)
for _, qt := range realTIDs {
var t []int64
t, err = s.answerDao.QidByType(c, qt, uint8(num))
if err != nil {
log.Error("s.answerDao.QidByType(%d, %f, %d) error(%+v)", qt, num, len(t), err)
return
}
if len(t) == 0 {
log.Error("mid:%d the QidByType(%d, %f, %d) of len is 0", mid, qt, num, len(t))
err = ecode.AnswerMidDBQueErr
return
}
allQids = append(allQids, t...)
}
if len(allQids) == 0 || len(allQids) < s.c.Answer.ProNum {
log.Error("ProQues allQids len is 0 or allQids len less(%d, %d, %f, %v, %d)", len(allQids), s.c.Answer.ProNum, num, realTIDs, mid)
err = ecode.NothingFound
return
}
}
if rqs, err = s.concatData(c, mid, allQids, lang, mobile, s.c.Answer.ProNum); err != nil {
log.Error("ProQues s.concatData(%d, %d, %d) error(%v)", c, mid, allQids, err)
return
}
if _, err = s.answerDao.UpdateStepTwoTime(c, ah.ID, mid, now); err != nil {
return
}
ah.StepTwoStartTime = now
ah.Mtime = now
s.userActionLog(mid, model.ProQues, ah)
s.answerDao.DelHistoryCache(c, mid)
return
}
func (s *Service) checkBase(c context.Context, mid int64, now time.Time) (ah *model.AnswerHistory, err error) {
ah, err = s.history(c, mid)
if err != nil || ah == nil || ah.StartTime.Add(s.answerDuration()).Before(now) || ah.Score != 0 || ah.StepOneCompleteTime == 0 {
err = ecode.AnswerBaseNotPassed
log.Error("checkBase(%d, %v) AnswerExpire error(%v)", mid, now, err)
return
}
if ah.StepExtraCompleteTime == 0 {
err = ecode.AnswerExtraNoPass
return
}
if ah.Score > 0 && ah.IsPassCaptcha == 0 {
err = ecode.AnswerCaptchaNoPassed
}
return
}
func (s *Service) checkTime(c context.Context, mid int64, now time.Time) (at *model.AnswerTime, rs bool) {
var err error
if at, err = s.answerDao.ExpireCache(c, mid); err != nil {
return
}
if at == nil || at.Stime.Add(s.answerDuration()).Before(now) {
return
}
rs = true
return
}
func (s *Service) concatData(c context.Context, mid int64, ids []int64, lang string, mobile bool, qs int) (rqs *model.AnsQuesList, err error) {
var (
list []*model.AnsQue
qm map[int64]*model.Question
)
if qm, err = s.answerDao.ByIds(c, ids); err != nil {
log.Error("s.answerDao.ByIds(%v) error(%v)", ids, err)
err = ecode.NothingFound
return
}
for _, d := range ids {
i := qm[d]
rq := s.imgPosition(c, i, mid, lang, mobile)
list = append(list, rq)
}
if len(list) > qs {
list = list[:qs]
}
rqs = &model.AnsQuesList{QuesList: list}
if err := s.answerDao.SetIdsCache(c, mid, ids, model.Q); err != nil {
log.Error("s.answerDao.SetIdsCache(%d, %d) error(%v)", mid, ids, err)
}
log.Info("s.concatData load que success(%d, %v, %v, %d)", mid, ids, mobile, qs)
return
}
func (s *Service) concatExtraData(c context.Context, mid int64, ids []int64, passids []int64, nopassids []int64, lang string, mobile bool, qs int) (rqs *model.AnsQuesList, err error) {
var (
list []*model.AnsQue
qm map[int64]*model.ExtraQst
)
if qm, err = s.answerDao.ExtraByIds(c, ids); err != nil || len(qm) < qs {
log.Error("s.answerDao.ExtraByIds(%v) error(%+v)", ids, err)
return
}
for _, d := range ids {
i := qm[d]
rq := s.imgExtraPosition(c, i, mid, lang, mobile)
list = append(list, rq)
}
if len(list) > qs {
list = list[:qs]
}
rqs = &model.AnsQuesList{QuesList: list}
if err = s.answerDao.SetIdsCache(c, mid, passids, model.BaseExtraPassQ); err != nil {
log.Error("s.answerDao.SetIdsCache(%d, %d) error(%v)", mid, passids, err)
return
}
if err = s.answerDao.SetIdsCache(c, mid, nopassids, model.BaseExtraNoPassQ); err != nil {
log.Error("s.answerDao.SetIdsCache(%d, %d) error(%v)", mid, nopassids, err)
return
}
log.Info("s.concatData extra load que success(%d, %v, %v, %d)", mid, ids, mobile, qs)
return
}
// ansHash get answer hash.
func (s *Service) ansHash(mid int64, ans string) (ansHash string) {
h := md5.New()
h.Write([]byte(fmt.Sprintf("%s%d%s", ans, mid, _hashSalt)))
return hex.EncodeToString(h.Sum(nil))
}
func (s *Service) imgPosition(c context.Context, qs *model.Question, mid int64, lang string, mobile bool) (rq *model.AnsQue) {
var (
y float64
qsLineLength float64 = 36
questionFontSize float64 = 10
questionTitleSize float64 = 12
ans = make([]*model.AnsPosition, 4)
imgStr = "v3_%s_A-%s_B-%s_C-%s_D-%s_%s"
p = map[bool]string{true: "H5", false: "PC"}
bfsHost = "https://i0.hdslb.com/bfs/member/"
as [4]string
)
rq = &model.AnsQue{ID: qs.ID}
if mobile {
qsLineLength = 11
questionFontSize = 12
questionTitleSize = 16
}
qsLength := utf8.RuneCountInString(qs.Question)
if float64(qsLength) > qsLineLength {
line := math.Ceil(float64(qsLength) / qsLineLength)
rq.Height = 2 * line * questionTitleSize
rq.PositionY = y
} else {
rq.Height = 2 * questionTitleSize
}
y = rq.Height
if lang == model.LangZhTW {
qs.Question = chinese.Convert(c, qs.Question)
qs.Ans[0] = chinese.Convert(c, qs.Ans[0])
qs.Ans[1] = chinese.Convert(c, qs.Ans[1])
qs.Ans[2] = chinese.Convert(c, qs.Ans[2])
qs.Ans[3] = chinese.Convert(c, qs.Ans[3])
}
idx := rand.Perm(4)
for i := range qs.Ans {
ans[i] = &model.AnsPosition{
AnsHash: s.ansHash(mid, qs.Ans[idx[i]]),
Height: 2 * questionFontSize,
PositionY: y,
}
y += 2 * questionFontSize
as[i] = qs.Ans[idx[i]]
}
m := md5.New()
m.Write([]byte(fmt.Sprintf(imgStr, strconv.FormatInt(qs.ID, 10), as[0], as[1], as[2], as[3], p[mobile])))
fname := hex.EncodeToString(m.Sum(nil)) + ".jpg"
if s.c.Answer.Debug {
fname = fmt.Sprintf("debug_%s", fname)
}
rq.Img = bfsHost + fname
rq.Ans = ans
return
}
func (s *Service) imgExtraPosition(c context.Context, qs *model.ExtraQst, mid int64, lang string, mobile bool) (rq *model.AnsQue) {
var (
y float64
qsLineLength float64 = 36
questionFontSize float64 = 10
questionTitleSize float64 = 12
ans = make([]*model.AnsPosition, 2)
imgStr = "%s_A-%s_B-%s_%s"
p = map[bool]string{true: "H5", false: "PC"}
bfsHost = "https://i0.hdslb.com/bfs/member/"
as [2]string
)
rq = &model.AnsQue{ID: qs.ID}
if mobile {
qsLineLength = 11
questionFontSize = 12
questionTitleSize = 16
}
qsLength := utf8.RuneCountInString(qs.Question)
if float64(qsLength) > qsLineLength {
line := math.Ceil(float64(qsLength) / qsLineLength)
rq.Height = 2 * line * questionTitleSize
rq.PositionY = y
} else {
rq.Height = 2 * questionTitleSize
}
y = rq.Height
if lang == model.LangZhTW {
as = [2]string{chinese.Convert(c, model.ExtraAnsA), chinese.Convert(c, model.ExtraAnsB)}
} else {
as = [2]string{model.ExtraAnsA, model.ExtraAnsB}
}
for k, v := range as {
ans[k] = &model.AnsPosition{
AnsHash: s.ansHash(mid, v),
Height: 2 * questionFontSize,
PositionY: y,
}
y += 2 * questionFontSize
}
m := md5.New()
m.Write([]byte(fmt.Sprintf(imgStr, strconv.FormatInt(qs.OriginID, 10), as[0], as[1], p[mobile])))
fname := hex.EncodeToString(m.Sum(nil)) + ".jpg"
if s.c.Answer.Debug {
fname = fmt.Sprintf("debug_%s", fname)
}
rq.Img = bfsHost + fname
rq.Ans = ans
return
}
func (s *Service) loadQidsCache() {
qs, err := s.answerDao.QidsByState(context.Background(), model.PassCheck)
if len(qs) == 0 || err != nil {
log.Error("s.answerDao.loadQidsCache(%d) size is zero error(%v)", model.PassCheck, err)
}
qmap := map[int8][]int64{}
for _, q := range qs {
qmap[q.TypeID] = append(qmap[q.TypeID], q.ID)
}
for k, v := range qmap {
s.answerDao.DelQidsCache(context.Background(), int(k))
s.answerDao.SetQids(context.Background(), v, int(k))
}
log.Info("s.answerDao.loadQidsCache suc(%v)", qmap)
}
func (s *Service) loadExtraQidsCache() {
qs, err := s.answerDao.QidsExtraByState(context.Background(), model.MaxLoadQueSize)
if len(qs) == 0 || err != nil {
log.Error("s.answerDao.QidsExtraByState(%d) size is zero error(%v)", model.MaxLoadQueSize, err)
return
}
qmap := map[int8][]int64{}
for _, q := range qs {
qmap[q.Ans] = append(qmap[q.Ans], q.ID)
}
for k, v := range qmap {
s.answerDao.DelExtraQidsCache(context.Background(), k)
s.answerDao.SetExtraQids(context.Background(), v, k)
}
log.Info("s.answerDao.loadExtraQidsCache suc(%v)", qmap)
}
// Cool .
func (s *Service) Cool(c context.Context, hid, mid int64) (cool *model.AnsCool, err error) {
var (
his *model.AnswerHistory
types []*model.TypeInfo
li = []*model.CoolPower{
{Name: "动画", Num: 0},
{Name: "艺术", Num: 0},
{Name: "游戏", Num: 0},
{Name: "科技", Num: 0},
{Name: "影视", Num: 0},
{Name: "鬼畜", Num: 0},
}
completeResult = make(map[int8]int64)
)
his, err = s.historyByHid(c, hid)
if err != nil {
return
}
cool = &model.AnsCool{
Score: his.Score,
IsSameUser: his.Mid == mid,
IsFirstPass: his.IsFirstPass,
Level: his.PassedLevel,
Share: &model.CoolShare{},
VideoInfo: &model.CoolVideo{},
Rank: &model.CoolRank{},
}
cool.CanShowRankBtn = his.Score >= 85 && his.Mtime.Before(time.Now().Add(_rankBtn))
r := _pendantIDNameMap[int(his.RankID)]
if r != "" {
if rid, ok := _oldPIDToNewMap[his.RankID]; ok {
his.RankID = rid
}
rs := _rankShire[his.RankID]
idx := rand.Perm(len(rs.VideoArr))
cool.ViewMore = rs.ViewMore
cool.Share = rs.Share
cool.VideoInfo = rs.VideoArr[idx[0]]
cool.Rank = &model.CoolRank{
ID: int(his.RankID),
Name: r,
Img: "https://i0.hdslb.com" + _pendantIDImgMap[his.RankID],
}
}
us, err := s.accInfo(c, his.Mid)
if err != nil || us == nil {
log.Error("CheckQueCaptcha accInfo(%d) info is null error(%v)", mid, err)
return
}
cool.Name = us.Name
cool.Face = us.Face
if err = json.Unmarshal([]byte(his.CompleteResult), &completeResult); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", his.CompleteResult, err)
err = nil
}
log.Info("hid(%d), completeResult: %v+", hid, completeResult)
for k := range completeResult {
for _, t := range s.questionTypeCache {
if len(t.Subs) != 0 {
for _, s := range t.Subs {
if int64(k) == s.ID {
types = append(types, &model.TypeInfo{ID: s.ID, Name: s.Name, LabelName: s.LabelName})
}
}
}
if int64(k) == t.ID {
types = append(types, t)
}
}
}
log.Info("hid(%d), cool types: %v+", hid, types)
for _, t := range types {
for _, p := range li {
if p.Name == t.LabelName {
p.Num += completeResult[int8(t.ID)]
}
}
}
log.Info("hid(%d), power types: %v+", hid, li)
cool.Powers = append(cool.Powers, li...)
if _, ok := _recTypeIDMap[int(his.RankID)]; ok {
cool.MainTids = _recTypeIDMap[int(his.RankID)]["main_tid"]
cool.SubTids = _recTypeIDMap[int(his.RankID)]["sub_tid"]
}
return
}
// ExtraScore .
func (s *Service) ExtraScore(c context.Context, mid int64) (res *model.ExtraScoreReply, err error) {
res = &model.ExtraScoreReply{}
h, err := s.history(c, mid)
if err != nil {
return
}
res.Score = h.StepExtraScore + int64(s.c.Answer.BaseNum)
return
}
func (s *Service) history(c context.Context, mid int64) (ah *model.AnswerHistory, err error) {
cok := true
if ah, err = s.answerDao.HistoryCache(c, mid); err != nil {
cok = false
return
}
if ah != nil {
return
}
ah, err = s.answerDao.History(c, mid)
if err != nil {
return
}
if ah != nil && cok {
s.answerDao.SetHistoryCache(c, mid, ah)
}
return
}
func (s *Service) answerDuration() (d time.Duration) {
return time.Duration(s.c.Answer.Duration) * time.Minute
}
func sliceAtoi(sa []string) ([]int, error) {
si := make([]int, 0, len(sa))
for _, a := range sa {
i, err := strconv.Atoi(a)
if err != nil {
return si, err
}
si = append(si, i)
}
return si, nil
}
func (s *Service) extraQueByBigData(c context.Context, mid int64, ip string) (ok bool, passids []int64, npassids []int64) {
passids, npassids, err := s.accountDao.ExtraIds(c, mid, ip)
if err != nil || len(passids) != s.c.Answer.BaseExtraPassNum || len(npassids) != s.c.Answer.BaseExtraNoPassNum {
return
}
ids := append(passids, npassids...)
if qm, err := s.answerDao.ExtraByIds(c, ids); err != nil || len(qm) != (s.c.Answer.BaseExtraPassNum+s.c.Answer.BaseExtraNoPassNum) {
log.Error("s.answerDao.ExtraByIds(%v) error(%v)", ids, err)
return
}
ok = true
return
}
func (s *Service) loadtypes() (t map[int64]*model.TypeInfo) {
tys, err := s.answerDao.Types(context.Background())
if err != nil {
log.Error("s.questionDao.Types error(%v)", err)
return
}
tmp := map[int64]*model.TypeInfo{}
for _, v := range tys {
if v.Parentid == 0 && tmp[v.ID] == nil {
tmp[v.ID] = &model.TypeInfo{ID: v.ID, Name: v.Name, Subs: []*model.SubType{}}
} else if tmp[v.Parentid] != nil {
tmp[v.Parentid].Subs = append(tmp[v.Parentid].Subs, &model.SubType{ID: v.ID, Name: v.Name, LabelName: v.LabelName})
}
}
s.questionTypeCache = tmp
t = tmp
log.Info("load question type cacheproc success,%v", t)
return
}

View File

@@ -0,0 +1,202 @@
package service
import (
"context"
"fmt"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceBaseQ(t *testing.T) {
convey.Convey("BaseQ", t, func() {
res, err := s.BaseQ(context.Background(), 14771787, "", false)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceBaseQs(t *testing.T) {
convey.Convey("BaseQs", t, func() {
rqs, err := s.BaseQs(context.Background(), 14771787, "", false)
convey.So(err, convey.ShouldBeNil)
convey.So(rqs, convey.ShouldNotBeNil)
})
}
func TestServiceConvertExtraQs(t *testing.T) {
convey.Convey("ConvertExtraQs", t, func() {
res, err := s.ConvertExtraQs(context.Background(), 14771787, "", false)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceExtraQs(t *testing.T) {
convey.Convey("ExtraQs", t, func() {
rqs, err := s.ExtraQs(context.Background(), 14771787, "", false)
convey.So(err, convey.ShouldBeNil)
convey.So(rqs, convey.ShouldNotBeNil)
})
}
func TestServicecheckExtraState(t *testing.T) {
convey.Convey("checkExtraState", t, func() {
h, err := s.checkExtraState(context.Background(), 14771787, time.Now())
convey.So(err, convey.ShouldBeNil)
convey.So(h, convey.ShouldNotBeNil)
})
}
func TestServiceProTypes(t *testing.T) {
convey.Convey("ProTypes", t, func() {
res, err := s.proTypes(context.Background(), 14771787)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceProType(t *testing.T) {
convey.Convey("ProType", t, func() {
res, err := s.ProType(context.Background(), 14771787, "")
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceConvertProQues(t *testing.T) {
convey.Convey("ConvertProQues", t, func() {
res, err := s.ConvertProQues(context.Background(), 14771787, "", "", false)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceProQues(t *testing.T) {
convey.Convey("ProQues", t, func() {
rqs, err := s.ProQues(context.Background(), 14771787, "", "", false)
convey.So(err, convey.ShouldBeNil)
convey.So(rqs, convey.ShouldNotBeNil)
})
}
func TestServicecheckBase(t *testing.T) {
convey.Convey("checkBase", t, func() {
ah, err := s.checkBase(context.Background(), 0, time.Now())
convey.So(err, convey.ShouldBeNil)
convey.So(ah, convey.ShouldNotBeNil)
})
}
func TestServicecheckTime(t *testing.T) {
convey.Convey("checkTime", t, func() {
at, rs := s.checkTime(context.Background(), 0, time.Now())
convey.So(rs, convey.ShouldNotBeNil)
convey.So(at, convey.ShouldNotBeNil)
})
}
func TestServiceconcatData(t *testing.T) {
convey.Convey("concatData", t, func() {
rqs, err := s.concatData(context.Background(), 14771787, []int64{}, "", false, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(rqs, convey.ShouldNotBeNil)
})
}
func TestServiceconcatExtraData(t *testing.T) {
convey.Convey("concatExtraData", t, func() {
rqs, err := s.concatExtraData(context.Background(), 14771787, []int64{}, []int64{}, []int64{}, "", false, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(rqs, convey.ShouldNotBeNil)
})
}
func TestServiceansHash(t *testing.T) {
convey.Convey("ansHash", t, func() {
ansHash := s.ansHash(0, "")
convey.So(ansHash, convey.ShouldNotBeNil)
})
}
func TestServiceimgPosition(t *testing.T) {
convey.Convey("imgPosition", t, func() {
rq := s.imgPosition(context.Background(), nil, 14771787, "", false)
convey.So(rq, convey.ShouldNotBeNil)
})
}
func TestServiceimgExtraPosition(t *testing.T) {
convey.Convey("imgExtraPosition", t, func() {
rq := s.imgExtraPosition(context.Background(), nil, 14771787, "", false)
convey.So(rq, convey.ShouldNotBeNil)
})
}
func TestServiceloadQidsCache(t *testing.T) {
convey.Convey("loadQidsCache", t, func() {
s.loadQidsCache()
})
}
func TestServiceloadExtraQidsCache(t *testing.T) {
convey.Convey("loadExtraQidsCache", t, func() {
s.loadExtraQidsCache()
})
}
func TestServiceCool(t *testing.T) {
convey.Convey("Cool", t, func() {
cool, err := s.Cool(context.Background(), 0, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(cool, convey.ShouldNotBeNil)
})
}
func TestServiceExtraScore(t *testing.T) {
convey.Convey("ExtraScore", t, func() {
score, err := s.ExtraScore(context.Background(), 6383240)
fmt.Println(score)
convey.So(err, convey.ShouldBeNil)
convey.So(score, convey.ShouldBeGreaterThanOrEqualTo, 0)
})
}
func TestServicehistory(t *testing.T) {
convey.Convey("history", t, func() {
ah, err := s.history(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ah, convey.ShouldNotBeNil)
})
}
func TestServiceanswerDuration(t *testing.T) {
convey.Convey("answerDuration", t, func() {
d := s.answerDuration()
convey.So(d, convey.ShouldNotBeNil)
})
}
func TestSliceAtoi(t *testing.T) {
convey.Convey("sliceAtoi", t, func() {
p1, p2 := sliceAtoi([]string{})
convey.So(p2, convey.ShouldBeNil)
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestServiceextraQueByBigData(t *testing.T) {
convey.Convey("extraQueByBigData", t, func() {
ok, passids, npassids := s.extraQueByBigData(context.Background(), 0, "")
convey.So(npassids, convey.ShouldNotBeNil)
convey.So(passids, convey.ShouldNotBeNil)
convey.So(ok, convey.ShouldNotBeNil)
})
}
func TestServiceloadtypes(t *testing.T) {
convey.Convey("loadtypes", t, func() {
t := s.loadtypes()
convey.So(t, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,746 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"go-common/app/interface/main/answer/conf"
"go-common/app/interface/main/answer/model"
accoutCli "go-common/app/service/main/account/api"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/log/infoc"
"go-common/library/net/metadata"
"go-common/library/text/translate/chinese"
)
var (
// concat type type_id : _pendantIDNameMap id
_rankIDPendantMap = map[int]int{
// 11: 6,
// 12: 7,
// 14: 8,
// 15: 9,
// 17: 11,
// 27: 10,
// 28: 12,
// 41: 13,
// 9: 14,
27: 124,
28: 127,
31: 126,
29: 123,
18: 121,
8: 125,
19: 129,
15: 130,
7: 128,
}
// concat pendant
_pendantIDNameMap = map[int]string{
5: "哔哩王",
6: "声控",
7: "追番党",
8: "调教师",
9: "动感DJ",
10: "局座",
11: "攻略组",
12: "学霸",
13: "迷影者",
14: "全明星",
122: "哔哩王",
124: "声控",
127: "追番党",
126: "调教师",
123: "动感DJ",
121: "局座",
125: "攻略组",
129: "学霸",
130: "迷影者",
128: "全明星",
}
// 老挂件id对应新挂件id
_oldPIDToNewMap = map[int]int{
5: 122,
6: 124,
7: 127,
8: 126,
9: 123,
10: 121,
11: 125,
12: 129,
13: 130,
14: 128,
122: 122,
124: 124,
127: 127,
126: 126,
123: 123,
121: 121,
125: 125,
129: 129,
130: 130,
128: 128,
}
_pendantIDImgMap = map[int]string{
122: "/bfs/face/67ed957ae789852bcc59b1c1e3097ea23179f793.png",
124: "/bfs/face/ff61b405cdcf8f7860c67293218340aeaed6e233.png",
127: "/bfs/face/369098093a07af821b767eac44b51f97ee8501c5.png",
126: "/bfs/face/9e775c3ebe224a774d4b2f99fd5be342eb6f51ec.png",
123: "/bfs/face/939fa982d8b1c1fd653de5c7890db03d62e87226.png",
121: "/bfs/face/7f6b5cb11ea7abd2e05b04f65f190dfb10456554.png",
125: "/bfs/face/90cc47168e40326dc934fad7b9abb82aa748d6ac.png",
129: "/bfs/face/42869dad53926c75e3010150c15b16a8925fb268.png",
130: "/bfs/face/3d5ee491c125bf452b2dbec082dbb8209b645316.png",
128: "/bfs/face/b53937110e8009a720e2426ea69c449483718b3c.png",
}
// 125: "攻略组",--> 题库(8,9,12,13,14)
// 130: "迷影者",--> 题库(15,16,17)
// 121: "局座",--> 题库(18)
// 129: "学霸",--> 题库(19,20,21,22,23,24,25,26)
// 124: "声控",--> 题库(27)
// 127: "追番党",--> 题库(28)
// 126: "调教师",--> 题库(31)
// 123: "动感DJ",--> 题库(30,29)
// 128: "全明星",--> 题库(35,34,33,32)
// 122: "哔哩王",
// 分区合并归类
_typeIDMap = map[int][]int{
// 11: {12, 13}, // 动漫作品+动漫内容
// 15: {15, 16}, // ACG+三次元音乐
// 17: {17, 18, 19, 20, 21, 22, 23, 24, 25}, // 各类游戏
// 28: {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 7, 8}, // 科学技术+音频+视频技术
// 41: {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52}, // 各类影视剧
8: {8, 9, 12, 13, 14}, // 游戏
19: {19, 20, 21, 22, 23, 24, 25, 26}, // 科技
15: {15, 16, 17}, // 影视
29: {29, 30}, // 音乐
7: {7, 35, 34, 33, 32}, // 鬼畜+流行前线
}
// 兼容账号rank错误
_rank0 = int32(0)
)
const (
_unBindTel = 0
)
// ProCheck check second step questions
func (s *Service) ProCheck(c context.Context, mid int64, ids []int64, ansHash map[int64]string, lang string) (hid int64, err error) {
var now = time.Now()
if len(ids) != s.c.Answer.ProNum {
err = ecode.AnswerQsNumErr
return
}
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
ah, err := s.history(c, mid)
if err != nil || ah == nil || ah.StartTime.Add(s.answerDuration()).Before(now) || ah.Score != 0 {
err = ecode.AnswerBaseNotPassed
return
}
if (now.Unix() - ah.StepTwoStartTime.Unix()) < s.c.Answer.BlockedTimestamp {
s.answerDao.SetBlockCache(c, mid)
log.Error("member user answer block, time space(%v)", now.Unix()-ah.StepTwoStartTime.Unix())
err = ecode.AnswerBlock
return
}
qsidsMc, err := s.answerDao.IdsCache(c, mid, model.Q)
if err != nil {
err = ecode.AnswerMidCacheQidsErr
log.Error("s.answerDao.IdsCache(%d) err(%v) ", mid, err)
return
}
ok, err := s.checkQsIDs(c, ids, mid, qsidsMc, s.c.Answer.ProNum)
if !ok {
return
}
errIds, rc, err := s.checkAns(c, mid, ids, ansHash, lang, s.c.Answer.ProNum)
if err != nil {
return
}
rcJSON, err := json.Marshal(rc)
if err != nil {
log.Error("json.Marshal(%v) error(%v)", rc, err)
return
}
total := s.c.Answer.BaseNum + s.c.Answer.ProNum + int(ah.StepExtraScore)
score := total - len(errIds)
ahDB := &model.AnswerHistory{
ID: ah.ID,
Hid: ah.Hid,
CompleteResult: string(rcJSON),
CompleteTime: now,
Score: int8(score),
IsFirstPass: 0,
}
log.Info("user: %d, score:%d, his: %v", mid, score, ahDB)
member, err := s.accInfo(c, mid)
if err == nil && member != nil && score >= model.Score60 && member.Rank == model.UserInfoRank {
ahDB.IsFirstPass = 1
}
ahDB.RankID = s.pendant(c, ahDB, mid, metadata.String(c, metadata.RemoteIP), rc)
r, err := s.answerDao.SetHistory(c, mid, ahDB)
if err != nil || r != 1 {
return
}
ah.CompleteResult = ahDB.CompleteResult
ah.CompleteTime = ahDB.CompleteTime
ah.Score = ahDB.Score
ah.IsFirstPass = ahDB.IsFirstPass
ah.RankID = ahDB.RankID
ah.Mtime = now
s.userActionLog(mid, model.ProCheck, ah)
if ahDB.Score >= model.Score60 && ahDB.RankID > 0 {
if hid, _, err = s.answerDao.PendantHistory(c, mid); err != nil {
return
}
if hid <= 0 {
s.answerDao.AddPendantHistory(c, mid, ah.Hid)
}
}
hid = ah.Hid
s.missch.Do(c, func(ctx context.Context) {
s.answerDao.DelHistoryCache(ctx, mid)
s.answerDao.DelIdsCache(ctx, mid, model.Q)
})
return
}
// CheckBase check base question all
func (s *Service) CheckBase(c context.Context, mid int64, ids []int64, ansHas map[int64]string, lang string) (res *model.AnsCheck, err error) {
var (
now = time.Now()
errIds []int64
profileReply *accoutCli.ProfileReply
)
// 检查手机绑定
if profileReply, err = s.accountSvc.Profile3(c, &accoutCli.MidReq{Mid: mid}); err != nil || profileReply == nil || profileReply.Profile == nil {
log.Error("s.accRPC.Profile3(%d) err(%+v)", mid, err)
err = ecode.AnswerAccCallErr
return
}
if profileReply.Profile.TelStatus == _unBindTel {
err = ecode.AnswerNeedBindTel
return
}
if len(ids) < s.c.Answer.BaseNum {
err = ecode.RequestErr
return
}
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
res = &model.AnsCheck{}
at, ok := s.checkTime(c, mid, now)
if !ok {
err = ecode.AnswerTimeExpire
return
}
if len(ids) != s.c.Answer.BaseNum {
err = ecode.AnswerQsNumErr
return
}
qsIdsMc, err := s.answerDao.IdsCache(c, mid, model.Q)
if err != nil {
log.Error("s.answerDao.IdsCache(%d) err(%v) ", mid, err)
err = ecode.AnswerMidCacheQidsErr
return
}
ok, err = s.checkQsIDs(c, ids, mid, qsIdsMc, s.c.Answer.BaseNum)
if err != nil || !ok {
return
}
errIds, _, err = s.checkAns(c, mid, ids, ansHas, lang, s.c.Answer.BaseNum)
res.QidList = errIds
if err != nil {
return
}
if len(errIds) > 0 {
return
}
s.basePass(c, mid, at, now)
res.Pass = true
return
}
// Captcha get question captcha
func (s *Service) Captcha(c context.Context, mid int64, clientType string, newCaptcha int) (res *model.ProcessRes, err error) {
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
ah, err := s.history(c, mid)
if err != nil || ah == nil || ah.Score == model.Score0 {
log.Info("answer Captcha(%d) answer history is null or score is zero err(%v) ", mid, err)
if ah != nil {
if ah.StepOneCompleteTime == 0 {
err = ecode.AnswerBaseNotPassed
return
}
if ah.StepExtraCompleteTime == 0 {
err = ecode.AnswerExtraNoPass
return
}
}
err = ecode.AnswerProNoPass
return
}
if ah.IsPassCaptcha == model.CaptchaPass {
err = ecode.AnswerCaptchaPassed
return
}
if !conf.Conf.Answer.Captcha {
if res, err = s.preProcess(c, mid, metadata.String(c, metadata.RemoteIP), clientType, newCaptcha); err == nil {
return
}
log.Error("s.preProcess(%d,%s,%d) err:%+v", mid, clientType, newCaptcha, err)
}
var token, url string
if token, url, err = s.answerDao.Captcha(c); err != nil {
return
}
res = &model.ProcessRes{
Token: token,
URL: url,
CaptchaType: model.BiliCaptcha,
}
return
}
// Validate check question captcha
func (s *Service) Validate(c context.Context, challenge, validate, seccode, clientType string, success int, mid int64,
cookie, captchaType string, comargs map[string]string) (res *model.AnsCheck, err error) {
var now = time.Now()
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
res = &model.AnsCheck{}
ah, err := s.history(c, mid)
log.Info(" Validate ah (%d) res(%v) ", mid, ah)
if err != nil || ah == nil || ah.Score == model.Score0 {
log.Info("answer Validate(%d) answer history is null or score is zero err(%v) ", mid, err)
if ah != nil {
if ah.StepOneCompleteTime == 0 {
err = ecode.AnswerBaseNotPassed
return
}
if ah.StepExtraCompleteTime == 0 {
err = ecode.AnswerExtraNoPass
return
}
}
err = ecode.AnswerProNoPass
return
}
// passed go to next page
if ah.IsPassCaptcha == model.CaptchaPass {
res.Pass = true
res.HistoryID = ah.Hid
return
}
ip := metadata.String(c, metadata.RemoteIP)
switch captchaType {
case model.BiliCaptcha:
if err = s.answerDao.Verify(c, validate, seccode, ip); err != nil {
log.Error("answerDao.Verify(%s,%s,%s) error:%+v", validate, seccode, ip, err)
return
}
res.Pass = true
default:
if ok := s.validate(c, challenge, validate, seccode, clientType, ip, success, mid); !ok {
log.Error("Validate validate(%v,%v,%v,%v,%v,%d) error(%v)", challenge, validate, seccode, clientType, success, mid, err)
err = ecode.AnswerGeetestVaErr
return
}
res.Pass = true
}
member, err := s.accInfo(c, mid)
if err != nil || member == nil {
log.Error("Validate accInfo(%d) info is null error(%v)", mid, err)
return
}
if _, err = s.answerDao.UpdateCaptcha(c, ah.ID, ah.Mid, model.CaptchaPass); err != nil {
log.Error("s.answerDao.UpdateCaptcha error (%v) ", err)
err = ecode.ServerErr
return
}
ah.IsPassCaptcha = model.CaptchaPass
ah.Mtime = now
s.userActionLog(mid, model.Captcha, ah)
s.answerDao.DelHistoryCache(c, mid)
log.Info(" Validate member (%v) rank(%d) ", ah, member.Rank)
if ah.Score >= model.Score60 && (member.Rank == model.UserInfoRank || member.Rank == _rank0) {
log.Info(" beFormal in (%d) ", mid)
s.sendData(c, comargs, ah, ip)
if err = s.accountDao.BeFormal(c, mid, ip); err != nil {
log.Error(" beFormal fail(%d) err(%v)", mid, err)
s.addRetryBeFormal(&model.Formal{Mid: mid, IP: ip})
err = ecode.AnswerFormalFailed
return
}
s.answerDao.UpdateLevel(c, ah.ID, ah.Mid, 1, 1)
ah.IsFirstPass = 1
ah.PassedLevel = 1
ah.Mtime = now
s.userActionLog(mid, model.Level, ah)
s.answerDao.DelHistoryCache(c, mid)
s.PendantRec(c, &model.ReqPendant{HID: ah.Hid, MID: mid})
}
res.HistoryID = ah.Hid
return
}
// checkQsIDs check question id param.
func (s *Service) checkQsIDs(c context.Context, ids []int64, mid int64, qsIdsMc []int64, qs int) (ok bool, err error) {
if qsIdsMc == nil {
log.Error("CheckBase.qsIdsMc is nil (%d,%v) )", mid, qsIdsMc)
err = ecode.AnswerMidCacheQidsErr
return
}
if len(ids) != qs {
err = ecode.AnswerQsNumErr
return
}
qidMap := map[int64]bool{}
for _, v := range qsIdsMc {
qidMap[v] = true
}
i := 0
for _, v := range ids {
if qidMap[v] {
i++
}
}
if i == qs {
ok = true
} else {
err = ecode.AnswerQidDiffRequestErr
}
return
}
// checkAns check question ans.
func (s *Service) checkAns(c context.Context, mid int64, ids []int64, ansHash map[int64]string, lang string, count int) (errIds []int64, rc map[int8]int, err error) {
qs, err := s.answerDao.ByIds(c, ids)
if err != nil || qs == nil || len(qs) != count {
log.Error("checkAns.qs is nil (%v,%v) error(%v)", ids, qs, err)
err = ecode.AnswerMidDBQueErr
return
}
errIds = []int64{}
rc = make(map[int8]int)
for _, q := range qs {
if lang == model.LangZhTW {
q.Ans[0] = chinese.Convert(c, q.Ans[0])
}
if h := s.ansHash(mid, q.Ans[0]); h != ansHash[q.ID] {
errIds = append(errIds, q.ID)
} else {
rc[q.TypeID]++
}
}
return
}
// basePass base question pass.
func (s *Service) basePass(c context.Context, mid int64, at *model.AnswerTime, now time.Time) {
h := &model.AnswerHistory{
Mid: mid,
StartTime: at.Stime,
StepOneErrTimes: at.Etimes,
StepOneCompleteTime: now.Unix() - at.Stime.Unix(),
Ctime: now,
Mtime: now,
}
r, hid, err := s.answerDao.AddHistory(c, mid, h)
if err != nil || r != 1 {
log.Error("answerDao.AddHistory r !=1 (%d,%v) error(%v)", mid, h, err)
return
}
h.Hid, _ = strconv.ParseInt(hid, 10, 64)
s.userActionLog(mid, model.BasePass, h)
s.answerDao.DelHistoryCache(c, mid)
s.answerDao.DelExpireCache(c, mid)
s.answerDao.DelIdsCache(c, mid, model.Q)
}
// setPendant set pendant.
func (s *Service) pendant(c context.Context, ah *model.AnswerHistory, mid int64, ip string, rc map[int8]int) (rankID int) {
var (
ok bool
ht int
typeIDScore = map[int8]int{ // key:_typeIDMap`key,value:Score
8: 0,
19: 0,
15: 0,
29: 0,
7: 0,
}
)
if ah.Score == model.FullScore {
return model.RankTop // 122: "哔哩王",
}
for k, v := range rc {
switch k {
case 8, 9, 12, 13, 14: // 游戏 125: "攻略组",--> 题库(8,9,12,13,14)
typeIDScore[8] += v
case 15, 16, 17: // 影视 130: "迷影者",--> 题库(15,16,17)
typeIDScore[15] += v
case 19, 20, 21, 22, 23, 24, 25, 26: // 科技 129: "学霸",--> 题库(19,20,21,22,23,24,25,26)
typeIDScore[19] += v
case 29, 30: // 音乐 123: "动感DJ",--> 题库(30,29)
typeIDScore[29] += v
case 7, 35, 34, 33, 32: // 鬼畜+流行前线 128: "全明星",--> 题库(35,34,33,32)
typeIDScore[7] += v
default:
// 121: "局座",--> 题库(18)
// 124: "声控",--> 题库(27)
// 127: "追番党",--> 题库(28)
// 126: "调教师",--> 题库(31)
typeIDScore[k] += v
}
}
score := 0
for k, v := range typeIDScore {
if score < v {
score = v
ht = int(k)
}
}
rankID, ok = _rankIDPendantMap[ht]
if !ok {
log.Warn("user(%d),pendant() rankId(%d) result:%+v ", mid, _rankIDPendantMap[ht], rc)
}
return
}
func (s *Service) checkAnswerBlock(c context.Context, mid int64) (block bool) {
block, _ = s.answerDao.CheckBlockCache(c, mid)
return
}
func (s *Service) sendData(c context.Context, comargs map[string]string, ah *model.AnswerHistory, ip string) {
s.promBeFormal.Incr("count")
// add report bigdata log
ans := []interface{}{
strconv.FormatInt(ah.StepOneCompleteTime, 10),
ah.CompleteResult,
strconv.FormatInt(ah.CompleteTime.Unix()-ah.StepTwoStartTime.Unix(), 10),
fmt.Sprintf("%d", ah.Score),
strconv.FormatInt(time.Now().Unix(), 10),
}
s.missch.Do(c, func(ctx context.Context) {
ac := map[string]string{
"itemType": infoc.ItemTypeLV,
"action": infoc.ActionAnswer,
"ip": ip,
"mid": strconv.FormatInt(ah.Mid, 10),
"sid": comargs["sid"],
"ua": comargs["ua"],
"buvid": comargs["buvid"],
"refer": comargs["refer"],
"url": comargs["url"],
}
log.Info("s.infoc2.ServiceAntiCheatBus(%v,%v)", ac, ans)
s.infoc2.ServiceAntiCheatBus(ac, ans)
})
}
// ExtraCheck extra check.
func (s *Service) ExtraCheck(c context.Context, mid int64, ids []int64, ansHash map[int64]string, ua string, lang string, refer string, buvid string) (err error) {
var now = time.Now()
if len(ids) < s.c.Answer.ExtraNum {
err = ecode.RequestErr
return
}
if s.checkAnswerBlock(c, mid) {
err = ecode.AnswerBlock
return
}
ah, err := s.history(c, mid)
if err != nil || ah == nil || ah.StartTime.Add(s.answerDuration()).Before(now) || ah.Score != 0 {
err = ecode.AnswerBaseNotPassed
return
}
if len(ids) != (s.c.Answer.BaseExtraPassNum + s.c.Answer.BaseExtraNoPassNum) {
return ecode.AnswerQsNumErr
}
passids, err := s.answerDao.IdsCache(c, mid, model.BaseExtraPassQ)
if err != nil {
log.Error("s.answerDao.IdsCache(%d) extra pass err(%v) ", mid, err)
return ecode.AnswerMidCacheQidsErr
}
nopassids, err := s.answerDao.IdsCache(c, mid, model.BaseExtraNoPassQ)
if err != nil {
log.Error("s.answerDao.IdsCache(%d) extra nopass err(%v) ", mid, err)
return ecode.AnswerMidCacheQidsErr
}
idsmc := append(passids, nopassids...)
ok, err := s.checkQsIDs(c, ids, mid, idsmc, s.c.Answer.BaseExtraPassNum+s.c.Answer.BaseExtraNoPassNum)
if err != nil || !ok {
return
}
ret, qs, _ := s.checkExtraPassAns(c, mid, passids, ansHash, lang, s.c.Answer.BaseExtraPassNum)
ah.StepExtraScore = int64(ret * s.c.Answer.BaseExtraScore)
ah.StepExtraCompleteTime = now.Unix() - ah.StartTime.Unix()
if _, err = s.answerDao.UpdateExtraRet(c, ah.ID, mid, ah.StepExtraCompleteTime, ah.StepExtraScore); err != nil {
log.Error("s.answerDao.UpdateExtraRet(%d) err(%v) ", mid, err)
return
}
ah.Mtime = now
s.userActionLog(mid, model.ExtraCheck, ah)
s.answerDao.DelHistoryCache(c, mid)
s.answerDao.DelIdsCache(c, mid, model.BaseExtraPassQ)
s.answerDao.DelIdsCache(c, mid, model.BaseExtraNoPassQ)
// send answer ret to bigdata
rs, err := s.sendExtraRetMsg(c, mid, qs, nopassids, ansHash, s.c.Answer.BaseExtraNoPassNum)
if err != nil {
log.Error("s.sendExtraRetMsg(%d,%v,%v,%v) err(%v) ", mid, qs, nopassids, ansHash, err)
return
}
s.answerDao.PubExtraRet(c, mid, &model.DataBusResult{
Mid: mid,
Buvid: buvid,
IP: metadata.String(c, metadata.RemoteIP),
Ua: ua,
Refer: refer,
Score: int8(ah.StepExtraScore),
Rs: rs,
Hid: ah.Hid,
})
return
}
// checkExtraPassAns check extra question ans.
func (s *Service) checkExtraPassAns(c context.Context, mid int64, ids []int64, ansHash map[int64]string, lang string, count int) (ret int, qs map[int64]*model.ExtraQst, err error) {
qs, err = s.answerDao.ExtraByIds(c, ids)
if err != nil || qs == nil || len(qs) != count {
log.Error("checkAns extra qs is nil (%v,%v) error(%v)", ids, qs, err)
err = ecode.AnswerMidDBQueErr
return
}
for _, q := range qs {
var ans string
switch q.Ans {
case model.NormalQ:
if lang == model.LangZhTW {
ans = s.ansHash(mid, chinese.Convert(c, model.ExtraAnsA))
} else {
ans = s.ansHash(mid, model.ExtraAnsA)
}
case model.ViolationQ:
if lang == model.LangZhTW {
ans = s.ansHash(mid, chinese.Convert(c, model.ExtraAnsB))
} else {
ans = s.ansHash(mid, model.ExtraAnsB)
}
}
if ansHash[q.ID] == ans {
ret++
}
}
return
}
func (s *Service) sendExtraRetMsg(c context.Context, mid int64, passqs map[int64]*model.ExtraQst, nopassids []int64,
ansHash map[int64]string, count int) (rs []*model.Rs, err error) {
var (
qs map[int64]*model.ExtraQst
)
qs, err = s.answerDao.ExtraByIds(c, nopassids)
if err != nil || qs == nil || len(qs) != count {
log.Error("checkAns extra nopassqs is nil (%v) error(%v)", qs, err)
err = ecode.AnswerMidDBQueErr
return
}
for k, v := range passqs {
qs[k] = v
}
for _, q := range qs {
var (
userAns int8
)
ansA := s.ansHash(mid, model.ExtraAnsA)
ansB := s.ansHash(mid, model.ExtraAnsB)
switch ansHash[q.ID] {
case ansA:
userAns = model.NormalQ
case ansB:
userAns = model.ViolationQ
default:
userAns = model.UnKownQ
}
rs = append(rs, &model.Rs{
ID: q.OriginID,
Question: q.Question,
Ans: userAns,
TrueAns: q.Ans,
AvID: q.AvID,
Status: q.Status,
Source: q.Source,
Ctime: q.Ctime,
Mtime: q.Mtime,
})
}
return
}
// PendantRec .
func (s *Service) PendantRec(c context.Context, arg *model.ReqPendant) (err error) {
var (
ok bool
status int8
hid, ret int64
his *model.AnswerHistory
)
if hid, status, err = s.answerDao.PendantHistory(c, arg.MID); err != nil {
return
}
if hid != arg.HID {
log.Warn("mid(%d) arg.hid(%d) db.hid(%d) is invald!", arg.MID, arg.HID, hid)
return
}
if status != model.PendantNotGet {
log.Warn("mid(%d) hid(%d) not first get!", arg.MID, arg.HID)
return
}
his, err = s.historyByHid(c, arg.HID)
if err != nil {
return
}
if his.Score < model.Score60 || his.IsFirstPass != 1 {
log.Warn("mid(%d) hid(%d) score(%d) isFirstPass(%d) not pass or first answer !", arg.MID, arg.HID, his.Score, his.IsFirstPass)
return
}
if _, ok = _pendantIDNameMap[int(his.RankID)]; !ok {
log.Warn("mid(%d) get illegal pid(%d) by answer first!", arg.MID, int(his.RankID))
return
}
if ret, err = s.answerDao.UpPendantHistory(c, arg.MID, arg.HID); err != nil {
return
}
if ret <= 0 {
log.Warn("mid(%d) hid(%d) pid(%d) history answer not get!", arg.MID, arg.HID, int(his.RankID))
return
}
s.missch.Do(c, func(ctx context.Context) {
if pendantErr := s.accountDao.GivePendant(ctx, arg.MID, int64(his.RankID), model.PenDantDays, metadata.String(c, metadata.RemoteIP)); pendantErr != nil {
log.Error("s.accountDao.GivePendant(%d,%d) error(%+v)", arg.MID, int64(his.RankID), pendantErr)
}
})
return
}

View File

@@ -0,0 +1,115 @@
package service
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceProCheck(t *testing.T) {
convey.Convey("ProCheck", t, func() {
hid, err := s.ProCheck(context.Background(), 14771787, []int64{}, nil, "")
convey.So(err, convey.ShouldBeNil)
convey.So(hid, convey.ShouldNotBeNil)
})
}
func TestServiceCheckBase(t *testing.T) {
convey.Convey("CheckBase", t, func() {
res, err := s.CheckBase(context.Background(), 14771787, []int64{}, nil, "")
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceCaptcha(t *testing.T) {
convey.Convey("Captcha", t, func() {
res, err := s.Captcha(context.Background(), 14771787, "", 0)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceValidate(t *testing.T) {
convey.Convey("Validate", t, func() {
res, err := s.Validate(context.Background(), "", "", "", "", 0, 0, "", "", nil)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServicecheckQsIDs(t *testing.T) {
convey.Convey("checkQsIDs", t, func() {
ok, err := s.checkQsIDs(context.Background(), []int64{}, 0, []int64{}, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ok, convey.ShouldNotBeNil)
})
}
func TestServicecheckAns(t *testing.T) {
convey.Convey("checkAns", t, func() {
errIds, rc, err := s.checkAns(context.Background(), 14771787, []int64{}, nil, "", 0)
convey.So(err, convey.ShouldBeNil)
convey.So(rc, convey.ShouldNotBeNil)
convey.So(errIds, convey.ShouldNotBeNil)
})
}
func TestServicebasePass(t *testing.T) {
convey.Convey("basePass", t, func() {
s.basePass(context.Background(), 0, nil, time.Now())
})
}
func TestServicependant(t *testing.T) {
convey.Convey("pendant", t, func() {
err := s.pendant(context.Background(), nil, 0, "", nil)
convey.So(err, convey.ShouldBeNil)
})
}
func TestServicecheckAnswerBlock(t *testing.T) {
convey.Convey("checkAnswerBlock", t, func() {
block := s.checkAnswerBlock(context.Background(), 0)
convey.So(block, convey.ShouldNotBeNil)
})
}
func TestServicesendData(t *testing.T) {
convey.Convey("sendData", t, func() {
s.sendData(context.Background(), nil, nil, "")
})
}
func TestServiceExtraCheck(t *testing.T) {
convey.Convey("ExtraCheck", t, func() {
err := s.ExtraCheck(context.Background(), 14771787, []int64{}, nil, "", "", "", "")
convey.So(err, convey.ShouldBeNil)
})
}
func TestServicecheckExtraPassAns(t *testing.T) {
convey.Convey("checkExtraPassAns", t, func() {
ret, qs, err := s.checkExtraPassAns(context.Background(), 14771787, []int64{}, nil, "", 0)
convey.So(err, convey.ShouldBeNil)
convey.So(qs, convey.ShouldNotBeNil)
convey.So(ret, convey.ShouldNotBeNil)
})
}
func TestServicesendExtraRetMsg(t *testing.T) {
convey.Convey("sendExtraRetMsg", t, func() {
rs, err := s.sendExtraRetMsg(context.Background(), 0, nil, []int64{}, nil, 0)
convey.So(err, convey.ShouldBeNil)
convey.So(rs, convey.ShouldNotBeNil)
})
}
func TestServicePendantRec(t *testing.T) {
convey.Convey("PendantRec", t, func() {
err := s.PendantRec(context.Background(), nil)
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,59 @@
package service
import (
"context"
"fmt"
"go-common/app/interface/main/answer/model"
"go-common/library/ecode"
)
func (s *Service) convertModel(rpcRes *model.AnsQuesList) (res *model.AnsQueDetailList) {
res = &model.AnsQueDetailList{CurrentTime: rpcRes.CurrentTime.Unix(), EndTime: rpcRes.EndTime.Unix()}
for _, q := range rpcRes.QuesList {
que := &model.AnsQueDetail{
ID: q.ID, AnsImg: q.Img, QsHeight: q.Height, QsPositionY: q.PositionY,
Ans1Hash: q.Ans[0].AnsHash, Ans0Height: q.Ans[0].Height, Ans0PositionY: q.Ans[0].PositionY,
Ans2Hash: q.Ans[1].AnsHash, Ans1Height: q.Ans[1].Height, Ans1PositionY: q.Ans[1].PositionY,
Ans3Hash: q.Ans[2].AnsHash, Ans2Height: q.Ans[2].Height, Ans2PositionY: q.Ans[2].PositionY,
Ans4Hash: q.Ans[3].AnsHash, Ans3Height: q.Ans[3].Height, Ans3PositionY: q.Ans[3].PositionY,
}
res.QuesList = append(res.QuesList, que)
}
return
}
func (s *Service) convertExtraModel(rpcRes *model.AnsQuesList) (res *model.AnsQueDetailList) {
res = &model.AnsQueDetailList{CurrentTime: rpcRes.CurrentTime.Unix(), EndTime: rpcRes.EndTime.Unix()}
for _, q := range rpcRes.QuesList {
que := &model.AnsQueDetail{
ID: q.ID, AnsImg: q.Img, QsHeight: q.Height, QsPositionY: q.PositionY,
Ans1Hash: q.Ans[0].AnsHash, Ans0Height: q.Ans[0].Height, Ans0PositionY: q.Ans[0].PositionY,
Ans2Hash: q.Ans[1].AnsHash, Ans1Height: q.Ans[1].Height, Ans1PositionY: q.Ans[1].PositionY,
}
res.QuesList = append(res.QuesList, que)
}
return
}
func (s *Service) historyByHid(ctx context.Context, hid int64) (his *model.AnswerHistory, err error) {
his, err = s.answerDao.HidCache(ctx, hid)
if err != nil {
return
}
if his != nil {
return
}
if len(fmt.Sprintf("%d", hid)) < 10 {
i, _ := s.answerDao.SharingIndexByHid(ctx, hid)
his, err = s.answerDao.OldHistory(ctx, hid, i)
} else {
his, err = s.answerDao.HistoryByHid(ctx, hid)
}
if err != nil || his == nil {
err = ecode.NothingFound
return
}
s.answerDao.SetHidCache(ctx, his)
return
}

View File

@@ -0,0 +1,21 @@
package service
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceconvertModel(t *testing.T) {
convey.Convey("convertModel", t, func() {
res := s.convertModel(nil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServiceconvertExtraModel(t *testing.T) {
convey.Convey("convertExtraModel", t, func() {
res := s.convertExtraModel(nil)
convey.So(res, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,150 @@
package service
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"math"
"math/rand"
"strconv"
"strings"
"go-common/app/interface/main/answer/model"
"go-common/library/log"
"github.com/pkg/errors"
)
// preProcess getGeetestChal
func (s *Service) preProcess(c context.Context, mid int64, ip, clientType string, newCaptcha int) (res *model.ProcessRes, err error) {
var pre string
res = &model.ProcessRes{}
gc, geeType := s.geetestDao.GeeConfig(clientType, s.c.Geetest)
res.CaptchaID = gc.CaptchaID
res.NewCaptcha = newCaptcha
if pre, err = s.geetestDao.PreProcess(c, mid, ip, geeType, gc, newCaptcha); err != nil || pre == "" {
log.Error("s.geetestDao.PreProcess(%d) err(%v)", mid, err)
randOne := md5.Sum([]byte(strconv.Itoa(rand.Intn(100))))
randTwo := md5.Sum([]byte(strconv.Itoa(rand.Intn(100))))
challenge := hex.EncodeToString(randOne[:]) + hex.EncodeToString(randTwo[:])[0:2]
res.Challenge = challenge
err = nil
return
}
res.Success = 1
slice := md5.Sum([]byte(pre + gc.PrivateKEY))
res.Challenge = hex.EncodeToString(slice[:])
return
}
// validate recheck the seccode
func (s *Service) validate(c context.Context, challenge, validate, seccode, clientType, ip string, success int, mid int64) (stat bool) {
if len(validate) != 32 {
log.Error("s.Validate(%s,%s,%s,%d) err(validate not eq 32byte)", challenge, validate, seccode, mid)
stat = s.failbackValidate(c, challenge, validate, seccode)
log.Info("s.failbackValidate(%s,%s,%s,%d), stat(%t)", challenge, validate, seccode, mid, stat)
return
}
if success != 1 {
slice := md5.Sum([]byte(challenge))
stat = hex.EncodeToString(slice[:]) == validate
return
}
gc, geeType := s.geetestDao.GeeConfig(clientType, s.c.Geetest)
slice := md5.Sum([]byte(gc.PrivateKEY + "geetest" + challenge))
if hex.EncodeToString(slice[:]) != validate {
log.Error("s.Validate(%s,%s,%s,%d) err(challenge not found)", challenge, validate, seccode, mid)
return
}
res, err := s.geetestDao.Validate(c, challenge, seccode, geeType, ip, gc.CaptchaID, mid)
if err != nil {
if errors.Cause(err) == context.DeadlineExceeded { // for geetest timeout
stat = true
return
}
log.Error("s.geetestDao.Validate(%d) err(%v)", mid, err)
return
}
slice = md5.Sum([]byte(seccode))
stat = hex.EncodeToString(slice[:]) == res.Seccode
return
}
//failbackValidate geetest failback model.
func (s *Service) failbackValidate(c context.Context, challenge, validate, seccode string) bool {
varr := strings.Split(validate, "_")
if len(varr) < 3 {
return false
}
encodeAns := varr[0]
encodeFbii := varr[1]
encodeIgi := varr[2]
decodeAns := s.decodeResponse(challenge, encodeAns)
decodeFbii := s.decodeResponse(challenge, encodeFbii)
decodeIgi := s.decodeResponse(challenge, encodeIgi)
return s.validateFailImage(decodeAns, decodeFbii, decodeIgi)
}
func (s *Service) decodeResponse(challenge, userresponse string) (res int) {
if len(userresponse) > 100 {
return
}
digits := []int{1, 2, 5, 10, 50}
key := make(map[rune]int)
for _, i := range challenge {
if _, exist := key[i]; exist {
continue
}
value := digits[len(key)%5]
key[i] = value
}
for _, i := range userresponse {
res += key[i]
}
res -= s.decodeRandBase(challenge)
return
}
func (s *Service) decodeRandBase(challenge string) int {
baseStr := challenge[32:]
var tempList []int
for _, char := range baseStr {
tempChar := int(char)
result := tempChar - 48
if tempChar > 57 {
result = tempChar - 87
}
tempList = append(tempList, result)
}
return tempList[0]*36 + tempList[1]
}
func (s *Service) md5Encode(values []byte) string {
return fmt.Sprintf("%x", md5.Sum(values))
}
func (s *Service) validateFailImage(ans, fullBgIndex, imgGrpIndex int) bool {
var thread float64 = 3
fullBg := s.md5Encode([]byte(strconv.Itoa(fullBgIndex)))[0:10]
imgGrp := s.md5Encode([]byte(strconv.Itoa(imgGrpIndex)))[10:20]
var answerDecode []byte
for i := 0; i < 9; i++ {
if i%2 == 0 {
answerDecode = append(answerDecode, fullBg[i])
} else if i%2 == 1 {
answerDecode = append(answerDecode, imgGrp[i])
}
}
xDecode := answerDecode[4:]
xInt64, err := strconv.ParseInt(string(xDecode), 16, 32)
if err != nil {
log.Error("%+v", err)
}
xInt := int(xInt64)
result := xInt % 200
if result < 40 {
result = 40
}
return math.Abs(float64(ans-result)) < thread
}

View File

@@ -0,0 +1,58 @@
package service
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServicePreProcess(t *testing.T) {
convey.Convey("PreProcess", t, func() {
res, err := s.preProcess(context.Background(), 0, "", "", 0)
convey.So(err, convey.ShouldBeNil)
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServicevalidate(t *testing.T) {
convey.Convey("validate", t, func() {
stat := s.validate(context.Background(), "", "", "", "", "", 0, 0)
convey.So(stat, convey.ShouldNotBeNil)
})
}
func TestServicefailbackValidate(t *testing.T) {
convey.Convey("failbackValidate", t, func() {
p1 := s.failbackValidate(context.Background(), "", "", "")
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestServicedecodeResponse(t *testing.T) {
convey.Convey("decodeResponse", t, func() {
res := s.decodeResponse("", "")
convey.So(res, convey.ShouldNotBeNil)
})
}
func TestServicedecodeRandBase(t *testing.T) {
convey.Convey("decodeRandBase", t, func() {
p1 := s.decodeRandBase("")
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestServicemd5Encode(t *testing.T) {
convey.Convey("md5Encode", t, func() {
p1 := s.md5Encode([]byte{})
convey.So(p1, convey.ShouldNotBeNil)
})
}
func TestServicevalidateFailImage(t *testing.T) {
convey.Convey("validateFailImage", t, func() {
p1 := s.validateFailImage(0, 0, 0)
convey.So(p1, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,381 @@
package service
import "go-common/app/interface/main/answer/model"
// RankShire struct .
type RankShire struct {
Share *model.CoolShare
ViewMore string
VideoArr []*model.CoolVideo
}
var (
_rankShire = map[int]*RankShire{
122: {
Share: &model.CoolShare{Content: "老司机中的老司机,哲学王中的哲学王。",
ShortContent: "老司机中的老司机,哲学王中的哲学王。"},
ViewMore: "http://www.bilibili.com/ranking",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av221107/",
Name: "乾杯 - ( ゜- ゜)つロ",
Img: "https://static-s.bilibili.com/account/img/answer/221107.jpg",
WatchNum: "85.8万",
UpNum: "87515",
}, {
URL: "http://www.bilibili.com/video/av296938/",
Name: "我们的BILIBILI",
Img: "https://static-s.bilibili.com/account/img/answer/296938.jpg",
WatchNum: "83.4万",
UpNum: "94643",
},
},
},
124: {
Share: &model.CoolShare{Content: "在无限大的梦想后面,<br/>无论喜欢动画的你去哪,<br/>那只幻化的蝴蝶,永远会陪伴在你身边。",
ShortContent: "无限大的梦想后面,那只蝴蝶会永远陪伴着你。"},
ViewMore: "http://www.bilibili.com/video/bangumi.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av19781/",
Name: "【催泪向MAD】butter-fly (主MIX) 【数码宝贝】",
Img: "https://static-s.bilibili.com/account/img/answer/19781.jpg",
WatchNum: "104.1万",
UpNum: "173642",
}, {
URL: "http://www.bilibili.com/video/av2216404/",
Name: "海尔兄弟【bilibili正版】",
Img: "https://static-s.bilibili.com/account/img/answer/2216404.jpg",
WatchNum: "40.8万",
UpNum: "39693",
}, {
URL: "http://www.bilibili.com/video/av491271/",
Name: "【综漫MAD】致一直陪伴着我的二次元——谢谢",
Img: "https://static-s.bilibili.com/account/img/answer/491271.jpg",
WatchNum: "29.8万",
UpNum: "19541",
}, {
URL: "http://www.bilibili.com/video/av101457/",
Name: "【MAD】因为我是活在二次元的女孩",
Img: "https://static-s.bilibili.com/account/img/answer/101457.jpg",
WatchNum: "53.7万",
UpNum: "19034",
}, {
URL: "http://www.bilibili.com/video/av853594/",
Name: "致二次元,谢谢你给了我整个世界",
Img: "https://static-s.bilibili.com/account/img/answer/853594.png",
WatchNum: "17.0万",
UpNum: "16830",
},
},
},
127: {
Share: &model.CoolShare{Content: "在无限大的梦想后面,<br/>无论喜欢动画的你去哪,<br/>那只幻化的蝴蝶,永远会陪伴在你身边。",
ShortContent: "无限大的梦想后面,那只蝴蝶会永远陪伴着你。"},
ViewMore: "http://www.bilibili.com/video/bangumi.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av19781/",
Name: "【催泪向MAD】butter-fly (主MIX) 【数码宝贝】",
Img: "https://static-s.bilibili.com/account/img/answer/19781.jpg",
WatchNum: "104.1万",
UpNum: "173642",
},
{
URL: "http://www.bilibili.com/video/av2216404/",
Name: "海尔兄弟【bilibili正版】",
Img: "https://static-s.bilibili.com/account/img/answer/2216404.jpg",
WatchNum: "40.8万",
UpNum: "39693",
},
{
URL: "http://www.bilibili.com/video/av491271/",
Name: "【综漫MAD】致一直陪伴着我的二次元——谢谢",
Img: "https://static-s.bilibili.com/account/img/answer/491271.jpg",
WatchNum: "29.8万",
UpNum: "19541",
},
{
URL: "http://www.bilibili.com/video/av101457/",
Name: "【MAD】因为我是活在二次元的女孩",
Img: "https://static-s.bilibili.com/account/img/answer/101457.jpg",
WatchNum: "53.7万",
UpNum: "19034",
},
{
URL: "http://www.bilibili.com/video/av853594/",
Name: "致二次元,谢谢你给了我整个世界",
Img: "https://static-s.bilibili.com/account/img/answer/853594.png",
WatchNum: "17.0万",
UpNum: "16830",
},
},
},
126: {
Share: &model.CoolShare{Content: "给你最想听的音乐,让耳朵来一场旅行,我是旋律的向导。",
ShortContent: "给你最想听的音乐,让耳朵来一场旅行,我是旋律的向导。"},
ViewMore: "http://www.bilibili.com/video/music-vocaloid-1.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av2129461/",
Name: "洛天依言和原创《普通DISCO》",
Img: "https://static-s.bilibili.com/account/img/answer/2129461.jpg",
WatchNum: "173.7万",
UpNum: "53752",
},
{
URL: "http://www.bilibili.com/video/av644136/",
Name: "【洛天依翻唱】跳蛋的性福理论【手书PV】【JumPingEgG】",
Img: "https://static-s.bilibili.com/account/img/answer/644136.jpg",
WatchNum: "64.6万",
UpNum: "25293",
},
{
URL: "http://www.bilibili.com/video/av2075941/",
Name: "【洛天依古风原创曲】权御天下【原创PV付】",
Img: "https://static-s.bilibili.com/account/img/answer/2075941.jpg",
WatchNum: "128.7万",
UpNum: "27589",
},
{
URL: "http://www.bilibili.com/video/av482844/",
Name: "【洛天依原创】一半一半",
Img: "https://static-s.bilibili.com/account/img/answer/482844.jpg",
WatchNum: "50.5万",
UpNum: "22569",
},
{
URL: "http://www.bilibili.com/video/av556019/",
Name: "【niconico超会议2現場版】威风堂々【Vmoe字幕组】",
Img: "https://static-s.bilibili.com/account/img/answer/556019.jpg",
WatchNum: "67.4万",
UpNum: "19833",
},
},
},
123: {
Share: &model.CoolShare{Content: "给你最想听的音乐,让耳朵来一场旅行,我是旋律的向导。",
ShortContent: "给你最想听的音乐,让耳朵来一场旅行,我是旋律的向导。"},
ViewMore: "http://www.bilibili.com/video/music.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av643809/",
Name: "茶太--团子大家族 现场版",
Img: "https://static-s.bilibili.com/account/img/answer/643809.jpg",
WatchNum: "14.8万",
UpNum: "7947",
},
{
URL: "http://www.bilibili.com/video/av345249/",
Name: "【AMV、時代的眼泪、泪腺崩坏、燃烧殆尽】最后的Butterfly重制版",
Img: "https://static-s.bilibili.com/account/img/answer/345249.png",
WatchNum: "31.7万",
UpNum: "39937",
},
{
URL: "http://www.bilibili.com/video/av736852/",
Name: "看到鼓手时我跪下尿了一地!",
Img: "https://static-s.bilibili.com/account/img/answer/736852.jpg",
WatchNum: "91.2万",
UpNum: "11302",
},
{
URL: "http://www.bilibili.com/video/av1393947/",
Name: "电二胡的咆哮 【致童年—数码宝贝】butterfly",
Img: "https://static-s.bilibili.com/account/img/answer/1393947.jpg",
WatchNum: "35.6万",
UpNum: "14199",
},
{
URL: "http://www.bilibili.com/video/av1507163/",
Name: "你们要的小苹果交响版",
Img: "https://static-s.bilibili.com/account/img/answer/1507163.jpg",
WatchNum: "30.9万",
UpNum: "7092",
},
},
},
121: {
Share: &model.CoolShare{Content: "常存好奇之心,<br/>新技能 get 是他们的座右铭。",
ShortContent: "常存好奇之心,新技能 get 是他们的座右铭。"},
ViewMore: "http://www.bilibili.com/video/tech-future-military-1.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av2075767/",
Name: "巴雷特狙击枪后座力有多强?",
Img: "https://static-s.bilibili.com/account/img/answer/2075767.jpg",
WatchNum: "33.7万",
UpNum: "2002",
},
{
URL: "http://www.bilibili.com/video/av1952604/",
Name: "【张召忠】 印度史上最搞笑的大阅兵 铁血军情20150201",
Img: "https://static-s.bilibili.com/account/img/answer/1952604.jpg",
WatchNum: "28.3万",
UpNum: "12463",
},
{
URL: "http://www.bilibili.com/video/av927165/",
Name: "【军武次位面】第九期:十大战列舰",
Img: "https://static-s.bilibili.com/account/img/answer/927165.png",
WatchNum: "32.6万",
UpNum: "10880",
},
},
},
125: {
Share: &model.CoolShare{Content: "你就是来自那个世界的勇士吗?<br/>果然你在这个虚拟世界有着强大的力量啊!<br/>准备好攻略这场战斗了么那么…Link Start!",
ShortContent: "准备好攻略这场战斗了么那么…Link Start!"},
ViewMore: "http://www.bilibili.com/video/game.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av1293479/",
Name: "【若风噩梦人机】瞬间爆炸的恐怖电脑们!",
Img: "https://static-s.bilibili.com/account/img/answer/1293479.jpg",
WatchNum: "73.1万",
UpNum: "25452",
},
{
URL: "http://www.bilibili.com/video/av1561567/",
Name: "【散人】大型励志剧 娱乐圈小助理养成计划更新至P20 遇龙2杀青",
Img: "https://static-s.bilibili.com/account/img/answer/1561567.jpg",
WatchNum: "306.5万",
UpNum: "581921",
},
{
URL: "http://www.bilibili.com/video/av862182/",
Name: "【文明5】美丽新世界神级实况7P完结",
Img: "https://static-s.bilibili.com/account/img/answer/862182.jpg",
WatchNum: "14.4万",
UpNum: "9325",
},
{
URL: "http://www.bilibili.com/video/av885977/",
Name: "<Mugen>狂下左右节操全无大会最终章-燃烧热情吧!向着梦想的彼方!",
Img: "https://static-s.bilibili.com/account/img/answer/885977.jpg",
WatchNum: "14.2万",
UpNum: "11825",
},
{
URL: "http://www.bilibili.com/video/av2269587/",
Name: "LOL这也能翻盘史上最奇葩的翻盘这竟然是钻石排位",
Img: "https://static-s.bilibili.com/account/img/answer/2269587.jpg",
WatchNum: "99.3万",
UpNum: "37457",
},
},
},
129: {
Share: &model.CoolShare{Content: "常存好奇之心,<br/>新技能 get 是他们的座右铭。",
ShortContent: "常存好奇之心,新技能 get 是他们的座右铭。"},
ViewMore: "http://www.bilibili.com/video/technology.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av1787919/",
Name: "史上最无节操的手机架子鼓教程 ,结尾开大招。",
Img: "https://static-s.bilibili.com/account/img/answer/dcc6580b4dcc7e339545018cc312bf76.jpg",
WatchNum: "29.2万",
UpNum: "4744",
},
{
URL: "http://www.bilibili.com/video/av679319/",
Name: "论爱情动作片和真实生活啪啪啪的区别",
Img: "https://static-s.bilibili.com/account/img/answer/13752405809fed020ca2372901.jpg",
WatchNum: "43.3万",
UpNum: "3028",
},
{
URL: "http://www.bilibili.com/video/av2275735/",
Name: "Besiege贴吧4月作品精选",
Img: "https://static-s.bilibili.com/account/img/answer/d382baaee8f144b924975b05cc592c60.jpg",
WatchNum: "10.5万",
UpNum: "6506",
},
{
URL: "http://www.bilibili.com/video/av2278660/",
Name: "英梨梨&诗羽de绘成方法 五一特别篇【别人君】",
Img: "https://static-s.bilibili.com/account/img/answer/fd41bb17a5f512c37f01570ddd994b49.jpg",
WatchNum: "4.7万",
UpNum: "2837",
},
{
URL: "http://www.bilibili.com/video/av2278660/",
Name: "这大概是最好的日语入门教学了吧----五十音学习",
Img: "https://static-s.bilibili.com/account/img/answer/8c7436785697cc5c450b33eb93c2353f.jpg",
WatchNum: "63.6万",
UpNum: "104048",
},
},
},
130: {
Share: &model.CoolShare{Content: "一天不看片会死星人就是你吗?<br/>大开脑洞YY主人公也大丈夫~<br/>在异次元的空间里跟我们一起做梦吧。",
ShortContent: "一天不看片会死星人就是你吗?在异次元的空间里跟我们一起做梦吧。"},
ViewMore: "http://www.bilibili.com/video/tv-presentation-1.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av2019928/",
Name: "泰剧《不一样的美男》中字第一集@天府泰剧",
Img: "https://static-s.bilibili.com/account/img/answer/daab100da59667394e70822e3e63d254.jpg",
WatchNum: "203.5万",
UpNum: "219407",
},
{
URL: "http://www.bilibili.com/video/av639407/",
Name: "【熟肉】半泽直树 01【人人字幕】",
Img: "https://static-s.bilibili.com/account/img/answer/076d81509a1c93a4569e942b281f46c1.jpg",
WatchNum: "141.8万",
UpNum: "61416",
},
{
URL: "http://www.bilibili.com/video/av1999475/",
Name: "【国产】少年包青天 第一部 2000 40集全集",
Img: "https://static-s.bilibili.com/account/img/answer/33a6c9cf7b38bb08c4da7ec5ac3b12f4.jpg",
WatchNum: "45.8万",
UpNum: "167676",
},
},
},
128: {
Share: &model.CoolShare{Content: "从未见过如此才华横溢之人,<br/>他们的技术、创意和努力,<br/>使「鬼畜」成为一种艺术。",
ShortContent: "他们的技术、创意和努力,使「鬼畜」成为一种艺术。"},
ViewMore: "http://www.bilibili.com/video/kichiku.html",
VideoArr: []*model.CoolVideo{
{
URL: "http://www.bilibili.com/video/av75179/",
Name: "【葛平金曲】循环(完整版)",
Img: "https://static-s.bilibili.com/account/img/answer/1301416217-61b.jpg",
WatchNum: "45.1万",
UpNum: "21196",
},
{
URL: "http://www.bilibili.com/video/av1858893/",
Name: "全是猴【白金王司猴】",
Img: "https://static-s.bilibili.com/account/img/answer/f1d17e3ce7f9e71e1508ced43d6a8656.jpg",
WatchNum: "111.4万",
UpNum: "14230",
},
{
URL: "http://www.bilibili.com/video/av1076105/",
Name: "妮可 妮可 妮可",
Img: "https://static-s.bilibili.com/account/img/answer/ee087327bf0a239ad114633e2806fa79.jpg",
WatchNum: "94.6万",
UpNum: "25738",
},
{
URL: "http://www.bilibili.com/video/av2271112/",
Name: "【循环向】跟着雷总摇起来Are you OK",
Img: "https://static-s.bilibili.com/account/img/answer/2fc528fee5d0cbfb98b266bb7ec3a1ad.jpg",
WatchNum: "118.1万",
UpNum: "18960",
},
{
URL: "http://www.bilibili.com/video/av794506/",
Name: "【元首葛炮】要金坷垃",
Img: "https://static-s.bilibili.com/account/img/answer/1d34e9f58def856acf501289d2cacc56.jpg",
WatchNum: "64.3万",
UpNum: "13795",
},
}},
}
)

View File

@@ -0,0 +1,126 @@
package service
import (
"context"
"time"
"go-common/app/interface/main/answer/conf"
"go-common/app/interface/main/answer/dao"
accountDao "go-common/app/interface/main/answer/dao/account"
geetestDao "go-common/app/interface/main/answer/dao/geetest"
"go-common/app/interface/main/answer/model"
accoutCli "go-common/app/service/main/account/api"
memrpc "go-common/app/service/main/member/api/gorpc"
"go-common/library/log"
"go-common/library/log/anticheat"
"go-common/library/queue/databus/report"
"go-common/library/stat/prom"
"go-common/library/sync/pipeline/fanout"
)
// Service struct of service.
type Service struct {
c *conf.Config
answerDao *dao.Dao
geetestDao *geetestDao.Dao
accountDao *accountDao.Dao
accountSvc accoutCli.AccountClient
memRPC *memrpc.Service
missch *fanout.Fanout
beformalch chan *model.Formal
questionTypeCache map[int64]*model.TypeInfo
rankCache []*model.RankInfo
mRankCache []*model.RankInfo
tcQestTick time.Duration
rankQuestTick time.Duration
promBeFormal *prom.Prom
infoc2 *anticheat.AntiCheat
}
// New create service instance and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
answerDao: dao.New(c),
geetestDao: geetestDao.New(c),
accountDao: accountDao.New(c),
memRPC: memrpc.New(c.RPCClient.Member),
missch: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
beformalch: make(chan *model.Formal, 1024),
questionTypeCache: map[int64]*model.TypeInfo{},
rankCache: []*model.RankInfo{},
mRankCache: []*model.RankInfo{},
tcQestTick: time.Duration(c.Question.TcQestTick),
rankQuestTick: time.Duration(c.Question.RankQestTick),
promBeFormal: prom.New().WithCounter("answer_beformal_count", []string{"name"}),
}
accountSvc, err := accoutCli.NewClient(c.AccountRPC)
if err != nil {
panic(err)
}
s.accountSvc = accountSvc
s.loadQidsCache()
s.loadExtraQidsCache()
s.loadtypes()
go s.rankcacheproc()
go s.beformalproc()
if c.Infoc2 != nil {
s.infoc2 = anticheat.New(c.Infoc2)
}
return
}
func (s *Service) addRetryBeFormal(msg *model.Formal) {
select {
case s.beformalch <- msg:
default:
log.Warn("beformalch chan full")
}
}
func (s *Service) beformalproc() {
var (
err error
c = context.Background()
msg *model.Formal
)
for {
msg = <-s.beformalch
for retries := 0; retries < s.c.Answer.MaxRetries; retries++ {
if err = s.accountDao.BeFormal(c, msg.Mid, msg.IP); err != nil {
sleep := s.c.Backoff.Backoff(retries)
log.Error("beFormal fail(%d) sleep(%d) err(%+v)", msg.Mid, sleep, err)
time.Sleep(sleep * time.Second)
continue
}
break
}
}
}
// Close dao.
func (s *Service) Close() {
s.answerDao.Close()
}
func (s *Service) rankcacheproc() {
for {
time.Sleep(s.tcQestTick)
s.loadtypes()
s.loadQidsCache()
s.loadExtraQidsCache()
}
}
func (s *Service) userActionLog(mid int64, typ string, ah *model.AnswerHistory) {
report.User(&report.UserInfo{
Mid: mid,
Business: model.AnswerLogID,
Action: model.AnswerUpdate,
Ctime: time.Now(),
Content: map[string]interface{}{
typ: ah,
},
})
}

View File

@@ -0,0 +1,64 @@
package service
import (
"flag"
"os"
"testing"
"go-common/app/interface/main/answer/conf"
"github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account-law.answer")
flag.Set("conf_appid", "main.account-law.answer")
flag.Set("conf_token", "ba3ee255695e8d7b46782268ddc9c8a3")
flag.Set("tree_id", "25260")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_env", "10")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/answer-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
s = New(conf.Conf)
os.Exit(m.Run())
}
func TestServiceaddRetryBeFormal(t *testing.T) {
convey.Convey("addRetryBeFormal", t, func() {
s.addRetryBeFormal(nil)
})
}
func TestServicebeformalproc(t *testing.T) {
convey.Convey("beformalproc", t, func() {
s.beformalproc()
})
}
func TestServiceClose(t *testing.T) {
convey.Convey("Close", t, func() {
s.Close()
})
}
func TestServicerankcacheproc(t *testing.T) {
convey.Convey("rankcacheproc", t, func() {
s.rankcacheproc()
})
}

View File

@@ -0,0 +1,41 @@
package service
import (
"context"
"go-common/app/interface/main/answer/model"
accoutCli "go-common/app/service/main/account/api"
memModel "go-common/app/service/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
// CheckBirthday check user had birthday info
func (s *Service) CheckBirthday(c context.Context, mid int64) (ok bool) {
var (
err error
res *memModel.BaseInfo
arg = &memModel.ArgMemberMid{Mid: mid, RemoteIP: metadata.String(c, metadata.RemoteIP)}
)
if res, err = s.memRPC.Base(c, arg); err != nil {
log.Error("s.accRPC.Detail(mid:%d) error (%v)", mid, err)
return
}
if res != nil && res.Birthday != 0 {
birthday := res.Birthday.Time().Format("2006-01-02")
if birthday != model.DefBirthday1 && birthday != model.DefBirthday2 {
ok = true
}
}
return
}
func (s *Service) accInfo(c context.Context, mid int64) (*accoutCli.Info, error) {
accInfo, err := s.accountSvc.Info3(c, &accoutCli.MidReq{Mid: mid})
if err != nil || accInfo == nil || accInfo.Info == nil {
log.Error("s.accRPC.Info(%d) error(%v)", mid, err)
return nil, ecode.AnswerAccCallErr
}
return accInfo.Info, nil
}

View File

@@ -0,0 +1,22 @@
package service
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceCheckBirthday(t *testing.T) {
convey.Convey("CheckBirthday", t, func() {
ok := s.CheckBirthday(context.Background(), 0)
convey.So(ok, convey.ShouldNotBeNil)
})
}
func TestServiceaccInfo(t *testing.T) {
convey.Convey("accInfo", t, func() {
ai, err := s.accInfo(context.Background(), 0)
convey.So(err, convey.ShouldBeNil)
convey.So(ai, convey.ShouldNotBeNil)
})
}