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/admin/main/appstatic/cmd:all-srcs",
"//app/admin/main/appstatic/conf:all-srcs",
"//app/admin/main/appstatic/dao:all-srcs",
"//app/admin/main/appstatic/http:all-srcs",
"//app/admin/main/appstatic/model:all-srcs",
"//app/admin/main/appstatic/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,56 @@
### appstatic-admin
### v1.1.3
1. 新增逻辑二次触发时同时向redis中存入需要推送的resID
### v1.1.2
1. 分割出appstatic-admin中的定时任务至appstatic-job中
2. 增加ut
3. 修复正在计算中的增量包会重复计算的问题
### v1.1.1
1. 支持config中的仅wifi的条件
2. 增加UT修复saga问题
### v1.1.0
1. 新增资源包时验证mod添加not deleted和valid条件
### v1.0.9
1. 调整参数验证逻辑,如大小范围允许一端为空
### v1.0.8
1. 支持更多增量包10个
2. 支持二次触发提供接口由mgr后台告知需要二次触发
3. 增加添加资源后的返回值(版本+资源ID
### v1.0.7
1. 添加限制job在非正式环境不运行
### v1.0.6
1. 支持更多参数,level, sysver, scale
2. 使用框架的client进行文件下载设置timeout
### v1.0.5
1. 修改上传后重命名的文件名格式
2. 修改增量包的文件名格式
### v1.0.4
1. 取值检验使用bm Bind方法简化写法更清真
2. 新增适配测试权限点的上传接口
3. 新增从接口指定default_package的逻辑
4. 文件类型校验改为读取配置,如果配置为空则放开所有文件类型上传
### v1.0.3
1. 增加测试包和正式包的判断逻辑,计算差量包时,算出三个正式包的差量+三个测试包的差量
### v1.0.2
1. 新增加一层 department
### v1.0.1
1. 修改差量包计算逻辑:立即计算新上传,失败后每小时重试
2. 增加mod_name和file_name的正则检验
### v1.0.0
1. 支持从manager上传zip包创建新版本
2. 支持从第三方系统上传zip包创建新版本

View File

@@ -0,0 +1,11 @@
# Owner
liweijia
renwei
# Author
zhaoshichen
# Reviewer
liuxuan
guanyanliang
wuhao

View File

@@ -0,0 +1,17 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- liweijia
- renwei
- zhaoshichen
labels:
- admin
- admin/main/appstatic
- main
options:
no_parent_owners: true
reviewers:
- guanyanliang
- liuxuan
- wuhao
- zhaoshichen

View File

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 = ["appstatic-admin-test.toml"],
importpath = "go-common/app/admin/main/appstatic/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/appstatic/conf:go_default_library",
"//app/admin/main/appstatic/http:go_default_library",
"//app/admin/main/appstatic/service:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/os/signal:go_default_library",
"//library/syscall: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,147 @@
version = "1.0.0"
user = "nobody"
pid = "/tmp/appstatic-admin.pid"
dir = "./"
unameTicker = "10s"
[identify]
whiteAccessKey = ""
whiteMid = 0
csrfOn = false
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 10
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.co"
secret = "http://open.bilibili.co"
[identify.HTTPClient]
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[identify.HTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.HTTPClient.URL]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[auth]
managerHost = "http://uat-manager.bilibili.co"
dashboardHost = "http://uat-dashboard-mng.bilibili.co"
dashboardCaller = "manager-go"
[auth.DsHTTPClient]
key = "manager-go"
secret = "949bbb2dd3178252638c2407578bc7ad"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[auth.DsHTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[auth.MaHTTPClient]
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[auth.MaHTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[auth.session]
sessionIDLength = 32
cookieLifeTime = 1800
cookieName = "mng-go"
domain = ".bilibili.co"
[auth.session.Memcache]
name = "go-business/auth"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 10
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[BM]
addr = "0.0.0.0:6683"
maxListen = 10
timeout = "1s"
[orm]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_show?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
idleTimeout = "4h"
[cfg]
historyVer = 10
freDiff = "10s"
folder = "/tmp/test"
storage = "bfs"
filetypes = []
[xlog]
dir = "/data/log/appstatic-admin"
family = "appstatic-admin"
[nas]
key = "53e2fa226f5ad348"
secret = "3cf6bd1b0ff671021da5f424fea4b04a"
host = "http://macross.bilibili.co/api/v1/macross/manager/upload"
timeout = 1000
oldURL = "dl-hdslb-com"
newURL = "http://dl.hdslb.com"
[bfs]
key = "fed7d6b5948c614f"
secret = "280e9140721cff3879cd13f59fc28a"
host = "http://uat-bfs.bilibili.co"
timeout = 1000
[httpClient]
key = "b525299741c108ef"
secret = "ef1d0b536d4bcef04dd7c75014c51f"
dial = "500ms"
timeout = "2s"
keepAlive = "60s"
timer = 10
[httpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
name = "tv-job"
proto = "tcp"
addr = "127.0.0.1:6379"
active = 10
idle = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"

View File

@@ -0,0 +1,58 @@
package main
import (
"flag"
"os"
"time"
"go-common/app/admin/main/appstatic/conf"
"go-common/app/admin/main/appstatic/http"
"go-common/app/admin/main/appstatic/service"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/os/signal"
"go-common/library/syscall"
)
var (
s *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.XLog)
defer log.Close()
trace.Init(conf.Conf.Tracer)
defer trace.Close()
// service init
s = service.New(conf.Conf)
http.Init(conf.Conf, s)
log.Info("appstatic-admin start")
signalHandler()
}
func signalHandler() {
var (
ch = make(chan os.Signal, 1)
)
signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP)
for {
si := <-ch
switch si {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT:
time.Sleep(time.Second * 2)
log.Info("get a signal %s, stop the appstatic-admin process", si.String())
s.Wait()
s.Close()
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,38 @@
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/admin/main/appstatic/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/orm:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//library/net/trace: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,122 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/orm"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"go-common/library/net/trace"
"github.com/BurntSushi/toml"
)
var (
// config
confPath string
client *conf.Client
// Conf .
Conf = &Config{}
)
// Config def.
type Config struct {
// base
// auth
Auth *permit.Config
// http
BM *bm.ServerConfig
// db
ORM *orm.Config
// log
XLog *log.Config
// tracer
Tracer *trace.Config
// cfg
Cfg *Cfg
// bfs
Bfs *Bfs
// nas
Nas *Bfs
// HTTPClient .
HTTPClient *bm.ClientConfig
// Redis
Redis *Redis
}
// Redis redis
type Redis struct {
*redis.Config
}
// Bfs reprensents the bfs config
type Bfs struct {
Key string
Secret string
Host string
Timeout int
OldURL string
NewURL string
}
// Cfg def.
type Cfg struct {
HistoryVer int
Storage string // NAS or BFS
Filetypes []string // allowed file type to upload
}
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
}
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
}
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,59 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"redis_test.go",
"up_nas_test.go",
"upbfs_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/admin/main/appstatic/conf: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",
"redis.go",
"up_nas.go",
"upbfs.go",
],
importpath = "go-common/app/admin/main/appstatic/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/appstatic/conf:go_default_library",
"//app/admin/main/appstatic/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/orm:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/github.com/jinzhu/gorm: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,38 @@
package dao
import (
"go-common/app/admin/main/appstatic/conf"
"go-common/library/cache/redis"
"go-common/library/database/orm"
httpx "go-common/library/net/http/blademaster"
"github.com/jinzhu/gorm"
)
// Dao .
type Dao struct {
DB *gorm.DB
c *conf.Config
client *httpx.Client
redis *redis.Pool
}
// New new a instance
func New(c *conf.Config) (d *Dao) {
d = &Dao{
// db
DB: orm.NewMySQL(c.ORM),
c: c,
client: httpx.NewClient(c.HTTPClient),
redis: redis.NewPool(c.Redis.Config),
}
d.DB.LogMode(true)
return
}
// Close close connection of db , mc.
func (d *Dao) Close() {
if d.DB != nil {
d.DB.Close()
}
}

View File

@@ -0,0 +1,49 @@
package dao
import (
"flag"
"os"
"strings"
"go-common/app/admin/main/appstatic/conf"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/h2non/gock.v1"
)
var d *Dao
func init() {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.web-svr.appstatic-admin")
flag.Set("conf_token", "26eec0d4137fac469c03e5ae147ed101")
flag.Set("tree_id", "22976")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
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/appstatic-admin-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
d.client.SetTransport(gock.DefaultTransport)
return r
}
func WithDao(f func(d *Dao)) func() {
return func() {
Reset(func() {})
f(d)
}
}

View File

@@ -0,0 +1,32 @@
package dao
import (
"context"
"time"
"go-common/library/log"
)
const _pushKey = "appstatic-admin-topush"
// ZAddPush adds one to push data into the redis sorted set
func (d *Dao) ZAddPush(c context.Context, resID int) (err error) {
var (
conn = d.redis.Get(c)
ctime = time.Now().Unix()
)
defer conn.Close()
if err = conn.Send("ZADD", _pushKey, ctime, resID); err != nil {
log.Error("conn.Send(ZADD %s - %v) error(%v)", _pushKey, resID, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
return
}

View File

@@ -0,0 +1,15 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDao_ZAddPush(t *testing.T) {
Convey("TestDao_ZAddPush", t, WithDao(func(d *Dao) {
err := d.ZAddPush(context.Background(), 381)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,129 @@
package dao
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"crypto/md5"
"encoding/hex"
"go-common/app/admin/main/appstatic/conf"
"go-common/app/admin/main/appstatic/model"
"go-common/library/log"
)
// Sign fn
func Sign(params url.Values) (query string, err error) {
if len(params) == 0 {
return
}
if params.Get("appkey") == "" {
err = fmt.Errorf("utils http get must have parameter appkey")
return
}
if params.Get("appsecret") == "" {
err = fmt.Errorf("utils http get must have parameter appsecret")
return
}
if params.Get("sign") != "" {
err = fmt.Errorf("utils http get must have not parameter sign")
return
}
// sign
secret := params.Get("appsecret")
params.Del("appsecret")
tmp := params.Encode()
if strings.IndexByte(tmp, '+') > -1 {
tmp = strings.Replace(tmp, "+", "%20", -1)
}
mh := md5.Sum([]byte(tmp + secret))
params.Set("sign", hex.EncodeToString(mh[:]))
query = params.Encode()
return
}
// get sign for NAS storage
func getSign(nas *conf.Bfs) (uri string, err error) {
var (
params = url.Values{}
query string
)
params.Set("appkey", nas.Key)
params.Set("appsecret", nas.Secret)
params.Set("ts", strconv.FormatInt(time.Now().Unix(), 10))
if query, err = Sign(params); err != nil {
log.Error("UpNAS getSign Error (%s)-(%v)-(%v)", nas, err)
return
}
uri = nas.Host + "?" + query
return
}
// UploadNas can upload the file into Nas Storage
func (d *Dao) UploadNas(c context.Context, fileName string, data []byte, nas *conf.Bfs) (location string, err error) {
var (
req *http.Request
resp *http.Response
client = &http.Client{Timeout: time.Duration(nas.Timeout) * time.Millisecond}
url string
res = model.ResponseNas{}
)
// get sign
if url, err = getSign(nas); err != nil {
log.Error("UpNAS getSign Error (%s)-(%v)-(%v)", nas, err)
return
}
// prepare the data of the file and init the request
buf := new(bytes.Buffer)
bodyWriter := multipart.NewWriter(buf)
fileWriter, err := bodyWriter.CreateFormFile("file", fileName)
if err != nil {
log.Error("UpNAS fileWriter Error (%v)-(%v)", nas, err)
return
}
if _, err = io.Copy(fileWriter, bytes.NewReader(data)); err != nil {
log.Error("UpNAS fileWriter Copy Error (%v)-(%v)", nas, err)
return
}
bodyWriter.Close()
// request setting
if req, err = http.NewRequest(_methodNas, url, buf); err != nil {
log.Error("http.NewRequest() Upload(%v) error(%v)", url, err)
return
}
req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
resp, err = client.Do(req)
// response treatment
if err != nil {
log.Error("Nas client.Do(%s) error(%v)", url, err)
return
}
defer resp.Body.Close()
log.Info("NasAPI returns (%v)", resp)
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("Nas status code error:%v", resp.StatusCode)
return
}
respBody, err := ioutil.ReadAll(resp.Body)
if err = json.Unmarshal(respBody, &res); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", string(respBody), err)
return
}
log.Info("NasAPI res struct (%v)", res)
location = res.Data
// workaround solution for Macross Upload URL issue
if d.c.Nas.NewURL != "" {
location = strings.Replace(location, d.c.Nas.OldURL, d.c.Nas.NewURL, -1)
log.Error("NasURL replace [%s] to [%s]", res.Data, location)
}
return
}

View File

@@ -0,0 +1,55 @@
package dao
import (
"context"
"net/url"
"strconv"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoSign(t *testing.T) {
var (
params = url.Values{}
nas = d.c.Nas
)
params.Set("appkey", nas.Key)
params.Set("appsecret", nas.Secret)
params.Set("ts", strconv.FormatInt(time.Now().Unix(), 10))
convey.Convey("Sign", t, func(ctx convey.C) {
query, err := Sign(params)
ctx.Convey("Then err should be nil.query should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(query, convey.ShouldNotBeNil)
})
})
}
func TestDaogetSign(t *testing.T) {
var nas = d.c.Nas
convey.Convey("getSign", t, func(ctx convey.C) {
uri, err := getSign(nas)
ctx.Convey("Then err should be nil.uri should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(uri, convey.ShouldNotBeNil)
})
})
}
func TestDaoUploadNas(t *testing.T) {
var (
c = context.Background()
fileName = "test.txt"
data = []byte("test123")
nas = d.c.Nas
)
convey.Convey("UploadNas", t, func(ctx convey.C) {
location, err := d.UploadNas(c, fileName, data, nas)
ctx.Convey("Then err should be nil.location should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(location, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,89 @@
package dao
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"hash"
"net/http"
"strconv"
"time"
"go-common/app/admin/main/appstatic/conf"
"go-common/library/ecode"
"go-common/library/log"
)
// bfs info
const (
_uploadURL = "/bfs/%s/%s"
_template = "%s\n%s\n%s\n%d\n"
_method = "PUT"
_methodNas = "POST"
_bucket = "app-static"
)
// Upload upload picture or log file to bfs
func (d *Dao) Upload(c context.Context, fileName string, fileType string, timing int64, data []byte, bfs *conf.Bfs) (location string, err error) {
var (
req *http.Request
resp *http.Response
code int
client = &http.Client{Timeout: time.Duration(bfs.Timeout) * time.Millisecond}
url = fmt.Sprintf(bfs.Host+_uploadURL, _bucket, fileName)
)
// prepare the data of the file and init the request
buf := new(bytes.Buffer)
_, err = buf.Write(data)
if err != nil {
log.Error("Upload.buf.Write.error(%v)", err)
err = ecode.RequestErr
return
}
if req, err = http.NewRequest(_method, url, buf); err != nil {
log.Error("http.NewRequest() Upload(%v) error(%v)", url, err)
return
}
// request setting
authorization := authorize(bfs.Key, bfs.Secret, _method, _bucket, fileName, timing)
req.Header.Set("Date", fmt.Sprint(timing))
req.Header.Set("Authorization", authorization)
req.Header.Set("Content-Type", fileType)
resp, err = client.Do(req)
// response treatment
if err != nil {
log.Error("Bfs client.Do(%s) error(%v)", url, err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("Bfs status code error:%v", resp.StatusCode)
return
}
code, err = strconv.Atoi(resp.Header.Get("code"))
if err != nil || code != 200 {
err = fmt.Errorf("Bfs response code error:%v", code)
return
}
location = resp.Header.Get("Location")
return
}
// authorize returns authorization for upload file to bfs
func authorize(key, secret, method, bucket, file string, expire int64) (authorization string) {
var (
content string
mac hash.Hash
signature string
)
content = fmt.Sprintf(_template, method, bucket, file, 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,57 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUpload(t *testing.T) {
var (
c = context.Background()
fileName = "test.txt"
fileType = "txt"
timing = time.Now().Unix()
bfs = d.c.Bfs
data = []byte("123")
url = fmt.Sprintf(bfs.Host+_uploadURL, "app-static", fileName)
)
convey.Convey("Upload", t, func(ctx convey.C) {
ctx.Convey("http code error", func(ctx convey.C) {
httpMock(_method, url).Reply(-400)
_, err := d.Upload(c, fileName, fileType, timing, data, bfs)
ctx.So(err, convey.ShouldNotBeNil)
})
ctx.Convey("business code error", func(ctx convey.C) {
httpMock(_method, url).Reply(200).JSON(`{"code":-400}`)
_, err := d.Upload(c, fileName, fileType, timing, data, bfs)
ctx.So(err, convey.ShouldNotBeNil)
})
ctx.Convey("everything is fine", func(ctx convey.C) {
httpMock(_method, url).Reply(200).SetHeader("Location", "test").SetHeader("code", "200").JSON(`{"code":200}`)
location, err := d.Upload(c, fileName, fileType, timing, data, bfs)
ctx.So(err, convey.ShouldBeNil)
ctx.So(location, convey.ShouldNotBeNil)
})
})
}
func TestDaoauthorize(t *testing.T) {
var (
key = "key"
secret = "secret"
method = "put"
bucket = "tv-cover"
file = "file"
expire = int64(0)
)
convey.Convey("authorize", t, func(ctx convey.C) {
authorization := authorize(key, secret, method, bucket, file, expire)
ctx.Convey("Then authorization should not be nil.", func(ctx convey.C) {
ctx.So(authorization, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"add_ver.go",
"http.go",
"publish.go",
],
importpath = "go-common/app/admin/main/appstatic/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/appstatic/conf:go_default_library",
"//app/admin/main/appstatic/model:go_default_library",
"//app/admin/main/appstatic/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/permit:go_default_library",
"//library/net/http/blademaster/middleware/verify: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,242 @@
package http
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"time"
"encoding/json"
"go-common/app/admin/main/appstatic/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
const nameFmt = `^[a-zA-Z0-9._-]+$`
const fileFmt = "Mod_%d-%s/%s"
func httpCode(c *bm.Context, message string, err error) {
c.JSON(map[string]interface{}{
"message": message,
}, err)
}
// validate required data
func validateRequired(reqInfo *model.RequestVer) (err error) {
reg := regexp.MustCompile(nameFmt)
if res := reg.MatchString(reqInfo.ModName); !res {
err = fmt.Errorf("mod_name %s contains illegal character", reqInfo.ModName)
return
}
if res := reg.MatchString(reqInfo.Department); !res {
err = fmt.Errorf("department %s contains illegal character", reqInfo.Department)
return
}
return
}
// check whether the build range is valid
func checkRange(build *model.Build) (res bool) {
if (build.GE != 0 && build.GT != 0) || (build.LE != 0 && build.LT != 0) { // two values by one side
return false
}
var (
gt = build.GT
lt = build.LT
)
// transform E to T
if build.GE != 0 {
gt = build.GE - 1
}
if build.LE != 0 {
lt = build.LE + 1
}
// range check
if lt != 0 && gt != 0 && lt-gt <= 1 {
return false
}
return true
}
// transform []int to []string
func sliceString(is []int) (ss []string) {
for _, v := range is {
ss = append(ss, fmt.Sprintf("%d", v))
}
return
}
// check limit data and build the Limit Struct, error is json error here
func checkLimit(reqInfo *model.RequestVer) (res *model.Limit, err error) {
getFormat := "GetLimit Param (%s), Value = (%s)"
res = &model.Limit{}
// mobi_app
if len(reqInfo.MobiAPP) != 0 {
res.MobiApp = reqInfo.MobiAPP
}
// device
if len(reqInfo.Device) != 0 {
res.Device = reqInfo.Device
}
// plat
if len(reqInfo.Plat) != 0 {
res.Plat = reqInfo.Plat
}
if reqInfo.IsWifi != 0 {
res.IsWifi = reqInfo.IsWifi
}
// Scale & Arch & Level
if len(reqInfo.Scale) != 0 {
res.Scale = sliceString(reqInfo.Scale)
}
if len(reqInfo.Arch) != 0 {
res.Arch = sliceString(reqInfo.Arch)
}
if reqInfo.Level != 0 {
res.Level = sliceString([]int{reqInfo.Level}) // treat level as others ( []int )
}
// build_range
if buildStr := reqInfo.BuildRange; buildStr != "" {
log.Info(getFormat, "build_range", buildStr)
var build = model.Build{}
if err = json.Unmarshal([]byte(buildStr), &build); err != nil { // json err
log.Error("buildStr (%s) json.Unmarshal error(%v)", buildStr, err)
return
}
if isValid := checkRange(&build); !isValid { // range not valid
err = fmt.Errorf("build range (%s) not valid", buildStr)
log.Error("buildStr CheckRange Error (%v)", err)
return
}
res.Build = &build
}
// sysver
if sysverStr := reqInfo.Sysver; sysverStr != "" {
var build = model.Build{}
if err = json.Unmarshal([]byte(sysverStr), &build); err != nil { // json err
log.Error("buildStr (%s) json.Unmarshal error(%v)", sysverStr, err)
return
}
if isValid := checkRange(&build); !isValid { // range not valid
err = fmt.Errorf("build range (%s) not valid", sysverStr)
log.Error("sysverStr CheckRange Error (%v)", err)
return
}
res.Sysver = &build
}
// time_range
if timeStr := reqInfo.TimeRange; timeStr != "" {
log.Info(getFormat, "time_range", timeStr)
var tr = model.TimeRange{}
if err = json.Unmarshal([]byte(timeStr), &tr); err != nil {
log.Error("timeStr (%s) json.Unmarshal error(%v)", timeStr, err)
return
}
if tr.Stime != 0 && tr.Etime != 0 && tr.Stime > tr.Etime {
err = fmt.Errorf("Stime(%d) is bigger than Etime(%d)", tr.Stime, tr.Etime)
log.Error("Time Range Error(%v)", err)
return
}
res.TimeRange = &tr
}
return
}
// validate the file type, content and upload it to the BFS storage
func validateFile(ctx *bm.Context, req *http.Request, pool *model.ResourcePool) (fInfo *model.FileInfo, err error) {
// get the file
file, header, err := req.FormFile("file")
if err != nil {
return
}
defer file.Close()
// read the file
content, err := ioutil.ReadAll(file)
if err != nil {
log.Error("resource uploadFile.ReadAll error(%v)", err)
return
}
// parse file, get type, size, md5
fInfo, err = apsSvc.ParseFile(content)
if err != nil {
log.Error("[validateFile]-[ParseFile] Error-[%v]", err)
return
}
if !apsSvc.TypeCheck(fInfo.Type) {
log.Error("[validateFile]-[FileType] Error-[%v]", fInfo.Type)
err = fmt.Errorf("请上传指定类型文件")
return
}
// regex checking
reg := regexp.MustCompile(nameFmt)
if res := reg.MatchString(header.Filename); !res {
err = fmt.Errorf("fileName %s contains illegal character", header.Filename)
return
}
// upload file to BFS
fInfo.Name = fmt.Sprintf(fileFmt, pool.ID, fInfo.Md5, header.Filename) // rename with the MD5 and poolID
location, err := apsSvc.Upload(ctx, fInfo.Name, fInfo.Type, time.Now().Unix(), content)
if err != nil {
log.Error("[validateFile]-[UploadBFS] Error-[%v]", err)
return
}
fInfo.URL = location
return
}
// for other systems
func addVer(c *bm.Context) {
var (
pool = model.ResourcePool{}
department = model.Department{}
req = c.Request
limitData *model.Limit
fInfo *model.FileInfo
err error
reqInfo = model.RequestVer{}
respData = &model.RespAdd{}
)
req.ParseMultipartForm(apsSvc.MaxSize)
if err = c.Bind(&reqInfo); err != nil {
return
}
// validate required data
if err = validateRequired(&reqInfo); err != nil {
log.Error("addVer ModName, ResName Error (%v)", err)
c.JSON(nil, err)
return
}
// validate department
if err = apsSvc.DB.Where("`name` = ?", reqInfo.Department).First(&department).Error; err != nil {
log.Error("addVer First department Error (%v)", err)
httpCode(c, fmt.Sprintf("department %s doesn't exist", reqInfo.Department), ecode.RequestErr)
return
}
// validate mod Name
if err = apsSvc.DB.Where("`name` = ? AND `department_id` = ? AND `deleted` = 0 AND `action` = 1", reqInfo.ModName, department.ID).First(&pool).Error; err != nil {
log.Error("addVer First Pool Error (%v)", err)
httpCode(c, fmt.Sprintf("Mod_name %s doesn't exist", reqInfo.ModName), ecode.RequestErr)
return
}
// check limit & config data
if limitData, err = checkLimit(&reqInfo); err != nil {
log.Error("addVer CheckLimit Error (%v)", err)
httpCode(c, fmt.Sprintf("Limit Params JSON Error:(%v)", err), ecode.RequestErr)
return
}
// validate file data
if fInfo, err = validateFile(c, req, &pool); err != nil {
log.Error("addVer ValidateFile Error (%v)", err)
httpCode(c, fmt.Sprintf("File Error:(%v)", err), ecode.RequestErr)
return
}
// DB & storage operation
if respData.ResID, respData.Version, err = apsSvc.GenerateVer(reqInfo.ResName, limitData, fInfo, &pool, reqInfo.DefaultPackage); err != nil {
log.Error("addVer GenerateVer Error (%v)", err)
httpCode(c, fmt.Sprintf("Generate Version Error:(%v)", err), ecode.ServerErr)
return
}
c.JSON(respData, nil)
}

View File

@@ -0,0 +1,52 @@
package http
import (
"go-common/app/admin/main/appstatic/conf"
"go-common/app/admin/main/appstatic/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
vfySvc *verify.Verify
authSvc *permit.Permit
apsSvc *service.Service
)
// Init http server
func Init(c *conf.Config, s *service.Service) {
initService(c, s)
engine := bm.DefaultServer(c.BM)
innerRouter(engine)
// init internal server
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
// initService init service
func initService(c *conf.Config, s *service.Service) {
apsSvc = s
authSvc = permit.New(c.Auth)
vfySvc = verify.New(nil)
}
// innerRouter
func innerRouter(e *bm.Engine) {
// ping monitor
e.GET("/monitor/ping", ping)
// internal api
bg := e.Group("/x/admin/appstatic/res")
{
bg.POST("/add_ver", authSvc.Permit("APP_RESOURCE_POOL_MGT"), addVer) // 从mgr上传正式权限
bg.POST("/add_ver_test", authSvc.Permit("APP_RESOURCE_POOL_MGT_EDIT"), addVer) // 从mgr上传测试权限
bg.POST("/upload", vfySvc.Verify, addVer) // 从其他系统上传
bg.POST("/publish", vfySvc.Verify, publish) // 告知某资源包的第一次发布,用于触发增量包补充计算
}
}
// ping check server ok.
func ping(c *bm.Context) {}

View File

@@ -0,0 +1,15 @@
package http
import (
bm "go-common/library/net/http/blademaster"
)
func publish(c *bm.Context) {
arg := new(struct {
ResID int `form:"res_id" validate:"required"`
})
if err := c.Bind(arg); err != nil {
return
}
c.JSON(apsSvc.Publish(c, arg.ResID))
}

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"add_ver.go",
"publish.go",
],
importpath = "go-common/app/admin/main/appstatic/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/time: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,171 @@
package model
import "go-common/library/time"
// Limit def
type Limit struct {
MobiApp []string // white list
Device []string // black list
Plat []string // white list
Build *Build // build range
TimeRange *TimeRange // time range
Sysver *Build // system version
Scale []string
Arch []string
Level []string
IsWifi int // only wifi download
}
// Build def
type Build struct {
LT int `json:"lt"` // less than
GT int `json:"gt"` // great than
LE int `json:"le"` // less than or equal
GE int `json:"ge"` // great than or equal
}
// TimeRange def
type TimeRange struct {
Stime time.Time `json:"stime"`
Etime time.Time `json:"etime"`
}
// ResourceLimit def
type ResourceLimit struct {
ID int64
ConfigID int64
Column string
Condition string
Value string
IsDeleted int8
Mtime time.Time
Ctime time.Time
}
// ResourceConfig def
type ResourceConfig struct {
ID int64
ResourceID int64 `gorm:"column:resource_id"`
Stime time.Time
Etime time.Time
Valid int8
IsDeleted int8 `gorm:"column:is_deleted"`
Mtime time.Time
Ctime time.Time
DefaultPackage int8 `gorm:"column:default_package"`
IsWifi int `gorm:"column:is_wifi"`
}
//FileInfo : the uploaded file information
type FileInfo struct {
Name string `json:"name"`
Size int64 `json:"size"`
Type string `json:"type"`
Md5 string `json:"md5"`
URL string `json:"url"`
}
// ResourceFile represents the table structure
type ResourceFile struct {
ID int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Md5 string `json:"md5"`
Size int `json:"size"`
URL string `json:"url"`
ResourceID int `json:"resource_id"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
FileType int8 `json:"file_type"`
FromVer int64 `json:"from_ver"`
IsDeleted int8 `json:"is_deleted"`
}
// Resource reprensents the resource table
type Resource struct {
ID int64 `json:"id" params:"id"`
Name string `json:"name" params:"name"`
Version int64 `json:"version" params:"version"`
PoolID int64 `json:"pool_id" params:"pool_id"`
Ctime time.Time `json:"ctime" params:"ctime"`
Mtime time.Time `json:"mtime" params:"mtime"`
}
// ResourcePool reprensents the resource_pool table
type ResourcePool struct {
ID int64 `json:"id" params:"id"`
Name string `json:"name" params:"name"`
Ctime time.Time `json:"ctime" params:"ctime"`
Mtime time.Time `json:"mtime" params:"mtime"`
}
// Department reprensents the resource_department table
type Department struct {
ID int64 `json:"id" params:"id"`
Name string `json:"name" params:"name"`
Ctime time.Time `json:"ctime" params:"ctime"`
Mtime time.Time `json:"mtime" params:"mtime"`
Desc string `json:"desc" params:"desc"`
IsDeleted uint8 `json:"is_deleted" params:"is_deleted"`
}
// ResponseNas represents the NAS response struct
type ResponseNas struct {
Code int `json:"code"`
Data string `json:"data"`
Message string `json:"message"`
}
// RequestVer is the struct of the request to upload an new version's package
type RequestVer struct {
Department string `form:"department" validate:"required"`
DefaultPackage int `form:"default_package" validate:"min=0,max=1"`
ResName string `form:"res_name" validate:"required"`
ModName string `form:"mod_name" validate:"required"`
MobiAPP []string `form:"mobi_app,split"`
Plat []string `form:"plat,split"`
Device []string `form:"device,split"`
BuildRange string `form:"build_range"`
TimeRange string `form:"time_range"`
Sysver string `form:"sysver"`
Arch []int `form:"arch,split" validate:"dive,min=1,max=3"`
Level int `form:"level" validate:"min=0,max=3"`
Scale []int `form:"scale,split" validate:"dive,min=1,max=3"`
IsWifi int `form:"is_wifi" validate:"max=1"`
}
// RespAdd is the structure for add ver return
type RespAdd struct {
ResID int `json:"res_id"`
Version int `json:"version"`
}
// TableName gives the table name of the model
func (*Resource) TableName() string {
return "resource"
}
// TableName gives the table name of the model
func (*ResourcePool) TableName() string {
return "resource_pool"
}
// TableName gives the table name of the model
func (*ResourceFile) TableName() string {
return "resource_file"
}
// TableName gives the table name of the model
func (*ResourceLimit) TableName() string {
return "resource_limit"
}
// TableName gives the table name of the model
func (*ResourceConfig) TableName() string {
return "resource_config"
}
// TableName gives the table name of the model
func (*Department) TableName() string {
return "resource_department"
}

View File

@@ -0,0 +1,19 @@
package model
// PubResp Response for publish interface
type PubResp struct {
CurrVer int64 `json:"curr_ver"`
DiffProd []int64 `json:"diff_prod"`
DiffTest []int64 `json:"diff_test"`
}
// Ver reprensents the already generated versions
type Ver struct {
FromVer int64
ID int64
}
// TableName gives the table name of the model
func (*Ver) TableName() string {
return "resource_file"
}

View File

@@ -0,0 +1,59 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"add_ver_test.go",
"publish_test.go",
"service_test.go",
"upbfs_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/appstatic/conf:go_default_library",
"//app/admin/main/appstatic/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"add_ver.go",
"publish.go",
"push.go",
"service.go",
"upbfs.go",
],
importpath = "go-common/app/admin/main/appstatic/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/appstatic/conf:go_default_library",
"//app/admin/main/appstatic/dao:go_default_library",
"//app/admin/main/appstatic/model:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/jinzhu/gorm: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,355 @@
package service
import (
"database/sql"
"fmt"
"go-common/app/admin/main/appstatic/model"
"go-common/library/log"
"github.com/jinzhu/gorm"
)
const (
// file type
_fullPackage = 0
_diffPackge = 1
// diff file name format
_diffFormat = "Mod_%d/V_%d-V_%d.bspatch"
// limit column
deviceCol = "device"
mobiAppCol = "mobi_app"
platCol = "plat"
buildCol = "build"
sysverCol = "sysver"
scaleCol = "scale"
levelCol = "level"
archCol = "arch"
// condition column
_bk = "bk"
_wt = "wt"
buildLtCdt = "lt"
buildGtCdt = "gt"
buildLeCdt = "le"
buildGeCdt = "ge"
_valid = 1
)
// GenerateVer generates a new version ( resource ) and cover the diff logic
func (s *Service) GenerateVer(resName string, limitData *model.Limit, fInfo *model.FileInfo, pool *model.ResourcePool, defPkg int) (resID int, version int, err error) {
// create a new version
var tx = s.DB.Begin()
var resource = tx.Create(transResource(resName, pool.ID))
if err = resource.Error; err != nil {
log.Error("GenerateVer DBCreate Resource Error(%v)", err)
tx.Rollback()
return
}
resID = int(resource.Value.(*model.Resource).ID)
log.Info("Resource Generated: ID = %d", resID)
// create the full package in File Table
if err = tx.Create(transFile(fInfo, resID)).Error; err != nil {
log.Error("GenerateVer DBCreate ResourceFile Error(%v)", err)
tx.Rollback()
return
}
// create the resource config
var config = tx.Create(transConfig(int64(resID), limitData))
if err = config.Error; err != nil {
log.Error("GenerateVer DBCreate ResoureConfig Error(%v)", err)
tx.Rollback()
return
}
configID := int64(config.Value.(*model.ResourceConfig).ID)
log.Info("Resource Config Generated: ID = %d", configID)
// create the resource limits
limits := createLimit(configID, limitData)
if len(limits) != 0 {
for _, v := range limits {
if err = tx.Create(v).Error; err != nil {
log.Error("GenerateVer DCreate ResourceLimit (%v) Error(%v)", v, err)
tx.Rollback()
return
}
}
} else {
log.Error("[GenerateVer]-[createLimit]-No limit to create")
}
// commit the transaction
tx.Commit()
log.Info("Transaction Committed, ResID: %d", resID)
// treat the default package setting
if defPkg == 1 {
if err = s.DefaultPkg(resID, pool.ID, configID); err != nil {
log.Error("defaultPkg Error (%v)", err)
return
}
}
// defines the version of this resource
if version, err = s.defineVer(resID, pool.ID); err != nil {
log.Error("defineVer Error (%v)", err)
return
}
// create diff records
if err = s.createDiff(resID); err != nil {
log.Error("[GenerateVer]-[createDiff]-Error(%v)", err)
return
}
return
}
// DefaultPkg sets the resID's config as default package, and resets the other resources' config
func (s *Service) DefaultPkg(resID int, poolID int64, confID int64) (err error) {
var (
tx = s.DB.Begin()
rows *sql.Rows
rid int // resource id
)
// find out all the resources under the same pool, and put them as non-default pkg
if rows, err = s.DB.Model(&model.Resource{}).Where("pool_id = ?", poolID).Select("id").Rows(); err != nil {
return
}
for rows.Next() {
if err = rows.Scan(&rid); err != nil {
tx.Rollback()
return
}
if err = tx.Model(&model.ResourceConfig{}).Where("resource_id = ?", rid).Update("default_package", 0).Error; err != nil {
tx.Rollback()
return
}
}
// defines the new package as the default pkg
if err = tx.Model(&model.ResourceConfig{}).Where("id = ?", confID).Update("default_package", 1).Error; err != nil {
tx.Rollback()
return
}
tx.Commit()
return
}
// defines the version of the resource after the transaction commited
func (s *Service) defineVer(resID int, poolID int64) (version int, err error) {
var maxVer = model.Resource{}
if err = s.DB.Where("id < ?", resID).Where("pool_id = ?", poolID).Order("version desc").First(&maxVer).Error; err == gorm.ErrRecordNotFound {
err = nil
}
if err != nil {
log.Error("GenerateVer DBFind ResourceVer (%d) Error(%v)", resID, err)
return
}
version = int(maxVer.Version) + 1
if err = s.DB.Model(&model.Resource{}).Where("id = ?", resID).Update("version", version).Error; err != nil {
log.Error("GenerateVer DBUpdate ResourceVer (%d)-(%d) Error(%v)", resID, maxVer.Version+1, err)
return
}
return
}
// create diff packages for the latest version with the history versions
func (s *Service) createDiff(resID int) (err error) {
var (
prodVers, testVers []int64
currRes *model.Resource
)
// pick history versions to calculate diff
if prodVers, testVers, currRes, err = s.pickDiff(resID); err != nil {
return
}
// put diff packages in our DB
if err = s.putDiff(resID, mergeSlice(prodVers, testVers), currRes); err != nil {
return
}
return
}
// pick history versions to calculate diff
func (s *Service) pickDiff(resID int) (prodVers []int64, testVers []int64, currRes *model.Resource, err error) {
var (
VersProd = []*model.Resource{} // prod
VersTest = []*model.Resource{} // test
res = model.Resource{}
)
if err = s.DB.Where("id = ?", resID).First(&res).Error; err != nil {
log.Error("[createDiff]-[FindCurrentRes]-Error(%v)", err)
return
}
currRes = &res
poolID := currRes.PoolID
// calculate prod diffs
if err = s.DB.Joins("LEFT JOIN resource_config ON resource.id = resource_config.resource_id").
Where("resource.pool_id = ?", poolID).
Where("resource.id < ?", resID).
Where("resource_config.valid = ?", _valid).
Order("resource.version desc").Limit(s.c.Cfg.HistoryVer).
Select("resource.*").
Find(&VersProd).Error; err != nil {
log.Error("[createDiff]-[FindHistoryVers]-Error(%v)", err)
return
}
log.Info("Get Prod History Versions: %d", len(VersProd))
// calculate test diffs
if err = s.DB.Joins("LEFT JOIN resource_config ON resource.id = resource_config.resource_id").
Where("resource.pool_id = ?", poolID).
Where("resource.id < ?", resID).
Where("resource_config.valid != ?", _valid).
Where("resource_config.valid_test = ?", _valid).
Order("resource.version desc").Limit(s.c.Cfg.HistoryVer).
Select("resource.*").
Find(&VersTest).Error; err != nil {
log.Error("[createDiff]-[FindHistoryVers]-Error(%v)", err)
return
}
log.Info("Get Test History Versions: %d", len(VersTest))
// merge slices
prodVers = pickVersion(VersProd)
testVers = pickVersion(VersTest)
return
}
// put diff package in our DB
func (s *Service) putDiff(resID int, historyVers []int64, currRes *model.Resource) (err error) {
for _, v := range historyVers {
var diffPkg = &model.ResourceFile{
Name: fmt.Sprintf(_diffFormat, currRes.PoolID, v, currRes.Version),
FromVer: v,
ResourceID: resID,
FileType: _diffPackge,
}
if err = s.DB.Create(diffPkg).Error; err != nil {
log.Error("[createDiff]-[createDiffPkg]-Error(%v)", err)
return
}
}
log.Info("[createDiff]-Create (%d) Diff Pkg for ResID:(%d)", len(historyVers), resID)
return
}
// pick resource version
func pickVersion(s1 []*model.Resource) (res []int64) {
if len(s1) > 0 {
for _, v := range s1 {
res = append(res, v.Version)
}
}
return
}
// merge int64 slices
func mergeSlice(s1 []int64, s2 []int64) []int64 {
slice := make([]int64, len(s1)+len(s2))
copy(slice, s1)
copy(slice[len(s1):], s2)
return slice
}
// transform to Resource struct
func transResource(resMame string, id int64) *model.Resource {
return &model.Resource{
Name: resMame,
Version: 0, // will be updated after the transaction commits
PoolID: id,
}
}
// transform to File struct
func transFile(fInfo *model.FileInfo, resID int) *model.ResourceFile {
return &model.ResourceFile{
Name: fInfo.Name,
Type: fInfo.Type,
Md5: fInfo.Md5,
Size: int(fInfo.Size),
URL: fInfo.URL,
ResourceID: resID,
FileType: _fullPackage,
}
}
// transform to Config struct
func transConfig(resID int64, limitData *model.Limit) *model.ResourceConfig {
var cfg = &model.ResourceConfig{
ResourceID: resID,
Valid: 0,
IsDeleted: 0,
IsWifi: limitData.IsWifi,
}
if limitData.TimeRange != nil {
cfg.Etime = limitData.TimeRange.Etime
cfg.Stime = limitData.TimeRange.Stime
}
return cfg
}
// createLimit
func createLimit(configID int64, limitData *model.Limit) (res []*model.ResourceLimit) {
// create device limit
if len(limitData.Device) != 0 {
generateDevice(limitData.Device, &res, configID, deviceCol, _bk)
}
// create plat limit
if len(limitData.Plat) != 0 {
generateDevice(limitData.Plat, &res, configID, platCol, _wt)
}
// create mobi_app limit
if len(limitData.MobiApp) != 0 {
generateDevice(limitData.MobiApp, &res, configID, mobiAppCol, _wt)
}
// scale, level, arch
if len(limitData.Level) != 0 {
generateDevice(limitData.Level, &res, configID, levelCol, _wt)
}
if len(limitData.Scale) != 0 {
generateDevice(limitData.Scale, &res, configID, scaleCol, _wt)
}
if len(limitData.Arch) != 0 {
generateDevice(limitData.Arch, &res, configID, archCol, _wt)
}
// create build & sysver limit
if build := limitData.Build; build != nil {
generateBuild(build, configID, &res, buildCol)
}
if sysver := limitData.Sysver; sysver != nil {
generateBuild(sysver, configID, &res, sysverCol)
}
log.Info("createLimit creates %d limits", len(res))
return
}
// generate build-like limits data (json, range), insert them into the slice 'res'
func generateBuild(build *model.Build, configID int64, res *[]*model.ResourceLimit, column string) {
if build.GT != 0 {
*res = append(*res, transBuild(buildGtCdt, configID, build.GT, column))
}
if build.LT != 0 {
*res = append(*res, transBuild(buildLtCdt, configID, build.LT, column))
}
if build.GE != 0 {
*res = append(*res, transBuild(buildGeCdt, configID, build.GE, column))
}
if build.LE != 0 {
*res = append(*res, transBuild(buildLeCdt, configID, build.LE, column))
}
}
// generate device-like limits data([]string), insert them into the slice 'res'
func generateDevice(device []string, res *[]*model.ResourceLimit, configID int64, col string, cdt string) {
for _, v := range device {
*res = append(*res, &model.ResourceLimit{
ConfigID: configID,
Column: col,
Condition: cdt,
Value: v,
IsDeleted: 0,
})
}
}
func transBuild(condition string, configID int64, value int, column string) *model.ResourceLimit {
return &model.ResourceLimit{
ConfigID: configID,
Column: column,
Condition: condition,
Value: fmt.Sprintf("%d", value),
IsDeleted: 0,
}
}

View File

@@ -0,0 +1,14 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_DefaultPkg(t *testing.T) {
Convey("AddFile should return without err", t, WithService(func(svf *Service) {
err := svf.DefaultPkg(74, 3, 54)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,64 @@
package service
import (
"context"
"go-common/app/admin/main/appstatic/model"
"go-common/library/log"
)
// GendDiff picks the already generated diff packages
func (s *Service) GendDiff(resID int) (generated map[int64]int64, err error) {
generated = make(map[int64]int64)
genVers := []*model.Ver{}
if err = s.DB.Where("file_type IN (1,2)"). // 1=diff pkg, 2=diff pkg calculation in progress
Where("is_deleted = 0").Where("resource_id = ?", resID).Select("id, from_ver").Find(&genVers).Error; err != nil {
log.Error("generatedDiff Error %v", err)
return
}
for _, v := range genVers {
generated[v.FromVer] = v.ID
}
return
}
// Publish returns the second trigger result
func (s *Service) Publish(ctx context.Context, resID int) (data *model.PubResp, err error) {
var (
prodVers, testVers []int64 // the history versions that we should generate for
currRes *model.Resource
generated map[int64]int64
prodMore, testMore []int64
)
// pick history versions to calculate diff
if prodVers, testVers, currRes, err = s.pickDiff(resID); err != nil {
return
}
// pick already generated diff packages
if generated, err = s.GendDiff(resID); err != nil {
return
}
// filter already generated
for _, v := range prodVers {
if _, ok := generated[v]; !ok {
prodMore = append(prodMore, v)
}
}
for _, v := range testVers {
if _, ok := generated[v]; !ok {
testMore = append(testMore, v)
}
}
// put diff packages in our DB
if err = s.putDiff(resID, mergeSlice(prodMore, testMore), currRes); err != nil {
return
}
data = &model.PubResp{
CurrVer: currRes.Version,
DiffProd: prodMore,
DiffTest: testMore,
}
// add the publish resID into to push list
err = s.newPush(ctx, resID)
return
}

View File

@@ -0,0 +1,27 @@
package service
import (
"context"
"testing"
"fmt"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_GendDiff(t *testing.T) {
Convey("TestService_GendDiff", t, WithService(func(svf *Service) {
generated, err := svf.GendDiff(57)
So(err, ShouldBeNil)
So(len(generated), ShouldBeGreaterThan, 0)
fmt.Println(generated)
}))
}
func TestService_Publish(t *testing.T) {
Convey("TestService_Publish", t, WithService(func(svf *Service) {
data, err := svf.Publish(context.Background(), 57)
So(err, ShouldBeNil)
fmt.Println(data)
}))
}

View File

@@ -0,0 +1,15 @@
package service
import (
"context"
"go-common/library/log"
)
// add a new resource ID into the to push list
func (s *Service) newPush(ctx context.Context, resID int) (err error) {
if err = s.dao.ZAddPush(ctx, resID); err != nil {
log.Error("NewPush Redis for ResID: %d, Error: %v", resID, err)
}
return
}

View File

@@ -0,0 +1,61 @@
package service
import (
"context"
"sync"
"go-common/app/admin/main/appstatic/conf"
"go-common/app/admin/main/appstatic/dao"
"go-common/library/log"
"github.com/jinzhu/gorm"
)
// Service biz service def.
type Service struct {
c *conf.Config
dao *dao.Dao
DB *gorm.DB
waiter *sync.WaitGroup
daoClosed bool // logic close the dao's DB
MaxSize int64 // max size supported for the file to upload
}
// New new a Service and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
daoClosed: false,
waiter: new(sync.WaitGroup),
}
s.DB = s.dao.DB
if s.c.Cfg.Storage == "nas" {
s.MaxSize = 200 * 1024 * 1024 // 200M NAS
} else {
s.MaxSize = 20 * 1024 * 1024 // 20M BFS
}
return s
}
// Ping check dao health.
func (s *Service) Ping(c context.Context) (err error) {
return
}
// Wait wait all closed.
func (s *Service) Wait() {
if s.dao != nil {
s.daoClosed = true
log.Info("Dao is logically closed!")
}
log.Info("Wait waiter!")
s.waiter.Wait()
}
// Close close all dao.
func (s *Service) Close() {
log.Info("Close Dao physically!")
s.dao.Close()
log.Info("Service Closed!")
}

View File

@@ -0,0 +1,70 @@
package service
import (
"context"
"flag"
"fmt"
"path/filepath"
"testing"
"time"
"go-common/app/admin/main/appstatic/conf"
"go-common/app/admin/main/appstatic/model"
. "github.com/smartystreets/goconvey/convey"
)
var srv *Service
func init() {
dir, _ := filepath.Abs("../cmd/appstatic-admin-test.toml")
flag.Set("conf", dir)
conf.Init()
srv = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
f(srv)
}
}
func TestService_Ping(t *testing.T) {
Convey("Ping", t, WithService(func(svf *Service) {
svf.Ping(context.Background())
fmt.Println("service ping successfully")
}))
}
func TestService_Wait(t *testing.T) {
Convey("Ping", t, WithService(func(svf *Service) {
svf.Wait()
fmt.Println("service wait successfully")
}))
}
func TestService_Close(t *testing.T) {
Convey("Close", t, WithService(func(svf *Service) {
svf.Close()
fmt.Println("service closed successfully")
}))
}
func TestService_GenerateVer(t *testing.T) {
Convey("Generate Version", t, WithService(func(svf *Service) {
resID, version, err := svf.GenerateVer("myTestRes", &model.Limit{}, &model.FileInfo{
Name: "testResFile",
Size: 333,
Type: "application/zip",
Md5: "333",
URL: "www.bilibili.com",
}, &model.ResourcePool{
ID: 1,
Name: "resourcefile",
}, 1)
So(err, ShouldBeNil)
So(resID, ShouldBeGreaterThan, 0)
So(version, ShouldBeGreaterThan, 0)
}))
}

View File

@@ -0,0 +1,74 @@
package service
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"io"
"net/http"
"go-common/app/admin/main/appstatic/model"
"go-common/library/log"
)
// Upload can upload a file object: store the info in Redis, and transfer the file to Bfs
func (s *Service) Upload(c context.Context, fileName string, fileType string, timing int64, body []byte) (location string, err error) {
if s.c.Cfg.Storage == "nas" { // nas
if location, err = s.dao.UploadNas(c, fileName, body, s.c.Nas); err != nil {
log.Error("s.upload.UploadNas() error(%v)", err)
}
return
}
if location, err = s.dao.Upload(c, fileName, fileType, timing, body, s.c.Bfs); err != nil { // bfs
log.Error("s.upload.UploadBfs() error(%v)", err)
}
return
}
// AddFile inserts file info into DB and updates its resource version+1
func (s *Service) AddFile(c context.Context, file *model.ResourceFile, version int) (err error) {
if err = s.DB.Create(file).Error; err != nil {
log.Error("resSrv.DB.Create error(%v)", err)
return
}
// the resource containing the file updates its version
if err = s.DB.Model(&model.Resource{}).Where("id = ?", file.ResourceID).Update("version", version+1).Error; err != nil {
log.Error("resSrv.Update Version error(%v)", err)
return
}
return nil
}
// ParseFile analyses file info
func (s *Service) ParseFile(content []byte) (file *model.FileInfo, err error) {
fType := http.DetectContentType(content)
// file md5
md5hash := md5.New()
if _, err = io.Copy(md5hash, bytes.NewReader(content)); err != nil {
log.Error("resource uploadFile.Copy error(%v)", err)
return
}
md5 := md5hash.Sum(nil)
fMd5 := hex.EncodeToString(md5[:])
file = &model.FileInfo{
Md5: fMd5,
Type: fType,
Size: int64(len(content)),
}
return
}
// TypeCheck checks whether the file type is allowed
func (s *Service) TypeCheck(fType string) (canAllow bool) {
allowed := s.c.Cfg.Filetypes
if len(allowed) == 0 {
return true
}
for _, v := range allowed {
if v == fType {
return true
}
}
return false
}

View File

@@ -0,0 +1,78 @@
package service
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/admin/main/appstatic/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
var (
c = context.TODO()
now = time.Now().Unix()
)
func TestService_AddFile(t *testing.T) {
Convey("AddFile should return without err", t, WithService(func(svf *Service) {
// simulate resource & file data
res := &model.Resource{}
res.ID = 1
resFile := &model.ResourceFile{
ID: 0,
Name: "tName",
Type: "image/png",
Md5: "d41d8cd98f00b204e9800998ecf8427e",
Size: 20,
URL: "http://uat-i0.hdslb.com/bfs/app-static/timg.png",
ResourceID: 2,
Ctime: xtime.Time(now),
Mtime: xtime.Time(now),
}
// DB insert
err := svf.AddFile(c, resFile, 3)
// testing
So(err, ShouldBeNil)
}))
}
func TestService_Upload(t *testing.T) {
Convey("Upload should return without err", t, WithService(func(svf *Service) {
// simulate file content
str2 := "Testing File Content"
data2 := []byte(str2)
// upload to bfs
location, err := svf.Upload(c, "", "image/png", now, data2)
// testing
So(err, ShouldBeNil)
So(location, ShouldNotBeNil)
fmt.Println(location)
}))
}
func TestService_ParseFile(t *testing.T) {
Convey("ParseFile can get file info", t, WithService(func(svf *Service) {
// simulate file content
str2 := "Testing File Content"
data2 := []byte(str2)
// upload to bfs
fInfo, err := svf.ParseFile(data2)
// testing
So(err, ShouldBeNil)
So(fInfo.Size, ShouldBeGreaterThan, 0)
So(fInfo.Type, ShouldNotBeBlank)
So(fInfo.Md5, ShouldNotBeBlank)
}))
}
func TestService_TypeCheck(t *testing.T) {
Convey("File Type Check", t, WithService(func(svf *Service) {
So(svf.TypeCheck("application/zip"), ShouldBeTrue)
So(svf.TypeCheck("application/xml"), ShouldBeTrue)
So(svf.TypeCheck("application/json"), ShouldBeFalse)
}))
}