Gin API 文档

package gin

import "gopkg.in/gin-gonic/gin.v1"

Index

Package Files

auth.go context.go debug.go deprecated.go errors.go fs.go gin.go logger.go mode.go path.go recovery.goresponse_writer.go routergroup.go tree.go utils.go

Constants

const (
    MIMEJSON              = binding.MIMEJSON
    MIMEHTML              = binding.MIMEHTML
    MIMEXML               = binding.MIMEXML
    MIMEXML2              = binding.MIMEXML2
    MIMEPlain             = binding.MIMEPlain
    MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm )

Content-Type MIME of the most common data formats

const (
    DebugMode   string = "debug"
    ReleaseMode string = "release"
    TestMode    string = "test"
)
const AuthUserKey = "user"
const BindKey = "_gin-gonic/gin/bindkey"
const ENV_GIN_MODE = "GIN_MODE"
const Version = "v1.0rc2"

Version is Framework's version

Variables

var DefaultErrorWriter io.Writer = os.Stderr
var DefaultWriter io.Writer = os.Stdout

DefaultWriter is the default io.Writer used the Gin for debug output and middleware output like Logger() or Recovery(). Note that both Logger and Recovery provides custom ways to configure their output io.Writer. To support coloring in Windows use:

import "github.com/mattn/go-colorable"
gin.DefaultWriter = colorable.NewColorableStdout()

func Dir

func Dir(root string, listDirectory bool) http.FileSystem

Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally in router.Static(). if listDirectory == true, then it works the same as http.Dir() otherwise it returns a filesystem that prevents http.FileServer() to list the directory files.

func DisableBindValidation

func DisableBindValidation()

func IsDebugging

func IsDebugging() bool

IsDebugging returns true if the framework is running in debug mode. Use SetMode(gin.Release) to switch to disable the debug mode.

func Mode

func Mode() string

func SetMode

func SetMode(value string)

type Accounts

type Accounts map[string]string

type Context

type Context struct {
    Request *http.Request
    Writer  ResponseWriter

    Params Params

    Keys     map[string]interface{}
    Errors   errorMsgs
    Accepted []string // contains filtered or unexported fields }

Context is the most important part of gin. It allows us to pass variables between middleware, manage the flow, validate the JSON of a request and render a JSON response for example.

func (*Context) Abort
func (c *Context) Abort()

Abort prevents pending handlers from being called. Note that this will not stop the current handler. Let's say you have an authorization middleware that validates that the current request is authorized. If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers for this request are not called.

func (*Context) AbortWithError
func (c *Context) AbortWithError(code int, err error) *Error

AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and pushes the specified error to `c.Errors`. See Context.Error() for more details.

func (*Context) AbortWithStatus
func (c *Context) AbortWithStatus(code int)

AbortWithStatus calls `Abort()` and writes the headers with the specified status code. For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401).

func (*Context) Bind
func (c *Context) Bind(obj interface{}) error

Bind checks the Content-Type to select a binding engine automatically, Depending the "Content-Type" header different bindings are used:

"application/json" --> JSON binding
"application/xml"  --> XML binding

otherwise --> returns an error It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer. Like ParseBody() but this method also writes a 400 error if the json is not valid.

func (*Context) BindJSON
func (c *Context) BindJSON(obj interface{}) error

BindJSON is a shortcut for c.BindWith(obj, binding.JSON)

func (*Context) BindWith
func (c *Context) BindWith(obj interface{}, b binding.Binding) error

BindWith binds the passed struct pointer using the specified binding engine. See the binding package.

func (*Context) ClientIP
func (c *Context) ClientIP() string

ClientIP implements a best effort algorithm to return the real client IP, it parses X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.

func (*Context) ContentType
func (c *Context) ContentType() string

ContentType returns the Content-Type header of the request.

func (*Context) Cookie
func (c *Context) Cookie(name string) (string, error)
func (*Context) Copy
func (c *Context) Copy() *Context

Copy returns a copy of the current context that can be safely used outside the request's scope. This have to be used then the context has to be passed to a goroutine.

func (*Context) Data
func (c *Context) Data(code int, contentType string, data []byte)

Data writes some data into the body stream and updates the HTTP code.

func (*Context) Deadline
func (c *Context) Deadline() (deadline time.Time, ok bool)
func (*Context) DefaultPostForm
func (c *Context) DefaultPostForm(key, defaultValue string) string

DefaultPostForm returns the specified key from a POST urlencoded form or multipart form when it exists, otherwise it returns the specified defaultValue string. See: PostForm() and GetPostForm() for further information.

func (*Context) DefaultQuery
func (c *Context) DefaultQuery(key, defaultValue string) string

DefaultQuery returns the keyed url query value if it exists, othewise it returns the specified defaultValue string. See: Query() and GetQuery() for further information.

GET /?name=Manu&lastname=
c.DefaultQuery("name", "unknown") == "Manu"
c.DefaultQuery("id", "none") == "none"
c.DefaultQuery("lastname", "none") == ""
func (*Context) Done
func (c *Context) Done() <-chan struct{}
func (*Context) Err
func (c *Context) Err() error
func (*Context) Error
func (c *Context) Error(err error) *Error

Attaches an error to the current context. The error is pushed to a list of errors. It's a good idea to call Error for each error that occurred during the resolution of a request. A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response.

func (*Context) File
func (c *Context) File(filepath string)

File writes the specified file into the body stream in a efficient way.

func (*Context) Get
func (c *Context) Get(key string) (value interface{}, exists bool)

Get returns the value for the given key, ie: (value, true). If the value does not exists it returns (nil, false)

func (*Context) GetCookie
func (c *Context) GetCookie(name string) (string, error)
func (*Context) GetPostForm
func (c *Context) GetPostForm(key string) (string, bool)

GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded form or multipart form when it exists `(value, true)` (even when the value is an empty string), otherwise it returns ("", false). For example, during a PATCH request to update the user's email:

email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
email=  			  	-->  ("", true) := GetPostForm("email") // set email to ""
					 	-->  ("", false) := GetPostForm("email") // do nothing with email
func (*Context) GetPostFormArray
func (c *Context) GetPostFormArray(key string) ([]string, bool)

GetPostFormArray returns a slice of strings for a given form key, plus a boolean value whether at least one value exists for the given key.

func (*Context) GetQuery
func (c *Context) GetQuery(key string) (string, bool)

GetQuery is like Query(), it returns the keyed url query value if it exists `(value, true)` (even when the value is an empty string), othewise it returns `("", false)`. It is shortcut for `c.Request.URL.Query().Get(key)`

GET /?name=Manu&lastname=
("Manu", true) == c.GetQuery("name")
("", false) == c.GetQuery("id")
("", true) == c.GetQuery("lastname")
func (*Context) GetQueryArray
func (c *Context) GetQueryArray(key string) ([]string, bool)

GetQueryArray returns a slice of strings for a given query key, plus a boolean value whether at least one value exists for the given key.

func (*Context) HTML
func (c *Context) HTML(code int, name string, obj interface{})

HTML renders the HTTP template specified by its file name. It also updates the HTTP code and sets the Content-Type as "text/html". See http://golang.org/doc/articles/wiki/

func (*Context) HandlerName
func (c *Context) HandlerName() string

HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", this function will return "main.handleGetUsers"

func (*Context) Header
func (c *Context) Header(key, value string)

Header is a intelligent shortcut for c.Writer.Header().Set(key, value) It writes a header in the response. If value == "", this method removes the header `c.Writer.Header().Del(key)`

func (*Context) IndentedJSON
func (c *Context) IndentedJSON(code int, obj interface{})

IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. It also sets the Content-Type as "application/json". WARNING: we recommend to use this only for development propuses since printing pretty JSON is more CPU and bandwidth consuming. Use Context.JSON() instead.

func (*Context) IsAborted
func (c *Context) IsAborted() bool

IsAborted returns true if the current context was aborted.

func (*Context) JSON
func (c *Context) JSON(code int, obj interface{})

JSON serializes the given struct as JSON into the response body. It also sets the Content-Type as "application/json".

func (*Context) MustGet
func (c *Context) MustGet(key string) interface{}

MustGet returns the value for the given key if it exists, otherwise it panics.

func (*Context) Negotiate
func (c *Context) Negotiate(code int, config Negotiate)
func (*Context) NegotiateFormat
func (c *Context) NegotiateFormat(offered ...string) string
func (*Context) Next
func (c *Context) Next()

Next should be used only inside middleware. It executes the pending handlers in the chain inside the calling handler. See example in github.

func (*Context) Param
func (c *Context) Param(key string) string

Param returns the value of the URL param. It is a shortcut for c.Params.ByName(key)

router.GET("/user/:id", func(c *gin.Context) {
	// a GET request to /user/john
	id := c.Param("id") // id == "john"
})
func (*Context) PostForm
func (c *Context) PostForm(key string) string

PostForm returns the specified key from a POST urlencoded form or multipart form when it exists, otherwise it returns an empty string `("")`.

func (*Context) PostFormArray
func (c *Context) PostFormArray(key string) []string

PostFormArray returns a slice of strings for a given form key. The length of the slice depends on the number of params with the given key.

func (*Context) Query
func (c *Context) Query(key string) string

Query returns the keyed url query value if it exists, othewise it returns an empty string `("")`. It is shortcut for `c.Request.URL.Query().Get(key)`

GET /path?id=1234&name=Manu&value=
c.Query("id") == "1234"
c.Query("name") == "Manu"
c.Query("value") == ""
c.Query("wtf") == ""
func (*Context) QueryArray
func (c *Context) QueryArray(key string) []string

QueryArray returns a slice of strings for a given query key. The length of the slice depends on the number of params with the given key.

func (*Context) Redirect
func (c *Context) Redirect(code int, location string)

Redirect returns a HTTP redirect to the specific location.

func (*Context) Render
func (c *Context) Render(code int, r render.Render)
func (*Context) SSEvent
func (c *Context) SSEvent(name string, message interface{})

SSEvent writes a Server-Sent Event into the body stream.

func (*Context) Set
func (c *Context) Set(key string, value interface{})

Set is used to store a new key/value pair exclusivelly for this context. It also lazy initializes c.Keys if it was not used previously.

func (*Context) SetAccepted
func (c *Context) SetAccepted(formats ...string)
func (*Context) SetCookie
func (c *Context) SetCookie(
    name string,
    value string,
    maxAge int,
    path string,
    domain string,
    secure bool,
    httpOnly bool,
)
func (*Context) Status
func (c *Context) Status(code int)
func (*Context) Stream
func (c *Context) Stream(step func(w io.Writer) bool)
func (*Context) String
func (c *Context) String(code int, format string, values ...interface{})

String writes the given string into the response body.

func (*Context) Value
func (c *Context) Value(key interface{}) interface{}
func (*Context) XML
func (c *Context) XML(code int, obj interface{})

XML serializes the given struct as XML into the response body. It also sets the Content-Type as "application/xml".

func (*Context) YAML
func (c *Context) YAML(code int, obj interface{})

YAML serializes the given struct as YAML into the response body.

type Engine

type Engine struct {
    RouterGroup
    HTMLRender render.HTMLRender

    // Enables automatic redirection if the current route can't be matched but a
    // handler for the path with (without) the trailing slash exists.
    // For example if /foo/ is requested but a route only exists for /foo, the
    // client is redirected to /foo with http status code 301 for GET requests
    // and 307 for all other request methods.
    RedirectTrailingSlash bool

    // If enabled, the router tries to fix the current request path, if no
    // handle is registered for it.
    // First superfluous path elements like ../ or // are removed.
    // Afterwards the router does a case-insensitive lookup of the cleaned path.
    // If a handle can be found for this route, the router makes a redirection
    // to the corrected path with status code 301 for GET requests and 307 for
    // all other request methods.
    // For example /FOO and /..//Foo could be redirected to /foo.
    // RedirectTrailingSlash is independent of this option.
    RedirectFixedPath bool

    // If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the NotFound // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool // contains filtered or unexported fields }

Engine is the framework's instance, it contains the muxer, middleware and configuration settings. Create an instance of Engine, by using New() or Default()

func Default
func Default() *Engine

Default returns an Engine instance with the Logger and Recovery middleware already attached.

func New
func New() *Engine

New returns a new blank Engine instance without any middleware attached. By default the configuration is: - RedirectTrailingSlash: true - RedirectFixedPath: false - HandleMethodNotAllowed: false - ForwardedByClientIP: true

func (*Engine) LoadHTMLFiles
func (engine *Engine) LoadHTMLFiles(files ...string)
func (*Engine) LoadHTMLGlob
func (engine *Engine) LoadHTMLGlob(pattern string)
func (*Engine) NoMethod
func (engine *Engine) NoMethod(handlers ...HandlerFunc)

NoMethod sets the handlers called when... TODO

func (*Engine) NoRoute
func (engine *Engine) NoRoute(handlers ...HandlerFunc)

NoRoute adds handlers for NoRoute. It return a 404 code by default.

func (*Engine) Routes
func (engine *Engine) Routes() (routes RoutesInfo)

Routes returns a slice of registered routes, including some useful information, such as: the http method, path and the handler name.

func (*Engine) Run
func (engine *Engine) Run(addr ...string) (err error)

Run attaches the router to a http.Server and starts listening and serving HTTP requests. It is a shortcut for http.ListenAndServe(addr, router) Note: this method will block the calling goroutine indefinitely unless an error happens.

func (*Engine) RunTLS
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error)

RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) Note: this method will block the calling goroutine indefinitely unless an error happens.

func (*Engine) RunUnix
func (engine *Engine) RunUnix(file string) (err error)

RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests through the specified unix socket (ie. a file). Note: this method will block the calling goroutine indefinitely unless an error happens.

func (*Engine) ServeHTTP
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request)

Conforms to the http.Handler interface.

func (*Engine) SetHTMLTemplate
func (engine *Engine) SetHTMLTemplate(templ *template.Template)
func (*Engine) Use
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes

Use attachs a global middleware to the router. ie. the middleware attached though Use() will be included in the handlers chain for every single request. Even 404, 405, static files... For example, this is the right place for a logger or error management middleware.

type Error

type Error struct {
    Err  error
    Type ErrorType
    Meta interface{}
}
func (*Error) Error
func (msg *Error) Error() string

Implements the error interface

func (*Error) IsType
func (msg *Error) IsType(flags ErrorType) bool
func (*Error) JSON
func (msg *Error) JSON() interface{}
func (*Error) MarshalJSON
func (msg *Error) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaller interface

func (*Error) SetMeta
func (msg *Error) SetMeta(data interface{}) *Error
func (*Error) SetType
func (msg *Error) SetType(flags ErrorType) *Error

type ErrorType

type ErrorType uint64
const (
    ErrorTypeBind    ErrorType = 1 << 63 // used when c.Bind() fails
    ErrorTypeRender  ErrorType = 1 << 62 // used when c.Render() fails
    ErrorTypePrivate ErrorType = 1 << 0
    ErrorTypePublic ErrorType = 1 << 1 ErrorTypeAny ErrorType = 1<<64 - 1 ErrorTypeNu = 2 )

type H

type H map[string]interface{}
func (H) MarshalXML
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error

MarshalXML allows type H to be used with xml.Marshal

type HandlerFunc

type HandlerFunc func(*Context)
func BasicAuth
func BasicAuth(accounts Accounts) HandlerFunc

BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where the key is the user name and the value is the password.

func BasicAuthForRealm
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc

BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where the key is the user name and the value is the password, as well as the name of the Realm. If the realm is empty, "Authorization Required" will be used by default. (see http://tools.ietf.org/html/rfc2617#section-1.2)

func Bind
func Bind(val interface{}) HandlerFunc
func ErrorLogger
func ErrorLogger() HandlerFunc
func ErrorLoggerT
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger
func Logger() HandlerFunc

Logger instances a Logger middleware that will write the logs to gin.DefaultWriter By default gin.DefaultWriter = os.Stdout

func LoggerWithWriter
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc

LoggerWithWriter instance a Logger middleware with the specified writter buffer. Example: os.Stdout, a file opened in write mode, a socket...

func Recovery
func Recovery() HandlerFunc

Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.

func RecoveryWithWriter
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH
func WrapH(h http.Handler) HandlerFunc

type HandlersChain

type HandlersChain []HandlerFunc
func (HandlersChain) Last
func (c HandlersChain) Last() HandlerFunc

Last returns the last handler in the chain. ie. the last handler is the main own.

type IRouter

type IRouter interface {
    IRoutes
    Group(string, ...HandlerFunc) *RouterGroup
}

type IRoutes

type IRoutes interface {
    Use(...HandlerFunc) IRoutes

    Handle(string, string, ...HandlerFunc) IRoutes
    Any(string, ...HandlerFunc) IRoutes
    GET(string, ...HandlerFunc) IRoutes
    POST(string, ...HandlerFunc) IRoutes
    DELETE(string, ...HandlerFunc) IRoutes PATCH(string, ...HandlerFunc) IRoutes PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes StaticFile(string, string) IRoutes Static(string, string) IRoutes StaticFS(string, http.FileSystem) IRoutes }

type Negotiate

type Negotiate struct {
    Offered  []string
    HTMLName string
    HTMLData interface{}
    JSONData interface{}
    XMLData  interface{}
    Data interface{} }

type Param

type Param struct {
    Key   string
    Value string
}

Param is a single URL parameter, consisting of a key and a value.

type Params

type Params []Param

Params is a Param-slice, as returned by the router. The slice is ordered, the first URL parameter is also the first slice value. It is therefore safe to read values by the index.

func (Params) ByName
func (ps Params) ByName(name string) (va string)

ByName returns the value of the first Param which key matches the given name. If no matching Param is found, an empty string is returned.

func (Params) Get
func (ps Params) Get(name string) (string, bool)

Get returns the value of the first Param which key matches the given name. If no matching Param is found, an empty string is returned.

type ResponseWriter

type ResponseWriter interface {
    http.ResponseWriter
    http.Hijacker
    http.Flusher
    http.CloseNotifier

    // Returns the HTTP response status code of the current request.
    Status() int

    // Returns the number of bytes already written into the response http body.
    // See Written()
    Size() int

    // Writes the string into the response body.
    WriteString(string) (int, error) // Returns true if the response body was already written. Written() bool // Forces to write the http header (status code + headers). WriteHeaderNow() }

type RouteInfo

type RouteInfo struct {
    Method  string
    Path    string
    Handler string
}

type RouterGroup

type RouterGroup struct {
    Handlers HandlersChain
    // contains filtered or unexported fields
}

RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix and an array of handlers (middleware)

func (*RouterGroup) Any
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes

Any registers a route that matches all the HTTP methods. GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE

func (*RouterGroup) BasePath
func (group *RouterGroup) BasePath() string
func (*RouterGroup) DELETE
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes

DELETE is a shortcut for router.Handle("DELETE", path, handle)

func (*RouterGroup) GET
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes

GET is a shortcut for router.Handle("GET", path, handle)

func (*RouterGroup) Group
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup

Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix. For example, all the routes that use a common middlware for authorization could be grouped.

func (*RouterGroup) HEAD
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes

HEAD is a shortcut for router.Handle("HEAD", path, handle)

func (*RouterGroup) Handle
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes

Handle registers a new request handle and middleware with the given path and method. The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. See the example code in github.

For GET, POST, PUT, PATCH and DELETE requests the respective shortcut functions can be used.

This function is intended for bulk loading and to allow the usage of less frequently used, non-standardized or custom methods (e.g. for internal communication with a proxy).

func (*RouterGroup) OPTIONS
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes

OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)

func (*RouterGroup) PATCH
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes

PATCH is a shortcut for router.Handle("PATCH", path, handle)

func (*RouterGroup) POST
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes

POST is a shortcut for router.Handle("POST", path, handle)

func (*RouterGroup) PUT
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes

PUT is a shortcut for router.Handle("PUT", path, handle)

func (*RouterGroup) Static
func (group *RouterGroup) Static(relativePath, root string) IRoutes

Static serves files from the given file system root. Internally a http.FileServer is used, therefore http.NotFound is used instead of the Router's NotFound handler. To use the operating system's file system implementation, use :

router.Static("/static", "/var/www")
func (*RouterGroup) StaticFS
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes

StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead. Gin by default user: gin.Dir()

func (*RouterGroup) StaticFile
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes

StaticFile registers a single route in order to server a single file of the local filesystem. router.StaticFile("favicon.ico", "./resources/favicon.ico")

func (*RouterGroup) Use
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes

Use adds middleware to the group, see example code in github.

type RoutesInfo

type RoutesInfo []RouteInfo

Directories

PathSynopsis
binding 
binding/examplePackage example is a generated protocol buffer package.
examples/app-engine 
examples/basic 
examples/realtime-advanced 
examples/realtime-chat 
ginS 
render
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值