当前位置:   article > 正文

uniapp小程序开发 | 从零实现一款影视类app (后台接口实现,go-zero微服务的使用)

uniapp小程序开发 | 从零实现一款影视类app (后台接口实现,go-zero微服务的使用)

uniapp小程序开发实战系列,完整介绍从零实现一款影视类小程序。包含小程序前端和后台接口的全部完整实现。系列连载中,喜欢的可以点击收藏。

该篇着重介绍获取轮播图后台接口和获取正在热映电影的两个后台接口的实现。 

后台服务使用golang,因为它太适合做后台服务了。而且配合使用go-zero微服务框架,不但强大,还提供了好用的goctl工具,自动生成接口框架代码,让你写接口速度飞升。

下文以两个接口(轮播图接口和豆瓣热门影视接口)示例,可以看到使用go-zero写服务接口是多么的简单。

为了示例和快速实现,暂无后台管理界面。只实现后台接口。

轮播图接口,返回json数据,图片存储在腾讯云的COS对象存储服务,作为图床使用。

豆瓣正在热映电影接口,使用go-zero的httpc转发客户端请求,到豆瓣v2的开源api服务(https://api.douban.com/v2)接口去请求数据。

go-zero 介绍

go-zero是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。

go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。

详细介绍:go-zero 缩短从需求到上线的距离

github地址:https://github.com/zeromicro/go-zero

文档介绍go-zero/readme-cn.md at master · zeromicro/go-zero · GitHub 

goctl 工具安装

goctl 是 go-zero 的内置脚手架,是提升开发效率的一大利器,可以一键生成代码、文档、部署 k8s yaml、dockerfile 等。

  1. # Go
  2. GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
  3. # For Mac
  4. brew install goctl
  5. # docker for amd64 architecture
  6. docker pull kevinwan/goctl
  7. # run goctl like
  8. docker run --rm -it -v `pwd`:/app kevinwan/goctl --help

由于我是在我的ubuntu20服务器上安装使用的,所以选择了方式三。其实如果使用vscode, 则可以直接安装插件即可,在vscode中搜索goctl插件。 在windows 上安装就不提了,更简单了。

由于我的测试代码跑在腾讯云服务器上,建议使用vscode远程连接的开发方式。在vscode上安装Remote - SSH插件,非常好用,使用方法:VSCODE远程连接服务器,远程开发

goctl快速使用文档:zero-doc/docs/zero/goctl-api.md at main · zeromicro/zero-doc · GitHub 

快速开始 

前提是具备golang环境和成功安装完成了goctl工具。

简易使用教程,参加我的博客:go-zero微服务框架入门教程_go-zero教程-CSDN博客

下面介绍下如何快速开始一个使用go-zero微服务框架的一个项目。

  1. goctl api new greet
  2. cd greet
  3. go mod tidy
  4. go run greet.go -f etc/greet-api.yaml

执行以上代码,会自动创建工程目录greet,生成一些可以运行的模板文件。一个工程就创建完啦,且执行go run 命令,后台网关服务就已经启动起来啦,默认端口8888,这么简单。

接下来可以使用 curl命令测试一下接口: 

curl -i http://localhost:8888/from/you
  • api 文件定义了服务对外 HTTP 接口,可参考 api 规范
  • 可以在 servicecontext.go 里面传递依赖给 logic,比如 mysql, redis 等。

生成 api 服务

完成上面后,只是一个空的服务接口,如何增加自己的呢?接下来详细介绍。其实就是写好api文件。api 文件定义了服务对外 HTTP 接口,按它的api规范定义自己的接口文件。

我的api文件如下:

  1. syntax = "v1"
  2. info (
  3. title: "doc title"
  4. desc: "imovie background service api"
  5. version: "1.0"
  6. )
  7. type (
  8. //轮播图--应答
  9. SwiperData {
  10. id int `json:"id"`
  11. imageUrl string `json:"imageUrl"`
  12. title string `json:"title"`
  13. desc string `json:"description"`
  14. }
  15. SwiperResp {
  16. code int `json:"code"`
  17. message string `json:"message"`
  18. data []SwiperData `json:"data"`
  19. }
  20. //热门影视--请求
  21. HotMovieReq {
  22. start int `json:"start"`
  23. count int `json:"count"`
  24. city string `json:"city"`
  25. }
  26. //热门影视--应答
  27. HotItem {
  28. id string `json:"id"`
  29. cover string `json:"cover"`
  30. title string `json:"title"`
  31. rate int `json:"rate"`
  32. }
  33. HotMovieResp {
  34. code int `json:"code"`
  35. message string `json:"message"`
  36. data []HotItem `json:"data"`
  37. count int `json:"count"`
  38. start int `json:"start"`
  39. total int `json:"total"`
  40. title string `json:"title"`
  41. }
  42. )
  43. type Request {
  44. Name string `path:"name,options=you|me"`
  45. }
  46. type Response {
  47. Message string `json:"message"`
  48. }
  49. service imovie-api {
  50. @doc (
  51. summary: "imovie api"
  52. )
  53. @handler TestHandler
  54. get /test/:name (Request) returns (Response)
  55. @handler SwiperHandler
  56. get /api/v1/swiperdata returns (SwiperResp)
  57. @handler HotMovieHandler
  58. post /api/v1/hotmovie (HotMovieReq) returns (HotMovieResp)
  59. }

由于我是在ubuntu服务器上以docker方式安装的goctl工具,所以使用起来有点儿麻烦,类似下面这样这么长一串:

docker run --rm -it -v `pwd`:/app kevinwan/goctl --help

为了简单使用docker方式安装部署的goctl工具,写了以下配置: 

docker-compose.yml

  1. version: '3'
  2. services:
  3. goctl:
  4. image: kevinwan/goctl
  5. volumes:
  6. - .:/app
  7. working_dir: /app

于是后续再使用goctl命令,变成了下面这种方式,使用下述命令自动生成接口代码: 

sudo docker-compose run goctl api go -api go-imovie/imovie.api -dir go-imovie/

上述命令,就根据 api 文件自动生成了服务接口代码。

参数含义介绍:

业务代码编写 

接下来开始关键的地方了,业务接口的业务逻辑编写,这部分主要在internal文件夹下的logic文件夹下实现。

轮播图接口

swiperlogic.go文件实现

  1. package logic
  2. import (
  3. "context"
  4. "imovie/internal/svc"
  5. "imovie/internal/types"
  6. "github.com/zeromicro/go-zero/core/logx"
  7. )
  8. type SwiperLogic struct {
  9. logx.Logger
  10. ctx context.Context
  11. svcCtx *svc.ServiceContext
  12. }
  13. var MyPic_ = "https://pic-1258623197.cos.ap-beijing.myqcloud.com"
  14. func NewSwiperLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SwiperLogic {
  15. return &SwiperLogic{
  16. Logger: logx.WithContext(ctx),
  17. ctx: ctx,
  18. svcCtx: svcCtx,
  19. }
  20. }
  21. func (l *SwiperLogic) Swiper() (resp *types.SwiperResp, err error) {
  22. // todo: add your logic here and delete this line
  23. //var item types.SwiperData
  24. var responseData []types.SwiperData
  25. item1 := types.SwiperData{
  26. Id: 1,
  27. ImageUrl: MyPic_ + "/pic0/1.jpg",
  28. Title: "标题1",
  29. }
  30. item2 := types.SwiperData{
  31. Id: 2,
  32. ImageUrl: MyPic_ + "/pic0/2.jpg",
  33. Title: "标题2",
  34. }
  35. item3 := types.SwiperData{
  36. Id: 3,
  37. ImageUrl: MyPic_ + "/pic0/3.jpg",
  38. Title: "标题3",
  39. }
  40. responseData = append(responseData, item1)
  41. responseData = append(responseData, item2)
  42. responseData = append(responseData, item3)
  43. resp = &types.SwiperResp{
  44. Code: 0,
  45. Message: "success",
  46. Data: responseData,
  47. }
  48. return resp, nil
  49. }

热映电影接口 

hotmovielogic.go文件实现,实现客户端接口转发到豆瓣服务接口。

  1. package logic
  2. import (
  3. "context"
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "imovie/internal/svc"
  8. "imovie/internal/types"
  9. "github.com/zeromicro/go-zero/core/logx"
  10. "github.com/zeromicro/go-zero/rest/httpc"
  11. )
  12. type HotMovieLogic struct {
  13. logx.Logger
  14. ctx context.Context
  15. svcCtx *svc.ServiceContext
  16. }
  17. var Url_ = "https://api.douban.com/v2/movie/in_theaters"
  18. var ApiKey_ = "xxxxxxxxxx"
  19. var Referer_ = "https://images.weserv.nl/?url="
  20. func NewHotMovieLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HotMovieLogic {
  21. return &HotMovieLogic{
  22. Logger: logx.WithContext(ctx),
  23. ctx: ctx,
  24. svcCtx: svcCtx,
  25. }
  26. }
  27. func (l *HotMovieLogic) HotMovie(req *types.HotMovieReq) (resp *types.HotMovieResp, err error) {
  28. // todo: add your logic here and delete this line
  29. type Request struct {
  30. Req types.HotMovieReq
  31. ApiKey string `json:"apikey"`
  32. }
  33. req_ := Request{
  34. Req: *req,
  35. ApiKey: ApiKey_,
  36. }
  37. l.Debug(req_)
  38. url := Url_
  39. res, err_ := httpc.Do(l.ctx, http.MethodPost, url, req_)
  40. if err_ != nil {
  41. l.Error(err_)
  42. return nil, err_
  43. }
  44. defer res.Body.Close()
  45. body, err := io.ReadAll(res.Body)
  46. if err != nil {
  47. l.Errorf("Failed to read response body:", err)
  48. return nil, err
  49. }
  50. //格式化输出json
  51. //var str bytes.Buffer
  52. //_ = json.Indent(&str, []byte(body), "", " ")
  53. //l.Debugf("formated: ", str.String())
  54. var keyVal map[string]interface{}
  55. err = json.Unmarshal(body, &keyVal)
  56. if err != nil {
  57. l.Errorf("Failed to extract key value:", err)
  58. }
  59. //l.Debug(keyValue)
  60. var hot types.HotItem
  61. var responseData []types.HotItem
  62. list_, ok := keyVal["subjects"].([]interface{})
  63. if ok {
  64. for _, item := range list_ {
  65. itemMap, ok := item.(map[string]interface{})
  66. if ok {
  67. //l.Debug(itemMap)
  68. hot.Id = itemMap["id"].(string)
  69. hot.Title = itemMap["title"].(string)
  70. tmp := itemMap["images"].(map[string]interface{})
  71. hot.Cover = Referer_ + tmp["small"].(string)
  72. hot.Rate = int(itemMap["rating"].(map[string]interface{})["average"].(float64))
  73. }
  74. responseData = append(responseData, hot)
  75. }
  76. }
  77. //t := reflect.TypeOf(keyVal["count"])
  78. //l.Debugf("Type: %v\n", t)
  79. resp = &types.HotMovieResp{
  80. Code: 0,
  81. Message: res.Status,
  82. Data: responseData,
  83. Count: int(keyVal["count"].(float64)),
  84. Start: int(keyVal["start"].(float64)),
  85. Total: int(keyVal["total"].(float64)),
  86. Title: keyVal["title"].(string),
  87. }
  88. return resp, nil
  89. }

启动服务

启动服务很简单,直接进入项目目录并执行 go run imovie.go即可。默认端口8888.

如果要更改配置,可以进入项目目录下的etc文件夹,找到imovie-api.yml文件修改。配置文件默认是yaml格式。日志的级别也可以在这里配置更改:

  1. Name: imovie-api
  2. Host: 0.0.0.0
  3. Port: 8000
  4. Log:
  5. Level: debug

关于YAML文件

YAML(YAML Ain't Markup Language)是一种简洁、易读、易写的,用于数据序列化的数据交换格式。它常用于配置文件,因为它比XML更简洁,比JSON更易读写(支持多行文本、注释等)。

YAML的基本特点:

1.大小写敏感

2.使用缩进表示层级关系:不允许使用Tab键,必须使用空格,且相同层级的元素左侧对齐。

3.# 表示注释,从这个字符开始直到行尾的内容都会被忽略。

YAML 文件示例

下面是一个简单的YAML文件示例,展示了如何定义键值对、数组、嵌套结构以及使用注释。

  1. # 这是一个YAML配置文件示例
  2. server:
  3. # 服务器地址
  4. host: "localhost"
  5. # 服务器端口
  6. port: 8080
  7. # 数据库配置
  8. database:
  9. # 数据库类型
  10. type: "mysql"
  11. # 数据库地址
  12. host: "127.0.0.1"
  13. # 用户名
  14. username: "root"
  15. # 密码
  16. password: "password123"
  17. # 应用日志设置
  18. logging:
  19. level: "info" # 日志级别
  20. # 日志文件路径
  21. file: "/var/log/app.log"
  22. # 开发者列表
  23. developers:
  24. - name: "张三"
  25. email: "zhangsan@example.com"
  26. - name: "李四"
  27. email: "lisi@example.com"

在这个示例中,server、database、logging 和 developers 是顶层键,每个键下可以有子键。
# 符号后面的内容是注释。

数组(如 developers)通过 - 符号标识每个元素,并且每个元素都是一个映射(键值对)。

YAML文件在许多编程语言和框架中都有良好的支持,用于配置应用程序的各种设置。 

接口测试工具

推荐使用vscode的rest client插件,编写以下测试(文件名test.http):

  1. post http://175.178.126.10:8000/api/v1/hotmovie
  2. Content-Type:application/json
  3. {
  4. "start": 0,
  5. "count": 1,
  6. "city": "郑州"
  7. }
  8. ### 下一项测试,注意前面三个###分割

 结果成功收到应答:

豆瓣接口介绍

获取正在热映的电影

使用vscode的rest client插件测试接口:

  1. ### Below is the code of douban.http,use vscode extension REST Client to send request.
  2. post https://api.douban.com/v2/movie/in_theaters
  3. Content-Type:application/json
  4. {
  5. "start": 0,
  6. "count": 1,
  7. "city": "郑州",
  8. "apikey": "xxxxxxxxxxx"
  9. }
  10. ### Respondse
  11. {
  12. "count": 1,
  13. "start": 0,
  14. "total": 43,
  15. "subjects": [
  16. ],
  17. "title": "\u6b63\u5728\u4e0a\u6620\u7684\u7535\u5f71-\u90d1\u5dde"
  18. }

或者使用curl命令测试接口: 

  1. curl --location --request POST 'https://api.douban.com/v2/movie/in_theaters?city=广州&start=0&count=1' --data-urlencode 'apikey=xxxxxxxxxxxxxx' |python3 -m json.tool
  2. % Total % Received % Xferd Average Speed Time Time Time Current
  3. Dload Upload Total Spent Left Speed
  4. 100 1732 100 1693 100 39 4480 103 --:--:-- --:--:-- --:--:-- 4582
  5. {
  6. "count": 1,
  7. "start": 0,
  8. "total": 46,
  9. "subjects": [
  10. {
  11. "rating": {
  12. "max": 10,
  13. "average": 9.5,
  14. "stars": "50",
  15. "min": 0
  16. },
  17. "genres": [
  18. "\u7eaa\u5f55\u7247",
  19. "\u97f3\u4e50"
  20. ],
  21. "title": "\u5742\u672c\u9f99\u4e00\uff1a\u6770\u4f5c",
  22. "casts": [
  23. {
  24. "alt": "https://movie.douban.com/celebrity/1148641/",
  25. "avatars": {
  26. "small": "https://img1.doubanio.com/view/personage/m/public/12ce4a9f67eac0cb029736ae87549dd0.jpg",
  27. "large": "https://img1.doubanio.com/view/personage/m/public/12ce4a9f67eac0cb029736ae87549dd0.jpg",
  28. "medium": "https://img1.doubanio.com/view/personage/m/public/12ce4a9f67eac0cb029736ae87549dd0.jpg"
  29. },
  30. "name": "\u5742\u672c\u9f99\u4e00",
  31. "id": "1148641"
  32. }
  33. ],
  34. "collect_count": 19158,
  35. "original_title": "Ryuichi Sakamoto | Opus",
  36. "subtype": "movie",
  37. "directors": [
  38. {
  39. "alt": "https://movie.douban.com/celebrity/1442776/",
  40. "avatars": {
  41. "small": "https://img1.doubanio.com/view/celebrity/m/public/pXA5FFrGwJ94cel_avatar_uploaded1597077781.8.jpg",
  42. "large": "https://img1.doubanio.com/view/celebrity/m/public/pXA5FFrGwJ94cel_avatar_uploaded1597077781.8.jpg",
  43. "medium": "https://img1.doubanio.com/view/celebrity/m/public/pXA5FFrGwJ94cel_avatar_uploaded1597077781.8.jpg"
  44. },
  45. "name": "\u7a7a\u97f3\u592e",
  46. "id": "1442776"
  47. }
  48. ],
  49. "year": "2023",
  50. "images": {
  51. "small": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2907966076.jpg",
  52. "large": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2907966076.jpg",
  53. "medium": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2907966076.jpg"
  54. },
  55. "alt": "https://movie.douban.com/subject/36491177/",
  56. "id": "36491177"
  57. }
  58. ],
  59. "title": "\u6b63\u5728\u4e0a\u6620\u7684\u7535\u5f71-\u5e7f\u5dde"
  60. }

访问外部图片返回 403 Forbidden 错误问题

遇到提示{"code":"40310015","msg":"referer uri is forbidden"},表明豆瓣对图片资源的访问实施了Referer策略,只允许特定来源(referer)的请求访问图片资源。当你直接在浏览器中输入图片链接能访问,是因为浏览器的请求被视为合法的直接访问,而前端应用(尤其是Web应用)发起请求时,如果其域名不在豆瓣允许的Referer列表中,就会被拒绝访问。

可以在前端页面头部添加一个meta:

<meta name="referrer" content="no-referrer" />

或者最简单的方式就是在<img>标签中增加:

<img src=""    referrerPolicy="no-referrer">

各有优缺点吧,某些旧版本或非主流浏览器可能不支持 referrer-policy。如果原始服务器严格限制Referer,上述这种方法可能无效。

也可以借助Images.weserv.nl图片缓存网站帮我们解决这个问题。

images.weserv.nl 是一个免费的图片托管和缓存服务,它可以用来间接访问受Referer限制的图片。这个服务会将原始图片URL作为参数传递,然后返回一个新的URL,这个新URL可以直接在前端使用,而不需要担心Referer限制。

使用 images.weserv.nl 的步骤如下:

替换图片URL:将豆瓣的原始图片URL替换为 https://images.weserv.nl/?url=<原始图片URL>。

在前端处理:

  1. // 图片防盗链问题解决
  2. function attachImageUrl(srcUrl) {
  3. if (srcUrl !== undefined) {
  4. return srcUrl.replace(/http\w{0,1}:\/\/p/g, 'https://images.weserv.nl/?url=p')
  5. }
  6. }

或者后台处理,返回的图片url上做处理。

var Referer_ = "https://images.weserv.nl/?url="
  1. var Referer_ = "https://images.weserv.nl/?url="
  2. var keyVal map[string]interface{}
  3. err = json.Unmarshal(body, &keyVal)
  4. if err != nil {
  5. l.Errorf("Failed to extract key value:", err)
  6. }
  7. //l.Debug(keyValue)
  8. var hot types.HotItem
  9. var responseData []types.HotItem
  10. list_, ok := keyVal["subjects"].([]interface{})
  11. if ok {
  12. for _, item := range list_ {
  13. itemMap, ok := item.(map[string]interface{})
  14. if ok {
  15. //l.Debug(itemMap)
  16. hot.Id = itemMap["id"].(string)
  17. hot.Title = itemMap["title"].(string)
  18. tmp := itemMap["images"].(map[string]interface{})
  19. hot.Cover = Referer_ + tmp["small"].(string)
  20. hot.Rate = int(itemMap["rating"].(map[string]interface{})["average"].(float64))
  21. }
  22. responseData = append(responseData, hot)
  23. }
  24. }

由于豆瓣接口返回的json数据比较多且略显杂乱,上述的json解析显得很麻烦。其实可以借助golang的三方库gjson处理这种格式的json数据。 gjson地址:GitHub - tidwall/gjson: Get JSON values quickly - JSON parser for Go

图床服务推荐

我使用的是腾讯云,上面的COS对象存储服务,可以作为开发测试用。它提供试用和免费额度,且提供免费域名直接可以https访问,挺不错的。虽然github和gitee也能用作免费图床,但是github访问慢,而gitee直接停止page服务,无法用了。

关于图床工具,推荐使用PicList.

PicList是一款高效的云存储和图床平台管理工具,在PicGo的基础上经过深度的二次开发,不仅完整保留了PicGo的所有功能,还增添了许多新的feature。例如相册支持同步云端删除文件,内置图床额外添加了WebDav、本地图床和SFTP等。

PicList同时增加了完整的云存储管理功能,包括云端目录查看、文件搜索、批量上传下载和删除文件,复制多种格式文件链接和图片/markdown/文本/视频预览等,另外还有更加强大的相册和多项功能新增或优化。

写在最后

最后,附上完整后台golang源码:https://download.csdn.net/download/qq8864/89401886

其他资源

0ab215a8b1977939201640fa14c66bab

https://go-zero.dev/docs/tutorials

https://zhuanlan.zhihu.com/p/570979109

https://github.com/zeromicro/go-zero?tab=readme-ov-file

https://github.com/zeromicro/zero-doc/blob/main/docs/zero/goctl-api.md

https://zhuanlan.zhihu.com/p/529462051

GitHub - tidwall/gjson: Get JSON values quickly - JSON parser for Go

https://www.jianshu.com/p/ef3fcf94295b

https://zhuanlan.zhihu.com/p/113500478

https://juejin.cn/post/6844903832040767496

https://blog.51cto.com/lanxf/5536521

uniapp中image不显示网络图片_uniapp image站外图片-CSDN博客

https://www.cnblogs.com/bigron/p/17334936.html

小白的最强保姆教学:PicGo + gitee +Typora免费搭建属于个人的图床工具_picgo+csdn-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/680317
推荐阅读
相关标签
  

闽ICP备14008679号