[golang gin框架] 4.自定义Model以及Gin 文件上传

一.Gin 中自定义 Model

  1. 关于 Model

如果应用非常简单的话,我们可以在 Controller 里面处理常见的业务逻辑,但是如果
有一个功能想在多个控制器、或者多个模板里面复用的话,那么就可以把公共的功
能单独抽取出来作为一个模块(Model),Model 是逐步抽象的过程,一般会在 Model
里面封装一些公共的方法让不同 Controller 使用,也可以在 Model 中实现和数据库打交道

2.Model 里面封装公共的方法

新建 models/ tools.go

package models

import (
    "crypto/md5"
    "fmt"
    "time"
)

//时间戳转换成日期函数
func UnixToTime(timestamp int) string {
    t := time.Unix(int64(timestamp), 0)
    return t.Format("2006-01-02 15:04:05")
}

//日期转换成时间戳
func DateToUnix(str string) int64 {
    template := "2006-01-02 15:04:05"
    t, err := time.ParseInLocation(template, str, time.Local)
    if err != nil {
        return 0
    }
    return t.Unix()
}

//获取当前时间戳
func GetUnix() int64 {
    return time.Now().Unix()
}

//获取当前日期
func GetDate() string  {
    template := "2006-01-02 15:04:05"
    return time.Now().Format(template)
}

//获取年月日
func GetDay() string {
    template := "20060102"
    return time.Now().Format(template)
}

func Md5(str string) string {
    data := []byte(str)
    return fmt.Sprintf("%x\n", md5.Sum(data))
}
func Hello(in string) (out string) {
    out = in + "world"
    return
}

3.控制器中调用 Model

package controllers
import (
    "gindemo/models"
)

day := models.GetDay()

4.调用 Model 注册全局模板函数

(1).main.go中

package main

import (
    "fmt"
    "gindemo/models"
    "github.com/gin-gonic/gin"
    "html/template"
)

//main中的公共方法
func Println(str1 string, str2 string) string {
    return str1 + str2
}

func main() {
    //初始化路由,会设置默认中间件:engine.Use(Logger(), Recovery()),可以使用gin.New()来设置路由
    r := gin.Default()
    //自定义模板函数,必须在r.LoadHTMLGlob前面
    r.SetFuncMap(template.FuncMap{
        "UnixToTime": models.UnixToTime, //注册模板函数
        "Println":    Println,
    })
    //加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行
    r.LoadHTMLGlob("templates/**/*")
    //配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录
    r.Static("/static", "./static")
    ...
}

(2).控制器,模板中:方式一

控制器UserController
func (c UserController) Add(c *gin.Context) {
    c.HTML(http.StatusOK, "admin/user/add.html", gin.H{ 
        "now": models.GetUnix(), 
    })
}
模板admin/user/add.html
<h2>{{.now | unixToDate}}</h2>

(3).控制器,模板中:方式二

控制器defaultController.go
package itying

import (
     _ "fmt"
    "gindemo/models"
    "github.com/gin-gonic/gin"
    "net/http"
)

type DefaultController struct {
    
}

func (con DefaultController) Index(c *gin.Context) {
    fmt.Println(models.GetDay())
    c.HTML(http.StatusOK, "default/index.html",gin.H{
        "msg":"我是一个msg",
        "t": 1629788010,
    })
}
模板default/index.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "default/index.html" }}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="/static/css/base.css">
        <title>首页</title>
    </head>
    <body>
    {{ template "public/page_header.html" .}}
    <h2>{{.msg}}</h2>
    <h3>{{UnixToTime .t}}</h3>
    {{ template "public/page_footer.html" .}}
    </body>
    </html>
{{end}}

5.Golang Md5 加密

打开 golang 包对应的网站: https://pkg.go.dev/,搜索 md5

方法一

package models

import (
    "crypto/md5"
)

func Md5(str string) string {
    data := []byte(str)
    return fmt.Sprintf("%x\n", md5.Sum(data))
}

方法二

package models

import (
    "crypto/md5"
)

func Md5(str string) string {
    h := md5.New()
    io.WriteString(h, str)
    return fmt.Sprintf("%x\n", h.Sum(nil))
}

二.Gin 文件上传

注意:需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"

1.单文件上传

文档:https://gin-gonic.com/zh-cn/docs/examples/upload-file/single-file/

官方案例:

func main() {
    router := gin.Default()
    // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
    router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // 单文件
        file, _ := c.FormFile("file")
        log.Println(file.Filename)

        dst := "./" + file.Filename
        // 上传文件至指定的完整文件路径
        c.SaveUploadedFile(file, dst)

        c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
    })
    router.Run(":8080")
}

项目中实现文件上传

(1).定义模板 需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/user/add.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.title}}</title>
</head>
<body>
<h2>演示文件上传</h2>
<form action="/admin/user/doUpload" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username" placeholder="用户名"><br/><br/>
    头像: <input type="file" name="face"><br/><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>
{{ end }}
(2).定义业务逻辑
package admin

import (
    "gindemo/models"
    "github.com/gin-gonic/gin"
    "net/http"
    "os"
    "path"
    "strconv"
)

//添加:单文件上传
func (con UserController) Add(c *gin.Context) {
    c.HTML(http.StatusOK, "admin/user/add.html", gin.H{
        "title": "添加用户",
    })
}

//单文件上传
func (con UserController) DoUpload(c *gin.Context) {
    //获取表单中提交的username
    username := c.PostForm("username")
    //获取文件
    file, err := c.FormFile("face")
    //判断上传文件上否存在
    if err != nil {//说明上传文件不存在
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": "fail",
            "message": err.Error(),
        })
        return
    }
    //设置需要上传的文件目录 file.Filename 获取文件名; "./static/upload" 是基于main.go文件路由的
    dst := path.Join("./static/upload", file.Filename)
    //上传文件到指定目录
    c.SaveUploadedFile(file, dst)
    c.JSON(http.StatusOK, gin.H{
        "success": "true",
        "username": username,
        "dst": dst,
    })
}
(3).增加路由配置
package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)

//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
    //路由分组: 配置全局中间件:middlewares.InitMiddleware
    adminRouters := r.Group("/admin", middlewares.InitMiddleware)
    {
        //单文件上传
        adminRouters.POST("/user/doUpload", admin.UserController{}.DoUpload)      
    }
}

2.多文件上传-不同名字的多个文件

(1).定义模板 需要在上传文件的 form 表单上面需要加入enctype="multipart/form-data"

<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/user/addnotfilenameupload.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.title}}</title>
</head>
<body>
<h2>演示不同文件名的文件上传</h2>
<form action="/admin/user/doNotFileNameUploads" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username" placeholder="用户名"><br/><br/>
    头像1: <input type="file" name="face1"><br/><br/>
    头像2: <input type="file" name="face2"><br/><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>
{{ end }}

(2).定义业务逻辑

package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)

//添加:不同名字的多文件上传
func (con UserController) AddNotFileNameUpload(c *gin.Context) {
    c.HTML(http.StatusOK, "admin/user/addnotfilenameupload.html", gin.H{
        "title": "添加不同名字的多文件上传",
    })
}


//不同文件名的多文件上传
func (con UserController) DoNotFileNameUploads(c *gin.Context) {
    //获取表单中提交的username
    username := c.PostForm("username")
    //获取文件
    file1, err1 := c.FormFile("face1")
    file2, err2 := c.FormFile("face2")
    //判断上传文件上否存在
    if err1 != nil {//说明上传文件不存在
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": "fail",
            "message": err1.Error(),
        })
        return
    }
    //判断上传文件上否存在
    if err2 != nil {//说明上传文件不存在
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": "fail",
            "message": err2.Error(),
        })
        return
    }

    //设置需要上传的文件目录 file.Filename 获取文件名; "./static/upload" 是基于main.go文件路由的
    dst1 := path.Join("./static/upload", file1.Filename)
    //上传文件到指定目录
    c.SaveUploadedFile(file1, dst1)

    //设置需要上传的文件目录 file.Filename 获取文件名; "./static/upload" 是基于main.go文件路由的
    dst2 := path.Join("./static/upload", file2.Filename)
    //上传文件到指定目录
    c.SaveUploadedFile(file2, dst2)

    c.JSON(http.StatusOK, gin.H{
        "success": "true",
        "username": username,
        "dst1": dst1,
        "dst2": dst2,
    })
}

(3).增加路由配置

package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)

//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
    //路由分组: 配置全局中间件:middlewares.InitMiddleware
    adminRouters := r.Group("/admin", middlewares.InitMiddleware)
    {
        //单文件上传
        adminRouters.POST("/user/doUpload", admin.UserController{}.DoUpload)  
        //不同名字的多文件上传
        adminRouters.GET("/user/addnotfilenameupload",admin.UserController{}.AddNotFileNameUpload)
        //不同文件名的多文件上传
        adminRouters.POST("/user/doNotFileNameUploads", admin.UserController{}.DoNotFileNameUploads)
      
    }
}

3.多文件上传-相同名字的多个文件

文档:https://gin-gonic.com/zh-cn/docs/examples/upload-file/multiple-file/

(1).定义模板 需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"

<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/user/addcommonfilenameupload.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.title}}</title>
</head>
<body>
<h2>演示相同文件名的文件上传</h2>
<form action="/admin/user/doCommonFileNameUploads" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username" placeholder="用户名"><br/><br/>
    头像1: <input type="file" name="face[]"><br/><br/>
    头像2: <input type="file" name="face[]"><br/><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>
{{ end }}

(2).定义业务逻辑

package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)
//添加:相同名字的多文件上传
func (con UserController) AddCommonFileNameUpload(c *gin.Context) {
    c.HTML(http.StatusOK, "admin/user/addcommonfilenameupload.html", gin.H{
        "title": "相同名字的多文件上传",
    })
}

//相同文件名的多文件上传
func (con UserController) DoCommonFileNameUploads(c *gin.Context) {
    //获取表单中提交的username
    username := c.PostForm("username")
    //获取form
    form,_ := c.MultipartForm()
    //获取多文件
    files := form.File["face[]"]
    //遍历文件,并上传
    for _, file := range files {
        //设置需要上传的文件目录 file.Filename 获取文件名; "./static/upload" 是基于main.go文件路由的
        dst := path.Join("./static/upload", file.Filename)
        //上传文件到指定目录
        c.SaveUploadedFile(file, dst)
    }

    c.JSON(http.StatusOK, gin.H{
        "success": "true",
        "username": username,
        "message": "文件上传成功",
    })
}

(3).增加路由配置

package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)

//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
    //路由分组: 配置全局中间件:middlewares.InitMiddleware
    adminRouters := r.Group("/admin", middlewares.InitMiddleware)
    {
        //单文件上传
        adminRouters.POST("/user/doUpload", admin.UserController{}.DoUpload)  
        
        //不同名字的多文件上传
        adminRouters.GET("/user/addnotfilenameupload",admin.UserController{}.AddNotFileNameUpload)
        //不同文件名的多文件上传
        adminRouters.POST("/user/doNotFileNameUploads", admin.UserController{}.DoNotFileNameUploads)

        //相同名字的多文件上传
        adminRouters.GET("/user/addcommonfilenameupload",admin.UserController{}.AddCommonFileNameUpload)
        //相同文件名的多文件上传
        adminRouters.POST("/user/doCommonFileNameUploads", admin.UserController{}.DoCommonFileNameUploads)      
    }
}

4.文件上传 按照日期存储

(1).定义模板 需要在上传文件的 form 表单上面需要加入 enctype="multipart/form-data"

<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "admin/user/add-by-date.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.title}}</title>
</head>
<body>
<h2>演示文件按日期上传</h2>
<form action="/admin/user/doUploadByDate" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username" placeholder="用户名"><br/><br/>
    头像: <input type="file" name="face"><br/><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>
{{ end }}

(2).定义业务逻辑

package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)

//添加:按日期单文件上传
func (con UserController) AddByDate(c *gin.Context) {
    c.HTML(http.StatusOK, "admin/user/add-by-date.html", gin.H{
        "title": "添加:按日期单文件上传",
    })
}

//按日期单文件上传
/**
1.获取上传文件
2.获取后缀名,判断后缀是否正确: .jpg,.png,.gif,.jpeg
3.创建图片保存目录 ./static/upload/20230203
4.生成文件名称和文件保存目录
5.执行上传
 */
func (con UserController) DoUploadByDate(c *gin.Context) {
    //获取表单中提交的username
    username := c.PostForm("username")
    //1.获取上传文件
    file, err := c.FormFile("face")
    //判断上传文件上否存在
    if err != nil {//说明上传文件不存在
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": "fail",
            "message": err.Error(),
        })
        return
    }
    //2.获取后缀名,判断后缀是否正确: .jpg,.png,.gif,.jpeg
    extName := path.Ext(file.Filename)
    //设置后缀map
    allowExtMap := map[string]bool {
        ".jpg" : true,
        ".png" : true,
        ".gif" : true,
        ".jpeg" : true,
    }
    //判断后缀是否合法
    if _, ok := allowExtMap[extName]; !ok {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": "fail",
            "message": "上传文件后缀不合法",
        })
        return
    }
    //3.创建图片保存目录 ./static/upload/20230203
    //获取日期
    day := models.GetDay()
    //拼接目录
    dir := "./static/upload/" + day
    //创建目录:MkdirAll 目录不存在,会一次性创建多层
    err = os.MkdirAll(dir, 0666)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": "fail",
            "message": "创建目录失败",
        })
        return
    }
    //4.生成文件名称和文件保存目录: models.GetUnix() 获取时间戳(int64); strconv.FormatInt() 把时间戳(int64)转换成字符串
    filename := strconv.FormatInt(models.GetUnix(), 10) + extName
    //5.执行上传
    dst := path.Join(dir, filename)
    //上传文件到指定目录
    c.SaveUploadedFile(file, dst)
    c.JSON(http.StatusOK, gin.H{
        "success": "true",
        "username": username,
        "dst": dst,
    })
}

(3).models/tools.go 添加获取当前时间戳函数

package models

import (
    "crypto/md5"
    "fmt"
    "time"
)

//时间戳转换成日期函数
func UnixToTime(timestamp int) string {
    t := time.Unix(int64(timestamp), 0)
    return t.Format("2006-01-02 15:04:05")
}

//日期转换成时间戳
func DateToUnix(str string) int64 {
    template := "2006-01-02 15:04:05"
    t, err := time.ParseInLocation(template, str, time.Local)
    if err != nil {
        return 0
    }
    return t.Unix()
}

//获取当前时间戳
func GetUnix() int64 {
    return time.Now().Unix()
}

//获取当前日期
func GetDate() string  {
    template := "2006-01-02 15:04:05"
    return time.Now().Format(template)
}

//获取年月日
func GetDay() string {
    template := "20060102"
    return time.Now().Format(template)
}

func Md5(str string) string {
    data := []byte(str)
    return fmt.Sprintf("%x\n", md5.Sum(data))
}

(4).增加路由配置

package routers

import (
    "gindemo/controllers/admin"
    "gindemo/middlewares"
    "github.com/gin-gonic/gin"
)

//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
    //路由分组: 配置全局中间件:middlewares.InitMiddleware
    adminRouters := r.Group("/admin", middlewares.InitMiddleware)
    {
        //单文件上传
        adminRouters.POST("/user/doUpload", admin.UserController{}.DoUpload)  
        
        //不同名字的多文件上传
        adminRouters.GET("/user/addnotfilenameupload",admin.UserController{}.AddNotFileNameUpload)
        //不同文件名的多文件上传
        adminRouters.POST("/user/doNotFileNameUploads", admin.UserController{}.DoNotFileNameUploads)

        //相同名字的多文件上传
        adminRouters.GET("/user/addcommonfilenameupload",admin.UserController{}.AddCommonFileNameUpload)
        //相同文件名的多文件上传
        adminRouters.POST("/user/doCommonFileNameUploads", admin.UserController{}.DoCommonFileNameUploads)      
    }

    //添加:按日期单文件上传
    adminRouters.GET("/user/addByDate",admin.UserController{}.AddByDate)
    //添加:按日期单文件上传
    adminRouters.POST("/user/doUploadByDate", admin.UserController{}.DoUploadByDate)
}

[上一节][golang gin框架] 3.路由详解,自定义控制器,中间件

[下一节]golang gin框架] 5.Cookie以及Session

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于使用 Golang Gin 框架进行项目实战的建议。 首先,您需要安装 GolangGin 框架。安装过程可以参考官方文档进行操作。然后,您可以按照以下步骤进行项目实战: 1. 创建项目结构 在您的 GOPATH 目录下创建一个新的项目文件夹。通常情况下,您需要为项目创建以下子文件夹: ``` . ├── main.go ├── config │ ├── config.go │ └── ... ├── controller │ ├── home_controller.go │ └── ... ├── middleware │ ├── auth.go │ └── ... ├── model │ ├── user.go │ └── ... ├── router │ ├── router.go │ └── ... └── service ├── user_service.go └── ... ``` 2. 创建路由 在 router 文件夹中创建一个 router.go 文件,并编写以下代码: ```go package router import ( "github.com/gin-gonic/gin" "your-project/controller" "your-project/middleware" ) func SetupRouter() *gin.Engine { r := gin.Default() // 中间件 r.Use(middleware.AuthMiddleware()) // 路由 r.GET("/", controller.HomePageHandler) return r } ``` 在上面的代码中,我们使用了 gin 框架中的 gin.Default() 函数来创建一个默认的 gin.Engine 实例,并使用中间件来进行身份验证。然后我们定义了一个路由,将根路径映射到 HomePageHandler 方法。 3. 创建控制器 在 controller 文件夹中创建 home_controller.go 文件,并编写以下代码: ```go package controller import ( "github.com/gin-gonic/gin" "your-project/service" ) func HomePageHandler(c *gin.Context) { users := service.GetAllUsers() c.JSON(200, users) } ``` 在上面的代码中,我们定义了一个 HomePageHandler 方法,它接收一个 gin.Context 对象作为参数,并使用 service 层中的 GetAllUsers 函数获取所有用户的数据。然后我们使用 JSON 方法将数据以 JSON 格式返回给客户端。 4. 创建服务层 在 service 文件夹中创建 user_service.go 文件,并编写以下代码: ```go package service import "your-project/model" func GetAllUsers() []*model.User { // TODO: 查询数据库获取所有用户数据 return []*model.User{} } ``` 在上面的代码中,我们定义了一个 GetAllUsers 函数,它返回一个包含所有用户数据的切片。在实际项目中,我们需要查询数据库来获取这些数据。 5. 创建模型 在 model 文件夹中创建 user.go 文件,并编写以下代码: ```go package model type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"` } ``` 在上面的代码中,我们定义了一个 User 结构体,它包含三个字段:ID、Name 和 Age。这些字段将用于存储每个用户的信息。 6. 创建配置文件 在 config 文件夹中创建 config.go 文件,并编写以下代码: ```go package config const ( DB_HOST = "localhost" DB_PORT = "3306" DB_USERNAME = "root" DB_PASSWORD = "password" DB_NAME = "your-database-name" ) ``` 在上面的代码中,我们定义了一些常量,用于存储数据库配置信息。这些常量将在连接数据库时使用。 7. 连接数据库 在 service 层中的 GetAllUsers 函数中,我们需要连接数据库来获取所有用户数据。可以使用以下代码来连接 MySQL 数据库: ```go package service import ( "database/sql" "fmt" "your-project/config" "your-project/model" ) func GetAllUsers() []*model.User { // 连接数据库 db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", config.DB_USERNAME, config.DB_PASSWORD, config.DB_HOST, config.DB_PORT, config.DB_NAME, )) if err != nil { panic(err) } defer db.Close() // 查询所有用户数据 rows, err := db.Query("SELECT * FROM users") if err != nil { panic(err) } // 解析查询结果 users := []*model.User{} for rows.Next() { user := &model.User{} err := rows.Scan(&user.ID, &user.Name, &user.Age) if err != nil { panic(err) } users = append(users, user) } return users } ``` 在上面的代码中,我们使用 sql.Open 函数来打开 MySQL 数据库连接。然后我们使用 db.Query 函数来查询所有用户数据,并使用 rows.Scan 函数将查询结果解析为 User 结构体的切片。 以上就是使用 Golang Gin 框架进行项目实战的流程。当然,在实际项目中,您需要根据自己的需求进行相应的修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值