俗话说:“工欲善其事,必先利其器”。还记得在函数专题里面讲过的“轮子”的概念吗?在日常开发中,有一些较为通用的函数,这些函数通常被各种项目使用以实现一些通用的功能。

这些功能有的是Go语法并未直接提供的,比如递归列出/删除文件夹中的子文件夹和文件;也有一些是公司的固定业务,比如从公司固定的服务器上获取特定的数据等等。

对于这些函数而言,都放在一个Go源码文件中显然是不太合适的,因为它们涉及不同的功能。特别是如果代码量太大的话,不便于日后的查找和维护。

一种更好的做法便是实现多个“小轮子”,再把这些小轮子分门别类地放在“工具箱”中。如此,我们便可根据分类,快速地找到当前最需要的“轮子”了。这些“小轮子”指的就是函数,“工具箱”指的是组织函数源码的包。

本讲将手把手地带大家实现实时天气数据的包,在实现这个包的过程中,我会介绍Go语言中自定义包的声明和导入以及相关的注意事项。

为方便大家实操,本讲源码工程位于gitee.com/wh1990xiao2… (opens new window) ,感兴趣的朋友可自行下载,运用到实际项目中,没有版权限制。

# 封装包

Go语言借助文件系统树形结构来组织包。具体来说,

  • 虽然Go语法没有强制要求包名与其所在的目录名相同,但习惯上我们还是会保持这二者相同
  • 可以定义在多层级的目录中
  • 单个包的所有源码应存在相同的目录下,不同目录通常包含不同的包源码;
  • 包名一般开头是小写的,采用小驼峰式命名法
  • 多个类似业务的公司可能会封装相同名称的包,为了确保唯一性,建议大家使用域名作为目录结构的一部分

对应到本例,首先新建一个工程,工程名称为go-juejin-weather,创建好后,依次创建juejin.cn(此为域名)目录,再进入该目录,创建weather(此为包名)目录。既然我们要封装获取实时天气的包,便可将包的源码命名为weather.go,放置在该目录下。如此一通操作后,整个工程的结构如下图所示:

image-20220328092833793.png

打开weather.go源码,分别使用Go SDK中的net包和io包中的函数进行网络数据请求和请求结果的解析,完整的代码如下:

go
复制代码package weather
import (
   "fmt"
   "io/ioutil"
   "net/http"
)
func CurrentWeather(cityCode string) string {
   //使用net包发起Get请求
   resp, err := http.Get("https://devapi.qweather.com/v7/weather/now?location=" + cityCode + "&key=[您自己申请的AppKey]")
   if err != nil {
      fmt.Println("HTTP请求失败:", err)
      panic(err)
   }
   //使用断言关闭网络请求
   defer resp.Body.Close()
   //使用ioutil工具包获取服务端响应数据
   body, err := ioutil.ReadAll(resp.Body)
   if err != nil {
      fmt.Println("读取网络响应失败:", err)
      panic(err)
   }
   return string(body)
}

上述代码中,一上来便声明了这个源码文件属于weather包,接着导入了fmt、io和net包。最后的部分是名为CurrentWeather()函数,该函数需要一个表示城市代码的string类型值作为参数,并返回了最终的网络响应数据。这个函数是大写字母开头的,只有这样才能被其它的go源码调用。

💡 提示:有关net包和io包的更多内容将在下一讲中详述。
❗️ 注意:本示例使用了和风天气提供的天气数据API。通常来说,若要使用某个公共数据平台的服务,都要先申请AppKey。AppKey简称API接口验证序号,相当于一个ID,用于验证API接入合法性的。它代表一个应用程序,便于区分和管理。只有通过申请的合法AppKey才能合法地获取数据,像百度/高德地图等API,若要正常使用都需要申请AppKey,且过程都是类似的。强烈建议大家自行登录和风天气开发者网站,注册开发者账户,体验一次完整的AppKey申请和使用的过程。

到此,一个简单的weather包的封装就结束了。

# 使用包

回到main.go中,尝试调用weather包中的函数——CurrentWeather()。

如果各位使用的是GoLand,在main函数中只需输入Cur三个字母,便可在代码提示中看到CurrentWeather()函数了。选中它并敲回车键,函数的调用会被自动补全,同时,weather包也会自动导入。

由于CurrentWeather()函数最终将返回string类型的数据,因此我们声明一个名为result的变量,并将函数的返回值赋给这个变量。整个main.go的代码如下所示:

go
复制代码package main
import (
   "fmt"
   "go_juejin_weather/juejin.cn/weather"
)
func main() {
   result := weather.CurrentWeather("101010100")
   fmt.Println(result)
}

运行后,控制台将输出:

{"code":"200","updateTime":"2022-03-28T09:02+08:00","fxLink":"hfx.link/2ax1","now"… (opens new window) commercial use"]}}

Process finished with the exit code 0

我是怎么知道请求地址的呢?城市代码又是什么意思呢?上述数据结果又该如何解读呢?

实际上,这些内容都可以在和风天气开发者网站找到。我们使用任何一个公共数据服务平台时,都可以阅读它的开发者文档找到该平台所能提供的一切能力,以及请求返回结果的解读方法。

本例中的实时天气的获取和解读方法就是从:dev.qweather.com/docs/api/we… (opens new window) 找到的。

本例中的城市代码使用了“101010100”,这个代码表示中国北京市。这个代码在开发者网站并没有直接提供,但可以通过“城市信息查询API(dev.qweather.com/docs/api/ge… (opens new window) )”获取答案。

# 思考题

最后,我们来继续完善这个示例。

既然我们可以通过城市信息查询API获取代码,不妨在weather包中在添加一个go源码,用于获取城市代码。最终的效果就是在main()函数中调用一次函数,然后自动获取城市代码,再通过城市代码获取该城市的天气情况。

欢迎大家到 gitee.com/wh1990xiao2… (opens new window) 一起讨论解决方案。

# 总结

🎉 恭喜,您完成了本次课程的学习!

📌 以下是本次课程的重点内容总结:

  • 自定义包的声明与导入

本讲是包系列的第二篇,介绍了封装包意义以及如何使用包。

封装可以对外隐藏实现细节,保证内部数据安全

本讲通过对函数和包进行封装,掌握了如何在Go语言中自定义包以及如何使用自定义包。

好了,本讲就到这里。

➡️ 在下次课程中,我们会介绍Go语言中包的更多知识,具体内容是:

  • Go SDK中的常用包一览