commit c113e38d82cb92b6192f00d3a36054c3d821cb58 Author: C菌 Date: Wed Apr 20 03:50:21 2022 +0800 init diff --git a/configs/config.yml b/configs/config.yml new file mode 100644 index 0000000..417aaf6 --- /dev/null +++ b/configs/config.yml @@ -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: diff --git a/main.go b/main.go new file mode 100644 index 0000000..1e199a2 --- /dev/null +++ b/main.go @@ -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") +} diff --git a/pkg/base62/base62.go b/pkg/base62/base62.go new file mode 100644 index 0000000..0a7b50f --- /dev/null +++ b/pkg/base62/base62.go @@ -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 +} diff --git a/pkg/base62/tinyurl.go b/pkg/base62/tinyurl.go new file mode 100644 index 0000000..66efd76 --- /dev/null +++ b/pkg/base62/tinyurl.go @@ -0,0 +1,10 @@ +package base62 + +import ( + mh "github.com/spaolacci/murmur3" +) + +func TinyUrl(in string) string { + return EncodeBase62(int(mh.Sum32([]byte(in)))) + +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..7dac28a --- /dev/null +++ b/pkg/config/config.go @@ -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 +} diff --git a/pkg/db/db.go b/pkg/db/db.go new file mode 100644 index 0000000..b2b37c4 --- /dev/null +++ b/pkg/db/db.go @@ -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) + } +} diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..eb750be --- /dev/null +++ b/pkg/log/log.go @@ -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)) +} diff --git a/pkg/trace/trace.go b/pkg/trace/trace.go new file mode 100644 index 0000000..ff3a895 --- /dev/null +++ b/pkg/trace/trace.go @@ -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) + } +} diff --git a/route/route.go b/route/route.go new file mode 100644 index 0000000..0a893fb --- /dev/null +++ b/route/route.go @@ -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") + } +} diff --git a/route/tinyurl/tinyurl.go b/route/tinyurl/tinyurl.go new file mode 100644 index 0000000..110e457 --- /dev/null +++ b/route/tinyurl/tinyurl.go @@ -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), + ) +} diff --git a/static/6p.jpeg b/static/6p.jpeg new file mode 100644 index 0000000..18fcf3a Binary files /dev/null and b/static/6p.jpeg differ