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

21
app/job/main/answer/BUILD Normal file
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/job/main/answer/cmd:all-srcs",
"//app/job/main/answer/conf:all-srcs",
"//app/job/main/answer/dao:all-srcs",
"//app/job/main/answer/http:all-srcs",
"//app/job/main/answer/model:all-srcs",
"//app/job/main/answer/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,31 @@
# answer-job
v2.1.0
1. 简繁体翻译
v2.0.1
1. sync.WaitGroup
v2.0.0
1. 答题v3
v1.0.5
1. bm http server
v1.0.4
1. 目录迁移
2. 对接转正接口
v1.0.3
1. 优化转正补偿
v1.0.2
1. job svr close代码
v1.0.1
1. job代码优化
v1.0.0
1. 初始化项目,更新依赖
2. 增加劳改题库消费
3. 自动生成bfs图片

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:
- job
- job/main/answer
- main
options:
no_parent_owners: true
reviewers:
- guhao
- liangkai
- zhangshengchao
- zhaogangtao

View File

@@ -0,0 +1,11 @@
#### answer-service
##### 项目简介
> 1.提供答题job
##### 编译环境
> 请只用golang v1.7.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common

View File

@@ -0,0 +1,45 @@
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-job-test.toml"],
importpath = "go-common/app/job/main/answer/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/answer/conf:go_default_library",
"//app/job/main/answer/http:go_default_library",
"//app/job/main/answer/service:go_default_library",
"//library/log:go_default_library",
"//library/os/signal:go_default_library",
"//library/syscall: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,93 @@
[log]
dir = "/data/log/answer-job/"
[bm]
addr = "0.0.0.0:9002"
maxListen = 10
timeout = "1s"
[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
queryTimeout = "100ms"
execTimeout = "100ms"
tranTimeout = "200ms"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[bfs]
timeout="5s"
maxFileSize=5242880
bucket="member"
url="http://test-bfs.bilibili.co/bfs/member/"
method="PUT"
key="3d34b1ea1dbbb0ca"
secret="d4caa344f3b115e302033b05dd0aa4"
host="test-bfs.bilibili.co"
[databus]
[databus.labour]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "MemberExpLog-UGC-S"
topic= "MemberExpLog-T"
action="sub"
name = "figure-job/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 2
active = 10
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[databus.account]
key = "0QEO9F8JuuIxZzNDvklH"
secret= "0QEO9F8JuuIxZzNDvklI"
group= "MemberExpLog-UGC-S"
topic= "MemberExpLog-T"
action="sub"
name = "figure-job/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
idle = 2
active = 10
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"
[httpClient]
dial = "500ms"
timeout = "1s"
keepAlive = "60s"
timer = 10
key = "zxnh4k92dwe61t27"
secret = "dnu3bwpxyswqwf1ixpsczthury1nqiew"
[httpClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[backoff]
maxDelay = 60
baseDelay = 5
factor = 1.6
jitter = 0.2
[properties]
uploadInterval = "10s"
maxRetries = 10
accountIntranetURI = "http://account.bilibili.co"
debug = true
fontFilePath = "/data/yahei.ttf"

View File

@@ -0,0 +1,53 @@
package main
import (
"flag"
"os"
"time"
"go-common/app/job/main/answer/conf"
"go-common/app/job/main/answer/http"
"go-common/app/job/main/answer/service"
"go-common/library/log"
"go-common/library/os/signal"
"go-common/library/syscall"
"go-common/library/text/translate/chinese"
)
var (
svr *service.Service
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
chinese.Init()
svr = service.New(conf.Conf)
http.Init(conf.Conf, svr)
log.Info("answer-job start")
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP)
for {
s := <-c
log.Info("answer-job get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT:
log.Info("answer-job exit")
if err := svr.Close(); err != nil {
log.Error("srv close consumer error(%v)", err)
}
time.Sleep(2 * time.Second)
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_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["conf_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/job/main/answer/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/netutil: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,108 @@
package conf
import (
"errors"
"flag"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/netutil"
"go-common/library/queue/databus"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf service config
Conf = &Config{}
)
// Config def.
type Config struct {
Log *log.Config
Databus *DataSource
Mysql *sql.Config
BFS *BFS
HTTPClient *bm.ClientConfig
Properties *Properties
Backoff *netutil.BackoffConfig
BM *bm.ServerConfig
}
// BFS bfs config
type BFS struct {
Timeout xtime.Duration
MaxFileSize int
Bucket string
URL string
Method string
Key string
Secret string
Host string
}
// Properties def.
type Properties struct {
UploadInterval xtime.Duration
AccountIntranetURI string
MaxRetries int
FontFilePath string
}
// DataSource databus source
type DataSource struct {
Labour *databus.Config
Account *databus.Config
}
func init() {
flag.StringVar(&confPath, "conf", "", "config path")
}
// Init init conf.
func Init() (err error) {
if confPath == "" {
return configCenter()
}
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func configCenter() (err error) {
if client, err = conf.New(); err != nil {
panic(err)
}
if err = load(); err != nil {
return
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
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
}

View File

@@ -0,0 +1,18 @@
package conf
import (
"flag"
"testing"
)
func TestInit(t *testing.T) {
flag.Set("conf", "../cmd/answer-job-test.toml")
flag.Parse()
var err error
if err = Init(); err != nil {
t.Fatal(err)
}
if Conf == nil {
t.Fatal()
}
}

View File

@@ -0,0 +1,63 @@
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",
"mysql_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/answer/conf:go_default_library",
"//app/job/main/answer/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"account.go",
"bfs.go",
"dao.go",
"draw.go",
"mysql.go",
],
importpath = "go-common/app/job/main/answer/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/answer/conf:go_default_library",
"//app/job/main/answer/model: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",
"//vendor/github.com/golang/freetype:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/golang.org/x/image/font:go_default_library",
"//vendor/golang.org/x/image/math/fixed: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,35 @@
package dao
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_wasFormal = -659
)
// 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.xclient.Post(c, d.beFormal, ip, params, &res); err != nil {
err = errors.Wrapf(err, "beFormal url(%s)", d.beFormal+"?"+params.Encode())
return
}
if res.Code != 0 && res.Code != _wasFormal {
err = errors.WithStack(fmt.Errorf("beFormal(%d) failed(%v)", mid, res.Code))
return
}
log.Info("beFormal suc(%d) ", mid)
return
}

View File

@@ -0,0 +1,76 @@
package dao
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"hash"
"io"
"net/http"
"strconv"
"time"
"go-common/library/ecode"
"go-common/library/log"
)
var (
errUpload = errors.New("Upload failed")
)
// Upload upload bfs.
func (d *Dao) Upload(c context.Context, fileType string, filename string, body io.Reader) (location string, err error) {
req, err := http.NewRequest(d.c.BFS.Method, d.c.BFS.URL+filename, body)
if err != nil {
log.Error("http.NewRequest error (%v) | fileType(%s)", err, fileType)
return
}
expire := time.Now().Unix()
authorization := authorize(d.c.BFS.Key, d.c.BFS.Secret, d.c.BFS.Method, d.c.BFS.Bucket, filename, expire)
req.Header.Set("Host", d.c.BFS.Host)
req.Header.Add("Date", fmt.Sprint(expire))
req.Header.Add("Authorization", authorization)
req.Header.Add("Content-Type", fileType)
// timeout
ctx, cancel := context.WithTimeout(c, time.Duration(d.c.BFS.Timeout))
req = req.WithContext(ctx)
defer cancel()
resp, err := d.client.Do(req)
if err != nil {
log.Error("d.Client.Do error(%v) | url(%s)", err, d.c.BFS.URL+filename)
err = ecode.BfsUploadServiceUnavailable
return
}
if resp.StatusCode != http.StatusOK {
log.Error("Upload http.StatusCode nq http.StatusOK (%d) | url(%s)", resp.StatusCode, d.c.BFS.URL+filename)
err = errUpload
return
}
header := resp.Header
code := header.Get("Code")
if code != strconv.Itoa(http.StatusOK) {
log.Error("strconv.Itoa err, code(%s) | url(%s)", code, d.c.BFS.URL+filename)
err = errUpload
return
}
location = header.Get("Location")
return
}
// authorize returns authorization for upload file to bfs
func authorize(key, secret, method, bucket string, fname string, expire int64) (authorization string) {
var (
content string
mac hash.Hash
signature string
)
content = fmt.Sprintf("%s\n%s\n%s\n%d\n", method, bucket, fname, expire)
mac = hmac.New(sha1.New, []byte(secret))
mac.Write([]byte(content))
signature = base64.StdEncoding.EncodeToString(mac.Sum(nil))
authorization = fmt.Sprintf("%s:%s:%d", key, signature, expire)
return
}

View File

@@ -0,0 +1,51 @@
package dao
import (
"context"
"net/http"
"time"
"go-common/app/job/main/answer/conf"
"go-common/library/database/sql"
xhttp "go-common/library/net/http/blademaster"
)
const (
_bfsTimeout = 5 * time.Second
_beFormal = "/api/internal/member/beFormal"
)
// Dao event dao def.
type Dao struct {
c *conf.Config
db *sql.DB
client *http.Client
xclient *xhttp.Client
beFormal string
}
// New create instance of dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
db: sql.NewMySQL(c.Mysql),
client: &http.Client{
Timeout: _bfsTimeout,
},
xclient: xhttp.NewClient(c.HTTPClient),
beFormal: c.Properties.AccountIntranetURI + _beFormal,
}
return
}
// Ping check db health.
func (d *Dao) Ping(c context.Context) (err error) {
return d.db.Ping(c)
}
// Close close all db connections.
func (d *Dao) Close() {
if d.db != nil {
d.db.Close()
}
}

View File

@@ -0,0 +1,36 @@
package dao
import (
"flag"
"go-common/app/job/main/answer/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "")
flag.Set("conf_appid", "")
flag.Set("conf_token", "")
flag.Set("tree_id", "")
flag.Set("conf_version", "server-1")
flag.Set("deploy_env", "dev")
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-job-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,130 @@
package dao
import (
"image"
"image/draw"
"io/ioutil"
"math"
"unicode/utf8"
"go-common/library/log"
"github.com/golang/freetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
// TextImgConf text img conf.
type TextImgConf struct {
Fontsize int
Length int
Ansfontsize int
Spacing float64
Ansspacing float64
}
// QueConf question img conf.
func (d *Dao) QueConf(mobile bool) (c *TextImgConf) {
if mobile {
// Mobile
c = &TextImgConf{
Fontsize: 16, // mobile font size in points
Length: 11, // mobile question length
Ansfontsize: 12, // mobile ans font size in points
}
} else {
// PC
c = &TextImgConf{
Fontsize: 12, //font size in points
Length: 36, //question length
Ansfontsize: 10, //ans font size in points
}
}
c.Spacing = 2 // line spacing (e.g. 2 means double spaced)
c.Ansspacing = 2 // line ansspacing (e.g. 2 means double spaced)
return
}
// DrawQue draw question title.
func (d *Dao) DrawQue(c *freetype.Context, s string, conf *TextImgConf, pt *fixed.Point26_6) {
c.SetFontSize(float64(conf.Fontsize))
srune := []rune(s)
var end = conf.Length
for len(srune) > 0 {
if conf.Length > len(srune) {
end = len(srune)
}
d.text(c, string(srune[:end]), pt, conf.Fontsize, conf.Spacing)
srune = srune[end:]
}
}
// DrawAns draw ans
func (d *Dao) DrawAns(c *freetype.Context, conf *TextImgConf, anss [2]string, pt *fixed.Point26_6) {
c.SetFontSize(float64(conf.Ansfontsize))
arr := [4]string{"A.", "B."}
for i, a := range anss {
d.text(c, arr[i]+a, pt, conf.Ansfontsize, conf.Ansspacing)
}
}
// Board init draw board.
func (d *Dao) Board(h int) (r *image.Gray) {
bg := image.White
r = image.NewGray(image.Rect(0, 0, 600, h))
draw.Draw(r, r.Bounds(), bg, image.ZP, draw.Src)
return
}
//Height get img height
func (d *Dao) Height(c *TextImgConf, que string, anslen int) (h int) {
len := utf8.RuneCountInString(que)
line := math.Ceil(float64(len) / float64(c.Length))
h = int(math.Ceil(c.Spacing*line*float64(c.Fontsize))) + int(math.Ceil(c.Ansspacing*float64(anslen)*float64(c.Ansfontsize)))
return
}
// text Draw text.
func (d *Dao) text(c *freetype.Context, s string, pt *fixed.Point26_6, size int, spacing float64) (err error) {
_, err = c.DrawString(s, *pt)
if err != nil {
log.Error("c.DrawString() error:%+v", err)
return
}
pt.Y += fixed.Int26_6(int(float64(size)*spacing) << 6)
return
}
var (
dpi = float64(72) // screen resolution in Dots Per Inch
hinting = "none" // none | full
)
// Context freetype init context.
func (d *Dao) Context(r *image.Gray, fileStr string) (c *freetype.Context) {
fg := image.Black
// Read the font data.
fontBytes, err := ioutil.ReadFile(fileStr)
if err != nil {
log.Error("ioutil.ReadFile(%s) error:%+v", fileStr, err)
return
}
f, err := freetype.ParseFont(fontBytes)
if err != nil {
log.Error("freetype.ParseFont(%s) error:%+v", fileStr, err)
return
}
c = freetype.NewContext()
c.SetDPI(dpi)
c.SetFont(f)
c.SetClip(r.Bounds())
c.SetDst(r)
c.SetSrc(fg)
switch hinting {
default:
c.SetHinting(font.HintingNone)
case "full":
c.SetHinting(font.HintingFull)
}
return
}

View File

@@ -0,0 +1,77 @@
package dao
import (
"context"
"go-common/app/job/main/answer/model"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_questionByIDSQL = "SELECT mid,question,ans,status FROM answer_extra_question WHERE id=?"
_insQsSQL = "INSERT INTO answer_extra_question(question,ans,av_id,status,source,state,origin_id) VALUES(?,?,?,?,?,?,?)"
_updateQsSQL = "UPDATE answer_extra_question SET ans=?,status=?,isdel=? WHERE origin_id=?"
_questionExtraTypeSQL = "SELECT origin_id,ans,question,av_id,status,source FROM answer_extra_question WHERE isdel=1 and state=0 LIMIT ?"
_updateStateSQL = "UPDATE answer_extra_question SET state=? WHERE origin_id=?"
)
// ByID get question by id.
func (d *Dao) ByID(c context.Context, id int64) (que *model.LabourQs, err error) {
var row = d.db.QueryRow(c, _questionByIDSQL, id)
que = new(model.LabourQs)
if err = row.Scan(&que.Mid, &que.Question, &que.Ans, &que.Status); err != nil {
if err == sql.ErrNoRows {
que = nil
err = nil
return
}
log.Error("row.Scan() error(%v)", err)
}
return
}
// AddQs add labour question log.
func (d *Dao) AddQs(c context.Context, qs *model.LabourQs) (err error) {
if _, err = d.db.Exec(c, _insQsSQL, qs.Question, qs.Ans, qs.AvID, qs.Status, qs.Source, qs.State, qs.ID); err != nil {
log.Error("AddQs: db.Exec(as:%v) error(%v)", qs, err)
}
return
}
// UpdateQs update question.
func (d *Dao) UpdateQs(c context.Context, que *model.LabourQs) (err error) {
if _, err = d.db.Exec(c, _updateQsSQL, que.Ans, que.Status, que.Isdel, que.ID); err != nil {
log.Error("setQs: db.Exec(%v) error(%v)", que, err)
}
return
}
// QidsExtraByState get extra question ids by check
func (d *Dao) QidsExtraByState(c context.Context, size int) (res []*model.LabourQs, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _questionExtraTypeSQL, size); err != nil {
log.Error("d._questionExtraTypeSQL.Query error(%v)", err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.LabourQs)
if err = rows.Scan(&r.ID, &r.Ans, &r.Question, &r.AvID, &r.Status, &r.Source); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// UpdateState update state.
func (d *Dao) UpdateState(c context.Context, que *model.LabourQs) (err error) {
if _, err = d.db.Exec(c, _updateStateSQL, que.State, que.ID); err != nil {
log.Error("UpdateState: db.Exec(%v) error(%v)", que, err)
}
return
}

View File

@@ -0,0 +1,62 @@
package dao
import (
"context"
"fmt"
"testing"
"go-common/app/job/main/answer/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestAddQue(t *testing.T) {
Convey("TestAddQue add question data", t, func() {
for i := 0; i < 10; i++ {
que := &model.LabourQs{
Question: fmt.Sprintf("测试d=====( ̄▽ ̄*)b厉害 %d", i),
Ans: int8(i%2 + 1),
AvID: int64(i),
Status: int8(i%2 + 1),
Source: 1,
State: model.HadCreateImg,
ID: int64(i),
}
err := d.AddQs(context.TODO(), que)
So(err, ShouldBeNil)
}
})
}
func TestUpdateState(t *testing.T) {
Convey("TestUpdateState", t, func() {
for i := 0; i < 10; i++ {
que := &model.LabourQs{
Question: fmt.Sprintf("测试d=====( ̄▽ ̄*)b厉害 %d", i),
Ans: int8(i%2 + 1),
AvID: int64(i),
Status: int8(i%2 + 1),
Source: 1,
State: model.HadCreateImg,
ID: int64(i),
}
err := d.UpdateState(context.TODO(), que)
So(err, ShouldBeNil)
}
})
}
func TestByID(t *testing.T) {
Convey("TestByID", t, func() {
res, err := d.ByID(context.TODO(), 1)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
})
}
func TestBeFormal(t *testing.T) {
Convey("TestBeFormal", t, func() {
err := d.BeFormal(context.TODO(), 7593623, "127.0.0.1")
So(err, ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/main/answer/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/answer/conf:go_default_library",
"//app/job/main/answer/service: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,35 @@
package http
import (
"net/http"
"go-common/app/job/main/answer/conf"
"go-common/app/job/main/answer/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var svr *service.Service
// Init init a http server
func Init(c *conf.Config, s *service.Service) {
svr = s
engine := bm.DefaultServer(c.BM)
innerRouter(engine)
if err := engine.Start(); err != nil {
log.Error("bm.Start() error(%v)", err)
panic(err)
}
}
func innerRouter(e *bm.Engine) {
e.Ping(ping)
}
// ping check server ok.
func ping(c *bm.Context) {
if err := svr.Ping(c); err != nil {
log.Error("answer-job service ping error")
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

@@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"msg.go",
"question.go",
],
importpath = "go-common/app/job/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,11 @@
package model
import "encoding/json"
// MsgCanal canal message struct
type MsgCanal struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}

View File

@@ -0,0 +1,57 @@
package model
import "time"
// LabourQs labour question.
type LabourQs struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Question string `json:"question"`
Ans int8 `json:"ans"`
AvID int64 `json:"av_id"`
Status int8 `json:"status"`
Source int8 `json:"source"`
Isdel int8 `json:"isdel"`
State int8 `json:"state"`
Ctime string `json:"ctime"`
Mtime string `json:"mtime"`
}
// Question question info .
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"`
Operator string `json:"operator"`
}
// answer constants
const (
ExtraAnsA = "符合规范"
ExtraAnsB = "不符合规范"
HadCreateImg = 1
LimitSize = 100
)
// 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,55 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/answer/conf:go_default_library",
"//app/job/main/answer/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"img.go",
"question.go",
"service.go",
],
importpath = "go-common/app/job/main/answer/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/answer/conf:go_default_library",
"//app/job/main/answer/dao:go_default_library",
"//app/job/main/answer/model:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/text/translate/chinese:go_default_library",
"//vendor/github.com/golang/freetype: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,62 @@
package service
import (
"bufio"
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"image/jpeg"
"strconv"
"go-common/app/job/main/answer/model"
"go-common/library/log"
"go-common/library/text/translate/chinese"
"github.com/golang/freetype"
)
var (
platform = map[string]bool{"H5": true, "PC": false}
fileFormat = "%s_A-%s_B-%s_%s"
language = []string{"zh-CN", "zh-TW"}
)
// createBFSImg create bfs img.
func (s *Service) createBFSImg(c context.Context, que *model.LabourQs) (err error) {
log.Info("createBFSImg(%v)", que)
as := [2]string{model.ExtraAnsA, model.ExtraAnsB}
for _, langv := range language {
if langv == "zh-TW" {
que.Question = chinese.Convert(c, que.Question)
as = [2]string{chinese.Convert(c, model.ExtraAnsA), chinese.Convert(c, model.ExtraAnsB)}
}
for ps, pb := range platform {
quec := s.dao.QueConf(pb)
imgh := s.dao.Height(quec, que.Question, 2)
r := s.dao.Board(imgh)
imgc := s.dao.Context(r, s.c.Properties.FontFilePath)
pt := freetype.Pt(0, int(quec.Fontsize))
s.dao.DrawQue(imgc, que.Question, quec, &pt)
s.dao.DrawAns(imgc, quec, as, &pt)
buf := new(bytes.Buffer)
jpeg.Encode(buf, r, nil)
bufReader := bufio.NewReader(buf)
m := md5.New()
m.Write([]byte(fmt.Sprintf(fileFormat, strconv.FormatInt(que.ID, 10), as[0], as[1], ps)))
fname := hex.EncodeToString(m.Sum(nil)) + ".jpg"
location, err := s.dao.Upload(c, "image/jpeg", fname, bufReader)
if err != nil {
log.Error("question (%v) bfs upload failed error(%s)", que, err)
continue
}
log.Info("upload img success.fname:%s,location:%s,lang:%s", fname, location, langv)
}
}
return
}

View File

@@ -0,0 +1,60 @@
package service
import (
"context"
"encoding/json"
"go-common/app/job/main/answer/model"
"go-common/library/log"
)
// AddLabourQuestion add labour question.
func (s *Service) AddLabourQuestion(c context.Context, msg *model.MsgCanal) (err error) {
var que = &model.LabourQs{}
if err = json.Unmarshal(msg.New, que); err != nil {
log.Error("json.Unmarshal(%v) err(%v)", msg, err)
return
}
log.Info("labour add (%+v)", que)
if err = s.createBFSImg(c, que); err != nil {
log.Error("createBFSImg(%v) err(%v)", que, err)
return
}
que.State = model.HadCreateImg
s.dao.AddQs(c, que)
return
}
// ModifyLabourQuestion nodify labour question.
func (s *Service) ModifyLabourQuestion(c context.Context, msg *model.MsgCanal) (err error) {
var (
newq = &model.LabourQs{}
oldq = &model.LabourQs{}
)
if err = json.Unmarshal(msg.New, newq); err != nil {
log.Error("newlqs json.Unmarshal(%v) err(%v)", msg, err)
return
}
if err = json.Unmarshal(msg.Old, oldq); err != nil {
log.Error("oldlqs json.Unmarshal(%v) err(%v)", msg, err)
return
}
log.Info("labour modify (%+v)(%+v)", newq, oldq)
if newq.Status == oldq.Status && newq.Ans == oldq.Ans && newq.Isdel == oldq.Isdel {
log.Error("ModifyLabourQuestion no change(%v, %v)", newq, oldq)
return
}
s.dao.UpdateQs(c, newq)
return
}
// UploadQueImg uplaod que img.
func (s *Service) UploadQueImg(c context.Context, que *model.LabourQs) (err error) {
if err = s.createBFSImg(c, que); err != nil {
log.Error("createBFSImg(%v) err(%v)", que, err)
return
}
que.State = model.HadCreateImg
s.dao.UpdateState(c, que)
return
}

View File

@@ -0,0 +1,161 @@
package service
import (
"context"
"encoding/json"
"sync"
"time"
"go-common/app/job/main/answer/conf"
"go-common/app/job/main/answer/dao"
"go-common/app/job/main/answer/model"
"go-common/library/log"
"go-common/library/queue/databus"
)
const (
_insertAction = "insert"
_updateAction = "update"
_labourTable = "blocked_labour_question"
)
// Service service def.
type Service struct {
c *conf.Config
dao *dao.Dao
labourDatabus *databus.Databus
accountFormal *databus.Databus
waiter sync.WaitGroup
uploadInterval time.Duration
closed bool
}
// New create a instance of Service and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
uploadInterval: time.Duration(c.Properties.UploadInterval),
}
if c.Databus.Labour != nil {
s.labourDatabus = databus.New(c.Databus.Labour)
s.waiter.Add(1)
go s.labourproc()
}
if c.Databus.Account != nil {
s.accountFormal = databus.New(c.Databus.Account)
go s.formalproc()
}
s.waiter.Add(1)
go s.loadextarqueproc()
return s
}
func (s *Service) labourproc() {
defer s.waiter.Done()
var (
err error
msg *databus.Message
msgChan = s.labourDatabus.Messages()
ok bool
)
for {
msg, ok = <-msgChan
if !ok {
log.Info("labour msgChan closed")
}
if s.closed {
return
}
if err = msg.Commit(); err != nil {
log.Error("msg.Commit err(%v)", err)
}
v := &model.MsgCanal{}
if err = json.Unmarshal([]byte(msg.Value), v); err != nil {
log.Error("json.Unmarshal(%v) err(%v)", v, err)
continue
}
if v.Table == _labourTable {
switch v.Action {
case _insertAction:
s.AddLabourQuestion(context.Background(), v)
case _updateAction:
s.ModifyLabourQuestion(context.Background(), v)
}
}
}
}
func (s *Service) formalproc() {
var (
ok bool
err error
msg *databus.Message
msgChan = s.accountFormal.Messages()
)
for {
msg, ok = <-msgChan
if !ok {
log.Info("account formal msgChan closed")
}
if s.closed {
return
}
v := &model.Formal{}
if err = json.Unmarshal([]byte(msg.Value), v); err != nil {
log.Error("json.Unmarshal(%v) err(%v)", v, err)
continue
}
for retries := 0; retries < s.c.Properties.MaxRetries; retries++ {
if err = s.dao.BeFormal(context.Background(), v.Mid, v.IP); err != nil {
sleep := s.c.Backoff.Backoff(retries)
log.Error("s.dao.BeFormal(%+v) sleep(%d) err(%+v)", v, sleep, err)
time.Sleep(sleep * time.Second)
continue
}
break
}
if err = msg.Commit(); err != nil {
log.Error("msg.Commit err(%v)", err)
}
}
}
// Close all resource.
func (s *Service) Close() (err error) {
defer s.waiter.Wait()
s.closed = true
s.dao.Close()
if err = s.labourDatabus.Close(); err != nil {
log.Error("s.labourDatabus.Close() error(%v)", err)
return
}
return
}
// Ping check dao health.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
func (s *Service) loadextarqueproc() {
defer s.waiter.Done()
for {
time.Sleep(s.uploadInterval)
if s.closed {
return
}
res, err := s.dao.QidsExtraByState(context.Background(), model.LimitSize)
if err != nil {
log.Error("s.dao.QidsExtraByState() error(%v)", err)
continue
}
if len(res) == 0 {
continue
}
for _, q := range res {
s.UploadQueImg(context.Background(), q)
}
}
}

View File

@@ -0,0 +1,81 @@
package service
import (
"context"
"flag"
"fmt"
"os"
"testing"
"go-common/app/job/main/answer/conf"
"go-common/app/job/main/answer/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "")
flag.Set("conf_appid", "")
flag.Set("conf_token", "")
flag.Set("tree_id", "")
flag.Set("conf_version", "server-1")
flag.Set("deploy_env", "dev")
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-job-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
s = New(conf.Conf)
os.Exit(m.Run())
}
func TestUpdateBfs(t *testing.T) {
Convey("create img", t, func(s *Service) {
err := s.createBFSImg(context.TODO(), &model.LabourQs{
Question: "测试附加题3",
ID: 3,
})
fmt.Println("err:", err)
So(err, ShouldBeNil)
})
}
func TestPing(t *testing.T) {
Convey("Test_Ping", t, func(s *Service) {
err := s.Ping(context.TODO())
So(err, ShouldBeNil)
})
}
// go test -test.v -test.run TestAddQue
func TestCreateBFSImg(t *testing.T) {
Convey("TestAddQue add question data", t, func(s *Service) {
for i := 90; i < 100; i++ {
que := &model.LabourQs{
Question: fmt.Sprintf("测试d=====( ̄▽ ̄*)b厉害 %d", i),
Ans: int8(i%2 + 1),
AvID: int64(i),
Status: int8(i%2 + 1),
Source: int8(1),
State: model.HadCreateImg,
ID: int64(i),
}
err := s.createBFSImg(context.TODO(), que)
So(err, ShouldBeNil)
err = s.dao.AddQs(context.TODO(), que)
So(err, ShouldBeNil)
}
})
}