当我刚开始使用 Go 编程时,我发现它很难理解,感觉它比我以前学的其他语言都更底层。学了几个月之后,我变成 Go 语言的忠粉,并用它开发许多项目。

在本文中,我会分享如何使用 Go 和 Vue 搭建全栈 Web 应用程序。

我们要搭建什么项目

我觉得搭建一个网站缩略图生成器会很酷,就是当你输入一个网站 URL,应用程序将生成该网站的缩略图。

配置 Go 模块

首先,创建一个新的目录。然后,通过运行以下命令来配置 Go 模块。

go mod init github.com/Dirk94/website-thumbnail-generator

这样就创建了一个 go.mod 文件,包含模块的所有依赖。这和 node 项目中的 package.json 文件类似。

接下来,创建一个新的目录 main,在里面增加一个 server.go 文件。这是应用程序的主入口。

现在,我们打印一行 “hello world”。

package main

import "fmt"

func main() {
	fmt.Println("Hello world")
}

从项目目录运行以下命令,运行该程序:

go run main/server.go
Hello world

太好了,到目前为止一切正常!

配置 web 服务器

我们应该创建一个 web 服务器来监听传入的请求。

更新 main 函数:

func main() {
	http.HandleFunc("/", homePageHandler)

	fmt.Println("Server listening on port 3000")
	log.Panic(
		http.ListenAndServe(":3000", nil),
	)
}

这将启动 web 服务器并监听端口 3000。

homePageHandler 函数将处理所有传入的请求。

func homePageHandler(w http.ResponseWriter, r *http.Request) {
	_, err := fmt.Fprintf(w, "hello world")
	checkError(err)
}

func checkError(err error) {
	if err != nil {
		log.Panic(err)
	}
}

这个函数做的事情就是把 “hello world” 写入 http.ResponseWriter

checkError 函数的作用是在出现 error 的时候停止程序并打印堆栈跟踪。

运行该程序时,web 服务器将正确打印 “hello world” 消息!

Screenshot-2020-05-14-at-17.02.19

创建 Vue 项目

从项目目录运行以下命令,创建一个新的 Vue 项目。

vue create frontend

这会创建很多文件,但是没关系,我们先运行 Vue 服务器。

yarn serve

访问 localhost:8081,可以看到 Vue app 了!

Screenshot-2020-05-15-at-19.11.06

接下来,我们清理一下前端目录。

删掉 assetscomponents,因为我们不需要它们。

然后更新 App.vue 文件。

<template>
  <div id="app" class="container">
    <div class="row">
      <div class="col-md-6 offset-md-3 py-5">
        <h1>Generate a thumbnail of a website</h1>

        <form v-on:submit.prevent="makeWebsiteThumbnail">
          <div class="form-group">
            <input v-model="websiteUrl" type="text" id="website-input" placeholder="Enter a website" class="form-control">
          </div>
          <div class="form-group">
            <button class="btn btn-primary">Generate!</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

使用 v-model 标签,在表单提交时调用 makeWebsiteThumbnail 函数。

<script>
export default {
  name: 'App',

  data() { return {
    websiteUrl: '',
  } },

  methods: {
    makeWebsiteThumbnail() {
      console.log(`I should create a website thumbnail of ${this.websiteUrl}`);
    }
  }
}
</script>

我也用了些 Bootstrap class,给 public/index.html 文件添加 CSS file。

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
      
      <!--- The other stuff in the head tag here... -->
  </head>

启动 web 服务器,检查是否看到日志消息。

Screenshot-2020-05-15-at-18.36.31

没有问题!

创建网站缩略图

要创建网站缩略图,我将使用 screenshotapi.net。这样,我只需要调用 API 即可完成复杂的工作。

安装 axios:

yarn add axios

将它引入到 App.vue 文件:

<script>
  import axios from 'axios';
  
  export default {
    name: 'App', 
    
    // The rest here...
    

然后,更新 makeWebsiteThumbnail 函数,调用 screenshot API。

makeWebsiteThumbnail() {
  axios.post("https://screenshotapi.net/api/v1/screenshot", {
    token: "SCREENSHOTAPI_TOKEN",
    url: this.websiteUrl,
    width: 1920,
    height: 1080,
    output: 'json',
    thumbnail_width: 300
  })
  .then((response) => {
    this.thumbnailUrl = response.data.screenshot;
  })
  .catch((error) => {
    window.alert(`The API returned an error: ${error}`);
  })
}

记得将 SCREENSHOTAPI_TOKEN 替换为你的 token。

将变量 thumbnailUrl 设置为通过 API 创建的 screenshot URL。要实现这个,需要做两件事。

首先,把 thumbnailUrl 变量添加到 Vue data 对象:

data: {
  websiteUrl: '',
  thumbnailUrl: '',
},

其次,创建一个 img 标签,显示 thumbnailUrl 图像:

<img :src="thumbnailUrl"/>

启动  web 服务器查看结果:

Screenshot-2020-05-15-at-18.57.00

显示 freeCodeCamp 缩略图了,很棒!

合并 Go 和 Vue 的代码

现在,我们已经使用 Vue 开发服务器加速了前端,但是开发服务器只能在本地开发时使用。

当我们在生产环境中托管这个应用程序时,需要使用“真实的” web 服务器来处理传入的请求。

幸运的是,我们有 Go 服务器。

我们要做的第一件事是编译前端。

yarn run build

这样就创建了一个 dist 目录。

更新 Go 服务器,以从该目录提供文件。

为此,我更新了 main.go 文件中的 main 函数。

func main() {
	// Serve static files from the frontend/dist directory.
	fs := http.FileServer(http.Dir("./frontend/dist"))
	http.Handle("/", fs)

	// Start the server.
	fmt.Println("Server listening on port 3000")
	log.Panic(
		http.ListenAndServe(":3000", nil),
	)
}

frontend/dist 目录传入 fileserver。

运行 Go 程序,访问 localhost:3000,就可以看到应用了!

提高应用程序的安全性

目前,这个应用存在一个重大的安全漏洞。screenshot API token 在前端代码中可见。这意味着检查该网页的任何人都可以窃取 token。

我们通过使用服务器调用 screenshot API 来解决这个问题。这样,只有服务器需要知道 token。

server.go 中,创建一个新函数,以监听对 /api/thumbnail 端点的任何请求。

type thumbnailRequest struct {
	Url string `json:"url"`
}

func thumbnailHandler(w http.ResponseWriter, r *http.Request) {
	var decoded thumbnailRequest

	// Try to decode the request into the thumbnailRequest struct.
	err := json.NewDecoder(r.Body).Decode(&decoded)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	fmt.Printf("Got the following url: %s\n", decoded.Url)
}

现在,我们仅从请求中提取并打印 URL 参数。通过更新 main 函数,使用 thumbnailHandler 函数来实现。

func main() {
	// Use the thumbnailHandler function 
	http.HandleFunc("/api/thumbnail", thumbnailHandler)

	fs := http.FileServer(http.Dir("./frontend/dist"))
	http.Handle("/", fs)

	fmt.Println("Server listening on port 3000")
	log.Panic(
		http.ListenAndServe(":3000", nil),
	)
}

最后,更新 App.vue 文件,调用 Go 服务器,而不是 screenshot API。

makeWebsiteThumbnail() {
  // Call the Go API, in this case we only need the URL parameter.
  axios.post("http://localhost:3000/api/thumbnail", {
    url: this.websiteUrl,
  })
  .then((response) => {
    this.thumbnailUrl = response.data.screenshot;
  })
  .catch((error) => {
    window.alert(`The API returned an error: ${error}`);
  })
}

测试新的设置,可以在 Go 服务器看到一条日志信息。

go run main/server.go
Got the following url: freecodecamp.org

从 Go 调用 screenshot API

现在我们从 Go 服务器调用 screenshot API。

首先,创建一个 struct,包含调用 screenshot API 所需的所有参数。

type screenshotAPIRequest struct {
	Token          string `json:"token"`
	Url            string `json:"url"`
	Output         string `json:"output"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	ThumbnailWidth int    `json:"thumbnail_width"`
}

然后,更新 thumbnailHandler 函数,创建一个 http POST 请求,调用 API。

func thumbnailHandler(w http.ResponseWriter, r *http.Request) {
	var decoded thumbnailRequest

	// Try to decode the request into the thumbnailRequest struct.
	err := json.NewDecoder(r.Body).Decode(&decoded)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Create a struct with the parameters needed to call the ScreenshotAPI.
	apiRequest := screenshotAPIRequest{
		Token:          "SCREENSHOTAPI_TOKEN",
		Url:            decoded.Url,
		Output:         "json",
		Width:          1920,
		Height:         1080,
		ThumbnailWidth: 300,
	}

	// Convert the struct to a JSON string.
	jsonString, err := json.Marshal(apiRequest)
	checkError(err)

	// Create a HTTP request.
	req, err := http.NewRequest("POST", "https://screenshotapi.net/api/v1/screenshot", bytes.NewBuffer(jsonString))
	req.Header.Set("Content-Type", "application/json")

	// Execute the HTTP request.
	client := &http.Client{}
	response, err := client.Do(req)
	checkError(err)

	// Tell Go to close the response at the end of the function.
	defer response.Body.Close();

	// Read the raw response into a Go struct.
	type screenshotAPIResponse struct {
		Screenshot string `json"screenshot"`
	}
	var apiResponse screenshotAPIResponse
	err = json.NewDecoder(response.Body).Decode(&apiResponse)
	checkError(err)

	// Pass back the screenshot URL to the frontend.
	_, err = fmt.Fprintf(w, `{ "screenshot": "%s" }`, apiResponse.Screenshot)
	checkError(err)
}

重新启动 Go 服务器时,会看到缩略图生成器运行成功!而且,现在没有人可以窃取我们的 token。

Screenshot-2020-05-16-at-10.00.18

总结

我们使用 Go 和 Vue 搭建了一个全栈网站缩略图生成器,前后端分离,并且我们添加了一个外部 API,通过 Go 服务器调用。

在线体验这个应用程序。附上 Github 源代码

Happy coding!

原文:How to Set Up a Real-World Project with Go and Vue,作者:Dirk Hoekstra