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

View File

@@ -0,0 +1,43 @@
### shorturl
### Version 1.4.0
> 1. 更改bm default server
### Version 1.3.9
> 1. metadata获取remoteIP
### Version 1.3.8
> 1. replace identify to verify
### Version 1.3.7
> 1.fix 普罗米修斯上报
### Version 1.3.6
> 1.fix err bug
### Version 1.3.5
> 1.支持mid和longurl搜索,升级BM
### Version 1.3.4
> 1.删除statsd
### Version 1.3.3
> 1.保证一个长链生成的短链接地址相同
### Version 1.3.2
> 1.缓存状态更新逻辑
### Version 1.3.1
> 1.缓存状态更新
### Version 1.3.0
> 1.增加短链的时候修改state
### Version 1.2.0
> 1.异步cache context
### Version 1.1.0
> 1.修复短连接合法性判断
### Version 1.0.0
> 1.短连接跳转服务初版

View File

@@ -0,0 +1,13 @@
# Owner
peiyifei
zhapuyu
zhoushuguang
# Author
peiyifei
haoguanwei
# Reviewer
peiyifei
haoguanwei

View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- haoguanwei
- peiyifei
- zhapuyu
- zhoushuguang
labels:
- interface
- interface/main/shorturl
- main
options:
no_parent_owners: true
reviewers:
- haoguanwei
- peiyifei

View File

@@ -0,0 +1,8 @@
#### ShortUrl 短链服务
##### 项目简介
> 1.提供短连接服务的接口录入、跳转
##### 编译环境
> golang v1.7.x 以上版本

View File

@@ -0,0 +1,42 @@
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 = ["shorturl-test.toml"],
importpath = "go-common/app/interface/main/shorturl/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/shorturl/conf:go_default_library",
"//app/interface/main/shorturl/http:go_default_library",
"//library/log:go_default_library",
"//library/net/trace: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,6 @@
#!/bin/bash
command -v goconvey >/dev/null 2>&1 || { echo >&2 "required goconvey but it's not installed."; echo "Aborting."; echo "Please run commond: go get github.com/smartystreets/goconvey"; exit 1; }
cd ../
goconvey -excludedDirs "vendor,node_modules,rpc" -packages 1

View File

@@ -0,0 +1,48 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/interface/main/shorturl/conf"
"go-common/app/interface/main/shorturl/http"
"go-common/library/log"
"go-common/library/net/trace"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
// init log
log.Init(conf.Conf.XLog)
defer log.Close()
log.Info("shorturl start")
// init trace
trace.Init(conf.Conf.Tracer)
defer trace.Close()
// service init
http.Init(conf.Conf)
// init pprof conf.Conf.Perf
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
// fmt.Println("7-1")
log.Info("shorturl get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT:
log.Info("shorturl exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,47 @@
version = "1.1.0"
user = "nobody"
pid = "/tmp/shorturl.pid"
dir = "./"
perf = "127.0.0.1:5101"
env = "dev"
[xlog]
dir = "/data/log/shorturl/"
[host]
default = "http://b23.tv/"
[mysql]
name = "[bilibili_shorturl]tcp@172.16.33.205:3308"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_shorturl?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
#dsn = "peiyifei:123456@tcp(0.0.0.0:3306)/pyfdb?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
[bm]
addr = "0.0.0.0:5102"
timeout = "1s"
[memcache]
name = "shorturl/memcache"
proto = "tcp"
addr = "172.16.33.54:11213"
active = 50
idle = 10
dialTimeout = "50ms"
readTimeout = "100ms"
writeTimeout = "100ms"
idleTimeout = "80s"
archiveExpire = "5m"
relateExpire = "20m"

View File

@@ -0,0 +1,39 @@
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/shorturl/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//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/http/blademaster/middleware/verify:go_default_library",
"//library/net/trace: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,104 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/trace"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
// Conf all configs
Conf = &Config{}
client *conf.Client
)
// Config all config
type Config struct {
// Env
Env string
// interface XLog
XLog *log.Config
// bm
BM *bm.ServerConfig
// tracer
Tracer *trace.Config
// db
Mysql *sql.Config
// mc
Memcache *Memcache
// host
Host *Host
// verify
Verify *verify.Config
}
// Host host config
type Host struct {
Default string
QuanZi string
}
// Memcache memcache config
type Memcache struct {
*memcache.Config
Expire xtime.Duration
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init config.
func Init() (err error) {
if confPath != "" {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
err = remote()
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
}

View File

@@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"shorturl_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/shorturl/conf:go_default_library",
"//app/interface/main/shorturl/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 = [
"dao.go",
"shorturl.go",
"shorturl_cache.go",
],
importpath = "go-common/app/interface/main/shorturl/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/shorturl/conf:go_default_library",
"//app/interface/main/shorturl/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/database/sql:go_default_library",
"//library/log: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,82 @@
package dao
import (
"context"
"runtime"
"time"
"go-common/app/interface/main/shorturl/conf"
"go-common/library/cache/memcache"
"go-common/library/database/sql"
"go-common/library/log"
)
// Dao struct conf
type Dao struct {
db *sql.DB
memchDB *memcache.Pool
mcExpire int32
cacheCh chan func()
}
// New new dao
func New(c *conf.Config) (d *Dao) {
d = &Dao{
db: sql.NewMySQL(c.Mysql),
memchDB: memcache.NewPool(c.Memcache.Config),
cacheCh: make(chan func(), 1024),
mcExpire: int32(time.Duration(c.Memcache.Expire) / time.Second),
}
for i := 0; i < runtime.NumCPU(); i++ {
go d.cacheproc()
}
return
}
func (d *Dao) cacheproc() {
for {
f, ok := <-d.cacheCh
if !ok {
return
}
f()
}
}
// AddCache add cache chan
func (d *Dao) AddCache(f func()) {
select {
case d.cacheCh <- f:
default:
log.Error("d.cacheCh is full")
}
}
// Close close db.
func (d *Dao) Close() {
if d.db != nil {
d.db.Close()
}
}
// Ping ping dao
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
log.Error("d.PingDB error(%v)", err)
}
if err = d.PingMC(c); err != nil {
log.Error("d.PingMC error(%v)", err)
}
return
}
// PingMC ping mc is ok.
func (d *Dao) PingMC(c context.Context) (err error) {
conn := d.memchDB.Get(c)
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcExpire}
if err = conn.Set(&item); err != nil {
log.Error("conn.Set(%s) error(%v)", item.Key, err)
}
conn.Close()
return
}

View File

@@ -0,0 +1,26 @@
package dao
import (
"flag"
"path/filepath"
"go-common/app/interface/main/shorturl/conf"
. "github.com/smartystreets/goconvey/convey"
)
var d *Dao
func init() {
dir, _ := filepath.Abs("../cmd/shorturl-test.toml")
flag.Set("conf", dir)
conf.Init()
d = New(conf.Conf)
}
func WithDao(f func(d *Dao)) func() {
return func() {
Reset(func() {})
f(d)
}
}

View File

@@ -0,0 +1,152 @@
package dao
import (
"context"
"database/sql"
"fmt"
"go-common/app/interface/main/shorturl/model"
"go-common/library/log"
)
const (
_prefix = "su_"
//short_url
_getSQL = "SELECT id,mid,short_url,long_url,state,ctime,mtime FROM short_url WHERE short_url=?"
_getAllSQL = "SELECT id,mid,short_url,long_url,state,ctime,mtime FROM short_url"
_getLimitSQL = "SELECT id,mid,short_url,long_url,state,ctime,mtime FROM short_url WHERE state=? " // TODO limit page
_shortInSQL = "INSERT IGNORE INTO short_url(mid,short_url,long_url,state,ctime) VALUES(?,?,?,?,?)"
_shortUpSQL = "UPDATE short_url SET mid=?,long_url=? WHERE id=?"
_shortCountSQL = "SELECT COUNT(*) FROM short_url WHERE state=?"
_shortByIDSQL = "SELECT id,mid,short_url,long_url,state,ctime,mtime FROM short_url WHERE id=?"
_updateStateSQL = "UPDATE short_url SET mid=?,state=? WHERE id=?"
)
// Short get short_url
func (d *Dao) Short(ctx context.Context, short string) (res *model.ShortUrl, err error) {
rows := d.db.QueryRow(ctx, _getSQL, short)
res = &model.ShortUrl{}
if err = rows.Scan(&res.ID, &res.Mid, &res.Short, &res.Long, &res.State, &res.CTime, &res.MTime); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
} else {
log.Error("rows.Scan error(%v)", err)
}
return
}
return
}
// ShortbyID get short_url by id
func (d *Dao) ShortbyID(ctx context.Context, id int64) (res *model.ShortUrl, err error) {
rows := d.db.QueryRow(ctx, _shortByIDSQL, id)
res = &model.ShortUrl{}
if err = rows.Scan(&res.ID, &res.Mid, &res.Short, &res.Long, &res.State, &res.CTime, &res.MTime); err != nil {
if err == sql.ErrNoRows {
res = nil
err = nil
} else {
log.Error("rows.Scan err (%v)", err)
}
return
}
res.FormatDate()
return
}
// AllShorts get all short_url
func (d *Dao) AllShorts(ctx context.Context) (res []*model.ShortUrl, err error) {
rows, err := d.db.Query(ctx, _getAllSQL)
if err != nil {
log.Error("query error (%v)", err)
return
}
defer rows.Close()
for rows.Next() {
su := &model.ShortUrl{}
if err = rows.Scan(&su.ID, &su.Mid, &su.Short, &su.Long, &su.State, &su.CTime, &su.MTime); err != nil {
log.Error("rows.Scan err (%v)", err)
return
}
su.FormatDate()
res = append(res, su)
}
return
}
// ShortCount get all short_url
func (d *Dao) ShortCount(ctx context.Context, mid int64, long string) (count int, err error) {
countSQL := _shortCountSQL
if mid > 0 {
countSQL = fmt.Sprintf("%s AND mid=%d", countSQL, mid)
}
if long != "" {
countSQL += " AND long_url='" + long + "'"
}
row := d.db.QueryRow(ctx, countSQL, model.StateNormal)
if err = row.Scan(&count); err != nil {
log.Error("row.Scan error(%v)", err)
return
}
return
}
// InShort add short_url
func (d *Dao) InShort(ctx context.Context, su *model.ShortUrl) (id int64, err error) {
res, err := d.db.Exec(ctx, _shortInSQL, su.Mid, su.Short, su.Long, su.State, su.CTime)
if err != nil {
log.Error("tx.Exec() error(%v)", err)
return
}
return res.LastInsertId()
}
// ShortUp add short_url
func (d *Dao) ShortUp(ctx context.Context, id, mid int64, long string) (rows int64, err error) {
res, err := d.db.Exec(ctx, _shortUpSQL, mid, long, id)
if err != nil {
log.Error("tx.Exec error(%v)", err)
return
}
return res.RowsAffected()
}
// UpdateState update state
func (d *Dao) UpdateState(ctx context.Context, id, mid int64, state int) (rows int64, err error) {
_, err = d.db.Exec(ctx, _updateStateSQL, mid, state, id)
if err != nil {
log.Error("d.db.Exec(%s) error(%v)", _updateStateSQL, err)
return
}
return
}
// ShortLimit get short_url list
func (d *Dao) ShortLimit(ctx context.Context, pn, ps int, mid int64, long string) (res []*model.ShortUrl, err error) {
limitSQL := _getLimitSQL
if mid > 0 {
limitSQL = fmt.Sprintf("%s AND mid=%d", limitSQL, mid)
}
if long != "" {
limitSQL += " AND long_url='" + long + "'"
}
limitSQL += " ORDER BY id DESC LIMIT ?,? "
rows, err := d.db.Query(ctx, limitSQL, model.StateNormal, pn, ps)
if err != nil {
log.Error("query error (%v)", err)
return
}
defer rows.Close()
res = []*model.ShortUrl{}
for rows.Next() {
su := &model.ShortUrl{}
if err = rows.Scan(&su.ID, &su.Mid, &su.Short, &su.Long, &su.State, &su.CTime, &su.MTime); err != nil {
log.Error("rows.Scan err (%v)", err)
return
}
su.FormatDate()
res = append(res, su)
}
return
}

View File

@@ -0,0 +1,62 @@
package dao
import (
"context"
"go-common/app/interface/main/shorturl/model"
"go-common/library/cache/memcache"
"go-common/library/log"
)
func cacheKey(short string) string {
return _prefix + short
}
// Cache get short url cache.
func (d *Dao) Cache(c context.Context, short string) (su *model.ShortUrl, err error) {
var (
key = cacheKey(short)
conn = d.memchDB.Get(c)
item *memcache.Item
)
defer conn.Close()
if item, err = conn.Get(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
} else {
log.Error("conn.Get(%s) error(%v)", key, err)
}
return
}
if err = conn.Scan(item, &su); err != nil {
log.Error("conn.Get(%s) error(%v)", key, err)
}
return
}
// SetEmptyCache set empty cache for a few time
func (d *Dao) SetEmptyCache(c context.Context, short string) (err error) {
var (
key = cacheKey(short)
conn = d.memchDB.Get(c)
)
defer conn.Close()
if err = conn.Set(&memcache.Item{Key: key, Object: &model.ShortUrl{}, Flags: memcache.FlagJSON, Expiration: 300}); err != nil {
log.Error("conn.Set error(%v)", err)
}
return
}
// SetCache save model.ShortUrl to memcache
func (d *Dao) SetCache(c context.Context, su *model.ShortUrl) (err error) {
var (
key = cacheKey(su.Short)
conn = d.memchDB.Get(c)
)
defer conn.Close()
if err = conn.Set(&memcache.Item{Key: key, Object: su, Flags: memcache.FlagJSON, Expiration: 0}); err != nil {
log.Error("conn.Set error(%v)", err)
return
}
return
}

View File

@@ -0,0 +1,72 @@
package dao
import (
"context"
. "github.com/smartystreets/goconvey/convey"
model "go-common/app/interface/main/shorturl/model"
xtime "go-common/library/time"
"testing"
"time"
)
func TestDao_Short(t *testing.T) {
Convey("Short", t, WithDao(func(d *Dao) {
_, err := d.Short(context.TODO(), "http://b23.tv/EbUzmu")
So(err, ShouldBeNil)
}))
}
func TestDao_ShortbyID(t *testing.T) {
Convey("ShortbyID", t, WithDao(func(d *Dao) {
_, err := d.ShortbyID(context.TODO(), 1)
So(err, ShouldBeNil)
}))
}
func TestDao_AllShorts(t *testing.T) {
Convey("AllShorts", t, WithDao(func(d *Dao) {
_, err := d.AllShorts(context.TODO())
So(err, ShouldBeNil)
}))
}
func TestDao_ShortCount(t *testing.T) {
Convey("ShortCount", t, WithDao(func(d *Dao) {
_, err := d.ShortCount(context.TODO(), 1, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}
func TestDao_InShort(t *testing.T) {
Convey("InShort", t, WithDao(func(d *Dao) {
su := &model.ShortUrl{
Long: "http://www.baidu.com",
Mid: 279,
State: model.StateNormal,
CTime: xtime.Time(time.Now().Unix()),
}
_, err := d.InShort(context.TODO(), su)
So(err, ShouldBeNil)
}))
}
func TestDao_ShortUp(t *testing.T) {
Convey("ShortUp", t, WithDao(func(d *Dao) {
_, err := d.ShortUp(context.TODO(), 1, 20, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}
func TestDao_UpdateState(t *testing.T) {
Convey("UpdateState", t, WithDao(func(d *Dao) {
_, err := d.UpdateState(context.TODO(), 1, 279, 0)
So(err, ShouldBeNil)
}))
}
func TestDao_ShortLimit(t *testing.T) {
Convey("ShortLimit", t, WithDao(func(d *Dao) {
_, err := d.ShortLimit(context.TODO(), 1, 20, 279, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"http.go",
"logger.go",
"shorturl.go",
],
importpath = "go-common/app/interface/main/shorturl/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/shorturl/conf:go_default_library",
"//app/interface/main/shorturl/model:go_default_library",
"//app/interface/main/shorturl/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/verify:go_default_library",
"//library/net/metadata:go_default_library",
"//library/stat:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,48 @@
package http
import (
"go-common/app/interface/main/shorturl/conf"
"go-common/app/interface/main/shorturl/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
// depend service
idfSvc *verify.Verify
suSvr *service.Service
)
// initService .
func initService(c *conf.Config) {
suSvr = service.New(c)
idfSvc = verify.New(c.Verify)
}
// Init init http
func Init(c *conf.Config) {
initService(c)
// init internal router
engineInner := bm.NewServer(c.BM)
engineInner.Use(bm.Recovery(), bm.Trace(), bm.CSRF(), bm.Mobile(), logger())
innerRouter(engineInner)
if err := engineInner.Start(); err != nil {
log.Error("engineInner.Start error(%v)", err)
panic(err)
}
}
// innerRouter .
func innerRouter(e *bm.Engine) {
e.GET("/monitor/ping", ping)
e.GET("/", jump)
b := e.Group("/x/internal/shorturl")
{
b.POST("/add", idfSvc.Verify, add)
b.POST("/update", idfSvc.Verify, shortUpdate)
b.GET("/detail", idfSvc.Verify, shortByID)
b.GET("/list", idfSvc.Verify, shortAll)
b.POST("/del", idfSvc.Verify, shortDel)
}
}

View File

@@ -0,0 +1,68 @@
package http
import (
"fmt"
"strconv"
"strings"
"time"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"go-common/library/stat"
)
// logger is logger middleware
func logger() bm.HandlerFunc {
const noUser = "no_user"
return func(c *bm.Context) {
now := time.Now()
ip := metadata.String(c, metadata.RemoteIP)
req := c.Request
path := req.URL.Path
params := req.Form
c.Next()
mid, _ := c.Get("mid")
userI, _ := c.Get("user")
err := c.Error
cerr := ecode.Cause(err)
dt := time.Since(now)
// user
user, ok := userI.(string)
if !ok || user == "" {
user = noUser
}
realPath := ""
if strings.HasPrefix(path, "/x/internal/shorturl") {
realPath = path[1:]
} else {
realPath = "shorturl"
}
stat.HTTPServer.Incr(user, realPath, strconv.FormatInt(int64(cerr.Code()), 10))
stat.HTTPServer.Timing(user, int64(dt/time.Millisecond), realPath)
lf := log.Infov
errmsg := ""
if err != nil {
errmsg = err.Error()
lf = log.Errorv
}
lf(c,
log.KV("method", req.Method),
log.KV("mid", mid),
log.KV("ip", ip),
log.KV("user", user),
log.KV("path", path),
log.KV("params", params.Encode()),
log.KV("ret", cerr.Code()),
log.KV("msg", cerr.Message()),
log.KV("stack", fmt.Sprintf("%+v", err)),
log.KV("err", errmsg),
)
}
}

View File

@@ -0,0 +1,163 @@
package http
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"go-common/app/interface/main/shorturl/conf"
"go-common/app/interface/main/shorturl/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// add short url from long url.
func add(c *bm.Context) {
param := &model.Param{}
if err := c.Bind(param); err != nil {
return
}
// check args
uri := strings.TrimSpace(param.Uri)
if uri == "" {
log.Error("add short url args empty long(%s)", uri)
c.JSON(nil, ecode.RequestErr)
return
}
short, err := suSvr.Add(c, param.Mid, uri)
if err != nil {
log.Error("suSvr.Add error(%v)", err)
c.JSON(nil, ecode.RequestErr)
return
}
data := map[string]string{
"url": conf.Conf.Host.Default + short,
}
c.JSON(data, nil)
}
// jump redirect short url to long url.
func jump(c *bm.Context) {
// check path
if len(c.Request.URL.Path) == 0 || c.Request.URL.Path == "/" || c.Request.URL.Path == "/favicon.ico" || strings.HasPrefix(c.Request.URL.Path, "/x/") {
c.JSON(nil, ecode.NothingFound)
return
}
su, err := suSvr.ShortCache(c, c.Request.URL.Path[1:])
if err != nil {
log.Error("suSvr.Get url(%v) error(%v)", c.Request.URL.Path[1:], err)
c.JSON(nil, err)
return
}
if su == nil || su.Long == "" || su.State == model.StateDelted {
c.JSON(nil, ecode.NothingFound)
return
}
if !strings.HasPrefix(su.Long, "http://") && !strings.HasPrefix(su.Long, "https://") {
su.Long = "http://" + su.Long
return
}
// redirect
http.Redirect(c.Writer, c.Request, su.Long, http.StatusFound)
}
// shortAll get shorturl list
func shortAll(c *bm.Context) {
param := &model.Param{}
if err := c.Bind(param); err != nil {
return
}
pn, err := strconv.Atoi(param.Pn)
if err != nil || pn < 1 {
pn = 1
}
ps, err := strconv.Atoi(param.Ps)
if err != nil || ps > 20 || ps <= 0 {
ps = 20
}
long := strings.TrimSpace(param.Uri)
data, err := suSvr.ShortLimit(c, pn, ps, param.Mid, long)
if err != nil {
log.Error("suSvr.ShortLimit error(%v)", err)
c.JSON(nil, ecode.RequestErr)
return
}
for _, su := range data {
su.Short = conf.Conf.Host.Default + su.Short
}
c.JSONMap(map[string]interface{}{
"data": data,
"size": 2233,
}, nil)
}
// shortState set state
func shortUpdate(c *bm.Context) {
param := &model.Param{}
if err := c.Bind(param); err != nil {
return
}
uri := strings.TrimSpace(param.Uri)
if uri == "" {
log.Error("add short url args empty long(%s)", param.Uri)
c.JSON(nil, ecode.RequestErr)
return
}
if param.Mid <= 0 {
log.Error("mid less than 0 error(%v)", param.Mid)
c.JSON(nil, ecode.RequestErr)
return
}
err := suSvr.ShortUpdate(context.TODO(), param.ID, param.Mid, uri)
if err != nil {
log.Error("suSvr.ShortUpdate error(%v)", err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
// shortState set state
func shortDel(c *bm.Context) {
param := &model.Param{}
if err := c.Bind(param); err != nil {
return
}
if param.Mid <= 0 {
log.Error("mid less than 0 error(%v)", param.Mid)
c.JSON(nil, ecode.RequestErr)
return
}
err := suSvr.ShortDel(c, param.ID, param.Mid, time.Now())
if err != nil {
log.Error("suSvr.ShortState error(%v)", err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
// shortById by id
func shortByID(c *bm.Context) {
param := &model.Param{}
if err := c.Bind(param); err != nil {
return
}
data, err := suSvr.ShortByID(c, param.ID)
if err != nil {
log.Error("suSvr.ShortState error(%v)", err)
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
func ping(c *bm.Context) {
if err := suSvr.Ping(c); err != nil {
c.AbortWithStatus(http.StatusServiceUnavailable)
log.Error("shorturl service ping error(%v)", err)
}
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"calc.go",
"const.go",
"shorturl.go",
],
importpath = "go-common/app/interface/main/shorturl/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,49 @@
package model
import (
"crypto/md5"
"encoding/hex"
"strconv"
)
// TODO move to model
const (
shortUrlLength = 6
)
var (
chars = [62]string{
"a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x",
"y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D",
"E", "F", "G", "H", "I", "J", "K", "L",
"M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z",
}
)
// generate short url from long url
func Generate(long string) [4]string {
var resUrl [4]string
h := md5.New()
h.Write([]byte(long))
hexstr := hex.EncodeToString(h.Sum(nil))
for i := 0; i < 4; i++ {
start := i * 8
end := start + 8
s := hexstr[start:end]
hexInt, _ := strconv.ParseInt(s, 16, 64)
hexInt = 0x3FFFFFFF & hexInt
var out string = ""
for n := 0; n < shortUrlLength; n++ {
index := 0x0000003D & hexInt
out += chars[index]
hexInt = hexInt >> 5
}
resUrl[i] = out
}
return resUrl
}

View File

@@ -0,0 +1,10 @@
package model
const (
// EnvPro is pro.
EnvPro = "pro"
// EnvTest is env.
EnvTest = "test"
// EnvDev is env.
EnvDev = "dev"
)

View File

@@ -0,0 +1,31 @@
package model
import xtime "go-common/library/time"
const (
StateNormal = 0
StateDelted = 1
)
type ShortUrl struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
Short string `json:"short"`
Long string `json:"long"`
State int8 `json:"state"`
CTime xtime.Time `json:"-"`
MTime xtime.Time `json:"-"`
CreateTime string `json:"ctime"`
}
type Param struct {
ID int64 `form:"id"`
Mid int64 `form:"mid"`
Uri string `form:"url"`
Pn string `form:"pn"`
Ps string `form:"ps"`
}
func (s *ShortUrl) FormatDate() {
s.CreateTime = s.CTime.Time().Format("2006-01-02 15:04:05")
}

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",
"short_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/shorturl/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"service.go",
"short.go",
],
importpath = "go-common/app/interface/main/shorturl/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/shorturl/conf:go_default_library",
"//app/interface/main/shorturl/dao:go_default_library",
"//app/interface/main/shorturl/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//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,29 @@
package service
import (
"context"
"go-common/app/interface/main/shorturl/conf"
shortdao "go-common/app/interface/main/shorturl/dao"
"go-common/library/log"
)
// Service service struct
type Service struct {
shortd *shortdao.Dao
}
// New new service
func New(c *conf.Config) (s *Service) {
s = &Service{
shortd: shortdao.New(c),
}
return
}
// Ping ping service.
func (s *Service) Ping(c context.Context) (err error) {
if err = s.shortd.Ping(c); err != nil {
log.Error("s.dao.Ping error(%v)", err)
}
return
}

View File

@@ -0,0 +1,28 @@
package service
import (
"flag"
"path/filepath"
"time"
"go-common/app/interface/main/shorturl/conf"
)
var (
s *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/shorturl-test.toml")
flag.Set("conf", dir)
conf.Init()
s = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
f(s)
}
}

View File

@@ -0,0 +1,145 @@
package service
import (
"context"
"time"
"go-common/app/interface/main/shorturl/model"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
// ShortCache get short by cache
func (s *Service) ShortCache(c context.Context, short string) (su *model.ShortUrl, err error) {
if su, err = s.shortd.Cache(c, short); err != nil {
log.Error("s.shrtd.Cache(%s) error(%v)", short, err)
return
}
if su != nil {
return
}
if su, err = s.shortd.Short(c, short); err != nil {
log.Error("s.shortd.Short(%s) error(%v)", short, err)
return
}
// add cache
if su == nil {
s.shortd.AddCache(func() {
s.shortd.SetEmptyCache(context.TODO(), short)
})
return
}
s.shortd.AddCache(func() {
s.shortd.SetCache(context.TODO(), su)
})
return
}
// ShortByID model.ShortUrl by short id
func (s *Service) ShortByID(c context.Context, id int64) (su *model.ShortUrl, err error) {
su, err = s.shortd.ShortbyID(c, id)
if err != nil || su == nil {
log.Error("ShortByID.Get(%d) error(%v)", id, err)
err = ecode.ShortURLNotFound
return
}
return
}
// Add new url to db
func (s *Service) Add(c context.Context, mid int64, long string) (short string, err error) {
su := &model.ShortUrl{
Long: long,
Mid: mid,
State: model.StateNormal,
CTime: xtime.Time(time.Now().Unix()),
}
ss := model.Generate(long) // detemine no repeat
for _, str := range ss {
su.Short = str
if su.ID, err = s.shortd.InShort(c, su); err != nil {
log.Error("s.shortd.InShort error(%v)", err)
err = ecode.ServerErr
return
}
if su.ID == 0 {
var shortRes *model.ShortUrl
// already exist
if shortRes, err = s.shortd.Short(c, str); err == nil && shortRes != nil {
if shortRes.Long == long {
if _, err = s.shortd.UpdateState(c, shortRes.ID, mid, model.StateNormal); err != nil {
break
}
short = shortRes.Short
break
} else {
continue
}
}
} else {
short = str
break
}
}
if short == "" {
err = ecode.ShortURLNotFound
return
}
s.shortd.AddCache(func() {
var su, err = s.shortd.Short(context.TODO(), short)
if err == nil && su != nil {
s.shortd.SetCache(context.TODO(), su)
}
})
return
}
// ShortUpdate model.ShortUpdate
func (s *Service) ShortUpdate(c context.Context, id, mid int64, long string) (err error) {
if _, err = s.shortd.ShortUp(c, id, mid, long); err != nil {
log.Error("s.shortd.ShortUp error(%v)", err)
return
}
s.shortd.AddCache(func() {
var short *model.ShortUrl
if short, err = s.shortd.ShortbyID(context.TODO(), id); err != nil {
log.Error("s.shortd.ShortByID(%d) error(%v)", id, err)
return
}
s.shortd.SetCache(context.TODO(), short)
})
return
}
// ShortDel model.ShortDel
func (s *Service) ShortDel(c context.Context, id, mid int64, now time.Time) (err error) {
if _, err = s.shortd.UpdateState(c, id, mid, model.StateDelted); err != nil {
log.Error("s.shortd.ShortDel error(%v)", err)
}
s.shortd.AddCache(func() {
var short *model.ShortUrl
if short, err = s.shortd.ShortbyID(context.TODO(), id); err != nil {
log.Error("s.shortd.ShortByID(%d) error(%v)", id, err)
return
}
s.shortd.SetCache(context.TODO(), short)
})
return
}
// ShortCount model.ShortCount count
func (s *Service) ShortCount(c context.Context, mid int64, long string) (count int, err error) {
if count, err = s.shortd.ShortCount(c, mid, long); err != nil {
log.Error("s.shortd.ShortState error(%v)", err)
}
return
}
// ShortLimit get short_url list
func (s *Service) ShortLimit(c context.Context, pn, ps int, mid int64, long string) (res []*model.ShortUrl, err error) {
if res, err = s.shortd.ShortLimit(c, (pn-1)*ps, ps, mid, long); err != nil {
log.Error("s.shortd.ShortLimit error(%v)", err)
}
return
}

View File

@@ -0,0 +1,58 @@
package service
import (
"testing"
"time"
"context"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_ShortCache(t *testing.T) {
Convey("ShortCache", t, WithService(func(s *Service) {
_, err := s.ShortCache(context.TODO(), "http://b23.tv/EbUzmu")
So(err, ShouldBeNil)
}))
}
func TestService_ShortByID(t *testing.T) {
Convey("ShortByID", t, WithService(func(s *Service) {
_, err := s.ShortByID(context.TODO(), 1)
So(err, ShouldBeNil)
}))
}
func TestService_Add(t *testing.T) {
Convey("Add", t, WithService(func(s *Service) {
_, err := s.Add(context.TODO(), 279, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}
func TestService_ShortUpdate(t *testing.T) {
Convey("ShortUpdate", t, WithService(func(s *Service) {
err := s.ShortUpdate(context.TODO(), 1, 279, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}
func TestService_ShortDel(t *testing.T) {
Convey("ShortDel", t, WithService(func(s *Service) {
err := s.ShortDel(context.TODO(), 1, 279, time.Now())
So(err, ShouldBeNil)
}))
}
func TestService_ShortCount(t *testing.T) {
Convey("ShortCount", t, WithService(func(s *Service) {
_, err := s.ShortCount(context.TODO(), 279, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}
func TestService_ShortLimit(t *testing.T) {
Convey("ShortLimit", t, WithService(func(s *Service) {
_, err := s.ShortLimit(context.TODO(), 1, 20, 279, "http://www.baidu.com")
So(err, ShouldBeNil)
}))
}