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 等。

bash 复制代码
# Go
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest

# For Mac
brew install goctl

# docker for amd64 architecture
docker pull kevinwan/goctl
# run goctl like
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微服务框架的一个项目。

bash 复制代码
goctl api new greet
cd greet
go mod tidy
go run greet.go -f etc/greet-api.yaml

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

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

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

生成 api 服务

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

我的api文件如下:

Go 复制代码
syntax = "v1"

info (
	title:   "doc title"
	desc:    "imovie background service api"
	version: "1.0"
)

type (
	//轮播图--应答
	SwiperData {
		id       int    `json:"id"`
		imageUrl string `json:"imageUrl"`
		title    string `json:"title"`
		desc     string `json:"description"`
	}
	SwiperResp {
		code    int          `json:"code"`
		message string       `json:"message"`
		data    []SwiperData `json:"data"`
	}
	//热门影视--请求
	HotMovieReq {
		start int    `json:"start"`
		count int    `json:"count"`
		city  string `json:"city"`
	}
	//热门影视--应答
	HotItem {
		id    string `json:"id"`
		cover string `json:"cover"`
		title string `json:"title"`
		rate  int    `json:"rate"`
	}
	HotMovieResp {
		code    int       `json:"code"`
		message string    `json:"message"`
		data    []HotItem `json:"data"`
		count   int       `json:"count"`
		start   int       `json:"start"`
		total   int       `json:"total"`
		title   string    `json:"title"`
	}
)

type Request {
	Name string `path:"name,options=you|me"`
}

type Response {
	Message string `json:"message"`
}

service imovie-api {
	@doc (
		summary: "imovie api"
	)
	@handler TestHandler
	get /test/:name (Request) returns (Response)

	@handler SwiperHandler
	get /api/v1/swiperdata returns (SwiperResp)

	@handler HotMovieHandler
	post /api/v1/hotmovie (HotMovieReq) returns (HotMovieResp)
}

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

bash 复制代码
docker run --rm -it -v `pwd`:/app kevinwan/goctl --help

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

docker-compose.yml

bash 复制代码
   version: '3'
   services:
     goctl:
       image: kevinwan/goctl
       volumes:
         - .:/app
       working_dir: /app
   

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

bash 复制代码
sudo docker-compose run goctl api go -api go-imovie/imovie.api -dir go-imovie/

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

参数含义介绍:

为了简化使用docker部署方式的goctl命令的过程,你可以采取以下方式:

bash 复制代码
echo 'alias goctl="sudo docker-compose run goctl"' >> ~/.bashrc
source ~/.bashrc

格式化api文件

接来下可以直接使用goctl命令,格式化api文件,顺便检查写法是否有误,命令:

bash 复制代码
#格式化api文件
goctl api format imovie.api -dir go-imovie/

#生成代码
ubuntu:~/test/go$ goctl api go -api go-imovie/imovie.api -dir go-imovie/

业务代码编写

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

轮播图接口

swiperlogic.go文件实现

Go 复制代码
package logic

import (
	"context"

	"imovie/internal/svc"
	"imovie/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
)

type SwiperLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

var MyPic_ = "https://pic-1258623197.cos.ap-beijing.myqcloud.com"

func NewSwiperLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SwiperLogic {
	return &SwiperLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *SwiperLogic) Swiper() (resp *types.SwiperResp, err error) {
	// todo: add your logic here and delete this line
	//var item types.SwiperData
	var responseData []types.SwiperData

	item1 := types.SwiperData{
		Id:       1,
		ImageUrl: MyPic_ + "/pic0/1.jpg",
		Title:    "标题1",
	}

	item2 := types.SwiperData{
		Id:       2,
		ImageUrl: MyPic_ + "/pic0/2.jpg",
		Title:    "标题2",
	}

	item3 := types.SwiperData{
		Id:       3,
		ImageUrl: MyPic_ + "/pic0/3.jpg",
		Title:    "标题3",
	}

	responseData = append(responseData, item1)
	responseData = append(responseData, item2)
	responseData = append(responseData, item3)

	resp = &types.SwiperResp{
		Code:    0,
		Message: "success",
		Data:    responseData,
	}
	return resp, nil
}

热映电影接口

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

Go 复制代码
package logic

import (
	"context"
	"encoding/json"
	"io"
	"net/http"

	"imovie/internal/svc"
	"imovie/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/rest/httpc"
)

type HotMovieLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

var Url_ = "https://api.douban.com/v2/movie/in_theaters"
var ApiKey_ = "xxxxxxxxxx"
var Referer_ = "https://images.weserv.nl/?url="

func NewHotMovieLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HotMovieLogic {
	return &HotMovieLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *HotMovieLogic) HotMovie(req *types.HotMovieReq) (resp *types.HotMovieResp, err error) {
	// todo: add your logic here and delete this line
	type Request struct {
		Req    types.HotMovieReq
		ApiKey string `json:"apikey"`
	}
	req_ := Request{
		Req:    *req,
		ApiKey: ApiKey_,
	}
	l.Debug(req_)
	url := Url_
	res, err_ := httpc.Do(l.ctx, http.MethodPost, url, req_)
	if err_ != nil {
		l.Error(err_)
		return nil, err_
	}
	defer res.Body.Close()
	body, err := io.ReadAll(res.Body)
	if err != nil {
		l.Errorf("Failed to read response body:", err)
		return nil, err
	}
	//格式化输出json
	//var str bytes.Buffer
	//_ = json.Indent(&str, []byte(body), "", "    ")
	//l.Debugf("formated: ", str.String())
	var keyVal map[string]interface{}
	err = json.Unmarshal(body, &keyVal)
	if err != nil {
		l.Errorf("Failed to extract key value:", err)
	}
	//l.Debug(keyValue)
	var hot types.HotItem
	var responseData []types.HotItem
	list_, ok := keyVal["subjects"].([]interface{})
	if ok {
		for _, item := range list_ {
			itemMap, ok := item.(map[string]interface{})
			if ok {
				//l.Debug(itemMap)
				hot.Id = itemMap["id"].(string)
				hot.Title = itemMap["title"].(string)
				tmp := itemMap["images"].(map[string]interface{})
				hot.Cover = Referer_ + tmp["small"].(string)
				hot.Rate = int(itemMap["rating"].(map[string]interface{})["average"].(float64))
			}
			responseData = append(responseData, hot)
		}
	}
	//t := reflect.TypeOf(keyVal["count"])
	//l.Debugf("Type: %v\n", t)
	resp = &types.HotMovieResp{
		Code:    0,
		Message: res.Status,
		Data:    responseData,
		Count:   int(keyVal["count"].(float64)),
		Start:   int(keyVal["start"].(float64)),
		Total:   int(keyVal["total"].(float64)),
		Title:   keyVal["title"].(string),
	}
	return resp, nil
}

启动服务

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

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

bash 复制代码
Name: imovie-api
Host: 0.0.0.0
Port: 8000
Log:
  Level: debug

关于YAML文件

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

YAML的基本特点:

1.大小写敏感

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

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

YAML 文件示例

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

bash 复制代码
# 这是一个YAML配置文件示例
server:
  # 服务器地址
  host: "localhost"
  # 服务器端口
  port: 8080

# 数据库配置
database:
  # 数据库类型
  type: "mysql"
  # 数据库地址
  host: "127.0.0.1"
  # 用户名
  username: "root"
  # 密码
  password: "password123"

# 应用日志设置
logging:
  level: "info"    # 日志级别
  # 日志文件路径
  file: "/var/log/app.log"

# 开发者列表
developers:
  - name: "张三"
    email: "zhangsan@example.com"
  - name: "李四"
    email: "lisi@example.com"

在这个示例中,server、database、logging 和 developers 是顶层键,每个键下可以有子键。

符号后面的内容是注释。

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

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

接口测试工具

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

bash 复制代码
post http://175.178.126.10:8000/api/v1/hotmovie
Content-Type:application/json
 
{
    "start": 0,
    "count": 1,
    "city": "郑州"
}

### 下一项测试,注意前面三个###分割

结果成功收到应答:

豆瓣接口介绍

获取正在热映的电影

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

bash 复制代码
### Below is the code of douban.http,use vscode extension REST Client to send request.

post https://api.douban.com/v2/movie/in_theaters
Content-Type:application/json
 
{
    "start": 0,
    "count": 1,
    "city": "郑州",
    "apikey": "xxxxxxxxxxx"
}


### Respondse

{
  "count": 1,
  "start": 0,
  "total": 43,
  "subjects": [
  ],
  "title": "\u6b63\u5728\u4e0a\u6620\u7684\u7535\u5f71-\u90d1\u5dde"
}

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

bash 复制代码
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

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1732  100  1693  100    39   4480    103 --:--:-- --:--:-- --:--:--  4582
{
    "count": 1,
    "start": 0,
    "total": 46,
    "subjects": [
        {
            "rating": {
                "max": 10,
                "average": 9.5,
                "stars": "50",
                "min": 0
            },
            "genres": [
                "\u7eaa\u5f55\u7247",
                "\u97f3\u4e50"
            ],
            "title": "\u5742\u672c\u9f99\u4e00\uff1a\u6770\u4f5c",
            "casts": [
                {
                    "alt": "https://movie.douban.com/celebrity/1148641/",
                    "avatars": {
                        "small": "https://img1.doubanio.com/view/personage/m/public/12ce4a9f67eac0cb029736ae87549dd0.jpg",
                        "large": "https://img1.doubanio.com/view/personage/m/public/12ce4a9f67eac0cb029736ae87549dd0.jpg",
                        "medium": "https://img1.doubanio.com/view/personage/m/public/12ce4a9f67eac0cb029736ae87549dd0.jpg"
                    },
                    "name": "\u5742\u672c\u9f99\u4e00",
                    "id": "1148641"
                }
            ],
            "collect_count": 19158,
            "original_title": "Ryuichi Sakamoto | Opus",
            "subtype": "movie",
            "directors": [
                {
                    "alt": "https://movie.douban.com/celebrity/1442776/",
                    "avatars": {
                        "small": "https://img1.doubanio.com/view/celebrity/m/public/pXA5FFrGwJ94cel_avatar_uploaded1597077781.8.jpg",
                        "large": "https://img1.doubanio.com/view/celebrity/m/public/pXA5FFrGwJ94cel_avatar_uploaded1597077781.8.jpg",
                        "medium": "https://img1.doubanio.com/view/celebrity/m/public/pXA5FFrGwJ94cel_avatar_uploaded1597077781.8.jpg"
                    },
                    "name": "\u7a7a\u97f3\u592e",
                    "id": "1442776"
                }
            ],
            "year": "2023",
            "images": {
                "small": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2907966076.jpg",
                "large": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2907966076.jpg",
                "medium": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2907966076.jpg"
            },
            "alt": "https://movie.douban.com/subject/36491177/",
            "id": "36491177"
        }
    ],
    "title": "\u6b63\u5728\u4e0a\u6620\u7684\u7535\u5f71-\u5e7f\u5dde"
}

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

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

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

html 复制代码
<meta name="referrer" content="no-referrer" />

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

html 复制代码
<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>。

在前端处理:

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

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

Go 复制代码
var Referer_ = "https://images.weserv.nl/?url="
Go 复制代码
    var Referer_ = "https://images.weserv.nl/?url="
    var keyVal map[string]interface{}
	err = json.Unmarshal(body, &keyVal)
	if err != nil {
		l.Errorf("Failed to extract key value:", err)
	}
	//l.Debug(keyValue)
	var hot types.HotItem
	var responseData []types.HotItem
	list_, ok := keyVal["subjects"].([]interface{})
	if ok {
		for _, item := range list_ {
			itemMap, ok := item.(map[string]interface{})
			if ok {
				//l.Debug(itemMap)
				hot.Id = itemMap["id"].(string)
				hot.Title = itemMap["title"].(string)
				tmp := itemMap["images"].(map[string]interface{})
				hot.Cover = Referer_ + tmp["small"].(string)
				hot.Rate = int(itemMap["rating"].(map[string]interface{})["average"].(float64))
			}
			responseData = append(responseData, hot)
		}
	}

由于豆瓣接口返回的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博客

相关推荐
懒大王爱吃狼21 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
秃头佛爷1 小时前
Python学习大纲总结及注意事项
开发语言·python·学习
待磨的钝刨1 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
XiaoLeisj3 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师4 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
尚梦5 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
记录成长java6 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet