init
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user