Compare commits
	
		
			1 Commits
		
	
	
		
			1634f1bc5f
			...
			feature/im
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e2ebb2ad75 | 
							
								
								
									
										586
									
								
								demo/context.go
									
									
									
									
									
								
							
							
						
						
									
										586
									
								
								demo/context.go
									
									
									
									
									
								
							@ -1,586 +0,0 @@
 | 
			
		||||
// Package demo ...
 | 
			
		||||
//
 | 
			
		||||
// Description : demo ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021-12-02 12:43 下午
 | 
			
		||||
package demo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Copyright 2014 The Go Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a BSD-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
// Package context defines the Context type, which carries deadlines,
 | 
			
		||||
// cancellation signals, and other request-scoped values across API boundaries
 | 
			
		||||
// and between processes.
 | 
			
		||||
//
 | 
			
		||||
// Incoming requests to a server should create a Context, and outgoing
 | 
			
		||||
// calls to servers should accept a Context. The chain of function
 | 
			
		||||
// calls between them must propagate the Context, optionally replacing
 | 
			
		||||
// it with a derived Context created using WithCancel, WithDeadline,
 | 
			
		||||
// WithTimeout, or WithValue. When a Context is canceled, all
 | 
			
		||||
// Contexts derived from it are also canceled.
 | 
			
		||||
//
 | 
			
		||||
// The WithCancel, WithDeadline, and WithTimeout functions take a
 | 
			
		||||
// Context (the parent) and return a derived Context (the child) and a
 | 
			
		||||
// CancelFunc. Calling the CancelFunc cancels the child and its
 | 
			
		||||
// children, removes the parent's reference to the child, and stops
 | 
			
		||||
// any associated timers. Failing to call the CancelFunc leaks the
 | 
			
		||||
// child and its children until the parent is canceled or the timer
 | 
			
		||||
// fires. The go vet tool checks that CancelFuncs are used on all
 | 
			
		||||
// control-flow paths.
 | 
			
		||||
//
 | 
			
		||||
// Programs that use Contexts should follow these rules to keep interfaces
 | 
			
		||||
// consistent across packages and enable static analysis tools to check context
 | 
			
		||||
// propagation:
 | 
			
		||||
//
 | 
			
		||||
// Do not store Contexts inside a struct type; instead, pass a Context
 | 
			
		||||
// explicitly to each function that needs it. The Context should be the first
 | 
			
		||||
// parameter, typically named ctx:
 | 
			
		||||
//
 | 
			
		||||
// 	func DoSomething(ctx context.Context, arg Arg) error {
 | 
			
		||||
// 		// ... use ctx ...
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
 | 
			
		||||
// if you are unsure about which Context to use.
 | 
			
		||||
//
 | 
			
		||||
// Use context Values only for request-scoped data that transits processes and
 | 
			
		||||
// APIs, not for passing optional parameters to functions.
 | 
			
		||||
//
 | 
			
		||||
// The same Context may be passed to functions running in different goroutines;
 | 
			
		||||
// Contexts are safe for simultaneous use by multiple goroutines.
 | 
			
		||||
//
 | 
			
		||||
// See https://blog.golang.org/context for example code for a server that uses
 | 
			
		||||
// Contexts.
 | 
			
		||||
 | 
			
		||||
// A Context carries a deadline, a cancellation signal, and other values across
 | 
			
		||||
// API boundaries.
 | 
			
		||||
//
 | 
			
		||||
// Context's methods may be called by multiple goroutines simultaneously.
 | 
			
		||||
type Context interface {
 | 
			
		||||
	// Deadline returns the time when work done on behalf of this context
 | 
			
		||||
	// should be canceled. Deadline returns ok==false when no deadline is
 | 
			
		||||
	// set. Successive calls to Deadline return the same results.
 | 
			
		||||
	Deadline() (deadline time.Time, ok bool)
 | 
			
		||||
 | 
			
		||||
	// Done returns a channel that's closed when work done on behalf of this
 | 
			
		||||
	// context should be canceled. Done may return nil if this context can
 | 
			
		||||
	// never be canceled. Successive calls to Done return the same value.
 | 
			
		||||
	// The close of the Done channel may happen asynchronously,
 | 
			
		||||
	// after the cancel function returns.
 | 
			
		||||
	//
 | 
			
		||||
	// WithCancel arranges for Done to be closed when cancel is called;
 | 
			
		||||
	// WithDeadline arranges for Done to be closed when the deadline
 | 
			
		||||
	// expires; WithTimeout arranges for Done to be closed when the timeout
 | 
			
		||||
	// elapses.
 | 
			
		||||
	//
 | 
			
		||||
	// Done is provided for use in select statements:
 | 
			
		||||
	//
 | 
			
		||||
	//  // Stream generates values with DoSomething and sends them to out
 | 
			
		||||
	//  // until DoSomething returns an error or ctx.Done is closed.
 | 
			
		||||
	//  func Stream(ctx context.Context, out chan<- Value) error {
 | 
			
		||||
	//  	for {
 | 
			
		||||
	//  		v, err := DoSomething(ctx)
 | 
			
		||||
	//  		if err != nil {
 | 
			
		||||
	//  			return err
 | 
			
		||||
	//  		}
 | 
			
		||||
	//  		select {
 | 
			
		||||
	//  		case <-ctx.Done():
 | 
			
		||||
	//  			return ctx.Err()
 | 
			
		||||
	//  		case out <- v:
 | 
			
		||||
	//  		}
 | 
			
		||||
	//  	}
 | 
			
		||||
	//  }
 | 
			
		||||
	//
 | 
			
		||||
	// See https://blog.golang.org/pipelines for more examples of how to use
 | 
			
		||||
	// a Done channel for cancellation.
 | 
			
		||||
	Done() <-chan struct{}
 | 
			
		||||
 | 
			
		||||
	// If Done is not yet closed, Err returns nil.
 | 
			
		||||
	// If Done is closed, Err returns a non-nil error explaining why:
 | 
			
		||||
	// Canceled if the context was canceled
 | 
			
		||||
	// or DeadlineExceeded if the context's deadline passed.
 | 
			
		||||
	// After Err returns a non-nil error, successive calls to Err return the same error.
 | 
			
		||||
	Err() error
 | 
			
		||||
 | 
			
		||||
	// Value returns the value associated with this context for key, or nil
 | 
			
		||||
	// if no value is associated with key. Successive calls to Value with
 | 
			
		||||
	// the same key returns the same result.
 | 
			
		||||
	//
 | 
			
		||||
	// Use context values only for request-scoped data that transits
 | 
			
		||||
	// processes and API boundaries, not for passing optional parameters to
 | 
			
		||||
	// functions.
 | 
			
		||||
	//
 | 
			
		||||
	// A key identifies a specific value in a Context. Functions that wish
 | 
			
		||||
	// to store values in Context typically allocate a key in a global
 | 
			
		||||
	// variable then use that key as the argument to context.WithValue and
 | 
			
		||||
	// Context.Value. A key can be any type that supports equality;
 | 
			
		||||
	// packages should define keys as an unexported type to avoid
 | 
			
		||||
	// collisions.
 | 
			
		||||
	//
 | 
			
		||||
	// Packages that define a Context key should provide type-safe accessors
 | 
			
		||||
	// for the values stored using that key:
 | 
			
		||||
	//
 | 
			
		||||
	// 	// Package user defines a User type that's stored in Contexts.
 | 
			
		||||
	// 	package user
 | 
			
		||||
	//
 | 
			
		||||
	// 	import "context"
 | 
			
		||||
	//
 | 
			
		||||
	// 	// User is the type of value stored in the Contexts.
 | 
			
		||||
	// 	type User struct {...}
 | 
			
		||||
	//
 | 
			
		||||
	// 	// key is an unexported type for keys defined in this package.
 | 
			
		||||
	// 	// This prevents collisions with keys defined in other packages.
 | 
			
		||||
	// 	type key int
 | 
			
		||||
	//
 | 
			
		||||
	// 	// userKey is the key for user.User values in Contexts. It is
 | 
			
		||||
	// 	// unexported; clients use user.NewContext and user.FromContext
 | 
			
		||||
	// 	// instead of using this key directly.
 | 
			
		||||
	// 	var userKey key
 | 
			
		||||
	//
 | 
			
		||||
	// 	// NewContext returns a new Context that carries value u.
 | 
			
		||||
	// 	func NewContext(ctx context.Context, u *User) context.Context {
 | 
			
		||||
	// 		return context.WithValue(ctx, userKey, u)
 | 
			
		||||
	// 	}
 | 
			
		||||
	//
 | 
			
		||||
	// 	// FromContext returns the User value stored in ctx, if any.
 | 
			
		||||
	// 	func FromContext(ctx context.Context) (*User, bool) {
 | 
			
		||||
	// 		u, ok := ctx.Value(userKey).(*User)
 | 
			
		||||
	// 		return u, ok
 | 
			
		||||
	// 	}
 | 
			
		||||
	Value(key interface{}) interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Canceled is the error returned by Context.Err when the context is canceled.
 | 
			
		||||
var Canceled = errors.New("context canceled")
 | 
			
		||||
 | 
			
		||||
// DeadlineExceeded is the error returned by Context.Err when the context's
 | 
			
		||||
// deadline passes.
 | 
			
		||||
var DeadlineExceeded error = deadlineExceededError{}
 | 
			
		||||
 | 
			
		||||
type deadlineExceededError struct{}
 | 
			
		||||
 | 
			
		||||
func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
 | 
			
		||||
func (deadlineExceededError) Timeout() bool   { return true }
 | 
			
		||||
func (deadlineExceededError) Temporary() bool { return true }
 | 
			
		||||
 | 
			
		||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
 | 
			
		||||
// struct{}, since vars of this type must have distinct addresses.
 | 
			
		||||
type emptyCtx int
 | 
			
		||||
 | 
			
		||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*emptyCtx) Done() <-chan struct{} {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*emptyCtx) Err() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*emptyCtx) Value(key interface{}) interface{} {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *emptyCtx) String() string {
 | 
			
		||||
	switch e {
 | 
			
		||||
	case background:
 | 
			
		||||
		return "context.Background"
 | 
			
		||||
	case todo:
 | 
			
		||||
		return "context.TODO"
 | 
			
		||||
	}
 | 
			
		||||
	return "unknown empty Context"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	background = new(emptyCtx)
 | 
			
		||||
	todo       = new(emptyCtx)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Background returns a non-nil, empty Context. It is never canceled, has no
 | 
			
		||||
// values, and has no deadline. It is typically used by the main function,
 | 
			
		||||
// initialization, and tests, and as the top-level Context for incoming
 | 
			
		||||
// requests.
 | 
			
		||||
func Background() Context {
 | 
			
		||||
	return background
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
 | 
			
		||||
// it's unclear which Context to use or it is not yet available (because the
 | 
			
		||||
// surrounding function has not yet been extended to accept a Context
 | 
			
		||||
// parameter).
 | 
			
		||||
func TODO() Context {
 | 
			
		||||
	return todo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A CancelFunc tells an operation to abandon its work.
 | 
			
		||||
// A CancelFunc does not wait for the work to stop.
 | 
			
		||||
// A CancelFunc may be called by multiple goroutines simultaneously.
 | 
			
		||||
// After the first call, subsequent calls to a CancelFunc do nothing.
 | 
			
		||||
type CancelFunc func()
 | 
			
		||||
 | 
			
		||||
// WithCancel returns a copy of parent with a new Done channel. The returned
 | 
			
		||||
// context's Done channel is closed when the returned cancel function is called
 | 
			
		||||
// or when the parent context's Done channel is closed, whichever happens first.
 | 
			
		||||
//
 | 
			
		||||
// Canceling this context releases resources associated with it, so code should
 | 
			
		||||
// call cancel as soon as the operations running in this Context complete.
 | 
			
		||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
 | 
			
		||||
	if parent == nil {
 | 
			
		||||
		panic("cannot create context from nil parent")
 | 
			
		||||
	}
 | 
			
		||||
	c := newCancelCtx(parent)
 | 
			
		||||
	propagateCancel(parent, &c)
 | 
			
		||||
	return &c, func() { c.cancel(true, Canceled) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newCancelCtx returns an initialized cancelCtx.
 | 
			
		||||
func newCancelCtx(parent Context) cancelCtx {
 | 
			
		||||
	return cancelCtx{Context: parent}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// goroutines counts the number of goroutines ever created; for testing.
 | 
			
		||||
var goroutines int32
 | 
			
		||||
 | 
			
		||||
// propagateCancel arranges for child to be canceled when parent is.
 | 
			
		||||
func propagateCancel(parent Context, child canceler) {
 | 
			
		||||
	done := parent.Done()
 | 
			
		||||
	if done == nil {
 | 
			
		||||
		return // parent is never canceled
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-done:
 | 
			
		||||
		// parent is already canceled
 | 
			
		||||
		child.cancel(false, parent.Err())
 | 
			
		||||
		return
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p, ok := parentCancelCtx(parent); ok {
 | 
			
		||||
		p.mu.Lock()
 | 
			
		||||
		if p.err != nil {
 | 
			
		||||
			// parent has already been canceled
 | 
			
		||||
			child.cancel(false, p.err)
 | 
			
		||||
		} else {
 | 
			
		||||
			if p.children == nil {
 | 
			
		||||
				p.children = make(map[canceler]struct{})
 | 
			
		||||
			}
 | 
			
		||||
			p.children[child] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
		p.mu.Unlock()
 | 
			
		||||
	} else {
 | 
			
		||||
		atomic.AddInt32(&goroutines, +1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-parent.Done():
 | 
			
		||||
				child.cancel(false, parent.Err())
 | 
			
		||||
			case <-child.Done():
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// &cancelCtxKey is the key that a cancelCtx returns itself for.
 | 
			
		||||
var cancelCtxKey int
 | 
			
		||||
 | 
			
		||||
// parentCancelCtx returns the underlying *cancelCtx for parent.
 | 
			
		||||
// It does this by looking up parent.Value(&cancelCtxKey) to find
 | 
			
		||||
// the innermost enclosing *cancelCtx and then checking whether
 | 
			
		||||
// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx
 | 
			
		||||
// has been wrapped in a custom implementation providing a
 | 
			
		||||
// different done channel, in which case we should not bypass it.)
 | 
			
		||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
 | 
			
		||||
	done := parent.Done()
 | 
			
		||||
	if done == closedchan || done == nil {
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
	pdone, _ := p.done.Load().(chan struct{})
 | 
			
		||||
	if pdone != done {
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
	return p, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeChild removes a context from its parent.
 | 
			
		||||
func removeChild(parent Context, child canceler) {
 | 
			
		||||
	p, ok := parentCancelCtx(parent)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	p.mu.Lock()
 | 
			
		||||
	if p.children != nil {
 | 
			
		||||
		delete(p.children, child)
 | 
			
		||||
	}
 | 
			
		||||
	p.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A canceler is a context type that can be canceled directly. The
 | 
			
		||||
// implementations are *cancelCtx and *timerCtx.
 | 
			
		||||
type canceler interface {
 | 
			
		||||
	cancel(removeFromParent bool, err error)
 | 
			
		||||
	Done() <-chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// closedchan is a reusable closed channel.
 | 
			
		||||
var closedchan = make(chan struct{})
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	close(closedchan)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
 | 
			
		||||
// that implement canceler.
 | 
			
		||||
type cancelCtx struct {
 | 
			
		||||
	Context
 | 
			
		||||
 | 
			
		||||
	mu       sync.Mutex            // protects following fields
 | 
			
		||||
	done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
 | 
			
		||||
	children map[canceler]struct{} // set to nil by the first cancel call
 | 
			
		||||
	err      error                 // set to non-nil by the first cancel call
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *cancelCtx) Value(key interface{}) interface{} {
 | 
			
		||||
	if key == &cancelCtxKey {
 | 
			
		||||
		return c
 | 
			
		||||
	}
 | 
			
		||||
	return c.Context.Value(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *cancelCtx) Done() <-chan struct{} {
 | 
			
		||||
	d := c.done.Load()
 | 
			
		||||
	if d != nil {
 | 
			
		||||
		return d.(chan struct{})
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
	d = c.done.Load()
 | 
			
		||||
	if d == nil {
 | 
			
		||||
		d = make(chan struct{})
 | 
			
		||||
		c.done.Store(d)
 | 
			
		||||
	}
 | 
			
		||||
	return d.(chan struct{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *cancelCtx) Err() error {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	err := c.err
 | 
			
		||||
	c.mu.Unlock()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type stringer interface {
 | 
			
		||||
	String() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func contextName(c Context) string {
 | 
			
		||||
	if s, ok := c.(stringer); ok {
 | 
			
		||||
		return s.String()
 | 
			
		||||
	}
 | 
			
		||||
	return reflect.TypeOf(c).String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *cancelCtx) String() string {
 | 
			
		||||
	return contextName(c.Context) + ".WithCancel"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cancel closes c.done, cancels each of c's children, and, if
 | 
			
		||||
// removeFromParent is true, removes c from its parent's children.
 | 
			
		||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		panic("context: internal error: missing cancel error")
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	if c.err != nil {
 | 
			
		||||
		c.mu.Unlock()
 | 
			
		||||
		return // already canceled
 | 
			
		||||
	}
 | 
			
		||||
	c.err = err
 | 
			
		||||
	d, _ := c.done.Load().(chan struct{})
 | 
			
		||||
	if d == nil {
 | 
			
		||||
		c.done.Store(closedchan)
 | 
			
		||||
	} else {
 | 
			
		||||
		close(d)
 | 
			
		||||
	}
 | 
			
		||||
	for child := range c.children {
 | 
			
		||||
		// NOTE: acquiring the child's lock while holding parent's lock.
 | 
			
		||||
		child.cancel(false, err)
 | 
			
		||||
	}
 | 
			
		||||
	c.children = nil
 | 
			
		||||
	c.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	if removeFromParent {
 | 
			
		||||
		removeChild(c.Context, c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
 | 
			
		||||
// to be no later than d. If the parent's deadline is already earlier than d,
 | 
			
		||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
 | 
			
		||||
// context's Done channel is closed when the deadline expires, when the returned
 | 
			
		||||
// cancel function is called, or when the parent context's Done channel is
 | 
			
		||||
// closed, whichever happens first.
 | 
			
		||||
//
 | 
			
		||||
// Canceling this context releases resources associated with it, so code should
 | 
			
		||||
// call cancel as soon as the operations running in this Context complete.
 | 
			
		||||
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
 | 
			
		||||
	if parent == nil {
 | 
			
		||||
		panic("cannot create context from nil parent")
 | 
			
		||||
	}
 | 
			
		||||
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
 | 
			
		||||
		// The current deadline is already sooner than the new one.
 | 
			
		||||
		return WithCancel(parent)
 | 
			
		||||
	}
 | 
			
		||||
	c := &timerCtx{
 | 
			
		||||
		cancelCtx: newCancelCtx(parent),
 | 
			
		||||
		deadline:  d,
 | 
			
		||||
	}
 | 
			
		||||
	propagateCancel(parent, c)
 | 
			
		||||
	dur := time.Until(d)
 | 
			
		||||
	if dur <= 0 {
 | 
			
		||||
		c.cancel(true, DeadlineExceeded) // deadline has already passed
 | 
			
		||||
		return c, func() { c.cancel(false, Canceled) }
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
	if c.err == nil {
 | 
			
		||||
		c.timer = time.AfterFunc(dur, func() {
 | 
			
		||||
			c.cancel(true, DeadlineExceeded)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return c, func() { c.cancel(true, Canceled) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
 | 
			
		||||
// implement Done and Err. It implements cancel by stopping its timer then
 | 
			
		||||
// delegating to cancelCtx.cancel.
 | 
			
		||||
type timerCtx struct {
 | 
			
		||||
	cancelCtx
 | 
			
		||||
	timer *time.Timer // Under cancelCtx.mu.
 | 
			
		||||
 | 
			
		||||
	deadline time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
 | 
			
		||||
	return c.deadline, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *timerCtx) String() string {
 | 
			
		||||
	return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
 | 
			
		||||
		c.deadline.String() + " [" +
 | 
			
		||||
		time.Until(c.deadline).String() + "])"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
 | 
			
		||||
	c.cancelCtx.cancel(false, err)
 | 
			
		||||
	if removeFromParent {
 | 
			
		||||
		// Remove this timerCtx from its parent cancelCtx's children.
 | 
			
		||||
		removeChild(c.cancelCtx.Context, c)
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	if c.timer != nil {
 | 
			
		||||
		c.timer.Stop()
 | 
			
		||||
		c.timer = nil
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
 | 
			
		||||
//
 | 
			
		||||
// Canceling this context releases resources associated with it, so code should
 | 
			
		||||
// call cancel as soon as the operations running in this Context complete:
 | 
			
		||||
//
 | 
			
		||||
// 	func slowOperationWithTimeout(ctx context.Context) (Result, error) {
 | 
			
		||||
// 		ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
 | 
			
		||||
// 		defer cancel()  // releases resources if slowOperation completes before timeout elapses
 | 
			
		||||
// 		return slowOperation(ctx)
 | 
			
		||||
// 	}
 | 
			
		||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
 | 
			
		||||
	return WithDeadline(parent, time.Now().Add(timeout))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithValue returns a copy of parent in which the value associated with key is
 | 
			
		||||
// val.
 | 
			
		||||
//
 | 
			
		||||
// Use context Values only for request-scoped data that transits processes and
 | 
			
		||||
// APIs, not for passing optional parameters to functions.
 | 
			
		||||
//
 | 
			
		||||
// The provided key must be comparable and should not be of type
 | 
			
		||||
// string or any other built-in type to avoid collisions between
 | 
			
		||||
// packages using context. Users of WithValue should define their own
 | 
			
		||||
// types for keys. To avoid allocating when assigning to an
 | 
			
		||||
// interface{}, context keys often have concrete type
 | 
			
		||||
// struct{}. Alternatively, exported context key variables' static
 | 
			
		||||
// type should be a pointer or interface.
 | 
			
		||||
func WithValue(parent Context, key, val interface{}) Context {
 | 
			
		||||
	if parent == nil {
 | 
			
		||||
		panic("cannot create context from nil parent")
 | 
			
		||||
	}
 | 
			
		||||
	if key == nil {
 | 
			
		||||
		panic("nil key")
 | 
			
		||||
	}
 | 
			
		||||
	if !reflect.TypeOf(key).Comparable() {
 | 
			
		||||
		panic("key is not comparable")
 | 
			
		||||
	}
 | 
			
		||||
	return &valueCtx{parent, key, val}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A valueCtx carries a key-value pair. It implements Value for that key and
 | 
			
		||||
// delegates all other calls to the embedded Context.
 | 
			
		||||
type valueCtx struct {
 | 
			
		||||
	Context
 | 
			
		||||
	key, val interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// stringify tries a bit to stringify v, without using fmt, since we don't
 | 
			
		||||
// want context depending on the unicode tables. This is only used by
 | 
			
		||||
// *valueCtx.String().
 | 
			
		||||
func stringify(v interface{}) string {
 | 
			
		||||
	switch s := v.(type) {
 | 
			
		||||
	case stringer:
 | 
			
		||||
		return s.String()
 | 
			
		||||
	case string:
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
	return "<not Stringer>"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *valueCtx) String() string {
 | 
			
		||||
	return contextName(c.Context) + ".WithValue(type " +
 | 
			
		||||
		reflect.TypeOf(c.key).String() +
 | 
			
		||||
		", val " + stringify(c.val) + ")"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *valueCtx) Value(key interface{}) interface{} {
 | 
			
		||||
	if c.key == key {
 | 
			
		||||
		return c.val
 | 
			
		||||
	}
 | 
			
		||||
	return c.Context.Value(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ======================== 以上为 context.go 源码
 | 
			
		||||
 | 
			
		||||
// MyContext 自定义 context
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 12:45 下午 2021/12/2
 | 
			
		||||
type MyContext struct {
 | 
			
		||||
	Context
 | 
			
		||||
}
 | 
			
		||||
@ -1,42 +0,0 @@
 | 
			
		||||
// Package demo ...
 | 
			
		||||
//
 | 
			
		||||
// Description : demo ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021-12-02 12:46 下午
 | 
			
		||||
package demo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TestCancelCtx_UDC 测试自定义context
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 12:46 下午 2021/12/2
 | 
			
		||||
func TestCancelCtx_UDC(t *testing.T) {
 | 
			
		||||
	childCancel := true
 | 
			
		||||
 | 
			
		||||
	parentCtx, parentFunc := WithCancel(Background())
 | 
			
		||||
	mctx := MyContext{parentCtx}
 | 
			
		||||
 | 
			
		||||
	childCtx, childFun := WithCancel(mctx)
 | 
			
		||||
 | 
			
		||||
	if childCancel {
 | 
			
		||||
		childFun()
 | 
			
		||||
	} else {
 | 
			
		||||
		parentFunc()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("parent context => ", parentCtx, reflect.TypeOf(parentCtx).String())
 | 
			
		||||
	fmt.Println("my context => ", mctx, reflect.TypeOf(mctx).String())
 | 
			
		||||
	fmt.Println("child context => ", childCtx, reflect.TypeOf(childCtx).String())
 | 
			
		||||
 | 
			
		||||
	// 防止主协程退出太快,子协程来不及打印
 | 
			
		||||
	time.Sleep(5 * time.Second)
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// Package easylock ...
 | 
			
		||||
// Package easylock...
 | 
			
		||||
//
 | 
			
		||||
// Description : easylock ...
 | 
			
		||||
// Description : easylock...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ type option struct {
 | 
			
		||||
	flag string // 锁的标识
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OptionFunc 设置option选项
 | 
			
		||||
// Option 设置option选项
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// Package easylock ...
 | 
			
		||||
// Package easylock...
 | 
			
		||||
//
 | 
			
		||||
// Description : easylock ...
 | 
			
		||||
// Description : easylock...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
// Package easylock ...
 | 
			
		||||
// Package easylock...
 | 
			
		||||
//
 | 
			
		||||
// Description : 包装各种姿势的锁
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
// Package easylock ...
 | 
			
		||||
// Package easylock...
 | 
			
		||||
//
 | 
			
		||||
// Description : 分段的锁
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
@ -19,36 +19,12 @@ import (
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 5:53 下午 2021/3/9
 | 
			
		||||
func InitRequest(startTimeField string, traceIDField string) gin.HandlerFunc {
 | 
			
		||||
func InitRequest() func(ctx *gin.Context) {
 | 
			
		||||
	return func(ctx *gin.Context) {
 | 
			
		||||
		// 设置请求开始时间
 | 
			
		||||
		if len(startTimeField) == 0 {
 | 
			
		||||
			startTimeField = "start_time"
 | 
			
		||||
		}
 | 
			
		||||
		if len(traceIDField) == 0 {
 | 
			
		||||
			traceIDField = "trace_id"
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Set(startTimeField, time.Now().UnixMilli())
 | 
			
		||||
		ctx.Set("start_time", time.Now().Unix())
 | 
			
		||||
		// 设置请求trace_id
 | 
			
		||||
		ctx.Set(traceIDField, commonUtil.GetHostIP()+"-"+ctx.ClientIP()+"-"+time.Now().Format("20060102150405")+"-"+commonUtil.Md5(commonUtil.GenRandomString("", 16)))
 | 
			
		||||
		ctx.Next()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SupportCross 支持跨域
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 9:40 PM 2022/1/13
 | 
			
		||||
func SupportCross(header map[string]string) gin.HandlerFunc {
 | 
			
		||||
	return func(ctx *gin.Context) {
 | 
			
		||||
		ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
 | 
			
		||||
		ctx.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, Admin-User-Token, admin-user-token")
 | 
			
		||||
		ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服务器支持的所有跨域请求的方
 | 
			
		||||
		ctx.Header("Access-Control-Expose-Headers", "admin-user-token, Admin-User-Token, Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
 | 
			
		||||
		for key, val := range header {
 | 
			
		||||
			ctx.Header(key, val)
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Set("trace_id", time.Now().Format("20060102150405")+"+"+commonUtil.GetHostIP()+"-"+commonUtil.Md5(commonUtil.GenRandomString("", 16)))
 | 
			
		||||
		ctx.Next()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
// Package static ...
 | 
			
		||||
//
 | 
			
		||||
// Description : static
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021/12/24 2:18 PM
 | 
			
		||||
package static
 | 
			
		||||
 | 
			
		||||
// MapRule 定义映射规则
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:18 PM 2021/12/24
 | 
			
		||||
type MapRule struct {
 | 
			
		||||
	URIPrefix     string `json:"uri_prefix"`      // 路由前缀
 | 
			
		||||
	StaticDirPath string `json:"static_dir_path"` // 静态资源路由
 | 
			
		||||
}
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
// Package static ...
 | 
			
		||||
//
 | 
			
		||||
// Description : 启动静态资源服务器
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021/12/24 2:14 PM
 | 
			
		||||
package static
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Register 静态资源服务器
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:17 PM 2021/12/24
 | 
			
		||||
func Register(router *gin.Engine, ruleList []*MapRule) {
 | 
			
		||||
	for _, rule := range ruleList {
 | 
			
		||||
		router.StaticFS(rule.URIPrefix, http.Dir(rule.StaticDirPath))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -27,7 +27,7 @@ func Response(ctx *gin.Context, code interface{}, message string, data interface
 | 
			
		||||
		"message":  message,
 | 
			
		||||
		"data":     data,
 | 
			
		||||
		"trace_id": ctx.GetString("trace_id"),
 | 
			
		||||
		"cost":     time.Now().UnixMilli() - ctx.GetInt64("start_time"),
 | 
			
		||||
		"cost":     time.Since(time.Unix(ctx.GetInt64("start_time"), 0)).Seconds(),
 | 
			
		||||
	}
 | 
			
		||||
	ctx.JSON(http.StatusOK, responseData)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								go.mod
									
									
									
									
									
								
							@ -7,7 +7,7 @@ replace github.com/coreos/bbolt v1.3.4 => go.etcd.io/bbolt v1.3.4
 | 
			
		||||
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Shopify/sarama v1.21.0
 | 
			
		||||
	github.com/Shopify/sarama v1.30.0
 | 
			
		||||
	github.com/apolloconfig/agollo/v4 v4.0.9
 | 
			
		||||
	github.com/coreos/etcd v3.3.27+incompatible
 | 
			
		||||
	github.com/ddliu/go-httpclient v0.6.9
 | 
			
		||||
@ -27,11 +27,10 @@ require (
 | 
			
		||||
	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0
 | 
			
		||||
	gorm.io/driver/mysql v1.2.0
 | 
			
		||||
	gorm.io/gorm v1.22.4
 | 
			
		||||
	gorm.io/gorm v1.22.3
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/DataDog/zstd v1.3.5 // indirect
 | 
			
		||||
	github.com/StackExchange/wmi v1.2.1 // indirect
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 | 
			
		||||
	github.com/coreos/bbolt v1.3.4 // indirect
 | 
			
		||||
@ -45,7 +44,6 @@ require (
 | 
			
		||||
	github.com/eapache/go-resiliency v1.2.0 // indirect
 | 
			
		||||
	github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
 | 
			
		||||
	github.com/eapache/queue v1.1.0 // indirect
 | 
			
		||||
	github.com/frankban/quicktest v1.14.0 // indirect
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.5.1 // indirect
 | 
			
		||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
			
		||||
	github.com/go-ole/go-ole v1.2.5 // indirect
 | 
			
		||||
@ -56,17 +54,24 @@ require (
 | 
			
		||||
	github.com/gogo/protobuf v1.3.2 // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.2 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.0-20210608040537-544b4180ac70 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/google/btree v1.0.1 // indirect
 | 
			
		||||
	github.com/google/uuid v1.3.0 // indirect
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2 // indirect
 | 
			
		||||
	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
 | 
			
		||||
	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
 | 
			
		||||
	github.com/hashicorp/go-uuid v1.0.2 // indirect
 | 
			
		||||
	github.com/hashicorp/hcl v1.0.0 // indirect
 | 
			
		||||
	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
 | 
			
		||||
	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
 | 
			
		||||
	github.com/jcmturner/gofork v1.0.0 // indirect
 | 
			
		||||
	github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect
 | 
			
		||||
	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
 | 
			
		||||
	github.com/jinzhu/inflection v1.0.0 // indirect
 | 
			
		||||
	github.com/jinzhu/now v1.1.4 // indirect
 | 
			
		||||
	github.com/jinzhu/now v1.1.2 // indirect
 | 
			
		||||
	github.com/jonboulle/clockwork v0.2.2 // indirect
 | 
			
		||||
	github.com/json-iterator/go v1.1.11 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.13.6 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.2.0 // indirect
 | 
			
		||||
	github.com/lestrrat-go/strftime v1.0.5 // indirect
 | 
			
		||||
	github.com/magiconair/properties v1.8.5 // indirect
 | 
			
		||||
@ -87,7 +92,7 @@ require (
 | 
			
		||||
	github.com/spf13/cast v1.4.1 // indirect
 | 
			
		||||
	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/spf13/viper v1.8.1 // indirect
 | 
			
		||||
	github.com/spf13/viper v1.9.0 // indirect
 | 
			
		||||
	github.com/subosito/gotenv v1.2.0 // indirect
 | 
			
		||||
	github.com/tidwall/match v1.1.1 // indirect
 | 
			
		||||
	github.com/tidwall/pretty v1.2.0 // indirect
 | 
			
		||||
@ -108,7 +113,6 @@ require (
 | 
			
		||||
	google.golang.org/grpc v1.40.0 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.27.1 // indirect
 | 
			
		||||
	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 | 
			
		||||
	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 | 
			
		||||
	gopkg.in/ini.v1 v1.64.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 | 
			
		||||
	sigs.k8s.io/yaml v1.3.0 // indirect
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										149
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								go.sum
									
									
									
									
									
								
							@ -17,6 +17,11 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
 | 
			
		||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 | 
			
		||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 | 
			
		||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
 | 
			
		||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 | 
			
		||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
 | 
			
		||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 | 
			
		||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
 | 
			
		||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 | 
			
		||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 | 
			
		||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 | 
			
		||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 | 
			
		||||
@ -26,6 +31,7 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
 | 
			
		||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 | 
			
		||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 | 
			
		||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 | 
			
		||||
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
 | 
			
		||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 | 
			
		||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 | 
			
		||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 | 
			
		||||
@ -38,12 +44,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
			
		||||
github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14=
 | 
			
		||||
github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 | 
			
		||||
github.com/Shopify/sarama v1.21.0 h1:0GKs+e8mn1RRUzfg9oUXv3v7ZieQLmOZF/bfnmmGhM8=
 | 
			
		||||
github.com/Shopify/sarama v1.21.0/go.mod h1:yuqtN/pe8cXRWG5zPaO7hCfNJp5MwmkoJEoLjkm5tCQ=
 | 
			
		||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
 | 
			
		||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 | 
			
		||||
github.com/Shopify/sarama v1.30.0 h1:TOZL6r37xJBDEMLx4yjB77jxbZYXPaDow08TSK6vIL0=
 | 
			
		||||
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
 | 
			
		||||
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae h1:ePgznFqEG1v3AjMklnK8H7BSc++FDSo7xfK9K7Af+0Y=
 | 
			
		||||
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
 | 
			
		||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
 | 
			
		||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
 | 
			
		||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 | 
			
		||||
@ -57,6 +61,7 @@ github.com/apolloconfig/agollo/v4 v4.0.9/go.mod h1:n/7qxpKOTbegygLmO5OKmFWCdy3T+
 | 
			
		||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 | 
			
		||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 | 
			
		||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 | 
			
		||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 | 
			
		||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
 | 
			
		||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 | 
			
		||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 | 
			
		||||
@ -81,6 +86,7 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7
 | 
			
		||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 | 
			
		||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
 | 
			
		||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
@ -93,7 +99,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
 | 
			
		||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 | 
			
		||||
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
 | 
			
		||||
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 | 
			
		||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
 | 
			
		||||
@ -103,8 +108,11 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 | 
			
		||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 | 
			
		||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
			
		||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
 | 
			
		||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
 | 
			
		||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 | 
			
		||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 | 
			
		||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 | 
			
		||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
 | 
			
		||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 | 
			
		||||
@ -160,6 +168,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 | 
			
		||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 | 
			
		||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 | 
			
		||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
 | 
			
		||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
@ -178,9 +187,9 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 | 
			
		||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 | 
			
		||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 | 
			
		||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20210608040537-544b4180ac70 h1:QvkCapbHb5N1sQMkoQkCLOcBmss196N/m2F64fCA7mY=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20210608040537-544b4180ac70/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 | 
			
		||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
			
		||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
			
		||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
 | 
			
		||||
@ -202,6 +211,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
 | 
			
		||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 | 
			
		||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 | 
			
		||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 | 
			
		||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 | 
			
		||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
@ -213,12 +223,19 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
 | 
			
		||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 | 
			
		||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 | 
			
		||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 | 
			
		||||
@ -228,17 +245,24 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
 | 
			
		||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 | 
			
		||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 | 
			
		||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 | 
			
		||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
 | 
			
		||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 | 
			
		||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
 | 
			
		||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 | 
			
		||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 | 
			
		||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 | 
			
		||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
 | 
			
		||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
 | 
			
		||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
 | 
			
		||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 | 
			
		||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 | 
			
		||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
			
		||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
			
		||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
 | 
			
		||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
			
		||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
			
		||||
@ -246,17 +270,30 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 | 
			
		||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 | 
			
		||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 | 
			
		||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
 | 
			
		||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 | 
			
		||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 | 
			
		||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 | 
			
		||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
 | 
			
		||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
 | 
			
		||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
 | 
			
		||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 | 
			
		||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
 | 
			
		||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 | 
			
		||||
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
 | 
			
		||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
 | 
			
		||||
github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
 | 
			
		||||
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
 | 
			
		||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
 | 
			
		||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 | 
			
		||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 | 
			
		||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 | 
			
		||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
 | 
			
		||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
			
		||||
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
			
		||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
 | 
			
		||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
			
		||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
 | 
			
		||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
 | 
			
		||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 | 
			
		||||
@ -272,14 +309,16 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
 | 
			
		||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 | 
			
		||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
 | 
			
		||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 | 
			
		||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 | 
			
		||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
			
		||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
			
		||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 | 
			
		||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
@ -295,14 +334,22 @@ github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR7
 | 
			
		||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
 | 
			
		||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 | 
			
		||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
			
		||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 | 
			
		||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
			
		||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 | 
			
		||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
 | 
			
		||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 | 
			
		||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 | 
			
		||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 | 
			
		||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 | 
			
		||||
@ -337,7 +384,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
 | 
			
		||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 | 
			
		||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
 | 
			
		||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 | 
			
		||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 | 
			
		||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
 | 
			
		||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 | 
			
		||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
@ -348,6 +394,7 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 | 
			
		||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
 | 
			
		||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 | 
			
		||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 | 
			
		||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 | 
			
		||||
@ -367,7 +414,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 | 
			
		||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 | 
			
		||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 | 
			
		||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 | 
			
		||||
github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI=
 | 
			
		||||
@ -376,16 +422,18 @@ github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9
 | 
			
		||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 | 
			
		||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 | 
			
		||||
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
 | 
			
		||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 | 
			
		||||
github.com/shirou/gopsutil v3.21.10+incompatible h1:AL2kpVykjkqeN+MFe1WcwSBVUjGjvdU8/ubvCuXAjrU=
 | 
			
		||||
github.com/shirou/gopsutil v3.21.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 | 
			
		||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 | 
			
		||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
			
		||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
 | 
			
		||||
@ -401,8 +449,9 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq
 | 
			
		||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 | 
			
		||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
			
		||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
			
		||||
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
 | 
			
		||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 | 
			
		||||
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
 | 
			
		||||
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
@ -432,6 +481,10 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
 | 
			
		||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 | 
			
		||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
 | 
			
		||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 | 
			
		||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 | 
			
		||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
			
		||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 | 
			
		||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
 | 
			
		||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 | 
			
		||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 | 
			
		||||
github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o=
 | 
			
		||||
@ -477,9 +530,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
			
		||||
@ -534,6 +590,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
@ -559,6 +616,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 | 
			
		||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 | 
			
		||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
			
		||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 | 
			
		||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf h1:R150MpwJIv1MpS0N/pc+NhTM8ajzvlmxlY5OYsrevXQ=
 | 
			
		||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
@ -574,6 +632,10 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
@ -591,6 +653,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 | 
			
		||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
@ -601,8 +664,12 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
			
		||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
@ -610,6 +677,7 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
			
		||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
@ -639,10 +707,15 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
			
		||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab h1:rfJ1bsoJQQIAoAxTxB7bme+vHrNkRw8CqfsYh9w54cw=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
@ -674,6 +747,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
 | 
			
		||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
@ -711,7 +785,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
 | 
			
		||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 | 
			
		||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
@ -740,6 +817,12 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
 | 
			
		||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 | 
			
		||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 | 
			
		||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
 | 
			
		||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 | 
			
		||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 | 
			
		||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 | 
			
		||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
 | 
			
		||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 | 
			
		||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 | 
			
		||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
			
		||||
@ -786,11 +869,22 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
 | 
			
		||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
			
		||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 | 
			
		||||
@ -810,13 +904,15 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gG
 | 
			
		||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 | 
			
		||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 | 
			
		||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/ini.v1 v1.64.0 h1:Mj2zXEXcNb5joEiSA0zc3HZpTst/iyjNiR4CN8tDzOg=
 | 
			
		||||
gopkg.in/ini.v1 v1.64.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
@ -835,9 +931,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gorm.io/driver/mysql v1.2.0 h1:l8+9VwjjyzEkw0PNPBOr2JHhLOGVk7XEnl5hk42bcvs=
 | 
			
		||||
gorm.io/driver/mysql v1.2.0/go.mod h1:4RQmTg4okPghdt+kbe6e1bTXIQp7Ny1NnBn/3Z6ghjk=
 | 
			
		||||
gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA=
 | 
			
		||||
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
 | 
			
		||||
gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM=
 | 
			
		||||
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								image/define.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								image/define.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
// Package image ...
 | 
			
		||||
//
 | 
			
		||||
// Description : image ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021-11-29 2:27 下午
 | 
			
		||||
package image
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// TypeGIF ...
 | 
			
		||||
	TypeGIF = "gif"
 | 
			
		||||
	// TypePNG ...
 | 
			
		||||
	TypePNG = "png"
 | 
			
		||||
	// TypeJPG ...
 | 
			
		||||
	TypeJPG = "jpg"
 | 
			
		||||
	// TypeJPEG ...
 | 
			
		||||
	TypeJPEG = "jpeg"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ImgInfo ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:45 下午 2021/11/29
 | 
			
		||||
type ImgInfo struct {
 | 
			
		||||
	Path string
 | 
			
		||||
	Type string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										195
									
								
								image/gif.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								image/gif.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,195 @@
 | 
			
		||||
// Package image ...
 | 
			
		||||
//
 | 
			
		||||
// Description : GIF相关操作
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021-11-29 12:43 下午
 | 
			
		||||
package image
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"image"
 | 
			
		||||
	"image/color"
 | 
			
		||||
	"image/color/palette"
 | 
			
		||||
	"image/draw"
 | 
			
		||||
	"image/gif"
 | 
			
		||||
	"image/jpeg"
 | 
			
		||||
	"image/png"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"git.zhangdeman.cn/zhangdeman/gopkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// GIF ...
 | 
			
		||||
	GIF *GIFConvert
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	GIF = &GIFConvert{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GIFConvert gif转换
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 12:44 下午 2021/11/29
 | 
			
		||||
type GIFConvert struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate 生成
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:13 下午 2021/11/29
 | 
			
		||||
func (g *GIFConvert) Generate(sourceImageList []string, savePath string) error {
 | 
			
		||||
	if len(sourceImageList) == 0 {
 | 
			
		||||
		return errors.New("source image list is empty")
 | 
			
		||||
	}
 | 
			
		||||
	var (
 | 
			
		||||
		err           error
 | 
			
		||||
		formatImgList []ImgInfo
 | 
			
		||||
		disposals     []byte
 | 
			
		||||
		images        []*image.Paletted
 | 
			
		||||
		// 播放速度设置 , 100 次多少秒
 | 
			
		||||
		delays []int
 | 
			
		||||
	)
 | 
			
		||||
	if formatImgList, err = g.checkImageType(sourceImageList); nil != err {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, imgInfo := range formatImgList {
 | 
			
		||||
		img, gErr := g.loadImage(imgInfo)
 | 
			
		||||
		if nil != gErr {
 | 
			
		||||
			return gErr
 | 
			
		||||
		}
 | 
			
		||||
		cp := g.getPalette(img)
 | 
			
		||||
		//cp:=append(palette.WebSafe,color.Transparent)
 | 
			
		||||
		disposals = append(disposals, gif.DisposalBackground) //透明图片需要设置
 | 
			
		||||
		p := image.NewPaletted(image.Rect(0, 0, 640, 996), cp)
 | 
			
		||||
		draw.Draw(p, p.Bounds(), img, image.ZP, draw.Src)
 | 
			
		||||
		images = append(images, p)
 | 
			
		||||
		delays = append(delays, 100)
 | 
			
		||||
	}
 | 
			
		||||
	gifInstance := &gif.GIF{
 | 
			
		||||
		Image:     images,
 | 
			
		||||
		Delay:     delays,
 | 
			
		||||
		LoopCount: -1,
 | 
			
		||||
		Disposal:  disposals,
 | 
			
		||||
	}
 | 
			
		||||
	f, fErr := os.Create(savePath)
 | 
			
		||||
	if fErr != nil {
 | 
			
		||||
		return fErr
 | 
			
		||||
	}
 | 
			
		||||
	defer func() { _ = f.Close() }()
 | 
			
		||||
	return gif.EncodeAll(f, gifInstance)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkImageType 检测图片类型
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:16 下午 2021/11/29
 | 
			
		||||
func (g *GIFConvert) checkImageType(sourceImageList []string) ([]ImgInfo, error) {
 | 
			
		||||
	result := make([]ImgInfo, 0)
 | 
			
		||||
	for _, item := range sourceImageList {
 | 
			
		||||
		imgType := util.GetFileType(item)
 | 
			
		||||
		if len(imgType) == 0 {
 | 
			
		||||
			return result, errors.New(item + " parse img type fail!")
 | 
			
		||||
		}
 | 
			
		||||
		result = append(result, ImgInfo{
 | 
			
		||||
			Path: item,
 | 
			
		||||
			Type: imgType,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadImage 加载图片内容
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:52 下午 2021/11/29
 | 
			
		||||
func (g *GIFConvert) loadImage(imgInfo ImgInfo) (image.Image, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		err error
 | 
			
		||||
		f   *os.File
 | 
			
		||||
	)
 | 
			
		||||
	if f, err = os.Open(imgInfo.Path); nil != err {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = f.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	switch imgInfo.Type {
 | 
			
		||||
	case TypePNG:
 | 
			
		||||
		if img, imgErr := png.Decode(f); nil != err {
 | 
			
		||||
			return nil, imgErr
 | 
			
		||||
		} else {
 | 
			
		||||
			return img, nil
 | 
			
		||||
		}
 | 
			
		||||
	case TypeJPEG:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case TypeJPG:
 | 
			
		||||
		if img, imgErr := jpeg.Decode(f); nil != err {
 | 
			
		||||
			return nil, imgErr
 | 
			
		||||
		} else {
 | 
			
		||||
			return img, nil
 | 
			
		||||
		}
 | 
			
		||||
	case TypeGIF:
 | 
			
		||||
		if img, imgErr := gif.Decode(f); nil != err {
 | 
			
		||||
			return nil, imgErr
 | 
			
		||||
		} else {
 | 
			
		||||
			return img, nil
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errors.New(imgInfo.Type + " is not support!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getPalette ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:01 下午 2021/11/29
 | 
			
		||||
func (g *GIFConvert) getPalette(m image.Image) color.Palette {
 | 
			
		||||
	p := color.Palette{color.RGBA{0x00, 0x00, 0x00, 0x00}}
 | 
			
		||||
	p9 := color.Palette(palette.Plan9)
 | 
			
		||||
	b := m.Bounds()
 | 
			
		||||
	black := false
 | 
			
		||||
	for y := b.Min.Y; y < b.Max.Y; y++ {
 | 
			
		||||
		for x := b.Min.X; x < b.Max.X; x++ {
 | 
			
		||||
			c := m.At(x, y)
 | 
			
		||||
			cc := p9.Convert(c)
 | 
			
		||||
			if cc == p9[0] {
 | 
			
		||||
				black = true
 | 
			
		||||
			}
 | 
			
		||||
			if g.isInPalette(p, cc) == -1 {
 | 
			
		||||
				p = append(p, cc)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(p) < 256 && black == true {
 | 
			
		||||
		p[0] = color.RGBA{0x00, 0x00, 0x00, 0x00} // transparent
 | 
			
		||||
		p = append(p, p9[0])
 | 
			
		||||
	}
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isInPalette ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:01 下午 2021/11/29
 | 
			
		||||
func (g *GIFConvert) isInPalette(p color.Palette, c color.Color) int {
 | 
			
		||||
	ret := -1
 | 
			
		||||
	for i, v := range p {
 | 
			
		||||
		if v == c {
 | 
			
		||||
			return i
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								image/gif_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								image/gif_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
// Package image ...
 | 
			
		||||
//
 | 
			
		||||
// Description : image ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2021-11-29 3:10 下午
 | 
			
		||||
package image
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TestGIFConvert_Generate ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:10 下午 2021/11/29
 | 
			
		||||
func TestGIFConvert_Generate(t *testing.T) {
 | 
			
		||||
	sourceImgList := []string{"./test/1.jpeg", "./test/2.jpeg", "./test/3.jpeg", "./test/4.jpeg"}
 | 
			
		||||
	savePath := "./test/test.gif"
 | 
			
		||||
	fmt.Println(GIF.Generate(sourceImgList, savePath))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								image/test/1.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								image/test/1.jpeg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 153 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								image/test/2.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								image/test/2.jpeg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 192 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								image/test/3.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								image/test/3.jpeg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 142 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								image/test/4.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								image/test/4.jpeg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 826 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								image/test/test.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								image/test/test.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 404 KiB  | 
@ -20,37 +20,26 @@ import (
 | 
			
		||||
	"git.zhangdeman.cn/zhangdeman/gopkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MapDataRule 数据映射结果
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 5:09 PM 2022/1/17
 | 
			
		||||
type MapDataRule struct {
 | 
			
		||||
	MapKey        string
 | 
			
		||||
	DefaultValue  string
 | 
			
		||||
	IsComplexType bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFilter 获取解析的实例
 | 
			
		||||
// NewParseJSONTree 获取解析的实例
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:43 下午 2021/3/14
 | 
			
		||||
func NewFilter(data interface{}, rule map[string]MapDataRule) *Filter {
 | 
			
		||||
func NewFilter(data interface{}, rule map[string]string) *Filter {
 | 
			
		||||
	return &Filter{
 | 
			
		||||
		data: data,
 | 
			
		||||
		rule: rule,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Filter 解析json树
 | 
			
		||||
// ParseJSONTree 解析json树
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:41 下午 2021/3/14
 | 
			
		||||
type Filter struct {
 | 
			
		||||
	data interface{}
 | 
			
		||||
	rule map[string]MapDataRule
 | 
			
		||||
	rule map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Result 数据过滤结果
 | 
			
		||||
@ -65,12 +54,12 @@ func (f *Filter) Result() (*DynamicJSON, error) {
 | 
			
		||||
	result := NewDynamicJSON()
 | 
			
		||||
	byteData, _ := json.Marshal(f.data)
 | 
			
		||||
	source := string(byteData)
 | 
			
		||||
	for extraDataPath, newDataRule := range f.rule {
 | 
			
		||||
	for extraDataPath, newDataPath := range f.rule {
 | 
			
		||||
		// 为数组的处理
 | 
			
		||||
		pathArr := strings.Split(extraDataPath, ".[].")
 | 
			
		||||
		val := gjson.Get(source, pathArr[0])
 | 
			
		||||
		if len(pathArr) == 1 {
 | 
			
		||||
			f.SetValue(result, newDataRule.MapKey, val.Value(), false, 0)
 | 
			
		||||
			f.SetValue(result, newDataPath, val.Value(), false, 0)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// 支持list再抽取一层,处于性能考虑,这么限制,不做递归无限深度处理
 | 
			
		||||
@ -80,11 +69,9 @@ func (f *Filter) Result() (*DynamicJSON, error) {
 | 
			
		||||
			data := item.Map()
 | 
			
		||||
			for _, key := range ketList {
 | 
			
		||||
				if v, exist := data[key]; exist {
 | 
			
		||||
					result.SetValue(strings.ReplaceAll(newDataRule.MapKey, "[]", fmt.Sprintf("[%d]", idx)), data[key].Value(), v.IsObject() || v.IsArray())
 | 
			
		||||
				} else {
 | 
			
		||||
					// 结果集中不存在对应key,设置默认值
 | 
			
		||||
					result.SetValue(strings.ReplaceAll(newDataRule.MapKey, "[]", fmt.Sprintf("[%d]", idx)), newDataRule.DefaultValue, newDataRule.IsComplexType)
 | 
			
		||||
					result.SetValue(strings.ReplaceAll(newDataPath, "[]", fmt.Sprintf("[%d]", idx)), data[key].Raw, v.IsObject() || v.IsArray())
 | 
			
		||||
				}
 | 
			
		||||
				// 结果集中不存在对应key,丢弃
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@
 | 
			
		||||
package json_tool
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
@ -69,19 +68,13 @@ func TestSelect(t *testing.T) {
 | 
			
		||||
		"table": []map[string]interface{}{
 | 
			
		||||
			{"name": "alex", "age": 18, "number": 1},
 | 
			
		||||
			{"name": "bob", "age": 28, "number": 2},
 | 
			
		||||
			{"name": "bob", "age": 28, "number": 2, "list": []int{1, 2, 3}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	rule := map[string]MapDataRule{
 | 
			
		||||
		"name": {
 | 
			
		||||
			MapKey:        "user_name",
 | 
			
		||||
			DefaultValue:  "用户姓名默认值",
 | 
			
		||||
			IsComplexType: false,
 | 
			
		||||
		},
 | 
			
		||||
		"extra.age":     {MapKey: "user_age", DefaultValue: "用户年龄默认值", IsComplexType: false},
 | 
			
		||||
		"extra.height":  {MapKey: "user_height", DefaultValue: "扩展高度默认值", IsComplexType: false},
 | 
			
		||||
		"table.[].name": {MapKey: "slice.[].name_modify", DefaultValue: "列表姓名默认值", IsComplexType: false},
 | 
			
		||||
		"table.[].list": {MapKey: "slice.[].data_list", DefaultValue: "[\"567\",\"678\",\"789\"]", IsComplexType: true},
 | 
			
		||||
	rule := map[string]string{
 | 
			
		||||
		"name":          "user_name",
 | 
			
		||||
		"extra.age":     "user_age",
 | 
			
		||||
		"extra.height":  "user_height",
 | 
			
		||||
		"table.[].name": "slice.[].name",
 | 
			
		||||
	}
 | 
			
		||||
	filter := NewFilter(source, rule)
 | 
			
		||||
	d, e := filter.Result()
 | 
			
		||||
@ -91,85 +84,3 @@ func TestSelect(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(d.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestParse 测试获取JSON数据结构
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:59 PM 2022/1/9
 | 
			
		||||
func TestParse(t *testing.T) {
 | 
			
		||||
	source := map[string]interface{}{
 | 
			
		||||
		"name": "zhangdeman",
 | 
			
		||||
		"extra": map[string]interface{}{
 | 
			
		||||
			"age":    18,
 | 
			
		||||
			"height": 180,
 | 
			
		||||
			"slice":  []int{1, 2, 3},
 | 
			
		||||
			"obj": map[string]interface{}{
 | 
			
		||||
				"la": "aaaa",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"slice": []int{1, 2, 3},
 | 
			
		||||
		"map":   map[string]interface{}{"a": 1, "b": 2, "c": 4},
 | 
			
		||||
		"table": []map[string]interface{}{
 | 
			
		||||
			{"name": "alex", "age": 18, "number": 1, "obj": map[string]interface{}{"enen": "en"}},
 | 
			
		||||
			{"name": "bob", "age": 28, "number": 2},
 | 
			
		||||
		},
 | 
			
		||||
		"two_slice": []map[string]interface{}{
 | 
			
		||||
			{
 | 
			
		||||
				"students": []map[string]interface{}{
 | 
			
		||||
					{
 | 
			
		||||
						"name":  "enen",
 | 
			
		||||
						"age":   18,
 | 
			
		||||
						"score": []float64{1, 2, 3, 45},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"other":     []interface{}{"others"},
 | 
			
		||||
				"read_only": 1,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	byteData, _ := json.Marshal(source)
 | 
			
		||||
	fmt.Println(GetJSONDataStruct(string(byteData)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestParseWithType 测试获取JSON数据结构
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:59 PM 2022/1/9
 | 
			
		||||
func TestParseWithType(t *testing.T) {
 | 
			
		||||
	source := map[string]interface{}{
 | 
			
		||||
		"name": "zhangdeman",
 | 
			
		||||
		"extra": map[string]interface{}{
 | 
			
		||||
			"age":    18,
 | 
			
		||||
			"height": 180,
 | 
			
		||||
			"slice":  []int{1, 2, 3},
 | 
			
		||||
			"obj": map[string]interface{}{
 | 
			
		||||
				"la": "aaaa",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"slice":      []int{1, 2, 3},
 | 
			
		||||
		"map":        map[string]interface{}{"a": 1, "d": 5.5, "e": "qqq"},
 | 
			
		||||
		"empty_obj":  map[string]interface{}{},
 | 
			
		||||
		"empty_list": make([]interface{}, 0),
 | 
			
		||||
		"table": []map[string]interface{}{
 | 
			
		||||
			{"name": "alex", "age": 18, "number": 1, "obj": map[string]interface{}{"enen": "en"}},
 | 
			
		||||
			{"name": "bob", "age": 28, "number": 2},
 | 
			
		||||
		},
 | 
			
		||||
		"two_slice": []map[string]interface{}{
 | 
			
		||||
			{
 | 
			
		||||
				"students": []map[string]interface{}{
 | 
			
		||||
					{
 | 
			
		||||
						"name":  "enen",
 | 
			
		||||
						"age":   18,
 | 
			
		||||
						"score": []float64{1, 2, 3, 45},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"other":     []interface{}{"others"},
 | 
			
		||||
				"read_only": 1,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	byteData, _ := json.Marshal(source)
 | 
			
		||||
	fmt.Println(GetJSONDataStructWithType(string(byteData)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,217 +0,0 @@
 | 
			
		||||
// Package json_tool ...
 | 
			
		||||
//
 | 
			
		||||
// Description : json_tool ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022-01-09 10:48 PM
 | 
			
		||||
package json_tool
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/tidwall/gjson"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Field ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/10 10:47 PM
 | 
			
		||||
type Field struct {
 | 
			
		||||
	Path    string `json:"path"`    // 路径
 | 
			
		||||
	Type    string `json:"type"`    // 类型
 | 
			
		||||
	Example string `json:"example"` // 示例值
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// FieldTypeInt ...
 | 
			
		||||
	FieldTypeInt = "int64"
 | 
			
		||||
	// FieldTypeIntSlice ...
 | 
			
		||||
	FieldTypeIntSlice = "[]int64"
 | 
			
		||||
	// FieldTypeFloat ...
 | 
			
		||||
	FieldTypeFloat = "float64"
 | 
			
		||||
	// FieldTypeFloatSlice ...
 | 
			
		||||
	FieldTypeFloatSlice = "[]float64"
 | 
			
		||||
	// FieldTypeBool ...
 | 
			
		||||
	FieldTypeBool = "bool"
 | 
			
		||||
	// FieldTypeBoolSlice ...
 | 
			
		||||
	FieldTypeBoolSlice = "[]bool"
 | 
			
		||||
	// FieldTypeString ...
 | 
			
		||||
	FieldTypeString = "string"
 | 
			
		||||
	// FieldTypeStringSLice ...
 | 
			
		||||
	FieldTypeStringSLice = "string"
 | 
			
		||||
	// FieldTypeAny ...
 | 
			
		||||
	FieldTypeAny = "interface{}"
 | 
			
		||||
	// FieldTypeAnySlice ...
 | 
			
		||||
	FieldTypeAnySlice = "[]interface{}"
 | 
			
		||||
	// FieldTypeSlice ...
 | 
			
		||||
	FieldTypeSlice = "[]interface{}"
 | 
			
		||||
	// FieldTypeMap ...
 | 
			
		||||
	FieldTypeMap = "map"
 | 
			
		||||
	// FieldTypeMapSlice ...
 | 
			
		||||
	FieldTypeMapSlice = "[]map"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetJSONDataStruct 获取JSON数据的结构
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:53 PM 2022/1/9
 | 
			
		||||
func GetJSONDataStruct(data string) ([]string, error) {
 | 
			
		||||
	if !gjson.Valid(data) {
 | 
			
		||||
		return make([]string, 0), errors.New("JSON format is invalid")
 | 
			
		||||
	}
 | 
			
		||||
	pathList := make([]string, 0)
 | 
			
		||||
	r := gjson.Parse(data)
 | 
			
		||||
	r.ForEach(func(key, value gjson.Result) bool {
 | 
			
		||||
		if value.Value() == nil {
 | 
			
		||||
			pathList = append(pathList, key.String())
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if value.IsObject() {
 | 
			
		||||
			if value.String() == "{}" {
 | 
			
		||||
				pathList = append(pathList, key.String())
 | 
			
		||||
			} else {
 | 
			
		||||
				list, _ := GetJSONDataStruct(value.String())
 | 
			
		||||
				for _, k := range list {
 | 
			
		||||
					pathList = append(pathList, key.String()+"."+k)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if value.IsArray() {
 | 
			
		||||
			dataList := value.Array()
 | 
			
		||||
			if len(dataList) > 0 {
 | 
			
		||||
				if !dataList[0].IsObject() && !dataList[0].IsArray() {
 | 
			
		||||
					pathList = append(pathList, key.String())
 | 
			
		||||
				} else {
 | 
			
		||||
					list, _ := GetJSONDataStruct(dataList[0].String())
 | 
			
		||||
					for _, k := range list {
 | 
			
		||||
						pathList = append(pathList, key.String()+".[]."+k)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				pathList = append(pathList, key.String())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !value.IsObject() && !value.IsArray() {
 | 
			
		||||
			pathList = append(pathList, key.String())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
	return pathList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetJSONDataStructWithType 获取数据结构,并获取类型
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/10 10:47 PM
 | 
			
		||||
func GetJSONDataStructWithType(data string) ([]Field, error) {
 | 
			
		||||
	if !gjson.Valid(data) {
 | 
			
		||||
		return make([]Field, 0), errors.New("JSON format is invalid")
 | 
			
		||||
	}
 | 
			
		||||
	pathList := make([]Field, 0)
 | 
			
		||||
	r := gjson.Parse(data)
 | 
			
		||||
	r.ForEach(func(key, value gjson.Result) bool {
 | 
			
		||||
		if value.Value() == nil {
 | 
			
		||||
			pathList = append(pathList, Field{
 | 
			
		||||
				Path:    key.String(),
 | 
			
		||||
				Type:    FieldTypeAny,
 | 
			
		||||
				Example: "nil",
 | 
			
		||||
			})
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if value.IsObject() {
 | 
			
		||||
			if value.String() == "{}" {
 | 
			
		||||
				pathList = append(pathList, Field{
 | 
			
		||||
					Path:    key.String(),
 | 
			
		||||
					Type:    FieldTypeMap,
 | 
			
		||||
					Example: "{}",
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				list, _ := GetJSONDataStructWithType(value.String())
 | 
			
		||||
				for _, field := range list {
 | 
			
		||||
					pathList = append(pathList, Field{
 | 
			
		||||
						Path:    key.String() + "." + field.Path,
 | 
			
		||||
						Type:    field.Type,
 | 
			
		||||
						Example: field.Example,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if value.IsArray() {
 | 
			
		||||
			dataList := value.Array()
 | 
			
		||||
			if len(dataList) > 0 {
 | 
			
		||||
				if !dataList[0].IsObject() && !dataList[0].IsArray() {
 | 
			
		||||
					pathList = append(pathList, Field{
 | 
			
		||||
						Path:    key.String(),
 | 
			
		||||
						Type:    "[]" + GetDataType(dataList[0]),
 | 
			
		||||
						Example: value.String(),
 | 
			
		||||
					})
 | 
			
		||||
				} else {
 | 
			
		||||
					list, _ := GetJSONDataStructWithType(dataList[0].String())
 | 
			
		||||
					for _, field := range list {
 | 
			
		||||
						pathList = append(pathList, Field{
 | 
			
		||||
							Path:    key.String() + ".[]." + field.Path,
 | 
			
		||||
							Type:    field.Type,
 | 
			
		||||
							Example: field.Example,
 | 
			
		||||
						})
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				pathList = append(pathList, Field{
 | 
			
		||||
					Path:    key.String(),
 | 
			
		||||
					Type:    FieldTypeSlice,
 | 
			
		||||
					Example: "[]",
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !value.IsObject() && !value.IsArray() {
 | 
			
		||||
			pathList = append(pathList, Field{
 | 
			
		||||
				Path:    key.String(),
 | 
			
		||||
				Type:    GetDataType(value),
 | 
			
		||||
				Example: value.String(),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
	return pathList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDataType 获取数据类型
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/10 11:00 PM
 | 
			
		||||
func GetDataType(value gjson.Result) string {
 | 
			
		||||
	switch value.Type.String() {
 | 
			
		||||
	default:
 | 
			
		||||
		return FieldTypeAny
 | 
			
		||||
	case "Null":
 | 
			
		||||
		return FieldTypeAny
 | 
			
		||||
	case "False":
 | 
			
		||||
		return FieldTypeBool
 | 
			
		||||
	case "True":
 | 
			
		||||
		return FieldTypeBool
 | 
			
		||||
	case "Number":
 | 
			
		||||
		if strings.Contains(value.String(), ".") {
 | 
			
		||||
			return FieldTypeFloat
 | 
			
		||||
		}
 | 
			
		||||
		return FieldTypeInt
 | 
			
		||||
	case "String":
 | 
			
		||||
		return FieldTypeString
 | 
			
		||||
	case "JSON":
 | 
			
		||||
		if strings.HasPrefix(strings.TrimSpace(value.String()), "[") {
 | 
			
		||||
			return FieldTypeSlice
 | 
			
		||||
		}
 | 
			
		||||
		return FieldTypeMap
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -10,11 +10,8 @@ package wrapper
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
 | 
			
		||||
	"go.uber.org/zap/zapcore"
 | 
			
		||||
@ -50,36 +47,15 @@ func NewGormV2(loggerLevel zapcore.Level, consoleOutput bool, encoder zapcore.En
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewGormLoggerWithInstance 获取gorm日志实现
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:36 PM 2021/12/24
 | 
			
		||||
func NewGormLoggerWithInstance(ctx *gin.Context, dbClient *gorm.DB, instance *zap.Logger, node string, extraCtxFieldList []string) logger.Interface {
 | 
			
		||||
	return &Gorm{
 | 
			
		||||
		dbClient:          dbClient,
 | 
			
		||||
		instance:          instance,
 | 
			
		||||
		traceIDField:      "",
 | 
			
		||||
		extraCtxFieldList: extraCtxFieldList,
 | 
			
		||||
		flag:              "",
 | 
			
		||||
		node:              node,
 | 
			
		||||
		ctx:               ctx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Gorm v2 版本库日志实现
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 9:55 下午 2021/3/1
 | 
			
		||||
type Gorm struct {
 | 
			
		||||
	dbClient          *gorm.DB
 | 
			
		||||
	instance          *zap.Logger  // 日志实例
 | 
			
		||||
	traceIDField      string       // 串联请求上下文的的ID
 | 
			
		||||
	extraCtxFieldList []string     // 从请求上线问提取的字段
 | 
			
		||||
	flag              string       // 数据库标识
 | 
			
		||||
	node              string       // 数据库节点 master / slave
 | 
			
		||||
	ctx               *gin.Context // gin上下文
 | 
			
		||||
	instance     *zap.Logger // 日志实例
 | 
			
		||||
	traceIDField string      // 串联请求上下文的的ID
 | 
			
		||||
	flag         string      // 数据库标识
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogMode ...
 | 
			
		||||
@ -97,7 +73,12 @@ func (g *Gorm) LogMode(level logger.LogLevel) logger.Interface {
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:18 下午 2021/3/1
 | 
			
		||||
func (g *Gorm) Info(ctx context.Context, s string, i ...interface{}) {
 | 
			
		||||
	g.write(nil, "info")
 | 
			
		||||
	g.instance.Info(
 | 
			
		||||
		"Info日志",
 | 
			
		||||
		zap.String(g.traceIDField, g.getTraceID(ctx)),
 | 
			
		||||
		zap.String("db_flag", g.flag),
 | 
			
		||||
		zap.String("message", fmt.Sprintf(s, i...)),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Warn ...
 | 
			
		||||
@ -106,7 +87,12 @@ func (g *Gorm) Info(ctx context.Context, s string, i ...interface{}) {
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:16 下午 2021/3/1
 | 
			
		||||
func (g *Gorm) Warn(ctx context.Context, s string, i ...interface{}) {
 | 
			
		||||
	g.write(nil, "warn")
 | 
			
		||||
	g.instance.Warn(
 | 
			
		||||
		"SQL执行产生Warning",
 | 
			
		||||
		zap.String(g.traceIDField, g.getTraceID(ctx)),
 | 
			
		||||
		zap.String("db_flag", g.flag),
 | 
			
		||||
		zap.String("message", fmt.Sprintf(s, i...)),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error 日志
 | 
			
		||||
@ -115,7 +101,12 @@ func (g *Gorm) Warn(ctx context.Context, s string, i ...interface{}) {
 | 
			
		||||
//
 | 
			
		||||
// Date : 10:18 下午 2021/3/1
 | 
			
		||||
func (g *Gorm) Error(ctx context.Context, s string, i ...interface{}) {
 | 
			
		||||
	g.write(nil, "error")
 | 
			
		||||
	g.instance.Warn(
 | 
			
		||||
		"SQL执行产生Error",
 | 
			
		||||
		zap.String(g.traceIDField, g.getTraceID(ctx)),
 | 
			
		||||
		zap.String("db_flag", g.flag),
 | 
			
		||||
		zap.String("message", fmt.Sprintf(s, i...)),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Trace Trace 记录
 | 
			
		||||
@ -132,7 +123,8 @@ func (g *Gorm) Trace(ctx context.Context, begin time.Time, fc func() (string, in
 | 
			
		||||
		sql, affectRows = fc()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dataList := []zap.Field{
 | 
			
		||||
	g.instance.Info(
 | 
			
		||||
		"SQL执行记录",
 | 
			
		||||
		zap.String(g.traceIDField, g.getTraceID(ctx)),
 | 
			
		||||
		zap.String("db_flag", g.flag),
 | 
			
		||||
		zap.Int64("begin_time", start),
 | 
			
		||||
@ -141,45 +133,7 @@ func (g *Gorm) Trace(ctx context.Context, begin time.Time, fc func() (string, in
 | 
			
		||||
		zap.String("sql", sql),
 | 
			
		||||
		zap.Int64("affect_rows", affectRows),
 | 
			
		||||
		zap.Error(err),
 | 
			
		||||
	}
 | 
			
		||||
	g.write(dataList, "info")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// write ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 4:11 PM 2021/12/24
 | 
			
		||||
func (g *Gorm) write(dataList []zap.Field, level string) {
 | 
			
		||||
	if nil == g.instance {
 | 
			
		||||
		// 未设置日志实例
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if nil == dataList {
 | 
			
		||||
		dataList = make([]zap.Field, 0)
 | 
			
		||||
	}
 | 
			
		||||
	if nil != g.ctx {
 | 
			
		||||
		for _, extraField := range g.extraCtxFieldList {
 | 
			
		||||
			dataList = append(dataList, zap.Any(extraField, g.ctx.Value(extraField)))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 补齐 flag、node
 | 
			
		||||
	sql := g.dbClient.Dialector.Explain(g.dbClient.Statement.SQL.String(), g.dbClient.Statement.Vars...)
 | 
			
		||||
	affectRows := g.dbClient.RowsAffected
 | 
			
		||||
	dataList = append(dataList, zap.String("db_node", g.node), zap.String("db_flag", g.flag), zap.String("execute_sql", sql), zap.Int64("affect_rows", affectRows))
 | 
			
		||||
	message := "SQL执行记录"
 | 
			
		||||
	switch strings.ToLower(level) {
 | 
			
		||||
	case "info":
 | 
			
		||||
		g.instance.Info(message, dataList...)
 | 
			
		||||
	case "warn":
 | 
			
		||||
		g.instance.Warn(message, dataList...)
 | 
			
		||||
	case "error":
 | 
			
		||||
		g.instance.Error(message, dataList...)
 | 
			
		||||
	default:
 | 
			
		||||
		g.instance.Info(message, dataList...)
 | 
			
		||||
	}
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getTraceID 获取traceID
 | 
			
		||||
@ -191,7 +145,7 @@ func (g *Gorm) getTraceID(ctx context.Context) string {
 | 
			
		||||
	return fmt.Sprintf("%v", ctx.Value(g.traceIDField))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetGormSQL 获取trace fn
 | 
			
		||||
// GetGormSQL 获取tracefn
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
@ -10,107 +10,14 @@ package mysql
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"git.zhangdeman.cn/zhangdeman/gopkg/logger/wrapper"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
 | 
			
		||||
	"git.zhangdeman.cn/zhangdeman/gopkg/logger"
 | 
			
		||||
	gormLogger "gorm.io/gorm/logger"
 | 
			
		||||
 | 
			
		||||
	"git.zhangdeman.cn/zhangdeman/gopkg/logger/wrapper"
 | 
			
		||||
 | 
			
		||||
	"gorm.io/driver/mysql"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewDBClient ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:09 PM 2021/12/24
 | 
			
		||||
func NewDBClient(masterConf *DBConfig, slaveConf *DBConfig, logConf *LogConfig, loggerInstance *zap.Logger, extraRequestFieldList []string) (*DBClient, error) {
 | 
			
		||||
	client := &DBClient{
 | 
			
		||||
		extraFieldList: extraRequestFieldList,
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	// 日志初始化失败
 | 
			
		||||
	if client.loggerInstance, err = getLogInstance(logConf, loggerInstance); nil != err {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if client.master, err = GetDatabaseClient(masterConf, nil); nil != err {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if client.slave, err = GetDatabaseClient(slaveConf, nil); nil != err {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DBClient 包装日志实例
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:09 PM 2021/12/24
 | 
			
		||||
type DBClient struct {
 | 
			
		||||
	loggerInstance *zap.Logger
 | 
			
		||||
	master         *gorm.DB
 | 
			
		||||
	slave          *gorm.DB
 | 
			
		||||
	extraFieldList []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMaster 获取主库连接
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:28 PM 2021/12/24
 | 
			
		||||
func (dc *DBClient) GetMaster(ctx *gin.Context) *gorm.DB {
 | 
			
		||||
	session := dc.master.Session(&gorm.Session{})
 | 
			
		||||
	session.Logger = dc.getLogger(ctx, session, "slave")
 | 
			
		||||
	return session
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSlave 获取从库链接
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:29 PM 2021/12/24
 | 
			
		||||
func (dc *DBClient) GetSlave(ctx *gin.Context) *gorm.DB {
 | 
			
		||||
	session := dc.slave.Session(&gorm.Session{})
 | 
			
		||||
	session.Logger = dc.getLogger(ctx, session, "slave")
 | 
			
		||||
	return session
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getLogger 获取日志实例
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:45 PM 2021/12/24
 | 
			
		||||
func (dc *DBClient) getLogger(ctx *gin.Context, dbClient *gorm.DB, node string) gormLogger.Interface {
 | 
			
		||||
	return wrapper.NewGormLoggerWithInstance(ctx, dbClient, dc.loggerInstance, node, dc.extraFieldList)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getLogInstance 获取日志实例
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 3:20 PM 2021/12/24
 | 
			
		||||
func getLogInstance(logConf *LogConfig, loggerInstance *zap.Logger) (*zap.Logger, error) {
 | 
			
		||||
	if nil != loggerInstance {
 | 
			
		||||
		return loggerInstance, nil
 | 
			
		||||
	}
 | 
			
		||||
	logConfList := []logger.SetLoggerOptionFunc{logger.WithEncoder(logConf.Encoder), logger.WithCallerSkip(logConf.Skip), logger.WithCaller()}
 | 
			
		||||
	if logConf.ConsoleOutput {
 | 
			
		||||
		logConfList = append(logConfList, logger.WithConsoleOutput())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	if loggerInstance, err = logger.NewLogger(logConf.Level, logConf.SplitConfig, logConfList...); nil != err {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return loggerInstance, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDatabaseClient 获取日志实例
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
@ -118,22 +25,35 @@ func getLogInstance(logConf *LogConfig, loggerInstance *zap.Logger) (*zap.Logger
 | 
			
		||||
// Date : 10:49 下午 2021/3/1
 | 
			
		||||
func GetDatabaseClient(conf *DBConfig, logConf *LogConfig) (*gorm.DB, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		instance       *gorm.DB
 | 
			
		||||
		err            error
 | 
			
		||||
		loggerInstance *zap.Logger
 | 
			
		||||
		instance *gorm.DB
 | 
			
		||||
		err      error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if instance, err = gorm.Open(mysql.Open(buildConnectionDSN(conf)), &gorm.Config{}); nil != err {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if nil != logConf {
 | 
			
		||||
		if loggerInstance, err = getLogInstance(logConf, nil); nil != err {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		instance.Logger = wrapper.NewGormLoggerWithInstance(nil, instance, loggerInstance, "", nil)
 | 
			
		||||
	if len(logConf.TraceFieldName) == 0 {
 | 
			
		||||
		logConf.TraceFieldName = defaultTraceFieldName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	splitConfigFuncList := []logger.SetRotateLogConfigFunc{
 | 
			
		||||
		logger.WithTimeIntervalType(logConf.SplitConfig.TimeIntervalType),
 | 
			
		||||
		logger.WithDivisionChar(logConf.SplitConfig.DivisionChar),
 | 
			
		||||
		logger.WithMaxAge(logConf.SplitConfig.MaxAge),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	splitConfig, _ := logger.NewRotateLogConfig(logConf.SplitConfig.LogPath, logConf.SplitConfig.LogFileName, splitConfigFuncList...)
 | 
			
		||||
 | 
			
		||||
	if instance.Logger, err = wrapper.NewGormV2(
 | 
			
		||||
		logConf.Level,
 | 
			
		||||
		logConf.ConsoleOutput,
 | 
			
		||||
		logConf.Encoder,
 | 
			
		||||
		splitConfig,
 | 
			
		||||
		logConf.TraceFieldName,
 | 
			
		||||
		logConf.Skip); nil != err {
 | 
			
		||||
		return nil, CreateDBLogError(err)
 | 
			
		||||
	}
 | 
			
		||||
	return instance, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -42,3 +42,8 @@ type LogConfig struct {
 | 
			
		||||
	TraceFieldName   string
 | 
			
		||||
	Skip             int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// defaultTraceFieldName 默认trace_id字段
 | 
			
		||||
	defaultTraceFieldName = "trace_id"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
// Package oauth ...
 | 
			
		||||
//
 | 
			
		||||
// Description : oauth ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/01/17 12:20 AM
 | 
			
		||||
package oauth
 | 
			
		||||
 | 
			
		||||
// Config OAuth相关配置
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:20 AM
 | 
			
		||||
type Config struct {
 | 
			
		||||
	ClientID               string                `json:"client_id"`                 // 授权的client id
 | 
			
		||||
	ClientSecret           string                `json:"client_secret"`             // 授权的client_secret
 | 
			
		||||
	CallbackURL            string                `json:"callback_url"`              // 授权后携带code回调的地址
 | 
			
		||||
	Domain                 string                `json:"domain"`                    // 服务器域名
 | 
			
		||||
	AuthorizationPageURI   string                `json:"authorization_page_uri"`    // 授权页面URI
 | 
			
		||||
	GetAccessTokenURI      string                `json:"get_access_token_uri"`      // 获取access token地址
 | 
			
		||||
	ValidateAccessTokenURI string                `json:"validate_access_token_uri"` // 验证access_token地址
 | 
			
		||||
	AccessTokenKey         string                `json:"access_token_key"`          // 验证access token时的key
 | 
			
		||||
	UseAuthorization       bool                  `json:"use_authorization"`         // 是否使用header中的Authorization
 | 
			
		||||
	FormatAccessTokenFunc  FormatAccessTokenFunc `json:"format_access_token_func"`  // 格式化token的函数
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatAccessTokenFunc 格式化token
 | 
			
		||||
type FormatAccessTokenFunc func(token string) string
 | 
			
		||||
							
								
								
									
										128
									
								
								oauth/oauth.go
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								oauth/oauth.go
									
									
									
									
									
								
							@ -1,128 +0,0 @@
 | 
			
		||||
// Package oauth ...
 | 
			
		||||
//
 | 
			
		||||
// Description : oauth ...
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<张德满>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/01/17 12:18 AM
 | 
			
		||||
package oauth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/ddliu/go-httpclient"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewOAuth 获取OAuth实例
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:19 AM
 | 
			
		||||
func NewOAuth(cfg Config) *OAuth {
 | 
			
		||||
	return &OAuth{
 | 
			
		||||
		cfg: cfg,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OAuth 管理
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:19 AM
 | 
			
		||||
type OAuth struct {
 | 
			
		||||
	cfg Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAuthorizationPageURL 获取授权页面的URL
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:33 AM
 | 
			
		||||
func (o *OAuth) GetAuthorizationPageURL(scope string, responseType string) string {
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%s%s?client_id=%s&redirect_uri=%s&scope=%s&response_type=%s",
 | 
			
		||||
		o.cfg.Domain,
 | 
			
		||||
		o.cfg.AuthorizationPageURI,
 | 
			
		||||
		o.cfg.ClientID,
 | 
			
		||||
		url.QueryEscape(o.cfg.CallbackURL),
 | 
			
		||||
		scope,
 | 
			
		||||
		responseType,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ApplyAccessTokenByCode 通过code申请授权token
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:35 AM
 | 
			
		||||
func (o *OAuth) ApplyAccessTokenByCode(code string, receiver interface{}) error {
 | 
			
		||||
	// OAuth 授权回调
 | 
			
		||||
	parameter := map[string]string{
 | 
			
		||||
		"code":          code,
 | 
			
		||||
		"client_id":     o.cfg.ClientID,
 | 
			
		||||
		"client_secret": o.cfg.ClientSecret,
 | 
			
		||||
		"grant_type":    "authorization_code",
 | 
			
		||||
	}
 | 
			
		||||
	client := httpclient.NewHttpClient()
 | 
			
		||||
	client.WithHeader("Accept", "application/json")
 | 
			
		||||
	resp, err := client.Post(o.cfg.Domain+o.cfg.GetAccessTokenURI+"?grant_type=authorization_code", parameter)
 | 
			
		||||
	if nil != err {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return fmt.Errorf("【%v】%v", resp.StatusCode, resp.Status)
 | 
			
		||||
	}
 | 
			
		||||
	body, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if nil != err {
 | 
			
		||||
		return errors.New("读取响应体失败: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	decoder := json.NewDecoder(bytes.NewReader(body))
 | 
			
		||||
	decoder.UseNumber()
 | 
			
		||||
	return decoder.Decode(receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateAccessToken 验证授权token
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:43 AM
 | 
			
		||||
func (o *OAuth) ValidateAccessToken(token string, receiver interface{}) error {
 | 
			
		||||
	if nil != o.cfg.FormatAccessTokenFunc {
 | 
			
		||||
		token = o.cfg.FormatAccessTokenFunc(token)
 | 
			
		||||
	}
 | 
			
		||||
	parameter := make(map[string]string)
 | 
			
		||||
	client := httpclient.NewHttpClient()
 | 
			
		||||
	client.WithHeader("Accept", "application/json")
 | 
			
		||||
	if o.cfg.UseAuthorization {
 | 
			
		||||
		client.WithHeader("Authorization", "token "+token)
 | 
			
		||||
	} else {
 | 
			
		||||
		parameter[o.cfg.AccessTokenKey] = token
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := client.Get("https://git.zhangdeman.cn/api/v1/user?grant_type=authorization_code", parameter)
 | 
			
		||||
	if nil != err {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	body, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if nil != err {
 | 
			
		||||
		return errors.New("读取响应体失败 : " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	decoder := json.NewDecoder(bytes.NewReader(body))
 | 
			
		||||
	decoder.UseNumber()
 | 
			
		||||
	return decoder.Decode(receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserDetailByToken 根据 token 获取用户详情
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2022/1/17 12:55 AM
 | 
			
		||||
func (o *OAuth) GetUserDetailByToken(token string, receiver interface{}) error {
 | 
			
		||||
	return o.ValidateAccessToken(token, receiver)
 | 
			
		||||
}
 | 
			
		||||
@ -8,11 +8,13 @@
 | 
			
		||||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httputil"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -22,30 +24,72 @@ import (
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:08 下午 2021/8/6
 | 
			
		||||
func Forward(rw http.ResponseWriter, req *http.Request, serverConfig *Server) {
 | 
			
		||||
	fmt.Printf("Received request %s %s %s\n", req.Method, req.Host, req.RemoteAddr)
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(serverConfig.URI, "/") {
 | 
			
		||||
		serverConfig.URI = "/" + serverConfig.URI
 | 
			
		||||
	transport := http.DefaultTransport
 | 
			
		||||
 | 
			
		||||
	// step 1
 | 
			
		||||
	outReq := new(http.Request)
 | 
			
		||||
	*outReq = *req // this only does shallow copies of maps
 | 
			
		||||
 | 
			
		||||
	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
 | 
			
		||||
		if prior, ok := outReq.Header["X-Forwarded-For"]; ok {
 | 
			
		||||
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
 | 
			
		||||
		}
 | 
			
		||||
		outReq.Header.Set("X-Forwarded-For", clientIP)
 | 
			
		||||
	}
 | 
			
		||||
	// 请求重写方法
 | 
			
		||||
	director := func(req *http.Request) {
 | 
			
		||||
		req.URL.Scheme = serverConfig.Scheme
 | 
			
		||||
		// req.URL.Host = projectDetail.GetProjectDetail().Domain + ":" + fmt.Sprintf("%v", projectDetail.GetProjectDetail().Port)
 | 
			
		||||
		// req.Host = projectDetail.GetProjectDetail().Domain + ":" + fmt.Sprintf("%v", projectDetail.GetProjectDetail().Port)
 | 
			
		||||
		req.Host = serverConfig.Host
 | 
			
		||||
		req.URL.Host = serverConfig.Host
 | 
			
		||||
		req.URL.Path = serverConfig.URI
 | 
			
		||||
		req.RequestURI = serverConfig.URI
 | 
			
		||||
		// 写入重写的请求Header
 | 
			
		||||
		for k, v := range serverConfig.RewriteRequestHeader {
 | 
			
		||||
			req.Header.Set(k, v)
 | 
			
		||||
 | 
			
		||||
	// 写入重写的请求Header
 | 
			
		||||
	for k, v := range serverConfig.RewriteRequestHeader {
 | 
			
		||||
		outReq.Header.Set(k, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 重写请求地址
 | 
			
		||||
	outReq.Host = serverConfig.Host
 | 
			
		||||
	outReq.URL.Path = serverConfig.URI
 | 
			
		||||
	outReq.URL.Scheme = serverConfig.Scheme
 | 
			
		||||
	outReq.URL.Host = serverConfig.Host
 | 
			
		||||
 | 
			
		||||
	// step 2
 | 
			
		||||
	res, err := transport.RoundTrip(outReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		rw.WriteHeader(http.StatusBadGateway)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// step 3
 | 
			
		||||
	for key, value := range res.Header {
 | 
			
		||||
		for _, v := range value {
 | 
			
		||||
			if strings.ToLower(key) == "content-encoding" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			rw.Header().Add(key, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// TODO : 重写响应数据
 | 
			
		||||
	modifyResponseFunc := func(rep *http.Response) error {
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	rw.WriteHeader(res.StatusCode)
 | 
			
		||||
 | 
			
		||||
	// 重写请求header
 | 
			
		||||
	for k, v := range serverConfig.RewriteResponseHeader {
 | 
			
		||||
		rw.Header().Set(k, v)
 | 
			
		||||
	}
 | 
			
		||||
	p := &httputil.ReverseProxy{Director: director, ModifyResponse: modifyResponseFunc}
 | 
			
		||||
	p.ServeHTTP(rw, req)
 | 
			
		||||
 | 
			
		||||
	defer res.Body.Close()
 | 
			
		||||
 | 
			
		||||
	// 重写响应数据
 | 
			
		||||
	if !strings.Contains(strings.ToLower(res.Header.Get("Content-Type")), "application/json") || nil == serverConfig.RewriteResponseData || len(serverConfig.RewriteResponseData) == 0 {
 | 
			
		||||
		_, _ = io.Copy(rw, res.Body)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var (
 | 
			
		||||
		responseData []byte
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	responseData, err = getResponseData(res)
 | 
			
		||||
	fmt.Println(string(responseData), err)
 | 
			
		||||
 | 
			
		||||
	bytesBuffer := bytes.NewReader([]byte(`{"data":{"permission":true}}`))
 | 
			
		||||
	_, _ = io.Copy(rw, bytesBuffer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getResultCompressType 获取返回结果的压缩方式
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@
 | 
			
		||||
package sql2go
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"git.zhangdeman.cn/zhangdeman/gopkg/util"
 | 
			
		||||
@ -55,10 +54,7 @@ func ParseCreateTableSql(sql string) (string, *BasicTableInfo, error) {
 | 
			
		||||
		return "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, ok := stmt.(*sqlparser.DDL)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", nil, errors.New("input sql is not ddl")
 | 
			
		||||
	}
 | 
			
		||||
	r := stmt.(*sqlparser.DDL)
 | 
			
		||||
	basic.TableName = sqlparser.String(r.NewName)
 | 
			
		||||
	basic.ModelStruct = util.SnakeCaseToCamel(basic.TableName)
 | 
			
		||||
	structResult := "type " + basic.ModelStruct + " struct { \n"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								util/file.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								util/file.go
									
									
									
									
									
								
							@ -85,3 +85,16 @@ func IsFileExist(filePath string) (bool, bool) {
 | 
			
		||||
	f, err := os.Stat(filePath)
 | 
			
		||||
	return nil == err || os.IsExist(err), (nil == err || os.IsExist(err)) && !f.IsDir()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFileType 获取文件类型
 | 
			
		||||
//
 | 
			
		||||
// Author : go_developer@163.com<白茶清欢>
 | 
			
		||||
//
 | 
			
		||||
// Date : 2:30 下午 2021/11/29
 | 
			
		||||
func GetFileType(fileName string) string {
 | 
			
		||||
	fileArr := strings.Split(fileName, ".")
 | 
			
		||||
	if len(fileArr) < 2 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return strings.ToLower(fileArr[len(fileArr)-1])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user