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