This commit is contained in:
C菌
2022-04-20 03:50:21 +08:00
commit c113e38d82
11 changed files with 465 additions and 0 deletions

22
configs/config.yml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB