Skip to content

API设计

Shawn Clovie edited this page Dec 31, 2019 · 5 revisions

RockGo API

目录结构与接口以实际代码为准

package与目录结构

|-- api 接口文档
|-- cmd 示例app,package main
| |-- config_rock.yaml
| \-- main.go
|-- docs 文档
|-- test 测试代码
|-- log Console与本地文件Log,package log
|-- middlewares 各种中间件
|-- rock package rock
| |-- Application.go
| |-- Config.go
| \-- ...
|-- CHANGELOG.md
|-- README.md
|-- Makefile
|-- go.mod
\-- ...
...

Application & Router

package rock
// 路由处理函数,执行完成后需调用ctx.Next(),除非不希望后续处理器生效
type Handler func(ctx iris.Context)

type Application interface {
  // 应用名称,来自rock config中的app_name
  Name()
  // 载入rock config,以初始化各个内部模块
  // 自动添加用于异常恢复的recover和sentry中间件、记录基础metric信息的中间件
  InitWithRockConfig(path string) error
  // 取得iris实例,以注册控制器、中间件等
  Iris() *iris.Application

  // 建立服务,以此注册各种请求的handler
  NewService(name, path string) *Service
  // 建立服务组以代理iris的Party功能
  NewServiceGroup(name, path string) *ServiceGroup

  // 启动服务,host可包含端口号,如省略域名或ip将与0.0.0.0等效
  // 将会检查没有通过
  Run(host string, conf ...iris.Configurator)
}

func NewApplication() Application
// implementation of Application
type application struct {
  name        string
  iris        *iris.Application
  rockConfig  map[string]interface{}
}

type Service struct {
  app  *application
  name string
}
func (s *Service) Get(fn func(iris.Context)) *Service
func (s *Service) Post(fn func(iris.Context)) *Service
func (s *Service) Put(fn func(iris.Context)) *Service
func (s *Service) Option(fn func(iris.Context)) *Service
func (s *Service) Delete(fn func(iris.Context)) *Service

type ServiceGroup struct {
  app   *application
  party iris.Party

  name string
  path string

  services      map[string]*Service
  handlerStatus map[string]bool
}
func (g *ServiceGroup) Use(mw ...iris.Handler) *ServiceGroup
func (g *ServiceGroup) NewService(name, path string) *Service
func (g *ServiceGroup) NewServiceGroup(name, path string) *ServiceGroup

var config  map[string]interface{}
var loggers map[string]*log.Logger
// 用默认的logger防止调用Logger(name)时对应logger不存在而得到空指针
var defaultLogger log.Logger

// 取得在Application.Init初始化过的该名称对应的Logger
func Logger(name string) *Logger

// 注册PanicHandler,由预定义中间件捕捉错误后调用
func SetPanicHandler(fn func(ctx iris.Context, err error))

Config

Config一方面作为加载json或yaml的工具,另一方面作为内部模块的数据源。

package rock
// 载入多个配置文件,遇到任何错误将立即返回
func ImportConfigFromPaths(paths ...string) error
// 载入指定目录下的所有文件(不递归)
// 文件将按照文件名作为map中的key,如app.yaml的内容将保存至data["app"]
func ImportConfigFilesFromDirectory(dir string) error

// 按以.分隔的keyPath查找配置
func ConfigIn(keyPath string) interface{}
// 载入的所有配置
func Config() map[string]interface{}

// 载入配置文件,支持json与yaml
// 如文件为空内容或不存在,将返回错误
func LoadConfigFromPath(path string, into interface{}) error

Log

stdOut与本地文件输出。

package log

type Level string
const (
  LevelFatal Level = "fatal"
  LevelError Level = "error"
  LevelWarn  Level = "warn"
  LevelInfo  Level = "info"
  LevelDebug Level = "debug"
)
type Logger interface {
  // 发送可结构化的消息
  // argPairs被视为键值对,键或值为nil的将不被记录
  // 本地log:直接发给zap.w处理
  // fluent log:将在内部拼成map {"msg": msg, arg0: arg1, arg2: arg3, ...},msg仅当参数msg非空串才会被记录
  Log(level Level, msg string, argPairs ...interface{})
  // 间接调用Log(LevelDebug, msg, args...),下同
  Debug(msg string, args ...interface{})
  Info(msg string, args ...interface{})
  Warn(msg string, args ...interface{})
  Error(msg string, args ...interface{})
  Fatal(msg string, args ...interface{})

  // 发送可结构化的消息
  // 本地log:解构后发给zap.w处理
  // fluent log:直接发送
  LogMap(level Level, msg string, values map[string]interface{})
  Debugm(msg string, values map[string]interface{})
  ...

  // 发送简单消息
  // 本地log:直接发给zap处理
  // fluent log:args如仅包含一个map,会用它发给fluent,若有多个将合并为信息字符串,并将map[string]interface{}{"m":信息字符串}发给fluent
  LogPlainMessage(level Level, args ...interface{})
  // 发送格式化后的简单消息
  // 内部逻辑与LogPlainMessage基本相同
  LogFormatted(level Level, format string, args ...interface{})
  // 调用LogFormatted,下同
  Debugf(format string, args ...interface{})
  Infof(format string, args ...interface{})
  Warnf(format string, args ...interface{})
  Errorf(format string, args ...interface{})
  Fatalf(format string, args ...interface{})
}

type MessageFormat string
const (
  MessageFormatJSON MessageFormat = "json"
  MessageFormatText MessageFormat = "text"
)
type TimeFormat string
const (
  TimeFormatISO8601 TimeFormat = "iso8601"
  TimeFormatSeconds TimeFormat = "seconds"
  TimeFormatMillis  TimeFormat = "millis"
  TimeFormatNanos   TimeFormat = "nanos"
)
type LocalFormat struct {
  Format MessageFormat

  MessageKey string // 默认为M
  TimeKey    string // 默认为T
  LevelKey   string // 默认为L
  NameKey    string // 默认为N
  CallerKey  string // 默认为C
  // 时间格式
  TimeFormat TimeFormat // 默认为TimeFormatISO8601
}
func MakeLocalFormat(msg MessageFormat) LocalFormat

type Output interface {
  Write(bs []byte) error
}
type ConsoleOutput struct {
  Format LocalFormat
  Level  Level

  output *zap.SugaredLogger
}
func MakeConsoleOutput(fmt LocalFormat, level Level) ConsoleOutput

type FileOutput struct {
  Format LocalFormat
  Level  Level
  Location string
  Rotation FileRotation

  output *zap.SugaredLogger
}
// 结构同目前的Rotation
type FileRotation struct {...}
func MakeFileOutput(fmt LocalFormat, level Level, location string, rotation FileRotation) FileOutput

// 以app_name作为fluent的tag
type FluentOutput struct {
  output *fluent.Fluent
}

type logger struct {
  outpers []Output
}

func NewLogger(outpers []Output) *Logger

example 自定义Logger

cfg := log.NewConfig(log.LevelDebug,
  log.ConsoleOutput{Format: log.FormatText},
  log.FileOutput{
    Format: log.FormatJSON,
    Location: "output/log.txt",
    Rotation: FileRotation {...},
  })
cfg.ShouldLogCaller = false
cfg.TimeFormat = TimeFormatSeconds
logger := NewLogger(cfg)
logger.Debug("print something")

Metric (stats)

以app_name作为prefix

package rock

func Metric() *metric.StatsdClient

// stats计数 + 1,当Application.Init初始化后可用
func MetricIncrease(key string)
// stats计数 - 1,当Application.Init初始化后可用
func MetricDecrease(key string)

// 记录时间
// duration: time.Now().Sub(oldTime)
func MetricTiming(key string, duration time.Duration)

func MetricGauge(bucket string, value interface{})

func MetricHistogram(bucket string, value interface{})

package metric
// 根据配置建立metric对象,与目前API相同
func New(opts ...Option) (*StatsdClient, error)

Sentry

可以用fluent插件进行中转,以避免sentry服务器承压不够影响响应速度 https://github.com/y-ken/fluent-plugin-sentry

  • 在fluent服务器定义一个过滤规则,用该插件发至sentry服务器
  • 定义一个包含fluent的Logger,设定符合该过滤规则的tag
  • 在PanicHandler中,将信息通过该Logger发送。

crypto

package crypto
// AES
type Coder interface {
  Encrypt([]byte) ([]byte, error)
  Decrypt([]byte) ([]byte, error)
}
func NewAESCoderWithECB(key []byte) (Coder, error)
type aesECBCoder struct {
  cipher cipher.Block
}
func NewAESCoderWithCBC(key, iv []byte) (Coder, error)
type aesCBCCoder struct {
  cipher cipher.Block
  iv []byte
}

// Digest
func MD5([]byte) []byte
func SHA1([]byte) []byte
func SHA256([]byte) []byte
func SHA512([]byte) []byte

// RSA
type RSAEncoder interface {
  Encrypt([]byte) ([]byte, error)
  VerifySign(msg, sign []byte) bool
}
type RSADecoder interface {
  Decrypt([]byte) ([]byte, error)
  Sign(msg []byte) ([]byte, error)
}
func NewRSAKeys(bits int) (publicKey []byte, privateKey []byte, err error)
func NewRSAEncoder(pubKey []byte) RSAEncoder
type rsaEncoder struct {
  publicKey []byte
}
func NewRSADecoder(privKey []byte) RSADecoder
type rsaDecoder struct {
  privateKey []byte
}

example

import (
  "git.atcloudbox.com/087-group/lib_rockgo_server/rock"
  "git.atcloudbox.com/087-group/lib_rockgo_server/log"
)

func main() {
  app := rock.NewApplication()

  app.InitWithRockConfig("config_rock.yaml")
  
  // 加载config目录下的yaml与json文件
  rock.ImportConfigFilesFromDirectory("config")
  
  logger := rock.Logger("Boost")
  logger.Debug("app已加载配置")
  
  // 配置中预定义的中间件已经添加(用于捕获panic、记录stats)
  // 在此增加其它中间件,中间件用ctx.Next()执行后续中间件和请求处理
  app.Iris().Use(func(ctx iris.Context) {
    // 例: 在最初启动时记录开始访问的时间,以免后续执行过多次的time.Now()
    t := time.Now()
    ctx.Values().Set("now", t)
    ctx.Next()
    // 记录access log
    rock.Logger("Access").Info("access", "remote_addr", ctx.RemoteAddr(), "status", ctx.GetStatusCode(), "now", t)
  })
  // 增加用于记录access log的中间件
  app.Iris.Use(rock.NewAccessLogMiddleware(rock.Logger("Access")))

  // 用service注册路由
  app.NewService("root", "/").Get(func(ctx iris.Context) {
    ctx.StatusCode(200)
  })
  app.NewService("user", "/user/{id:int min(1)}").
    // 自动添加统计 /user/{id}.$statusCode,如/user/{id}.200或/user/{id}.400
    Get(func(ctx iris.Context) {
    uid := ctx.Param("id")
    user, err := // get user with id
    if err == nil {
      ctx.StatusCode(http.StatusOK)
      ctx.ResponseWriter.Write(/* marshal user */)
    } else {
      ctx.StatusCode(http.StatusXxx)
    }
    }).
    Post(func(ctx iris.Context) {
    // handler start
    uid := ctx.Param("id")
    username := ctx.Request.FormValue("name")
    if username == "" {
      ctx.StatusCode(http.StatusBadRequest)
      return
    }
    // some codes in controller
    logger := rock.Logger("User")
    // 读取配置(或应在启动时读取)
    file, _ := rock.Config("account.apns.p8_file").(string)
    var err error
    ...
    // back to handler
    if err == nil {
      logger.Debug("update user success", "user_id", uid)
      ctx.StatusCode(http.StatusOK)
    } else {
      logger.Error("update user failure", "user_id", uid, "error", err)
      ctx.StatusCode(http.StatusInternalServerError)
    }
    })
  app.Iris().OnErrorCode(http.StatusNotFound, func(ctx iris.Context) {
    // 路由错误处理
  })
  logger.Info("server start", "time", time.Now().String()})
  app.Run(":8080")
}

func TestCrypto() {
  var key = []byte("1234567890abcdef") // or read from KMS
  plainData := []byte("Hello World")
  var encryptedData []byte
  println(security.NewAESCoderWithCBC(key).Encrypt(encryptedData))
  
  rsaPublicKey, rsaPrivateKey, err := NewRSAKeys(32)
  // or read keys from somewhere
  encryptedData = security.NewRSAEncoder(rsaPublicKey).Encrypt(plainData)
  println(security.NewRSADecoder(rsaPrivateKey).Decrypt(encryptedData))
}

Clone this wiki locally