init
This commit is contained in:
22
configs/config.yml
Normal file
22
configs/config.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
server:
|
||||||
|
addr: 0.0.0.0:2830
|
||||||
|
timeout: 1s
|
||||||
|
data:
|
||||||
|
database:
|
||||||
|
driver: mysql
|
||||||
|
source: root:root@tcp(127.0.0.1:3306)/test
|
||||||
|
redis:
|
||||||
|
addr: 127.0.0.1:6379
|
||||||
|
read_timeout: 0.2s
|
||||||
|
write_timeout: 0.2s
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
output: file
|
||||||
|
development: true
|
||||||
|
rotate:
|
||||||
|
filename: app.log
|
||||||
|
maxsize: 10
|
||||||
|
maxage: 7
|
||||||
|
maxbackups:
|
||||||
|
localtime:
|
||||||
|
compress:
|
||||||
58
main.go
Normal file
58
main.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"tinyurl/pkg/config"
|
||||||
|
"tinyurl/pkg/db"
|
||||||
|
"tinyurl/pkg/log"
|
||||||
|
"tinyurl/route"
|
||||||
|
|
||||||
|
"github.com/flamego/flamego"
|
||||||
|
"github.com/ory/graceful"
|
||||||
|
// "github.com/justinas/alice"
|
||||||
|
)
|
||||||
|
|
||||||
|
// go build -ldflags "-X main.Version=x.y.z"
|
||||||
|
var (
|
||||||
|
// Name is the name of the compiled software.
|
||||||
|
Name string
|
||||||
|
// Version is the version of the compiled software.
|
||||||
|
Version string
|
||||||
|
// flagconf is the config flag.
|
||||||
|
flagconf string
|
||||||
|
|
||||||
|
id, _ = os.Hostname()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&flagconf, "conf", "./configs/config.yml", "config path, eg: -conf config.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.Parse(flagconf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
logger, err := log.New(cfg.Log)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
f := flamego.Classic()
|
||||||
|
f.Map(cfg, logger, db.DB)
|
||||||
|
// f.Use(auth.Basic("admin", "1111"))
|
||||||
|
route.Route(f)
|
||||||
|
server := graceful.WithDefaults(
|
||||||
|
&http.Server{
|
||||||
|
Addr: "0.0.0.0:2830",
|
||||||
|
Handler: f,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := graceful.Graceful(server.ListenAndServe, server.Shutdown); err != nil {
|
||||||
|
fmt.Println("main: Failed to gracefully shutdown")
|
||||||
|
}
|
||||||
|
fmt.Println("main: Server was shutdown gracefully")
|
||||||
|
}
|
||||||
40
pkg/base62/base62.go
Normal file
40
pkg/base62/base62.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package base62
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BASE62 = "0123456789abcdefghijklmnoporstuvwxyzABCDEFGHIJKLMNOPORSTUVWXYZ"
|
||||||
|
LENGTH = 62
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ma = map[string]int{
|
||||||
|
"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15, "g": 16, "h": 17, "i": 18, "j": 19, "k": 20, "l": 21, "m": 22, "n": 23, "o": 24, "p": 25, "q": 26, "r": 27, "s": 28, "t": 29, "u": 30, "v": 31, "w": 32, "x": 33, "y": 34, "z": 35, "A": 36, "B": 37, "C": 38, "D": 39, "E": 40, "F": 41, "G": 42, "H": 43, "I": 44, "J": 45, "K": 46, "L": 47, "M": 48, "N": 49, "O": 50, "P": 51, "Q": 52, "R": 53, "S": 54, "T": 55, "U": 56, "V": 57, "W": 58, "X": 59, "Y": 60, "Z": 61,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func EncodeBase62(v int) string {
|
||||||
|
if v == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
var result []byte
|
||||||
|
for v > 0 {
|
||||||
|
quotient := v / LENGTH
|
||||||
|
residue := v % LENGTH
|
||||||
|
result = append(result, BASE62[residue])
|
||||||
|
v = quotient
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeBase62(v string) int {
|
||||||
|
strings.TrimSpace(v)
|
||||||
|
num := 0
|
||||||
|
for k, vi := range v {
|
||||||
|
num += ma[string(vi)] * int(math.Pow(LENGTH, float64(k)))
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
10
pkg/base62/tinyurl.go
Normal file
10
pkg/base62/tinyurl.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package base62
|
||||||
|
|
||||||
|
import (
|
||||||
|
mh "github.com/spaolacci/murmur3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TinyUrl(in string) string {
|
||||||
|
return EncodeBase62(int(mh.Sum32([]byte(in))))
|
||||||
|
|
||||||
|
}
|
||||||
72
pkg/config/config.go
Normal file
72
pkg/config/config.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server Server `yaml:"server"`
|
||||||
|
Data Data `yaml:"data"`
|
||||||
|
Log Log `yaml:"log"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Addr string `yaml:"addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Log struct {
|
||||||
|
Level string `yaml:"level"`
|
||||||
|
Output string `yaml:"output"`
|
||||||
|
Development bool `yaml:"development"`
|
||||||
|
Rotate Rotate `yaml:"rotate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rotate struct {
|
||||||
|
Filename string `yaml:"filename"`
|
||||||
|
MaxSize int `yaml:"maxsize"`
|
||||||
|
MaxAge int `yaml:"maxage"`
|
||||||
|
MaxBackups int `yaml:"maxbackups"`
|
||||||
|
LocalTime bool `yaml:"localtime"`
|
||||||
|
Compress bool `yaml:"compress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
DB DB `yaml:"database"`
|
||||||
|
Redis `yaml:"redis"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
User string `yaml:"user"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Database string `yaml:"database"`
|
||||||
|
ConnTimeout time.Duration `yaml:"conn_timeout"`
|
||||||
|
ReadTimeout time.Duration `yaml:"read_timeout"`
|
||||||
|
WriteTimeout time.Duration `yaml:"write_timeout"`
|
||||||
|
ConnMaxIdleTime time.Duration `yaml:"conn_max_dile_time"`
|
||||||
|
ConnMaxLifeTime time.Duration `yaml:"conn_max_life_time"`
|
||||||
|
MaxIdleConns int `yaml:"max_idle_conns"`
|
||||||
|
MaxOpenConns int `yaml:"max_open_conns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Redis struct {
|
||||||
|
Addr string `yaml:"addr"`
|
||||||
|
ReadTimeout string `yaml:"read_timeout"`
|
||||||
|
WriteTimeout string `yaml:"write_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(file string) (*Config, error) {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg := &Config{}
|
||||||
|
if err = yaml.Unmarshal(data, cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
37
pkg/db/db.go
Normal file
37
pkg/db/db.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DB *gorm.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
type TinyUrl struct {
|
||||||
|
ID int64
|
||||||
|
OriginUrl string
|
||||||
|
TinyUrl string
|
||||||
|
CreatTime time.Time
|
||||||
|
ExpireTime time.Time
|
||||||
|
Counter float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLDetail struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
ExpirationInMinutes time.Duration `json:"expiration_in_minutes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB() {
|
||||||
|
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
|
||||||
|
dsn := "tinyurl:tinyurl@tcp(42.192.36.14:3306)/tinyurl?charset=utf8mb4&parseTime=True&loc=Local"
|
||||||
|
var err error
|
||||||
|
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
152
pkg/log/log.go
Normal file
152
pkg/log/log.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"tinyurl/pkg/config"
|
||||||
|
"tinyurl/pkg/trace"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
StdOutput = "std"
|
||||||
|
FileOutput = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelMap = map[string]zapcore.Level{
|
||||||
|
"debug": zapcore.DebugLevel,
|
||||||
|
"info": zapcore.InfoLevel,
|
||||||
|
"warn": zapcore.WarnLevel,
|
||||||
|
"error": zapcore.ErrorLevel,
|
||||||
|
"dpanic": zapcore.DPanicLevel,
|
||||||
|
"panic": zapcore.PanicLevel,
|
||||||
|
"fatal": zapcore.FatalLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = map[color.Attribute]*color.Color{
|
||||||
|
color.FgGreen: color.New(color.FgGreen),
|
||||||
|
color.FgHiWhite: color.New(color.FgHiWhite),
|
||||||
|
color.FgYellow: color.New(color.FgYellow),
|
||||||
|
color.FgRed: color.New(color.FgRed, color.Underline),
|
||||||
|
color.FgHiRed: color.New(color.FgHiRed, color.Underline, color.Bold),
|
||||||
|
color.FgBlue: color.New(color.FgBlue),
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c config.Log) (*Logger, error) {
|
||||||
|
level := getLevel(c.Level)
|
||||||
|
var writer zapcore.WriteSyncer
|
||||||
|
if c.Output == StdOutput {
|
||||||
|
writer = getStdWriter()
|
||||||
|
} else if c.Output == FileOutput {
|
||||||
|
writer = getFileWriter(c.Rotate)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("output must in [std, file]")
|
||||||
|
}
|
||||||
|
encoder := getJSONEncoder()
|
||||||
|
var opts []zap.Option
|
||||||
|
if c.Development {
|
||||||
|
encoder = getConsoleEncoder()
|
||||||
|
opts = append(opts, zap.AddCaller(), zap.AddCallerSkip(1), zap.Development())
|
||||||
|
}
|
||||||
|
core := zapcore.NewCore(encoder, writer, level)
|
||||||
|
return &Logger{zap.New(core, opts...)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLevel(level string) zapcore.Level {
|
||||||
|
if l, ok := levelMap[level]; ok {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
return zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJSONEncoder() zapcore.Encoder {
|
||||||
|
conf := zap.NewProductionEncoderConfig()
|
||||||
|
conf.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
|
||||||
|
conf.EncodeCaller = zapcore.FullCallerEncoder
|
||||||
|
return zapcore.NewJSONEncoder(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConsoleEncoder() zapcore.Encoder {
|
||||||
|
conf := zap.NewProductionEncoderConfig()
|
||||||
|
conf.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
|
||||||
|
conf.EncodeCaller = zapcore.FullCallerEncoder
|
||||||
|
return zapcore.NewConsoleEncoder(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileWriter(c config.Rotate) zapcore.WriteSyncer {
|
||||||
|
logger := &lumberjack.Logger{
|
||||||
|
Filename: c.Filename,
|
||||||
|
MaxSize: c.MaxSize,
|
||||||
|
MaxAge: c.MaxAge,
|
||||||
|
MaxBackups: c.MaxBackups,
|
||||||
|
LocalTime: c.LocalTime,
|
||||||
|
Compress: c.Compress,
|
||||||
|
}
|
||||||
|
return zapcore.AddSync(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStdWriter() zapcore.WriteSyncer {
|
||||||
|
return os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Sync() error {
|
||||||
|
return l.logger.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgBlue].Sprintf("%s", msg)
|
||||||
|
l.logger.Debug(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgGreen].Sprintf("%s", msg)
|
||||||
|
l.logger.Info(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warn(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgYellow].Sprintf("%s", msg)
|
||||||
|
l.logger.Warn(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgRed].Sprintf("%s", msg)
|
||||||
|
l.logger.Error(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) DPanic(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgHiRed].Sprintf("%s", msg)
|
||||||
|
l.logger.DPanic(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Panic(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgHiRed].Sprintf("%s", msg)
|
||||||
|
l.logger.Panic(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Fatal(ctx context.Context, msg string, fields ...zap.Field) {
|
||||||
|
fields = append(fields, getTrace(ctx))
|
||||||
|
msg = colors[color.FgHiRed].Sprintf("%s", msg)
|
||||||
|
l.logger.Fatal(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTrace(ctx context.Context) zapcore.Field {
|
||||||
|
if ctx == nil {
|
||||||
|
return zap.Skip()
|
||||||
|
}
|
||||||
|
return zap.String("trace", trace.Trace(ctx))
|
||||||
|
}
|
||||||
32
pkg/trace/trace.go
Normal file
32
pkg/trace/trace.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/flamego/flamego"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
var activeSpanKey = contextKey{}
|
||||||
|
|
||||||
|
func Context(ctx context.Context) context.Context {
|
||||||
|
x := uuid.New().String()
|
||||||
|
return context.WithValue(ctx, activeSpanKey, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Trace(ctx context.Context) string {
|
||||||
|
traceValue := ctx.Value(activeSpanKey)
|
||||||
|
if trace, ok := traceValue.(string); ok {
|
||||||
|
return trace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tracer() flamego.ContextInvoker {
|
||||||
|
return func(ctx flamego.Context) {
|
||||||
|
r := ctx.Request().Clone(Context(context.Background()))
|
||||||
|
ctx.Map(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
route/route.go
Normal file
20
route/route.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tinyurl/route/tinyurl"
|
||||||
|
|
||||||
|
"github.com/flamego/auth"
|
||||||
|
"github.com/flamego/flamego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Route(f *flamego.Flame) {
|
||||||
|
f.Get("/version", auth.Basic("admin", "admin"), func() string { return "1.1.1" })
|
||||||
|
f.Get("/{url: **, capture: 10}", tinyurl.TinyurlHandler)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func tinyauth(c flamego.Context) {
|
||||||
|
if c.Query("token") == "123" {
|
||||||
|
c.Redirect("/signup")
|
||||||
|
}
|
||||||
|
}
|
||||||
22
route/tinyurl/tinyurl.go
Normal file
22
route/tinyurl/tinyurl.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package tinyurl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"tinyurl/pkg/base62"
|
||||||
|
|
||||||
|
"github.com/flamego/flamego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TinyurlHandler(c flamego.Context) string {
|
||||||
|
var originurl string
|
||||||
|
if len(c.Request().URL.RawQuery) > 1 {
|
||||||
|
originurl = c.Param("url") + "?" + c.Request().URL.RawQuery
|
||||||
|
} else {
|
||||||
|
originurl = c.Param("url")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"TinyUrl , %s to %s",
|
||||||
|
originurl,
|
||||||
|
base62.TinyUrl(originurl),
|
||||||
|
)
|
||||||
|
}
|
||||||
BIN
static/6p.jpeg
Normal file
BIN
static/6p.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Reference in New Issue
Block a user