From 76fde58ad952a33102cab9372d4d36b879815a97 Mon Sep 17 00:00:00 2001 From: silenceper Date: Tue, 17 Mar 2020 19:01:51 +0800 Subject: [PATCH] add cloudbase --- cloudbase/README.md | 53 ++++ cloudbase/guestbook-demo/cloudfunctions.md | 99 +++++++ cloudbase/guestbook-demo/database.md | 304 +++++++++++++++++++++ cloudbase/guestbook-demo/start.md | 185 +++++++++++++ cloudbase/guestbook-demo/storage.md | 143 ++++++++++ 5 files changed, 784 insertions(+) create mode 100644 cloudbase/README.md create mode 100644 cloudbase/guestbook-demo/cloudfunctions.md create mode 100644 cloudbase/guestbook-demo/database.md create mode 100644 cloudbase/guestbook-demo/start.md create mode 100644 cloudbase/guestbook-demo/storage.md diff --git a/cloudbase/README.md b/cloudbase/README.md new file mode 100644 index 0000000..7025e35 --- /dev/null +++ b/cloudbase/README.md @@ -0,0 +1,53 @@ +# 小程序-云开发 SDK + +[云开发(CloudBase)](https://tencentcloudbase.github.io/)是基于Serverless架构构建的一站式后端云服务,涵盖函数、数据库、存储、CDN等服务,免后端运维,支持小程序、Web和APP开发。 其中,小程序·云开发是微信和腾讯云联合推出的云端一体化解决方案,基于云开发可以免鉴权调用微信所有开放能力,在微信开发者工具中即可开通使用。 + + + +## 使用说明 +**引入依赖** +>推荐使用go module 进行管理 + +``` +go get github.com/silenceper/wechat@v1.2.3 +``` + +**初始化配置** + +```golang +//使用memcache保存access_token,也可选择redis或自定义cache +memCache=cache.NewMemcache("127.0.0.1:11211") + +//配置小程序参数 +config := &wechat.Config{ + AppID: "your app id", + AppSecret: "your app secret", + Cache: memCache, +} +wc := wechat.NewWechat(config) +wcTcb := wc.GetTcb() +``` + +### 使用API + +#### 触发云函数 +```golang +res, err := wcTcb.InvokeCloudFunction("test-xxxx", "add", `{"a":1,"b":2}`) +if err != nil { + panic(err) +} +``` + +更多使用方法参考[pkg.go.dev](https://pkg.go.dev/github.com/silenceper/wechat@v1.2.3/tcb?tab=doc#Tcb) + +## Demo +### 使用wechat sdk开发一个留言板 + +这是一个使用wechat sdk来完成一个留言板的例子,使用到了云开发中的云函数,数据库,存储API: + +- [起步:项目搭建](./guestbook-demo/start.md) +- [数据库:调用云开发数据库实现文本保存](./guestbook-demo/database.md) +- [云函数:调用云函数实现文本过滤](./guestbook-demo/cloudfunctions.md) +- [云开发存储:实现留言本附件上传](./guestbook-demo/storage.md) + +以上文中的所有代码都上传在 [https://github.com/go-demo/guestbook](https://github.com/go-demo/guestbook) \ No newline at end of file diff --git a/cloudbase/guestbook-demo/cloudfunctions.md b/cloudbase/guestbook-demo/cloudfunctions.md new file mode 100644 index 0000000..49b6e15 --- /dev/null +++ b/cloudbase/guestbook-demo/cloudfunctions.md @@ -0,0 +1,99 @@ +## API说明 +云开发中云函数[文档说明](https://developers.weixin.qq.com/minigame/dev/wxcloud/reference-http-api/functions/),可以先阅读原始http api需要的参数以及说明 + +**基本流程:** + +1. 创建云函数 +1. 通过微信开发者工具编写云函数 +1. 利用SDK实现云函数的调用 + +云函数调用主要使用到了sdk中 `InvokeCloudFunction` 方法的使用: + +```go +func (tcb *Tcb) InvokeCloudFunction(env, name, args string) (*InvokeCloudFunctionRes, error) +``` +**参数说明:**
1、第一个参数为云开发的环境
2、第二个参数为云函数名称
3、第三个参数为需要传入的参数,这里传入一个json,方便在云函数中接收并处理,函数的返回值也是json
**
**返回结果:** + +```go +type InvokeCloudFunctionRes struct { + util.CommonError + RespData string `json:"resp_data"` //云函数返回的buffer +} +``` + +> util.CommonError :包含了errcode和errmsg字段,因为微信http api中的返回结果都会包含这两个字段,所以作为了一个公共的struct + + +这里演示如何通过云函数实现对文本内容的过滤,比如对关键字的过滤。 + +## 创建一个云函数 +打开微信开发者工具,在cloudfunctions中创建一个filterText云函数:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/748713/1580023609925-93d7ece7-636f-46c8-83b8-be12a41c5f51.png#align=left&display=inline&height=236&name=image.png&originHeight=472&originWidth=746&size=75078&status=done&style=none&width=373) + +其中index.js文本内容实现了对关键字的替换,内容如下: + +```javascript +// 云函数入口文件 +//敏感词 +var keywords = ["色情"] + +// 云函数入口函数 +exports.main = async(event, context) => { + let { + text + } = event + keywords.map(word => { + let regExp = new RegExp(word, 'g') + text = text.replace(regExp, "****") + }) + return { + text + } +} +``` + +这里实现了对关键字 `色情` 替换为 `****` 。 + +## 调用云函数 + +在feedbackService中创建FilterText函数实现对云函数的调用,传入原始文本内容,返回最终过滤之后的内容。 + +```go +//FilterRes 过滤文件的结果 +type FilterRes struct { + Text string `json:"text"` +} + +//FilterText 调用云函数过滤文本 +func (svc *FeedbackService) FilterText(text string) (string, error) { + res, err := getTcb().InvokeCloudFunction(getConfig().TcbEnv, "filterText", fmt.Sprintf(`{"text":"%s"}`, text)) + //返回的是json + filterRes := &FilterRes{} + err = json.Unmarshal([]byte(res.RespData), filterRes) + if err != nil { + return "", nil + } + + return filterRes.Text, nil +} +``` +这里将云函数调用的返回值保存在FilterRes struct中。 + +最后再 feedbackService中的 `Save` 对Content内容进行替换: + +```go +//Save 保存内容 +func (svc *FeedbackService) Save(feedback *Feedback) error { + ..... + //content 调用云函数过滤 + var err error + feedback.Content, err = svc.FilterText(feedback.Content) + if err != nil { + return err + } + .... +} +``` + +最终的效果如下,当我们输入了含有关键字的留言内容最终就会被替换为****:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/748713/1580024391966-3cf8aab1-6630-4b5d-b172-150f6b43e53c.png#align=left&display=inline&height=155&name=image.png&originHeight=310&originWidth=1284&size=23217&status=done&style=none&width=642) + + diff --git a/cloudbase/guestbook-demo/database.md b/cloudbase/guestbook-demo/database.md new file mode 100644 index 0000000..5f1c54a --- /dev/null +++ b/cloudbase/guestbook-demo/database.md @@ -0,0 +1,304 @@ +在这一节,我们主要描述如何利用`wechat sdk`将留言的内容保存在云开发数据库中。 + + +## API说明 +参考微信云开发文档 [数据库篇](https://developers.weixin.qq.com/minigame/dev/wxcloud/reference-http-api/database/#%E6%95%B0%E6%8D%AE%E5%BA%93),可以先阅读其原始的api提供的方法和说明,在[SDK DOC](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#Tcb.DatabaseAdd)中都可以找到对应的方法以及参数。 + +主要利用到[SDK DOC](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#Tcb.DatabaseAdd)中的如下方法,其他方法可在文档中找到,sdk文档中以 `Database` 开头的方法即为数据库相关的方法调用。 +```go +func (tcb *Tcb) DatabaseAdd(env, query string) (*DatabaseAddRes, error) //数据库内容保存 +func (tcb *Tcb) DatabaseCount(env, query string) (*DatabaseCountRes, error)//数据库计数 +func (tcb *Tcb) DatabaseQuery(env, query string) (*DatabaseQueryRes, error)//数据库内容查询 +``` +返回结果对应字段说明: [`DatabaseAddRes`](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#DatabaseAddRes) , [`DatabaseCountRes`](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#DatabaseCountRes) , [`DatabaseQueryRes`](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#DatabaseQueryRes)  + + +## 包引入 +本例中引入的WeChat sdk版本为v1.2.3版本,通过如下方法引入 +```bash +go get github.com/silenceper/wechat@v1.2.3 +``` +可以在go.mod文件中看到引入的包以及对应的版本: +```go +module github.com/go-demo/guestbook + +go 1.13 + +require ( + github.com/gin-gonic/gin v1.5.0 + github.com/silenceper/wechat v1.2.3 // indirect +) +``` + + +## 保存至云开发数据库 + +### WeChat SDK配置 +为了方便在其他方法中调用
创建config.go用于解析云开发对应的配置参数,appkey,app_secret等: + +```go +package main + +import ( + "io/ioutil" + + "github.com/silenceper/wechat" + "github.com/silenceper/wechat/cache" + "github.com/silenceper/wechat/tcb" + "gopkg.in/yaml.v2" +) + +//Config 配置信息 +type Config struct { + TcbEnv string `yaml:"tcb_env"` + AppID string `yaml:"app_id"` + AppSecret string `yaml:"app_secret"` +} + +var cfg *Config +var _ = getConfig() + +//通过getConfig方法获取配置参数 +func getConfig() *Config { + if cfg != nil { + return cfg + } + data, err := ioutil.ReadFile("./config.yaml") + if err != nil { + panic(err) + } + cfg = &Config{} + err = yaml.Unmarshal(data, cfg) + if err != nil { + panic(err) + } + return cfg +} + +var wechatTcb *tcb.Tcb +var _ = getTcb() +//通过getTcb获取wechat sdk的配置参数 +func getTcb() *tcb.Tcb { + if wechatTcb != nil { + return wechatTcb + } + memCache := cache.NewMemory() + + //配置小程序参数 + config := &wechat.Config{ + AppID: getConfig().AppID, + AppSecret: getConfig().AppSecret, + Cache: memCache, + } + wc := wechat.NewWechat(config) + wechatTcb = wc.GetTcb() + return wechatTcb +} +``` + +其中config.yaml写入三个配置参数: + +```yaml +tcb_env: test-6ku2s //云开发环境 +app_id: xxxxxx //云开发appid +app_secret: xxxxxxxxx //云开发对应的app secret +``` + + +### 调用API +为了方便在其他方法中方便调用sdk中的方法,这里新建一个 `feedbackService` struct,创建对应的save方法用于保存留言, `feedback.go` : + +```go +package main + +import ( + "fmt" + "time" +) + +//FeedbackService service +type FeedbackService struct { +} + +//NewFeedbackService new +func NewFeedbackService() *FeedbackService { + return &FeedbackService{} +} + +//Feedback 留言记录 +type Feedback struct { + Username string `form:"username",json:"username"` + Content string `form:"content",json:"content"` + FilePath string `json:"filePath"`//文件路径 + FileID string `json:"fileId"` //存放文件 + CreateTime string `json:"createTime"` +} + +func (svc *FeedbackService) Save(feedback *Feedback) error { + if feedback.Username == "" || feedback.Content == "" { + return fmt.Errorf("用户名或留言内容不能为空") + } + query := `db.collection(\"%s\").add({ + data: [{ + username: \"%s\", + content: \"%s\", + filePath: \"%s\", + fileId: \"%s\", + createTime: \"%s\", + }] + })` + feedback.CreateTime = time.Now().Format("2006-01-02 15:04:05") + query = fmt.Sprintf(query, "guestbook", feedback.Username, feedback.Content, feedback.FilePath, feedback.FileID, feedback.CreateTime) + _, err := getTcb().DatabaseAdd(getConfig().TcbEnv, query) + if err != nil { + return err + } + return nil +} +``` + +其中对于数据库中的query语句可以参考[https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/add.html](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/add.html) + +这里调用 `DatabaseAdd` 方法实现内容的保存。 + + +## 接收表单提交内容 +将表单提交的内容提交到 `/feedback` 路由中,并创建 `feedback`方法接收表单提交的参数, + +```go +func main() { + r := gin.Default() + + //包含html模板 + r.LoadHTMLGlob("./template/*") + //渲染留言页面 + r.GET("/",index) + //提交留言 + r.POST("/feedback", feedback) + + r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") +} +``` + +其中feedback方法如下: + +```go +//接收提交的内容 +func feedback(c *gin.Context) { + //1、接收提交参数 + feedback := &Feedback{} + err := c.Bind(feedback) + if err != nil { + showError(c, err) + return + } + feedbackService := NewFeedbackService() + //保存内容 + err = feedbackService.Save(feedback) + if err != nil { + showError(c, err) + return + } + + c.Redirect(http.StatusMovedPermanently, "/") +} +``` + +这里通过c.Bind方法将form表单中的内容绑定到 `Feedback` stuct中,再通过调用feedbackService中的Save方法对文本内容进行保存。 + +## 展示留言内容 +留言内容的展示主要分为两步,一先从数据库展示出来,二是将留言内容展示在页面:
查询的sql语句为: +```go +db.collection("guestbook").orderBy('createTime','desc').skip(0).limit(10).get() +``` + +在 `feedback.go` 中新增List方法,其中参数传入skip和limit参数用于分页 + +```go +//List 文本列表 +func (svc *FeedbackService) List(skip, limit int) ([]*Feedback, error) { + query := fmt.Sprintf("db.collection(\"guestbook\").orderBy('createTime','desc').skip(%d).limit(%d).get()", skip, limit) + data, err := getTcb().DatabaseQuery(getConfig().TcbEnv, query) + if err != nil { + return nil, err + } + feedbacks := make([]*Feedback, 0, len(data.Data)) + for _, v := range data.Data { + feedbackItem := &Feedback{} + err := json.Unmarshal([]byte(v), feedbackItem) + if err != nil { + return nil, err + } + feedbacks = append(feedbacks, feedbackItem) + + } + //fmt.Println(data.Pager) + return feedbacks, nil +} +``` + +这里主要调用 `DatabaseQuery` 方法对db进行查询。 + +在main.go中的index方法在从数据中获取的数据取出并渲染在index.html中: + +```go +//首页 +func index(c *gin.Context) { + page := c.Query("page") + //获取记录数量 + feedbackService := NewFeedbackService() + count, err := feedbackService.Count() + if err != nil { + showError(c, err) + return + } + limit := 10 + totalPage := math.Ceil(float64(count) / float64(limit)) + totalPageInt := int(totalPage) + pageInt, _ := strconv.Atoi(page) + if pageInt > totalPageInt { + pageInt = totalPageInt + } + if pageInt < 1 { + pageInt = 1 + } + + //展示留言列表 + skip := (pageInt - 1) * limit + list, err := feedbackService.List(skip, limit) + if err != nil { + showError(c, err) + return + } + + c.HTML(http.StatusOK, "index.html", gin.H{ + "title": "留言板", + "list": list, + "prevPage": pageInt - 1, + "nextPage": pageInt + 1, + "page": pageInt, + "totalPage": totalPageInt, + }) +} +``` + +其中index.html通过go template语法对内容进行渲染 + +```html +
+ {{range .list}} +
+
{{.Username}} 在 {{.CreateTime}} 说:
+

{{.Content}}

+
+ {{end}} +
+
+ 第{{.page}}页 + {{if gt .page 1}}上一页{{end}} + {{if lt .page .totalPage}} 下一页{{end}} +
+``` + +这样就实现了对文本内容的保存 + diff --git a/cloudbase/guestbook-demo/start.md b/cloudbase/guestbook-demo/start.md new file mode 100644 index 0000000..c7f7376 --- /dev/null +++ b/cloudbase/guestbook-demo/start.md @@ -0,0 +1,185 @@ +# 起步:项目搭建 + + +## 目标 +通过完成一个留言板应用来熟悉云开发中go sdk中的使用,主要分为以下三个内容 + +1. 如何利用云开发中的[数据库](https://developers.weixin.qq.com/minigame/dev/wxcloud/reference-http-api/database/#%E6%95%B0%E6%8D%AE%E5%BA%93)进行留言内容的保存 +1. 使用[云函数](https://developers.weixin.qq.com/minigame/dev/wxcloud/reference-http-api/functions/)进行文本内容的过滤 +1. 使用[云开发存储](https://developers.weixin.qq.com/minigame/dev/wxcloud/reference-http-api/storage/)能力进行附件的保存 + + +## 环境介绍 + +### Golang 1.13 +项目中使用Golang 1.13版本进行开发,并且使用go module 进行依赖管理 + +### 编辑器:Goland +代码编辑工具 + +### 热编译工具:Gowatch +Go 程序热编译工具,提升开发效率
官网地址: [https://github.com/silenceper/gowatch](https://github.com/silenceper/gowatch)
**快速安装:** +```basic +go get -u github.com/silenceper/gowatch +``` + + +### web开发框架-Gin +一个web开发框架,方便快速构建一个web应用
官网: [https://github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) + +### Wechat SDK For Go +使用Golang对微信公众号,小程序,云开发等API进行封装,使得Go项目中可以方便上手
官网: [https://github.com/silenceper/wechat/](https://github.com/silenceper/wechat/) 
文档:[https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc) + +### 云开发 +集成数据库,存储,云函数等功能的平台
使用文档:[https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/)
在开始开发前,请注册一个小程序获取 `app_id` , `app_secret`参数,并开启云开发功能。 + + +## 初始化项目 +> 本项目中使用go module进行依赖管理 + +在工作目录创建一个项目`guestbook`,并使用`go mod init github.com/go-demo/guestbook`进行初始化,后面接的是`import path`。 + +```bash +mkdir guestbook +cd guestbook +go mod init github.com/go-demo/guestbook +``` + + +### 编写main.go文件 +使用goland编辑器中打开这个项目,并创建一个`main.go`文件,内容如下: + +```go +package main + +import "github.com/gin-gonic/gin" + +func main() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + r.Run() // listen and serve on 0.0.0.0:8080 +} +``` + + +### 编译并运行 +我们可以在Goland编辑器中Terminal面板中进入项目目录,使用`gowatch`命令对该项目进行热编译,看到图片中的log输出表示已经启动成功: +> gowatch会监听项目中文件的变化,当进行变化后,对项目进行build 和run,这样我们就可以在一边修改代码一边对项目进行编译及时发现错误,是不是效率提升了呢  :> + + +![image.png](https://cdn.nlark.com/yuque/0/2020/png/748713/1579680949745-4a9d705e-b2d1-4667-a7a7-b9a5200321c8.png#align=left&display=inline&height=777&name=image.png&originHeight=1554&originWidth=2470&size=400945&status=done&style=none&width=1235)
(初次build会通过go module自动下载依赖,请注意开启go module功能) + +我们通过访问`127.0.0.1:8080/ping`就可以看到页面上输出`{"message":"pong"}`说明服务启动成功。 + + +## 渲染留言页面 +我们可以先规划我们的UI是怎么样子? + +包含两部分: + +- 留言框:包含留言内容,附件上传,用户名,提交按钮 +- 内容展示:展示留言内容,附件以及留言者和留言日期 + +界面展示如下:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/748713/1579681903215-81a613c0-0a08-4196-ba6d-36c8942e107c.png#align=left&display=inline&height=331&name=image.png&originHeight=1312&originWidth=2352&size=99758&status=done&style=none&width=593) + +### 创建模板文件 +对应的html代码如下,我们保存在项目中的template/index.html文件中: + +```html + + + + 留言板 + + + +

留言板

+
+
+
+ +
+
+ + +
+
+ +
+
+
+

内容

+
+
+
silenceper 在 2020-01-21 12:33:45 说:
+

留言板内容

+
+
+
+ 第1页 +
+
+ + +``` + +这里引入了bootstrap样式文件,不需要自己写太多前端样式了,出来的UI也不会太难看。 + + +### 通过gin渲染模板 +我们想要通过访问`127.0.0.1:8080`直接访问到这个留言页面,main.go中代码如下: + +```go +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + //包含html模板 + r.LoadHTMLGlob("./template/*") + //渲染留言页面,GET请求,通过根路径可以直接访问 + //当路径匹配成功后,进入index访问进行处理 + r.GET("/",index) + + + r.Run() // listen and serve on 0.0.0.0:8080 +} + +//渲染留言板首页 +func index(c *gin.Context) { + //返回200,并渲染index.html页面 + c.HTML(http.StatusOK,"index.html",gin.H{ + "title":"留言板", + }) +} +``` + +其中`r.LoadHTMLGlob("./template/*") `指定了html模板的位置,这样在使用进行`c.HTML`进行渲染的时候就知道到哪个位置进行查找了。 + +**c.HTML说明:** + +- 第一个参数:http状态码 +- 第二个参数:需要渲染的模板 +- 第三个参数:需要传递的值(`gin.H`其实是一个`map[string]interface{}`的别名) + +这里`c.HTML`渲染了`index.html`,并以`200`状态码输出,第三个参数`gin.H`,传入`key:value` ,就可以在`index.html`页面中使用go-template语法进行值的替换,语法格式: + +`{{.title}}` + +这里可以查阅gin文档:[如何进行html渲染](https://github.com/gin-gonic/gin#html-rendering) + + +## 代码地址 +本文中所有代码都上传在 [https://github.com/go-demo/guestbook](https://github.com/go-demo/guestbook) + diff --git a/cloudbase/guestbook-demo/storage.md b/cloudbase/guestbook-demo/storage.md new file mode 100644 index 0000000..369674e --- /dev/null +++ b/cloudbase/guestbook-demo/storage.md @@ -0,0 +1,143 @@ +## API说明 +官方文档:[微信云开发存储文档](https://developers.weixin.qq.com/minigame/dev/wxcloud/reference-http-api/storage/),主要提供了三个API(上传,下载,删除),可以先分别看下参数 + +在这一节中主要利用到了sdk中的附件上传和下载的方法,方法如下: +```go +func (tcb *Tcb) UploadFile(env, path string) (*UploadFileRes, error) { +func (tcb *Tcb) BatchDownloadFile(env string, fileList []*DownloadFile) (*BatchDownloadFileRes, error) { +``` + +返回对应的返回结果在这里可以查看,[UploadFileRes](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#UploadFileRes),[BatchDownloadFileRes](https://pkg.go.dev/github.com/silenceper/wechat/tcb?tab=doc#BatchDownloadFileRes) + + +## 接收附件上传内容 +1、保存index.html中form表单中使用的是post方法并且enctype为 `multipart/form-data` : + +```html +
+``` +2、在main.go中的feedback方法中获取上传的文件 + +```go +//2、文件上传 + fileHeader, err := c.FormFile("file") + if err != nil && err != http.ErrMissingFile { + showError(c, err) + return + } +``` + + +## 将附件保存在云开发中 +在feedbackService中创建 `UploadFile` 方法,调用sdk中文件上传方法实现对文件的上传:
UploadFile 第一个参数为云开发环境,第二个参数为需要附件需要保存的路径 +> 注意,这里path应该为相对路径。不能为绝对路径比如 /guestbook 应该为 guestbook 否则会报错。 + + +```go + +//UploadFile 上传文件 +func (svc *FeedbackService) UploadFile(path string, file io.Reader) (string, error) { + //获取文件上传链接 + uploadRes, err := getTcb().UploadFile(getConfig().TcbEnv, path) + if err != nil { + return "", err + } + + data := make(map[string]io.Reader) + data["key"] = strings.NewReader(path) + data["Signature"] = strings.NewReader(uploadRes.Authorization) + data["x-cos-security-token"] = strings.NewReader(uploadRes.Token) + data["x-cos-meta-fileid"] = strings.NewReader(uploadRes.CosFileID) + data["file"] = file + + //上传文件 + _, err = goutils.PostFormWithFile(&http.Client{}, uploadRes.URL, data) + return uploadRes.FileID, err +} +``` + +其中 `PostFormWithFile` 方法是对 `mime/multipart` 方法的一个封装,用于将附件内容上传到指定的url中,在 `github.com/silenceper/goutils` 包中。 + +并在main.go中feedback方法调用并将返回的field和Filename保存在db中 + +```go + //2、文件上传 + fileHeader, err := c.FormFile("file") + if err != nil && err != http.ErrMissingFile { + showError(c, err) + return + } + feedbackService := NewFeedbackService() + if fileHeader != nil { + path := fmt.Sprintf("guestbook/%s", fileHeader.Filename) + file, err := fileHeader.Open() + if err != nil { + showError(c, err) + return + } + + fileID, err := feedbackService.UploadFile(path, file) + if err != nil { + showError(c, err) + return + } + feedback.FilePath = fileHeader.Filename + feedback.FileID = fileID + } +``` + +最终附件可以在index方法通过数据库查询在html中展示出来 + + +## 附件下载 +这里创建一个单独的路由,用于对附件进行下载,通过在get参数中传入fileId,定位到附件,并打开附件路径: + +```go + r.GET("/file", downloadFile) +``` + +```go +//附件下载 +func downloadFile(c *gin.Context) { + fileID := c.Query("id") + if fileID == "" { + showError(c, fmt.Errorf("fileID为空")) + return + } + downLoadURL, err := NewFeedbackService().DownloadFile(fileID) + if err != nil { + showError(c, err) + return + } + c.Redirect(http.StatusMovedPermanently, downLoadURL) +} +``` + +在feedbackService中创建DownloadFile方法,返回真实下载路径,并跳转: + +```go + +//DownloadFile 获取下载链接 +func (svc *FeedbackService) DownloadFile(id string) (string, error) { + files := []*tcb.DownloadFile{&tcb.DownloadFile{ + FileID: id, + MaxAge: 100, + }} + res, err := getTcb().BatchDownloadFile(getConfig().TcbEnv, files) + if err != nil { + return "", err + } + if len(res.FileList) >= 1 { + return res.FileList[0].DownloadURL, nil + } + return "", nil +} +``` + +这里BatchDownloadFile方法第一个参数是云开发环境,第二个参数接收的 `tcb.DownloadFile` 数组,指定fileID和下载链接的有效期。 + +最终完成的效果如下:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/748713/1580025839031-8efea9fe-3ce0-4a4b-a8cd-0ad2120c1a9e.png#align=left&display=inline&height=666&name=image.png&originHeight=1332&originWidth=2256&size=112605&status=done&style=none&width=1128) + + +本文中所有代码都上传在 [https://github.com/go-demo/guestbook](https://github.com/go-demo/guestbook) +