当我刚开始使用 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” 消息!
创建 Vue 项目
从项目目录运行以下命令,创建一个新的 Vue 项目。
vue create frontend
这会创建很多文件,但是没关系,我们先运行 Vue 服务器。
yarn serve
访问 localhost:8081,可以看到 Vue app 了!
接下来,我们清理一下前端目录。
删掉 assets
和 components
,因为我们不需要它们。
然后更新 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 服务器,检查是否看到日志消息。
没有问题!
创建网站缩略图
要创建网站缩略图,我将使用 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 服务器查看结果:
显示 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。
总结
我们使用 Go 和 Vue 搭建了一个全栈网站缩略图生成器,前后端分离,并且我们添加了一个外部 API,通过 Go 服务器调用。
在线体验这个应用程序。附上 Github 源代码。
Happy coding!
原文:How to Set Up a Real-World Project with Go and Vue,作者:Dirk Hoekstra